diff --git a/Legacy/DMotion/ArcDM.cs b/Legacy/DMotion/ArcDM.cs new file mode 100644 index 00000000..8788e64b --- /dev/null +++ b/Legacy/DMotion/ArcDM.cs @@ -0,0 +1,89 @@ +//! \file ArcDM.cs +//! \date 2023 Oct 24 +//! \brief D-Motion engine resource archive. +// +// Copyright (C) 2023 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System.Collections.Generic; +using System.ComponentModel.Composition; + +namespace GameRes.Formats.DMotion +{ + internal class ExtEntry : Entry + { + public int Count; + } + + [Export(typeof(ArchiveFormat))] + public class PakOpener : ArchiveFormat + { + public override string Tag => "256/DMOTION"; + public override string Description => "D-Motion engine resource archive"; + public override uint Signature => 0x4B434150; // 'PACK' + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (4, "FILE100DATA")) + return null; + if (!file.View.AsciiEqual (0x10, @".\\\")) + return null; + int ext_count = file.View.ReadUInt16 (0x16); + long index_pos = file.View.ReadUInt32 (0x18); + int total_count = 0; + var ext_dir = new List (ext_count); + for (int i = 0; i < ext_count; ++i) + { + var ext = new ExtEntry { + Name = file.View.ReadString (index_pos, 4), + Count = file.View.ReadUInt16 (index_pos+6), + Offset = file.View.ReadUInt32 (index_pos+8), + Size = file.View.ReadUInt32 (index_pos+12), + }; + ext_dir.Add (ext); + total_count += ext.Count; + index_pos += 0x10; + } + if (!IsSaneCount (total_count)) + return null; + + var dir = new List (total_count); + foreach (var ext in ext_dir) + { + index_pos = ext.Offset; + for (int i = 0; i < ext.Count; ++i) + { + var name = file.View.ReadString (index_pos, 8).TrimEnd() + ext.Name; + var entry = Create (name); + entry.Offset = file.View.ReadUInt32 (index_pos+8); + entry.Size = file.View.ReadUInt32 (index_pos+12); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_pos += 0x10; + } + } + return new ArcFile (file, this, dir); + } + } +} diff --git a/Legacy/Izumi/ImageMAI2.cs b/Legacy/Izumi/ImageMAI2.cs new file mode 100644 index 00000000..23234971 --- /dev/null +++ b/Legacy/Izumi/ImageMAI2.cs @@ -0,0 +1,436 @@ +//! \file ImageMAI2.cs +//! \date 2023 Oct 24 +//! \brief Izumi engine image format (PC-98). +// +// Copyright (C) 2023 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using GameRes.Utility; +using System; +using System.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace GameRes.Formats.Izumi +{ + internal class Mai2MetaData : ImageMetaData + { + public byte Flags; + public ushort Plane0Size; + public ushort Plane1Size; + public ushort Plane2Size; + public ushort Plane3Size; + + public bool HasPalette => (Flags & 0x80) != 0; + } + + [Export(typeof(ImageFormat))] + public class Mai2Format : ImageFormat + { + public override string Tag => "MAI/IZUMI"; + public override string Description => "Izumi engine image format"; + public override uint Signature => 0x3249414D; // 'MAI2' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x14); + ushort xy = header.ToUInt16 (4); + return new Mai2MetaData { + Width = (uint)(header.ToUInt16 (6) << 3), + Height = header.ToUInt16 (8), + OffsetX = xy % 0x50, + OffsetY = xy / 0x50, + BPP = 4, + Flags = header[0xA], + Plane0Size = header.ToUInt16 (0x0C), + Plane1Size = header.ToUInt16 (0x0E), + Plane2Size = header.ToUInt16 (0x10), + Plane3Size = header.ToUInt16 (0x12), + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new Mai2Reader (file, (Mai2MetaData)info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("Mai2Format.Write not implemented"); + } + } + + internal class Mai2Reader + { + IBinaryStream m_input; + Mai2MetaData m_info; + + public Mai2Reader (IBinaryStream input, Mai2MetaData info) + { + m_input = input; + m_info = info; + } + + byte[][] m_planes; + int m_stride; + int m_height; + + public ImageData Unpack () + { + m_input.Position = 0x14; + BitmapPalette palette = null; + if (m_info.HasPalette) + palette = ReadPalette(); + else + palette = BitmapPalettes.Gray16; + + m_height = m_info.iHeight; + m_stride = m_info.iWidth >> 3; + int plane_size = m_stride * m_info.iHeight; + m_planes = new byte[][] { + new byte[plane_size], new byte[plane_size], new byte[plane_size], new byte[plane_size], + }; + + long next_pos = m_input.Position + m_info.Plane0Size; + if ((m_info.Flags & 1) != 0) + UnpackPlane (m_planes[0]); + + m_input.Position = next_pos; + next_pos += m_info.Plane1Size; + if ((m_info.Flags & 2) != 0) + UnpackPlane (m_planes[1]); + + m_input.Position = next_pos; + next_pos += m_info.Plane2Size; + if ((m_info.Flags & 4) != 0) + UnpackPlane (m_planes[2]); + + m_input.Position = next_pos; + if ((m_info.Flags & 8) != 0) + UnpackPlane (m_planes[3]); + + int output_stride = m_info.iWidth >> 1; + var output = new byte[output_stride * m_info.iHeight]; + FlattenPlanes (output, output_stride); + + return ImageData.Create (m_info, PixelFormats.Indexed4, palette, output, output_stride); + } + + void UnpackPlane (byte[] plane) + { + int dst_row = 0; + for (int x = 0; x < m_stride; ++x) + { + int dst = dst_row; + int remaining = m_height; + while (remaining > 0) + { + int count = 1; + int ctl = m_input.ReadUInt8(); + if (ctl < 0x90) + { + count = ctl & 0x1F; + if (0 == count) + count = m_input.ReadUInt8(); + switch (ctl >> 5) + { + case 0: + for (int i = 0; i < count; ++i) + plane[dst++] = 0; + break; + case 1: + for (int i = 0; i < count; ++i) + plane[dst++] = 0xFF; + break; + case 2: + Buffer.BlockCopy (m_planes[0], dst, plane, dst, count); + dst += count; + break; + case 3: + Buffer.BlockCopy (m_planes[1], dst, plane, dst, count); + dst += count; + break; + case 4: + Buffer.BlockCopy (m_planes[2], dst, plane, dst, count); + dst += count; + break; + } + } + else if (ctl < 0xF0) + { + count = ctl & 0xF; + if (0 == count) + count = m_input.ReadUInt8(); + int off = 0; + switch (ctl >> 4) + { + case 0x9: off = 0x10; break; + case 0xA: off = 8; break; + case 0xB: off = 4; break; + case 0xC: off = 2; break; + case 0xD: off = m_height << 1; break; + case 0xE: off = m_height; break; + } + Binary.CopyOverlapped (plane, dst - off, dst, count); + dst += count; + } + else if (ctl < 0xF9) + { + count = ctl & 0xF; + if (0 == count) + count = m_input.ReadUInt8(); + m_input.Read (plane, dst, count); + dst += count; + } + else + { + count = m_input.ReadUInt8(); + switch (ctl) + { + case 0xF9: + dst += count; + break; + case 0xFA: + { + byte b = m_input.ReadUInt8(); + for (int i = 0; i < count; ++i) + plane[dst++] = b; + break; + } + case 0xFB: + { + int src = 0; + if ((count & 0x80) != 0) + { + count &= 0x7F; + src = 1; + } + if (0 == count) + count = m_input.ReadUInt8(); + for (int i = 0; i < count; ++i) + { + plane[dst] = (byte)~m_planes[src][dst]; + dst++; + } + break; + } + case 0xFC: + if ((count & 0x80) != 0) + { + count &= 0x7F; + if (0 == count) + count = m_input.ReadUInt8(); + byte al, ah; + al = m_input.ReadUInt8(); + ah = (byte)(al << 4 | al & 0x0F); + al = (byte)(al >> 4 | al & 0xF0); + for (int i = 0; i < count; ++i) + { + plane[dst++] = al; + plane[dst++] = ah; + } + count <<= 1; + } + else + { + if (0 == count) + count = m_input.ReadUInt8(); + for (int i = 0; i < count; ++i) + { + plane[dst] = (byte)~m_planes[2][dst]; + dst++; + } + } + break; + case 0xFD: + if ((count & 0x80) != 0) + { + ctl = count; + count = ctl & 0x3F; + if (0 == count) + count = m_input.ReadUInt8(); + byte al, ah, bl, bh; + al = m_input.ReadUInt8(); + bl = (byte)(al & 0xF0 | al >> 4); + bh = (byte)(al & 0x0F | al << 4); + if (ctl < 0xC0) + { + al = Binary.RotByteR (bl, 2); + ah = Binary.RotByteR (bh, 2); + } + else + { + ah = m_input.ReadUInt8(); + al = (byte)(ah & 0xF0 | ah >> 4); + ah = (byte)(ah & 0x0F | ah << 4); + } + for (int i = 0; i < count; ++i) + { + plane[dst++] = bl; + plane[dst++] = bh; + plane[dst++] = al; + plane[dst++] = ah; + } + count <<= 2; + } + else + { + if (0 == count) + count = m_input.ReadUInt8(); + byte al = m_input.ReadUInt8(); + byte ah = m_input.ReadUInt8(); + for (int i = 0; i < count; ++i) + { + plane[dst++] = al; + plane[dst++] = ah; + } + count <<= 1; + } + break; + case 0xFE: + ctl = count; + count &= 0x3F; + if (0 == count) + count = m_input.ReadUInt8(); + if (ctl < 0x40) + { + m_input.Read (plane, dst, 4); + count <<= 2; + Binary.CopyOverlapped (plane, dst, dst + 4, count - 4); + dst += count; + } + else + { + int psrc, pmask; + if ((ctl & 0x80) == 0) + { + psrc = 0; + pmask = 1; + } + else if (ctl < 0xC0) + { + psrc = 0; + pmask = 2; + } + else + { + psrc = 1; + pmask = 2; + } + for (int i = 0; i < count; ++i) + { + byte b = m_planes[psrc][dst]; + b &= m_planes[pmask][dst]; + plane[dst++] = b; + } + } + break; + case 0xFF: + { + Func op; + if (count < 0x40) + { + op = src => (byte)(m_planes[0][src] | m_planes[1][src]); + } + else if (count < 0x80) + { + op = src => (byte)(m_planes[0][src] ^ m_planes[1][src]); + count &= 0x3F; + } + else + { + if (count < 0xA0) + op = src => (byte)(m_planes[0][src] | m_planes[2][src]); + else if (count < 0xC0) + op = src => (byte)(m_planes[1][src] | m_planes[2][src]); + else if (count < 0xE0) + op = src => (byte)(m_planes[0][src] ^ m_planes[2][src]); + else + op = src => (byte)(m_planes[1][src] ^ m_planes[2][src]); + count &= 0x1F; + } + if (0 == count) + count = m_input.ReadUInt8(); + for (int i = 0; i < count; ++i) + { + plane[dst] = op (dst); + dst++; + } + break; + } + } + } + remaining -= count; + } + dst_row += m_height; + } + } + + void FlattenPlanes (byte[] output, int output_stride) + { + int plane_size = m_planes[0].Length; + int src = 0; + for (int x = 0; x < output_stride; x += 4) + { + int dst = x; + for (int y = 0; y < m_info.iHeight; ++y) + { + byte b0 = m_planes[0][src]; + byte b1 = m_planes[1][src]; + byte b2 = m_planes[2][src]; + byte b3 = m_planes[3][src]; + ++src; + for (int j = 0; j < 8; j += 2) + { + byte px = (byte)((((b0 << j) & 0x80) >> 3) + | (((b1 << j) & 0x80) >> 2) + | (((b2 << j) & 0x80) >> 1) + | (((b3 << j) & 0x80) >> 0)); + px |= (byte)((((b0 << j) & 0x40) >> 6) + | (((b1 << j) & 0x40) >> 5) + | (((b2 << j) & 0x40) >> 4) + | (((b3 << j) & 0x40) >> 3)); + output[dst+j/2] = px; + } + dst += output_stride; + } + } + } + + BitmapPalette ReadPalette () + { + using (var bits = new MsbBitStream (m_input.AsStream, true)) + { + var colors = new Color[16]; + for (int i = 0; i < 16; ++i) + { + int r = bits.GetBits (4) * 0x11; + int g = bits.GetBits (4) * 0x11; + int b = bits.GetBits (4) * 0x11; + colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b); + } + return new BitmapPalette (colors); + } + } + } +} diff --git a/Legacy/Izumi/ImageMAI3.cs b/Legacy/Izumi/ImageMAI3.cs new file mode 100644 index 00000000..0823372c --- /dev/null +++ b/Legacy/Izumi/ImageMAI3.cs @@ -0,0 +1,332 @@ +//! \file ImageMAI3.cs +//! \date 2023 Oct 23 +//! \brief Izumi engine image format (PC-98). +// +// Copyright (C) 2023 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace GameRes.Formats.Izumi +{ + internal class Mai3MetaData : ImageMetaData + { + public int DataOffset; + public bool HasPalette; + } + + [Export(typeof(ImageFormat))] + public class Mai3Format : ImageFormat + { + public override string Tag => "MI3"; + public override string Description => "Izumi engine image format"; + public override uint Signature => 0x3049414D; // 'MAI03' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (14); + if (!header.AsciiEqual ("MAI03\x1A")) + return null; + return new Mai3MetaData { + Width = (uint)(header.ToUInt16 (8) << 3), + Height = header.ToUInt16 (0xA), + BPP = 4, + DataOffset = header.ToUInt16 (0xC) & 0x7FFF, + HasPalette = (header[0xD] & 0x80) != 0, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new Mai3Reader (file, (Mai3MetaData)info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("Mai3Format.Write not implemented"); + } + } + + internal class Mai3Reader + { + IBinaryStream m_input; + Mai3MetaData m_info; + + public Mai3Reader (IBinaryStream input, Mai3MetaData info) + { + m_input = input; + m_info = info; + } + + ushort[] m_buffer; + int m_output_stride; + byte[] m_output; + + public ImageData Unpack () + { + m_input.Position = m_info.DataOffset; + BitmapPalette palette = null; + if (m_info.HasPalette) + palette = ReadPalette(); + else + palette = BitmapPalettes.Gray16; + m_buffer = new ushort[0x6D0]; + m_output_stride = m_info.iWidth >> 1; + m_output = new byte[m_output_stride * m_info.iHeight]; + InitPixels(); + InitBitReader(); + int output_dst = 0; + int stride = m_info.iWidth >> 3; + int x = stride >> 1; + while (x --> 0) + { + MoveBuffer(); + UnpackLine (0x1C0); + UnpackLine (0x10); + MoveBuffer(); + UnpackLine (0x1C0); + UnpackLine (0x10); + CopyOutput (output_dst); + output_dst += 8; + } + if ((stride & 1) != 0) + { + MoveBuffer(); + UnpackLine (0x1C0); + UnpackLine (0x10); + CopyOutput (output_dst, 1); + } + return ImageData.Create (m_info, PixelFormats.Indexed4, palette, m_output, m_output_stride); + } + + void UnpackLine (int dst) + { + int height = m_info.iHeight; + while (height > 0) + { + if (GetNextBit() == 0) + { + int offset; + if (GetNextBit() != 0) + offset = 0; + else if (GetNextBit() != 0) + offset = 0x1B0; + else + offset = 0x360; + if (0 == offset || GetNextBit() != 0) + { + if (GetNextBit() != 0) + offset = -1; + else if (GetNextBit() != 0) + offset -= 2; + else if (GetNextBit() != 0) + offset -= 4; + else if (GetNextBit() != 0) + offset -= 8; + else + offset -= 0x10; + } + else if (GetNextBit() == 0) + { + if (GetNextBit() != 0) + offset += 2; + else if (GetNextBit() != 0) + offset += 4; + else if (GetNextBit() != 0) + offset += 8; + else + offset += 0x10; + } + int length = GetCount (8); + int count = 1; + for (int j = 0; j < length; ++j) + count = count << 1 | GetNextBit(); + count += 1; + int src = dst + offset; + height -= count; + while (count --> 0) + m_buffer[dst++] = m_buffer[src++]; + } + else + { + ushort px = m_buffer[dst + 0x1B0]; + int prev = (px >> 8) & 1; + prev <<= 1; + prev |= (px >> 12) & 1; + prev <<= 1; + prev |= px & 1; + prev <<= 1; + prev |= (px >> 4) & 1; + + byte n0 = GetPixel ((byte)prev); + byte n1 = GetPixel (n0); + byte n2 = GetPixel (n1); + byte n3 = GetPixel (n2); + + px = m_patterns[0,n3]; + px |= m_patterns[1,n2]; + px |= m_patterns[2,n1]; + px |= m_patterns[3,n0]; + + m_buffer[dst++] = px; + --height; + } + } + } + + static readonly ushort[,] m_patterns = { + { 0, 0x10, 1, 0x11, 0x1000, 0x1010, 0x1001, 0x1011, 0x100, 0x110, 0x101, 0x111, 0x1100, 0x1110, 0x1101, 0x1111 }, + { 0, 0x20, 2, 0x22, 0x2000, 0x2020, 0x2002, 0x2022, 0x200, 0x220, 0x202, 0x222, 0x2200, 0x2220, 0x2202, 0x2222 }, + { 0, 0x40, 4, 0x44, 0x4000, 0x4040, 0x4004, 0x4044, 0x400, 0x440, 0x404, 0x444, 0x4400, 0x4440, 0x4404, 0x4444 }, + { 0, 0x80, 8, 0x88, 0x8000, 0x8080, 0x8008, 0x8088, 0x800, 0x880, 0x808, 0x888, 0x8800, 0x8880, 0x8808, 0x8888 }, + }; + + byte GetPixel (byte prev) + { + int count = GetCount (15); + prev <<= 4; + prev += 0xF; + int src = prev - count; + int dst = src; + byte al = m_pixels[src++]; + if (count > 0) + { + while (count --> 0) + m_pixels[dst++] = m_pixels[src++]; + m_pixels[dst] = al; + } + return al; + } + + int GetCount (int limit) + { + int count = 0; + while (count < limit && GetNextBit() == 0) + ++count; + return count; + } + + void MoveBuffer () + { + Buffer.BlockCopy (m_buffer, 0x20, m_buffer, 0x6E0, 0x360 << 1); + } + + void CopyOutput (int dst_line, int rows = 2) + { + int src = 0x10; + int height = m_info.iHeight; + for (int y = 0; y < height; ++y) + { + ushort cx = m_buffer[src + 0x510]; + ushort dx = m_buffer[src + 0x360]; + ushort bx = m_buffer[src + 0x1B0]; + ushort ax = m_buffer[src++]; + + int b0 = bx << 8 & 0xF000 | ax << 4 & 0x0F00 | cx & 0x00F0 | dx >> 4 & 0xF; + int b1 = bx << 12 & 0xF000 | ax << 8 & 0x0F00 | cx << 4 & 0x00F0 | dx & 0xF; + int b2 = bx & 0xF000 | ax >> 4 & 0x0F00 | cx >> 8 & 0x00F0 | dx >> 12; + int b3 = bx << 4 & 0xF000 | ax & 0x0F00 | cx >> 4 & 0x00F0 | dx >> 8 & 0xF; + + int dst = dst_line; + for (int i = 0; i < rows; ++i) + { + for (int j = 0; j < 8; j += 2) + { + byte px = (byte)((((b0 << j) & 0x80) >> 3) + | (((b1 << j) & 0x80) >> 2) + | (((b2 << j) & 0x80) >> 1) + | (((b3 << j) & 0x80) )); + px |= (byte)((((b0 << j) & 0x40) >> 6) + | (((b1 << j) & 0x40) >> 5) + | (((b2 << j) & 0x40) >> 4) + | (((b3 << j) & 0x40) >> 3)); + m_output[dst++] = px; + } + b0 >>= 8; + b1 >>= 8; + b2 >>= 8; + b3 >>= 8; + } + dst_line += m_output_stride; + } + } + + byte[] m_pixels = new byte[0x100]; + + void InitPixels () + { + int dst = m_pixels.Length - 1; + for (int i = 0x0F; i >= 0; --i) + { + byte n = (byte)i; + for (int j = 0; j < 0x10; ++j) + { + m_pixels[dst--] = (byte)(n-- & 0xF); + } + } + } + + int m_bits; + int m_bit_count; + + void InitBitReader () + { + m_bits = m_input.ReadUInt16(); + m_bit_count = 16; + } + + byte GetNextBit () + { + int bit = m_bits & 1; + m_bits >>= 1; + if (--m_bit_count <= 0) + { + if (m_input.PeekByte() != -1) + m_bits = m_input.ReadUInt16(); + else + m_bits = 0; + m_bit_count = 16; + } + return (byte)bit; + } + + BitmapPalette ReadPalette () + { + using (var bits = new MsbBitStream (m_input.AsStream, true)) + { + var colors = new Color[16]; + for (int i = 0; i < 16; ++i) + { + int r = bits.GetBits (4) * 0x11; + int g = bits.GetBits (4) * 0x11; + int b = bits.GetBits (4) * 0x11; + colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b); + } + return new BitmapPalette (colors); + } + } + } +} diff --git a/Legacy/Legacy.csproj b/Legacy/Legacy.csproj index ca12d073..57e41fe8 100644 --- a/Legacy/Legacy.csproj +++ b/Legacy/Legacy.csproj @@ -105,6 +105,7 @@ + @@ -115,6 +116,8 @@ + + @@ -157,6 +160,7 @@ + diff --git a/Legacy/Miami/ImageMIA.cs b/Legacy/Miami/ImageMIA.cs new file mode 100644 index 00000000..93a65c7f --- /dev/null +++ b/Legacy/Miami/ImageMIA.cs @@ -0,0 +1,318 @@ +//! \file ImageMIA.cs +//! \date 2023 Oct 21 +//! \brief Miamisoft image format (PC-98). +// +// Copyright (C) 2023 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using GameRes.Utility; +using System; +using System.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +// [950630][Miamisoft] Kotohigaoka Monogatari + +namespace GameRes.Formats.Miami +{ + [Export(typeof(ImageFormat))] + public class MiaFormat : ImageFormat + { + public override string Tag => "MIA"; + public override string Description => "Miamisoft image format"; + public override uint Signature => 0; + + public MiaFormat () + { + Signatures = new[] { 0x40u, 0u }; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x10); + if (!header.AsciiEqual (0xA, "CoB42")) + return null; + return new ImageMetaData { + Width = header.ToUInt16 (6), + Height = header.ToUInt16 (8), + BPP = 4, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new MiaReader (file, info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("MiaFormat.Write not implemented"); + } + } + + internal class MiaReader + { + IBinaryStream m_input; + ImageMetaData m_info; + + public MiaReader (IBinaryStream input, ImageMetaData info) + { + m_input = input; + m_info = info; + } + + byte[] m_buffer; + int m_buf_dst; + byte[] m_order; + + byte[] m_output; + int m_output_dst; + int m_output_stride; + + public ImageData Unpack () + { + m_input.Position = 0x10; + var palette = ReadPalette (m_input); + try + { + UnpackInternal(); + } + catch (EndOfStreamException) + { + FlushBuffer(); + } + return ImageData.Create (m_info, PixelFormats.Indexed4, palette, m_output, m_output_stride); + } + + void UnpackInternal () // 1374:7A54 + { + m_output_stride = m_info.iWidth >> 1; + m_output = new byte[m_output_stride * m_info.iHeight]; + int buffer_size = m_info.iHeight * 0x10; + m_buffer = new byte[buffer_size]; + SetupPattern(); + m_order = m_input.ReadBytes (6); + byte prev_pixel = 0x10; + m_buf_dst = 0; + m_output_dst = 0; + while (m_output_dst < m_output_stride) + { + int ctl = GetInt() - 1; + if (ctl < 0) // @1@ + { + m_buffer[m_buf_dst ] = 0; + m_buffer[m_buf_dst+1] = 0; + m_buffer[m_buf_dst+2] = 0; + m_buffer[m_buf_dst+3] = 0; + for (int i = 0; i < 4; ++i) + { + int count = GetInt(); + int dst = count + (prev_pixel << 4); + byte al = m_pattern[dst]; + int src = dst - 1; + while (count --> 0) + m_pattern[dst--] = m_pattern[src--]; + m_pattern[dst] = al; + prev_pixel = al; + for (int j = 0; j < 4; ++j) + { + m_buffer[m_buf_dst+j] <<= 1; + m_buffer[m_buf_dst+j] |= (byte)(al & 1); + al >>= 1; + } + } + ushort ax = LittleEndian.ToUInt16 (m_buffer, m_buf_dst); + ax <<= 4; + LittleEndian.Pack (ax, m_buffer, m_buf_dst+4); + ax = LittleEndian.ToUInt16 (m_buffer, m_buf_dst+2); + ax <<= 4; + LittleEndian.Pack (ax, m_buffer, m_buf_dst+6); + m_buf_dst += 8; + } + else if (ctl < 5) // @2@ + { + int count = 1 + GetInt(); + switch (m_order[ctl]) + { + case 1: CopyOp01 (count, 8); break; + case 2: CopyOp01 (count, 0x10); break; + case 3: CopyOp01 (count, 0x20); break; + case 4: CopyOp01 (count, m_info.iHeight << 3); break; + case 5: CopyOp05 (count); break; + default: throw new InvalidFormatException(); + } + } + else // ctl >= 5 + { + throw new InvalidFormatException(); + } + if (buffer_size == m_buf_dst) + FlushBuffer(); + } + } + + int GetInt () + { + int count = 0; + while (GetNextBit() == 0) + ++count; + return count; + } + + void CopyOp01 (int count, int offset) + { + int bx = count; + while (count > 0) + { + int dst = m_buf_dst; + if (dst < offset) + { + int src = dst; + dst = -(dst - offset) >> 3; + if (count > dst) + count = dst; + src += m_info.iHeight << 4; + src -= offset; + bx -= count; + count <<= 3; + Binary.CopyOverlapped (m_buffer, src, m_buf_dst, count); + m_buf_dst += count; + count = bx; + if (0 == count) + break; + } + int remaining = m_buffer.Length - m_buf_dst; + remaining >>= 3; + if (count > remaining) + count = remaining; + bx -= count; + count <<= 3; + Binary.CopyOverlapped (m_buffer, m_buf_dst - offset, m_buf_dst, count); + m_buf_dst += count; + count = bx; + if (m_buffer.Length == m_buf_dst) + FlushBuffer(); + } + } + + void CopyOp05 (int count) + { + int src = m_buf_dst - 8; + if (src < 0) + src = m_buffer.Length - 8; + + ushort ax = LittleEndian.ToUInt16 (m_buffer, src); + ax = (ushort)((ax << 1) & 0x0A0A | (ax >> 1) & 0x0505); + LittleEndian.Pack (ax, m_buffer, m_buf_dst); + ax <<= 4; + LittleEndian.Pack (ax, m_buffer, m_buf_dst+4); + + ax = LittleEndian.ToUInt16 (m_buffer, src+2); + ax = (ushort)((ax << 1) & 0x0A0A | (ax >> 1) & 0x0505); + LittleEndian.Pack (ax, m_buffer, m_buf_dst+2); + ax <<= 4; + LittleEndian.Pack (ax, m_buffer, m_buf_dst+6); + + m_buf_dst += 8; + if (m_buf_dst == m_buffer.Length) + FlushBuffer(); + if (--count != 0) + CopyOp01 (count, 0x10); + } + + void FlushBuffer () + { + int height = m_info.iHeight; + int hi = height << 3; + int src = 0; + int dst = m_output_dst; + for (int y = 0; y < height; ++y) + { + int b0 = m_buffer[src+4] | m_buffer[src+hi ]; + int b1 = m_buffer[src+5] | m_buffer[src+hi+1]; + int b2 = m_buffer[src+6] | m_buffer[src+hi+2]; + int b3 = m_buffer[src+7] | m_buffer[src+hi+3]; + for (int j = 0; j < 8; j += 2) + { + byte px = (byte)((((b0 << j) & 0x80) >> 3) + | (((b1 << j) & 0x80) >> 2) + | (((b2 << j) & 0x80) >> 1) + | (((b3 << j) & 0x80) )); + px |= (byte)((((b0 << j) & 0x40) >> 6) + | (((b1 << j) & 0x40) >> 5) + | (((b2 << j) & 0x40) >> 4) + | (((b3 << j) & 0x40) >> 3)); + m_output[dst+(j>>1)] = px; + } + src += 8; + dst += m_output_stride; + } + m_output_dst += 4; + m_buf_dst = 0; + } + + byte[] m_pattern = new byte[0x110]; + + void SetupPattern () + { + int dst = 0; + byte h = 0; + for (int i = 0; i < 0x11; ++i) + { + byte l = h; + for (int j = 0; j < 0x10; ++j) + m_pattern[dst++] = (byte)(l++ & 0xF); + h++; + } + } + + int m_bit_count = 0; + int m_bits; + + byte GetNextBit () + { + if (--m_bit_count <= 0) + { + m_bits = m_input.ReadUInt8(); + m_bit_count = 8; + } + int bit = m_bits & 1; + m_bits >>= 1; + return (byte)bit; + } + + BitmapPalette ReadPalette (IBinaryStream input) + { + const int count = 16; + var colors = new Color[count]; + for (int i = 0; i < count; ++i) + { + byte g = m_input.ReadUInt8(); + byte r = m_input.ReadUInt8(); + byte b = m_input.ReadUInt8(); + colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11)); + } + return new BitmapPalette (colors); + } + } +} diff --git a/Legacy/System98/ImageG.cs b/Legacy/System98/ImageG.cs index d17c2550..bd051827 100644 --- a/Legacy/System98/ImageG.cs +++ b/Legacy/System98/ImageG.cs @@ -76,12 +76,16 @@ public override void Write (Stream file, ImageData image) } } + /// + /// This compression format is used in several PC-98 game engines. + /// internal class GraBaseReader { protected IBinaryStream m_input; protected ImageMetaData m_info; protected int m_output_stride; protected byte[] m_pixels; + protected int m_dst; public byte[] Pixels => m_pixels; public int Stride => m_output_stride; @@ -94,7 +98,7 @@ public GraBaseReader (IBinaryStream file, ImageMetaData info) m_pixels = new byte[m_output_stride * m_info.iHeight]; } - ushort[] m_buffer; + protected ushort[] m_buffer; public void UnpackBits () { @@ -216,8 +220,6 @@ void UnpackBitsInternal () } } - int m_dst; - bool FlushBuffer () { MovePixels (m_buffer, m_info.iWidth * 4, 0, m_info.iWidth); @@ -231,14 +233,14 @@ bool FlushBuffer () return m_dst == m_pixels.Length; } - ushort ReadPair (int pos) + protected ushort ReadPair (int pos) { byte al = ReadPixel (pos); byte ah = ReadPixel (al); return (ushort)(al | ah << 8); } - byte ReadPixel (int pos) + protected byte ReadPixel (int pos) { byte px = 0; if (GetNextBit() == 0) @@ -277,7 +279,7 @@ byte ReadPixel (int pos) byte[] m_frame; - void InitFrame () + protected void InitFrame () { m_frame = new byte[0x100]; int p = 0; @@ -293,7 +295,7 @@ void InitFrame () } } - void MovePixels (ushort[] pixels, int src, int dst, int count) + protected void MovePixels (ushort[] pixels, int src, int dst, int count) { count <<= 1; if (dst > src) @@ -315,12 +317,12 @@ void MovePixels (ushort[] pixels, int src, int dst, int count) int m_bits; int m_bit_count; - void InitBitReader () + protected void InitBitReader () { m_bit_count = 1; } - byte GetNextBit () + protected byte GetNextBit () { if (--m_bit_count <= 0) {