diff --git a/Legacy/AyPio/ArcDLB.cs b/Legacy/AyPio/ArcDLB.cs index e35ffb03..3bbe2450 100644 --- a/Legacy/AyPio/ArcDLB.cs +++ b/Legacy/AyPio/ArcDLB.cs @@ -44,20 +44,56 @@ namespace GameRes.Formats.AyPio int count = file.View.ReadInt16 (0x16); if (!IsSaneCount (count)) return null; - uint index_pos = 0x18; + using (var index = file.CreateStream()) + { + index.Position = 0x18; + var dir = ReadIndex (index, count); + return new ArcFile (file, this, dir); + } + } + + internal List ReadIndex (IBinaryStream index, int count) + { + var max_offset = index.Length; var dir = new List (count); for (int i = 0; i < count; ++i) { - var name = file.View.ReadString (index_pos, 0xD); + var name = index.ReadCString (0xD); var entry = Create (name); - entry.Offset = file.View.ReadUInt32 (index_pos+0x0D); - entry.Size = file.View.ReadUInt32 (index_pos+0x11); - if (!entry.CheckPlacement (file.MaxOffset)) + entry.Offset = index.ReadUInt32(); + entry.Size = index.ReadUInt32(); + if (!entry.CheckPlacement (max_offset)) return null; dir.Add (entry); - index_pos += 0x15; } - return new ArcFile (file, this, dir); + return dir; + } + } + + [Export(typeof(ArchiveFormat))] + public class Dlb0Opener : DlbOpener + { + public override string Tag => "DLB/V0"; + public override string Description => "UK2 engine resource archive"; + public override uint Signature => 0; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + if (!file.Name.HasExtension (".DLB")) + return null; + int count = file.View.ReadInt16 (0); + if (!IsSaneCount (count)) + return null; + uint first_offset = file.View.ReadUInt32 (0xF); + if (first_offset != count * 0x15 + 2) + return null; + using (var index = file.CreateStream()) + { + index.Position = 2; + var dir = ReadIndex (index, count); + return new ArcFile (file, this, dir); + } } } } diff --git a/Legacy/AyPio/AudioVOC.cs b/Legacy/AyPio/AudioVOC.cs new file mode 100644 index 00000000..090f70df --- /dev/null +++ b/Legacy/AyPio/AudioVOC.cs @@ -0,0 +1,192 @@ +//! \file AudioVOC.cs +//! \date 2023 Oct 19 +//! \brief AyPio ADPCM-compressed audio. +// +// 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.ComponentModel.Composition; + +namespace GameRes.Formats.AyPio +{ + [Export(typeof(AudioFormat))] + public class VocAudio : AudioFormat + { + public override string Tag => "VOC/UK2"; + public override string Description => "UK2 engine compressed audio"; + public override uint Signature => 0x81564157; // 'WAV\x81' + public override bool CanWrite => false; + + public override SoundInput TryOpen (IBinaryStream file) + { + var header = file.ReadHeader (0x3C); + if (!header.AsciiEqual (0x38, "RIFF")) + return null; + var decoder = new VocDecoder (file); + var samples = decoder.Decode(); + var stream = new BinMemoryStream (samples, file.Name); + file.Dispose(); + return new RawPcmInput (stream, decoder.Format); + } + } + + internal sealed class VocDecoder + { + IBinaryStream m_input; + + int m_sample_count; + byte m_channels; + byte m_bits_per_sample; + byte[] m_prev_sample = new byte[2]; + long m_start_pos; + + public WaveFormat Format { get; private set; } + + public VocDecoder (IBinaryStream input) + { + m_input = input; + var header = input.ReadHeader (0x38); + Format = new WaveFormat { + FormatTag = header.ToUInt16 (0x21), + Channels = header.ToUInt16 (0x23), + SamplesPerSecond = header.ToUInt32 (0x25), + AverageBytesPerSecond = header.ToUInt32 (0x29), + BlockAlign = header.ToUInt16 (0x2D), + BitsPerSample = header.ToUInt16 (0x2F), + }; + m_sample_count = header.ToInt32 (0x18); + m_channels = header[8]; + m_prev_sample[0] = header[0xC]; + m_prev_sample[1] = header[0x10]; + m_bits_per_sample = header[0x20]; + m_output = new byte[m_sample_count << 1]; + m_output[0] = header[0xA]; + m_output[1] = header[0xB]; + m_output[2] = header[0xE]; + m_output[3] = header[0xF]; + m_start_pos = header.ToUInt32 (4) + header.ToUInt32 (0x14); + } + + byte[] m_output; + int[] m_samples; + int m_src; + + public byte[] Decode () + { + m_input.Position = m_start_pos; + m_src = 0; + BuildSamples(); + int count = m_sample_count - m_channels; + int src = 0; + int pos = 0; + while (src < count) + { + byte sample = GetSample(); + int v7 = sample & 7; + int channel; + if (m_channels == 1 || (src & 1) == 0) + channel = 0; + else + channel = 1; + byte prev = m_prev_sample[channel]; + int s = m_samples[89 * v7 + prev]; + if ((sample & 8) != 0) + s = -s; + s += m_output.ToInt16 (pos); + pos += 2; + LittleEndian.Pack (Clamp (s), m_output, pos); + int p = IndexTable[v7] + prev; + if (p < 0) + p = 0; + else if (p > 88) + p = 88; + m_prev_sample[channel] = (byte)p; + ++src; + } + return m_output; + } + + byte m_current_sample; + + byte GetSample () + { + if (0 == (m_src & 1)) + m_current_sample = m_input.ReadUInt8(); + ++m_src; + byte sample = m_current_sample; + m_current_sample >>= 4; + return sample &= 0xF; + } + + short Clamp (int sample) + { + if (sample > 0x7FFF) + sample = 0x7FFF; + else if (sample < -0x8000) + sample = -0x8000; + return (short)sample; + } + + void BuildSamples () + { + int b = 1 << (m_bits_per_sample - 1); + int i = 0; + m_samples = new int[89 * b]; + while (i < 89) + { + int ii = i; + int j = 0; + while (j < b) + { + double d = 0.0; + int a = 1; + int c = b; + do + { + if (j % a >= a / 2) + { + d += (double)StepTable[ii] / (double)c; + } + a <<= 1; + c >>= 1; + } + while (a <= b); + ++j; + m_samples[i] = (int)d; + i += 89; + } + i = ii + 1; + } + } + + static readonly short[] StepTable = { + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x10, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1C, 0x1F, + 0x22, 0x25, 0x29, 0x2D, 0x32, 0x37, 0x3C, 0x42, 0x49, 0x50, 0x58, 0x61, 0x6B, 0x76, 0x82, 0x8F, + 0x9D, 0x0AD, 0x0BE, 0x0D1, 0x0E6, 0x0FD, 0x117, 0x133, 0x151, 0x173, 0x198, 0x1C1, 0x1EE, 0x220, + 0x256, 0x292, 0x2D4, 0x31C, 0x36C, 0x3C3, 0x424, 0x48E, 0x502, 0x583, 0x610, 0x6AB, 0x756, 0x812, + 0x8E0, 0x9C3, 0x0ABD, 0x0BD0, 0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954, + 0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B, 0x3BB9, 0x41B2, 0x4844, 0x4F7E, + 0x5771, 0x602F, 0x69CE, 0x7462, 0x7FFF, + }; + static readonly sbyte[] IndexTable = { -1, -1, -1, -1, 1, 2, 3, 4 }; + } +} diff --git a/Legacy/AyPio/ImagePDT.cs b/Legacy/AyPio/ImagePDT.cs index c7f62250..07756e87 100644 --- a/Legacy/AyPio/ImagePDT.cs +++ b/Legacy/AyPio/ImagePDT.cs @@ -79,7 +79,7 @@ namespace GameRes.Formats.AyPio public override ImageData Read (IBinaryStream file, ImageMetaData info) { - var reader = new PdtReader (file, (PdtMetaData)info); + var reader = new Pdt4Reader (file, (PdtMetaData)info); return reader.Unpack(); } @@ -89,12 +89,12 @@ namespace GameRes.Formats.AyPio } } - internal class PdtReader + internal class Pdt4Reader { IBinaryStream m_input; PdtMetaData m_info; - public PdtReader (IBinaryStream input, PdtMetaData info) + public Pdt4Reader (IBinaryStream input, PdtMetaData info) { m_input = input; m_info = info; diff --git a/Legacy/AyPio/ImagePDT5.cs b/Legacy/AyPio/ImagePDT5.cs new file mode 100644 index 00000000..d2857805 --- /dev/null +++ b/Legacy/AyPio/ImagePDT5.cs @@ -0,0 +1,252 @@ +//! \file ImagePDT5.cs +//! \date 2023 Oct 16 +//! \brief AyPio image format. +// +// 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; + +// [960726][AyPio] Chuushaki + +namespace GameRes.Formats.AyPio +{ + [Export(typeof(ImageFormat))] + public class Pdt5Format : ImageFormat + { + public override string Tag => "PDT/5"; + public override string Description => "UK2 engine image format"; + public override uint Signature => 0; + + public Pdt5Format () + { + Extensions = new[] { "pdt", "anm" }; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + if (file.ReadByte() != 0x35) + return null; + file.Position = 0x21; + int left = file.ReadUInt16(); + int top = file.ReadUInt16(); + int right = file.ReadUInt16(); + int bottom = file.ReadUInt16(); + int width = (right - left + 1) << 3; + int height = bottom - top + 1; + if (width <= 0 || height <= 0 || width > 640 || height > 1024) + return null; + return new ImageMetaData { + Width = (uint)width, + Height = (uint)height, + OffsetX = left << 3, + OffsetY = top, + BPP = 4, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new Pdt5Reader (file, info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("Pdt5Format.Write not implemented"); + } + } + + internal class Pdt5Reader + { + IBinaryStream m_input; + ImageMetaData m_info; + + public Pdt5Reader (IBinaryStream input, ImageMetaData info) + { + m_input = input; + m_info = info; + } + + byte[] m_buffer; + + public ImageData Unpack () + { + m_input.Position = 1; + var palette = ReadPalette(); + m_input.Position = 0x29; + int width = m_info.iWidth; + int height = m_info.iHeight; + int output_stride = m_info.iWidth; + var pixels = new byte[output_stride * height]; + InitFrame(); + InitBitReader(); + byte px = 0; + m_buffer = new byte[1932]; + int output_pos = 0; + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + if (GetNextBit() != 0) + { + if (GetNextBit() != 0) + { + if (GetNextBit() != 0) + { + int count = GetCount() + 2; + int pos = 1290 + x; + x += count - 1; + while (count --> 0) + { + m_buffer[pos++] = px; + } + } + else + { + int count = GetCount() + 1; + px = m_buffer[1289 + x]; + int src = x + 1288; + int dst = x + 1290; + Binary.CopyOverlapped (m_buffer, src, dst, count * 2); + x += count * 2 - 1; + } + } + else + { + px = GetPixel (x); + m_buffer[x + 1290] = px; + } + } + else + { + int count = 0; + byte b = GetPixel (x); + while (GetNextBit() != 1) + ++count; + int src = 0x10 * b + count; + px = m_frame[src]; + m_buffer[x + 1290] = px; + while (count --> 0) + { + m_frame[src] = m_frame[src-1]; + --src; + } + m_frame[src] = px; + } + } + Buffer.BlockCopy (m_buffer, 1290, pixels, output_pos, width); + output_pos += output_stride; + Buffer.BlockCopy (m_buffer, 644, m_buffer, 0, 1288); + } + return ImageData.Create (m_info, PixelFormats.Indexed8, palette, pixels, output_stride); + } + + byte GetPixel (int src) + { + byte px = m_buffer[src + 647]; + if (m_buffer[src + 4] != px) + { + byte v = m_buffer[src + 2]; + if (v != px) + { + px = m_buffer[src + 645]; + if (px != v && m_buffer[src] != px) + return m_buffer[src + 2]; + } + } + return px; + } + + byte[] m_frame; + + void InitFrame () + { + m_frame = new byte[0x110]; + for (int j = 0; j < 0x110; j += 0x10) + { + for (byte i = 0; i < 0x10; ++i) + m_frame[j + i] = i; + } + } + + int GetCount () + { + int count = 0; + int bits = 1; + while (GetNextBit() != 1) + { + count += bits; + bits <<= 1; + } + if (bits > 1) + { + do + { + if (GetNextBit() != 0) + count += bits; + bits >>= 1; + } + while (bits != 0); + } + return count; + } + + uint m_bits; + int m_bit_count; + + void InitBitReader () + { + m_bit_count = 1; + } + + byte GetNextBit () + { + if (--m_bit_count <= 0) + { + m_bits = m_input.ReadUInt8(); + m_bit_count = 8; + } + uint bit = m_bits & 1; + m_bits >>= 1; + return (byte)bit; + } + + BitmapPalette ReadPalette () + { + var colors = new Color[16]; + for (int i = 0; i < 16; ++i) + { + ushort rgb = m_input.ReadUInt16(); + int b = (rgb & 0xF) * 0x11; + int r = ((rgb >> 4) & 0xF) * 0x11; + int g = ((rgb >> 8) & 0xF) * 0x11; + colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b); + } + return new BitmapPalette (colors); + } + } +} diff --git a/Legacy/AyPio/PdtBitmap.cs b/Legacy/AyPio/PdtBitmap.cs new file mode 100644 index 00000000..a56f94a6 --- /dev/null +++ b/Legacy/AyPio/PdtBitmap.cs @@ -0,0 +1,221 @@ +//! \file PdtBitmap.cs +//! \date 2023 Oct 19 +//! \brief UK2 engine compressed bitmap. +// +// 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; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +// [971031][AyPio] Satyr 95 + +namespace GameRes.Formats.AyPio +{ + [Export(typeof(ImageFormat))] + public class PdtBmpFormat : ImageFormat + { + public override string Tag => "PDT/BMP"; + public override string Description => "UK2 engine compressed bitmap"; + public override uint Signature => 0x544450; // 'PDT' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (8); + if (header.ToInt32 (4) != 0x118) + return null; + return new ImageMetaData { Width = 640, Height = 480, BPP = 32 }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var decoder = new PdtBmpDecoder (file, info); + return decoder.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("PdtFormat.Write not implemented"); + } + } + + internal sealed class PdtBmpDecoder + { + IBinaryStream m_input; + ImageMetaData m_info; + + public PdtBmpDecoder (IBinaryStream input, ImageMetaData info) + { + m_input = input; + m_info = info; + } + + int m_unpacked_size; + int m_packed_size; + + public ImageData Unpack () + { + long offset = 0; + var bitmap = UnpackBitmap (offset); + m_info.Width = (uint)bitmap.PixelWidth; + m_info.Height = (uint)bitmap.PixelHeight; + m_info.BPP = bitmap.Format.BitsPerPixel; + offset += m_packed_size; + var signature = m_input.ReadBytes (4); + if (signature.Length != 4 || !signature.AsciiEqual ("PDT\0")) + return new ImageData (bitmap, m_info); + var alpha = UnpackBitmap (offset); + if (alpha.Format != PixelFormats.Gray8) + alpha = new FormatConvertedBitmap (alpha, PixelFormats.Gray8, null, 0); + if (m_info.BPP != 32) + bitmap = new FormatConvertedBitmap (bitmap, PixelFormats.Bgr32, null, 0); + + int stride = m_info.iWidth * 4; + var pixels = new byte[stride * m_info.iHeight]; + bitmap.CopyPixels (pixels, stride, 0); + var rect = new Int32Rect (0, 0, Math.Min (m_info.iWidth, alpha.PixelWidth), + Math.Min (m_info.iHeight, alpha.PixelHeight)); + var a = new byte[m_info.iWidth * m_info.iHeight]; + alpha.CopyPixels (rect, a, m_info.iWidth, 0); + int src = 0; + for (int dst = 3; dst < pixels.Length; dst += 4) + { + pixels[dst] = a[src++]; + } + return ImageData.Create (m_info, PixelFormats.Bgra32, null, pixels, stride); + } + + byte[] m_bits; + byte[] m_output; + + BitmapSource UnpackBitmap (long offset) + { + m_input.Position = offset+8; + m_unpacked_size = m_input.ReadInt32(); + m_packed_size = m_input.ReadInt32(); + long data_offset = m_input.ReadUInt32() + offset; + long bits_offset = m_input.ReadUInt32() + offset; + string name = m_input.ReadCString (0x100); + + if (null == m_output || m_unpacked_size > m_output.Length) + m_output = new byte[m_unpacked_size]; + int bits_length = (int)(data_offset - bits_offset); + if (null == m_bits || bits_length > m_bits.Length) + m_bits = new byte[bits_length+4]; + + m_input.Position = bits_offset; + m_input.Read (m_bits, 0, bits_length); + + m_input.Position = data_offset; + UnpackBits(); + + using (var bmp_input = new BinMemoryStream (m_output, name)) + { + var decoder = new BmpBitmapDecoder (bmp_input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + return decoder.Frames[0]; + } + } + + public void UnpackBits () + { + InitBitReader(); + int dst = 0; + byte last_byte = 0; + while (dst < m_unpacked_size) + { + int ctl = 0; + while (GetNextBit() != 0) + ++ctl; + switch (ctl) + { + case 0: + last_byte = m_output[dst++] = m_input.ReadUInt8(); + break; + case 1: + { + int off = GetInteger(); + int count = GetInteger(); + Binary.CopyOverlapped (m_output, dst - off, dst, count); + dst += count; + break; + } + case 2: + { + int count = GetInteger(); + int step = GetInteger(); + int pos = 0; + for (int i = 0; i < step; i += count) + { + Binary.CopyOverlapped (m_output, dst - count, dst + pos, count); + pos += count * count; + } + dst += count * step; + break; + } + case 3: + m_output[dst++] = last_byte; + break; + } + } + } + + int GetInteger () + { + int i = 0; + while (GetNextBit() != 0) + ++i; + int n = 0; + for (int j = i; j > 0; --j) + { + n = n << 1 | GetNextBit(); + } + return n + (1 << i); + } + + uint m_current_bits; + int m_bit_count; + int m_bit_pos; + + void InitBitReader () + { + m_bit_pos = 0; + m_bit_count = 0; + } + + byte GetNextBit () + { + if (0 == m_bit_count--) + { + m_current_bits = m_bits.ToUInt32 (m_bit_pos); + m_bit_pos += 4; + m_bit_count = 31; + } + uint bit = m_current_bits >> 31; + m_current_bits <<= 1; + return (byte)bit; + } + } +} diff --git a/Legacy/Desire/ArcDSV.cs b/Legacy/Desire/ArcDSV.cs new file mode 100644 index 00000000..7075df32 --- /dev/null +++ b/Legacy/Desire/ArcDSV.cs @@ -0,0 +1,85 @@ +//! \file ArcDSV.cs +//! \date 2023 Oct 15 +//! \brief Desire resource archive (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.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes.Formats.Desire +{ + [Export(typeof(ArchiveFormat))] + public class D000Opener : ArchiveFormat + { + public override string Tag => "000/DESIRE"; + public override string Description => "Desire resource archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + if (!file.Name.HasAnyOfExtensions (".000", ".001", ".002", ".003")) + return null; + if (!IsAscii (file.View.ReadByte (0))) + return null; + uint index_pos = 0; + var dir = new List(); + while (index_pos < file.MaxOffset) + { + byte b = file.View.ReadByte (index_pos); + if (0 == b) + break; + if (!IsAscii (b)) + return null; + var name = file.View.ReadString (index_pos, 0xC); + var entry = Create (name); + entry.Size = file.View.ReadUInt32 (index_pos+0xC); + if (entry.Size >= file.MaxOffset || 0 == entry.Size) + return null; + dir.Add (entry); + index_pos += 0x10; + } + if (index_pos >= file.MaxOffset || file.View.ReadUInt32 (index_pos+0xC) != 0) + return null; + uint offset = index_pos + 0x10; + foreach (var entry in dir) + { + entry.Offset = offset; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + offset += entry.Size; + } + if (offset != file.MaxOffset) + return null; + return new ArcFile (file, this, dir); + } + + static internal bool IsAscii (byte b) + { + return b >= 0x20 && b < 0x7F; + } + } +} diff --git a/Legacy/Desire/ImageDES.cs b/Legacy/Desire/ImageDES.cs new file mode 100644 index 00000000..a2d4baeb --- /dev/null +++ b/Legacy/Desire/ImageDES.cs @@ -0,0 +1,74 @@ +//! \file ImageDES.cs +//! \date 2023 Oct 15 +//! \brief DES98 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.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; + +// [940720][Desire] H+ + +namespace GameRes.Formats.Desire +{ + [Export(typeof(ImageFormat))] + public class DesFormat : ImageFormat + { + public override string Tag => "DES98"; + public override string Description => "Des98 engine image format"; + public override uint Signature => 0; + + public DesFormat () + { + Extensions = new[] { "" }; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + ushort width = Binary.BigEndian (file.ReadUInt16()); + ushort height = Binary.BigEndian (file.ReadUInt16()); + if (0 == width || 0 == height || (width & 7) != 0 || width > 640 || height > 400) + return null; + return new ImageMetaData { + Width = width, + Height = height, + BPP = 4, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + file.Position = 4; + var palette = ReadPalette (file.AsStream, 16, PaletteFormat.Rgb); + var reader = new System98.GraBaseReader (file, info); + reader.UnpackBits(); + return ImageData.Create (info, PixelFormats.Indexed4, palette, reader.Pixels, reader.Stride); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("DesFormat.Write not implemented"); + } + } +} diff --git a/Legacy/Desire/ImageDPC.cs b/Legacy/Desire/ImageDPC.cs new file mode 100644 index 00000000..02cba3c7 --- /dev/null +++ b/Legacy/Desire/ImageDPC.cs @@ -0,0 +1,93 @@ +//! \file ImageDPC.cs +//! \date 2023 Oct 15 +//! \brief Desire 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; + +// [970801][Desire] Yuugiri ~Ningyoushi no Isan~ + +namespace GameRes.Formats.Desire +{ + [Export(typeof(ImageFormat))] + public class DpcFormat : ImageFormat + { + public override string Tag => "DPC"; + public override string Description => "Desire image format"; + public override uint Signature => 0; + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + if (!file.Name.HasExtension (".DPC")) + return null; + file.Position = 0x20; + short left = file.ReadInt16(); + short top = file.ReadInt16(); + ushort width = file.ReadUInt16(); + ushort height = file.ReadUInt16(); + if (0 == width || 0 == height || left < 0 || left + width > 2048 || top < 0 || top + height > 2048) + return null; + return new ImageMetaData { + Width = width, + Height = height, + OffsetX = left, + OffsetY = top, + BPP = 4, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var palette = ReadPalette (file); + file.Position = 0x28; + var reader = new System98.GraBaseReader (file, info); + reader.UnpackBits(); + return ImageData.Create (info, PixelFormats.Indexed4, palette, reader.Pixels, reader.Stride); + } + + BitmapPalette ReadPalette (IBinaryStream input) + { + var colors = new Color[16]; + for (int i = 0; i < 16; ++i) + { + ushort w = input.ReadUInt16(); + int alpha = (w & 1) == 0 ? 0xFF : 0; + int g = (w >> 12) * 0x11; + int r = ((w >> 7) & 0xF) * 0x11; + int b = ((w >> 2) & 0xF) * 0x11; + + colors[i] = Color.FromArgb ((byte)alpha, (byte)r, (byte)g, (byte)b); + } + return new BitmapPalette (colors); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("DpcFormat.Write not implemented"); + } + } +} diff --git a/Legacy/Legacy.csproj b/Legacy/Legacy.csproj index 14f41585..ca12d073 100644 --- a/Legacy/Legacy.csproj +++ b/Legacy/Legacy.csproj @@ -92,11 +92,17 @@ + + + + + + @@ -147,6 +153,7 @@ + @@ -155,6 +162,8 @@ + + @@ -234,14 +243,18 @@ + + + + diff --git a/Legacy/Mapl/ImageMI2.cs b/Legacy/Mapl/ImageMI2.cs new file mode 100644 index 00000000..38db3d88 --- /dev/null +++ b/Legacy/Mapl/ImageMI2.cs @@ -0,0 +1,438 @@ +//! \file ImageMI2.cs +//! \date 2023 Oct 19 +//! \brief Mapl engine image format. +// +// 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; + +// [980130][Pias] Rashin + +namespace GameRes.Formats.Mapl +{ + internal class Mi2MetaData : ImageMetaData + { + public int Colors; + } + + [Export(typeof(ImageFormat))] + public class Mi2Format : ImageFormat + { + public override string Tag => "MI2"; + public override string Description => "Mapl engine image format"; + public override uint Signature => 0x28; + + public Mi2Format () + { + Extensions = new[] { "mi2", "fcg" }; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x28); // BITMAPINFOHEADER + int bpp = header.ToUInt16 (0xE); + if (bpp != 8 && bpp != 24) + return null; + uint width = header.ToUInt32 (4); + uint height = header.ToUInt32 (8); + int colors = header.ToInt32 (0x20); + if (8 == bpp) + { + if (colors < 0 || colors > 0x100) + return null; + if (0 == colors) + colors = 0x100; + } + return new Mi2MetaData { + Width = width, + Height = height, + BPP = bpp, + Colors = colors, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new Mi2Reader (file, (Mi2MetaData)info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("Mi2Format.Write not implemented"); + } + } + + internal class Mi2Reader + { + IBinaryStream m_input; + Mi2MetaData m_info; + + public Mi2Reader (IBinaryStream file, Mi2MetaData info) + { + m_input = file; + m_info = info; + } + + byte[] m_block = new byte[64]; + byte[] m_buffer = new byte[32]; + + int stride; + int block_stride; + int blocksW; + int blocksH; + + public ImageData Unpack () + { + stride = (m_info.iWidth + 7) & ~7; + block_stride = stride * 8; + blocksW = m_info.iWidth >> 3; + if ((m_info.iWidth & 7) != 0) + blocksW++; + blocksH = m_info.iHeight >> 3; + if ((m_info.iHeight & 7) != 0) + blocksH++; + var channel = new byte[stride * m_info.iHeight]; + m_input.Position = 0x28; + if (8 == m_info.BPP) + { + var palette = ImageFormat.ReadPalette (m_input.AsStream, m_info.Colors); + UnpackChannel (channel); + return ImageData.Create (m_info, PixelFormats.Indexed8, palette, channel, stride); + } + else + { + int bgr_stride = m_info.iWidth * 3; + var bgr = new byte[bgr_stride * m_info.iHeight]; + for (int c = 0; c < 3; ++c) + { + UnpackChannel (channel); + int src = 0; + int dst = c; + for (int y = 0; y < m_info.iHeight; ++y) + { + for (int x = 0; x < m_info.iWidth; ++x) + { + bgr[dst] = channel[src+x]; + dst += 3; + } + src += stride; + } + } + return ImageData.Create (m_info, PixelFormats.Bgr24, null, bgr, bgr_stride); + } + } + + void UnpackChannel (byte[] output) + { + int dst_row = output.Length - stride; + for (int y = 0; y < blocksH; ++y) + { + int dst_pos = dst_row; + for (int x = 0; x < blocksW; ++x) + { + byte ctl = m_input.ReadUInt8(); + switch (ctl) + { + case 0: + m_input.Read (m_block, 0, m_block.Length); + break; + case 1: + { + byte b = m_input.ReadUInt8(); + for (int i = 0; i < m_block.Length; ++i) + m_block[i] = b; + break; + } + case 2: + Op2(); + break; + case 3: + Op3(); + break; + case 4: + Op4(); + break; + case 5: + Op5(); + break; + case 6: + Op6(); + break; + case 7: + Op7(); + break; + case 8: + Op8(); + AdjustBlock(); + break; + case 9: + Op9(); + AdjustBlock(); + break; + case 10: + Op4(); + AdjustBlock(); + break; + case 11: + Op5(); + AdjustBlock(); + break; + case 12: + Op6(); + AdjustBlock(); + break; + case 13: + Op7(); + AdjustBlock(); + break; + default: + throw new InvalidFormatException(); + } + int dst = dst_pos; + int src = 0; + while (src < m_block.Length && dst >= 0) + { + Buffer.BlockCopy (m_block, src, output, dst, 8); + src += 8; + dst -= stride; + } + dst_pos += 8; + } + dst_row -= block_stride; + } + } + + void Op2 () + { + byte b = m_input.ReadUInt8(); + m_input.Read (m_buffer, 0, 8); + int pos = 0; + for (int i = 0; i < 8; ++i) + { + byte mask = 0x80; + for (int j = 0; j < 8; ++j) + { + if ((mask & m_buffer[i]) != 0) + m_block[pos++] = b; + else + m_block[pos++] = m_input.ReadUInt8(); + mask >>= 1; + } + } + } + + void Op3 () + { + int pos = 0; + byte b1 = m_input.ReadUInt8(); + byte b2 = m_input.ReadUInt8(); + for (int i = 0; i < 8; ++i) + { + byte mask = 0x80; + byte b = m_input.ReadUInt8(); + for (int j = 0; j < 8; ++j) + { + if ((mask & b) != 0) + m_block[pos++] = b2; + else + m_block[pos++] = b1; + mask >>= 1; + } + } + } + + void Op4() + { + byte b1 = m_input.ReadUInt8(); + byte b2 = m_input.ReadUInt8(); + byte b3 = m_input.ReadUInt8(); + int dst = 0; + m_input.Read (m_buffer, 0, 16); + for (int i = 0; i < 16; ++i) + { + byte bits = m_buffer[i]; + for (int j = 0; j < 4; ++j) + { + switch (bits >> 6) + { + case 0: m_block[dst] = m_input.ReadUInt8(); break; + case 1: m_block[dst] = b1; break; + case 2: m_block[dst] = b2; break; + case 3: m_block[dst] = b3; break; + } + dst++; + bits <<= 2; + } + } + } + + void Op5() + { + byte b0 = m_input.ReadUInt8(); + byte b1 = m_input.ReadUInt8(); + byte b2 = m_input.ReadUInt8(); + byte b3 = m_input.ReadUInt8(); + m_input.Read (m_buffer, 0, 16); + int dst = 0; + for (int i = 0; i < 16; ++i) + { + byte bits = m_buffer[i]; + for (int j = 0; j < 4; ++j) + { + switch (bits >> 6) + { + case 0: m_block[dst] = b0; break; + case 1: m_block[dst] = b1; break; + case 2: m_block[dst] = b2; break; + case 3: m_block[dst] = b3; break; + } + dst++; + bits <<= 2; + } + } + } + + byte[] m_buffer2 = new byte[0x100]; + byte[] m_buffer6 = new byte[0x40]; + + void Op6() + { + int count = m_input.ReadUInt8(); + m_input.Read (m_buffer2, 0, count); + m_input.Read (m_buffer, 0, 24); + for (int i = 0; i < m_buffer6.Length; ++i) + m_buffer6[i] = 0; + int buf_pos = 0; + int bits = 0; + int bit_count = 0; + int bit_src = 0; + byte mask = 0x80; + for (int i = 0; i < 64; ++i) + { + byte n0 = 0; + byte n1 = 4; + for (int j = 0; j < 3; ++j) + { + if (0 == bit_count) + { + bits = m_buffer[bit_src++]; + bit_count = 8; + } + if ((bits & mask) != 0) + n0 |= n1; + --bit_count; + n1 >>= 1; + mask = Binary.RotByteR (mask, 1); + } + m_buffer6[buf_pos++] = n0; + } + for (int i = 0; i < 64; ++i) + { + byte b = m_buffer6[i]; + if (b > 0) + m_block[i] = m_buffer2[b-1]; + else + m_block[i] = m_input.ReadUInt8(); + } + } + + void Op7() + { + int count = m_input.ReadUInt8(); + m_input.Read (m_buffer2, 0, count); + m_input.Read (m_buffer, 0, 32); + int dst = 0; + for (int i = 0; i < 32; ++i) + { + byte b = m_buffer[i]; + for (int j = 0; j < 2; ++j) + { + int bits = b >> 4; + b <<= 4; + if (bits != 0) + m_block[dst++] = m_buffer2[bits-1]; + else + m_block[dst++] = m_input.ReadUInt8(); + } + } + } + + void Op8() + { + byte b0 = m_input.ReadUInt8(); + m_input.Read (m_buffer, 0, 8); + int src = 0; + int dst = 0; + for (int i = 0; i < 8; ++i) + { + byte mask = 0x80; + byte b = m_buffer[src++]; + for (int j = 0; j < 8; ++j) + { + if ((mask & b) != 0) + m_block[dst++] = b0; + else + m_block[dst++] = m_input.ReadUInt8(); + mask >>= 1; + } + } + } + + void Op9() + { + byte b0 = m_input.ReadUInt8(); + byte b1 = m_input.ReadUInt8(); + int dst = 0; + for (int i = 0; i < 8; ++i) + { + byte mask = 0x80; + byte b = m_input.ReadUInt8(); + for (int j = 0; j < 8; ++j) + { + if ((mask & b) != 0) + m_block[dst++] = b1; + else + m_block[dst++] = b0; + mask >>= 1; + } + } + } + + void AdjustBlock() + { + int dst = 0; + for (int i = 0; i < 8; ++i) + { + m_input.ReadByte(); + for (int j = 0; j < 8; ++j) + { + m_block[dst++] <<= 1; + } + } + } + } +} diff --git a/Legacy/Neon/ArcAR2.cs b/Legacy/Neon/ArcAR2.cs new file mode 100644 index 00000000..4ce6b765 --- /dev/null +++ b/Legacy/Neon/ArcAR2.cs @@ -0,0 +1,90 @@ +//! \file ArcAR2.cs +//! \date 2023 Oct 17 +//! \brief Neon 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; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +// [990527][Neon] Onegai! Maid☆Roid + +namespace GameRes.Formats.Neon +{ + [Export(typeof(ArchiveFormat))] + public class Ar2Opener : ArchiveFormat + { + public override string Tag => "AR2/NEON"; + public override string Description => "Neon resource archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + const byte DefaultKey = 0x55; + + public override ArcFile TryOpen (ArcView file) + { + uint uKey = DefaultKey | DefaultKey << 16; + uKey |= uKey << 8; + if (file.MaxOffset <= 0x10 + || file.View.ReadUInt32 (8) != uKey + || file.View.ReadUInt32 (0) != file.View.ReadUInt32 (4) + || (file.View.ReadUInt32 (0xC) ^ uKey) > 0x100) + return null; + using (var stream = file.CreateStream()) + using (var input = new XoredStream (stream, DefaultKey)) + { + var buffer = new byte[0x100]; + var dir = new List(); + while (0x10 == input.Read (buffer, 0, 0x10)) + { + uint size = buffer.ToUInt32 (0); +// uint orig_size = buffer.ToUInt32 (4); // original size? +// uint extra = buffer.ToUInt32 (8); // header size/compression? + int name_length = buffer.ToInt32 (0xC); + if (0 == size && 0 == name_length) + continue; + if (name_length <= 0 || name_length > buffer.Length) + return null; + input.Read (buffer, 0, name_length); + var name = Encodings.cp932.GetString (buffer, 0, name_length); + var entry = Create (name); + entry.Offset = input.Position; + entry.Size = size; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + input.Seek (size, SeekOrigin.Current); + } + return new ArcFile (file, this, dir); + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var input = arc.File.CreateStream (entry.Offset, entry.Size); + return new XoredStream (input, DefaultKey); + } + } +} diff --git a/Legacy/Pias/ArcDAT.cs b/Legacy/Pias/ArcDAT.cs index e5c59263..231e58a9 100644 --- a/Legacy/Pias/ArcDAT.cs +++ b/Legacy/Pias/ArcDAT.cs @@ -2,7 +2,7 @@ //! \date 2022 May 24 //! \brief Pias resource archive. // -// Copyright (C) 2022 by morkt +// Copyright (C) 2022-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 @@ -36,77 +36,136 @@ using System.Windows.Media; namespace GameRes.Formats.Pias { + enum ResourceType + { + Undefined = -1, + Graphics = 1, + Sound = 2, + } + + internal class IndexReader + { + internal const bool NamesAsHexOffset = true; + + protected ArcView m_arc; + protected ResourceType m_res; + protected List m_dir; + + public bool IsEncrypted { get; protected set; } + + public IndexReader (ArcView arc, ResourceType res) + { + m_arc = arc; + m_res = res; + m_dir = null; + } + + public List GetIndex () + { + if (m_res > 0) + { + var text_name = VFS.ChangeFileName (m_arc.Name, "text.dat"); + if (!VFS.FileExists (text_name)) + return null; + IBinaryStream input = VFS.OpenBinaryStream (text_name); + try + { + if (DatOpener.EncryptedSignatures.Contains (input.Signature)) + return null; + var reader = new TextReader (input); + m_dir = reader.GetResourceList ((int)m_res); + } + finally + { + input.Dispose(); + } + } + if (null == m_dir) + m_dir = new List(); + if (!FillEntries()) + return null; + return m_dir; + } + + protected bool FillEntries () + { + uint header_size = 4; + string entry_type = "audio"; + if (ResourceType.Graphics == m_res) + { + header_size = 8; + entry_type = "image"; + } + for (int i = m_dir.Count - 1; i >= 0; --i) + { + var entry = m_dir[i]; + entry.Size = m_arc.View.ReadUInt32 (entry.Offset) + header_size; + entry.Name = i.ToString("D4"); + entry.Type = entry_type; + } + var known_offsets = new HashSet (m_dir.Select (e => e.Offset)); + long offset = 0; + while (offset < m_arc.MaxOffset) + { + uint entry_size = m_arc.View.ReadUInt32(offset); + if (uint.MaxValue == entry_size) + { + entry_size = 4; + } + else + { + entry_size += header_size; + if (!known_offsets.Contains (offset)) + { + var entry = new Entry { + Name = NamesAsHexOffset ? offset.ToString ("X8") : m_dir.Count.ToString("D4"), + Type = entry_type, + Offset = offset, + Size = entry_size, + }; + if (!entry.CheckPlacement (m_arc.MaxOffset)) + return false; + m_dir.Add (entry); + } + } + offset += entry_size; + } + return true; + } + } + [Export(typeof(ArchiveFormat))] public class DatOpener : ArchiveFormat { - public override string Tag { get { return "DAT/PIAS"; } } - public override string Description { get { return "Pias resource archive"; } } - public override uint Signature { get { return 0; } } - public override bool IsHierarchic { get { return false; } } - public override bool CanWrite { get { return false; } } + public override string Tag => "DAT/PIAS"; + public override string Description => "Pias resource archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public DatOpener () + { + Signatures = new[] { 0x0002C026u, 0u }; + } + + internal static readonly HashSet EncryptedSignatures = new HashSet { 0x03184767u }; public override ArcFile TryOpen (ArcView file) { var arc_name = Path.GetFileName (file.Name).ToLowerInvariant(); - string entry_type = null; - if ("voice.dat" == arc_name || "music.dat" == arc_name || "sound.dat" == arc_name) - entry_type = "audio"; + ResourceType resource_type = ResourceType.Undefined; + if ("sound.dat" == arc_name) + resource_type = ResourceType.Sound; else if ("graph.dat" == arc_name) - entry_type = "image"; - else + resource_type = ResourceType.Graphics; + else if ("voice.dat" != arc_name && "music.dat" != arc_name) return null; - int resource_type = -1; - if ("graph.dat" == arc_name) - resource_type = 1; - else if ("sound.dat" == arc_name) - resource_type = 2; - - uint header_size = 1 == resource_type ? 8u : 4u; - List dir = null; - if (resource_type > 0) - { - var text_name = VFS.ChangeFileName (file.Name, "text.dat"); - if (!VFS.FileExists (text_name)) - return null; - using (var text_dat = VFS.OpenBinaryStream (text_name)) - { - var reader = new TextReader (text_dat); - dir = reader.GetResourceList (resource_type); - if (dir != null) - { - for (int i = dir.Count - 1; i >= 0; --i) - { - var entry = dir[i]; - entry.Size = file.View.ReadUInt32 (entry.Offset) + header_size; - entry.Name = i.ToString("D4"); - entry.Type = entry_type; - } - } - } - } + var index = new IndexReader (file, resource_type); + var dir = index.GetIndex(); if (null == dir) - dir = new List(); - var known_offsets = new HashSet (dir.Select (e => e.Offset)); - long offset = 0; - while (offset < file.MaxOffset) - { - uint entry_size = file.View.ReadUInt32(offset) + header_size; - if (!known_offsets.Contains (offset)) - { - var entry = new Entry { - Name = dir.Count.ToString("D4"), - Type = entry_type, - Offset = offset, - Size = entry_size, - }; - if (!entry.CheckPlacement (file.MaxOffset)) - return null; - dir.Add (entry); - } - offset += entry_size; - } + return null; return new ArcFile (file, this, dir); } @@ -120,7 +179,6 @@ namespace GameRes.Formats.Pias { if (entry.Type != "audio") return base.OpenEntry (arc, entry); - uint size = arc.File.View.ReadUInt32 (entry.Offset); var format = new WaveFormat { FormatTag = 1, @@ -138,6 +196,12 @@ namespace GameRes.Formats.Pias format.BlockAlign = 1; } format.SetBPS(); + return OpenAudioEntry (arc, entry, format); + } + + public Stream OpenAudioEntry (ArcFile arc, Entry entry, WaveFormat format) + { + uint size = arc.File.View.ReadUInt32 (entry.Offset); byte[] header; using (var buffer = new MemoryStream()) { diff --git a/Legacy/Pias/EncryptedGraphDat.cs b/Legacy/Pias/EncryptedGraphDat.cs new file mode 100644 index 00000000..560755a2 --- /dev/null +++ b/Legacy/Pias/EncryptedGraphDat.cs @@ -0,0 +1,377 @@ +//! \file EncryptedGraphDat.cs +//! \date 2023 Oct 20 +//! \brief Pias encrypted 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; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Windows.Media; + +// [000526][Pias] Ningyou no Hako + +namespace GameRes.Formats.Pias +{ + internal class PiasEncryptedArchive : ArcFile + { + public PiasEncryptedArchive (ArcView arc, ArchiveFormat impl, ICollection dir) + : base (arc, impl, dir) + { + } + } + + internal class EncryptedIndexReader : IndexReader + { + public EncryptedIndexReader (ArcView arc, ResourceType res) : base (arc, res) + { + } + + new public List GetIndex () + { + if (m_res > 0) + { + var text_name = VFS.ChangeFileName (m_arc.Name, "text.dat"); + if (!VFS.FileExists (text_name)) + return null; + IBinaryStream input = VFS.OpenBinaryStream (text_name); + try + { + if (!DatOpener.EncryptedSignatures.Contains (input.Signature)) + return null; + + input.Position = 4; + var rnd = new KeyGenerator (1); + rnd.Seed (input.Signature); + var crypto = new InputCryptoStream (input.AsStream, new PiasTransform (rnd)); + input = new BinaryStream (crypto, text_name); + + var reader = new TextReader (input); + m_dir = reader.GetResourceList ((int)m_res); + } + finally + { + input.Dispose(); + } + } + IsEncrypted = ResourceType.Graphics == m_res; + if (null == m_dir) + { + m_dir = new List(); + } + if (!IsEncrypted) + { + if (!FillEntries()) + return null; + return m_dir; + } + var buffer = new byte[4]; + var key = new KeyGenerator (0); + for (int i = m_dir.Count - 1; i >= 0; --i) + { + var entry = m_dir[i]; + uint seed = m_arc.View.ReadUInt32 (entry.Offset); + m_arc.View.Read (entry.Offset+4, buffer, 0, 4); + key.Seed (seed); + Decrypt (buffer, 0, 4, key); + entry.Size = (buffer.ToUInt32 (0) & 0xFFFFFu) + 8u; + entry.Name = NamesAsHexOffset ? entry.Offset.ToString ("X8") : i.ToString("D4"); + entry.Type = "image"; + } + var known_offsets = new HashSet (m_dir.Select (e => e.Offset)); + long offset = 0; + while (offset < m_arc.MaxOffset) + { + uint seed = m_arc.View.ReadUInt32 (offset); + m_arc.View.Read (offset+4, buffer, 0, 4); + key.Seed (seed); + Decrypt (buffer, 0, 4, key); + uint entry_size = (buffer.ToUInt32 (0) & 0xFFFFFu) + 8u; + if (!known_offsets.Contains (offset)) + { + var entry = new Entry { + Name = (NamesAsHexOffset ? offset.ToString ("X8") : m_dir.Count.ToString("D4")) + "_", + Type = "image", + Offset = offset, + Size = entry_size, + }; + if (!entry.CheckPlacement (m_arc.MaxOffset)) + return null; + m_dir.Add (entry); + } + offset += entry_size + 4; + } + return m_dir; + } + + internal static void Decrypt (byte[] data, int pos, int length, KeyGenerator key) + { + for (int i = 0; i < length; ++i) + { + data[pos+i] ^= (byte)key.Next(); + } + } + } + + [Export(typeof(ArchiveFormat))] + public class EncryptedDatOpener : DatOpener + { + public override string Tag => "DAT/PIAS/ENC"; + public override string Description => "Pias encrypted resource archive"; + public override uint Signature => 0; + public override bool CanWrite => false; + + public EncryptedDatOpener () + { + Signatures = new[] { 0x02F3A62Bu, 0u }; + } + + public override ArcFile TryOpen (ArcView file) + { + var arc_name = Path.GetFileName (file.Name).ToLowerInvariant(); + + ResourceType resource_type = ResourceType.Undefined; + if ("sound.dat" == arc_name) + resource_type = ResourceType.Sound; + else if ("graph.dat" == arc_name) + resource_type = ResourceType.Graphics; + else + return null; + + var index = new EncryptedIndexReader (file, resource_type); + var dir = index.GetIndex(); + if (null == dir) + return null; + if (index.IsEncrypted) + return new PiasEncryptedArchive (file, this, dir); + else + return new ArcFile (file, this, dir); + } + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var input = arc.OpenBinaryEntry (entry); + return new EncryptedGraphDecoder (input); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + if (entry.Type != "audio") + return OpenEncrypted (arc, entry); + var format = new WaveFormat + { + FormatTag = 1, + Channels = 2, + SamplesPerSecond = 22050, + AverageBytesPerSecond = 88200, + BitsPerSample = 16, + BlockAlign = 4, + }; + return OpenAudioEntry (arc, entry, format); + } + + public Stream OpenEncrypted (ArcFile arc, Entry entry) + { + uint seed = arc.File.View.ReadUInt32 (entry.Offset); + var stream = arc.File.CreateStream (entry.Offset+4, entry.Size); + var key = new KeyGenerator (0); + key.Seed (seed); + return new InputCryptoStream (stream, new PiasTransform (key)); + } + } + + internal class KeyGenerator + { + int m_type; + uint m_seed; + + // 0 -> graph.dat + // 1 -> text.dat + // 2 -> save.dat + + public KeyGenerator (int type) + { + m_type = type; + m_seed = 0; + } + + public void Seed (uint seed) + { + m_seed = seed; + } + + public uint Next () + { + uint y, x; + if (0 == m_type) + { + x = 0xD22; + y = 0x849; + } + else if (1 == m_type) + { + x = 0xF43; + y = 0x356B; + } + else if (2 == m_type) + { + x = 0x292; + y = 0x57A7; + } + else + { + x = 0; + y = 0; + } + uint a = x + m_seed * y; + uint b = 0; + if ((a & 0x400000) != 0) + b = 1; + if ((a & 0x400) != 0) + b ^= 1; + if ((a & 1) != 0) + b ^= 1; + m_seed = (a >> 1) | (b != 0 ? 0x80000000u : 0u); + return m_seed; + } + } + + internal sealed class PiasTransform : ByteTransform + { + KeyGenerator m_key; + + public PiasTransform (KeyGenerator key) + { + m_key = key; + } + + public override int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, + byte[] outputBuffer, int outputOffset) + { + for (int i = 0; i < inputCount; ++i) + { + outputBuffer[outputOffset++] = (byte)(m_key.Next() ^ inputBuffer[inputOffset+i]); + } + return inputCount; + } + } + + internal class EncryptedGraphDecoder : BinaryImageDecoder + { + public EncryptedGraphDecoder (IBinaryStream input) : base (input, new ImageMetaData { BPP = 16 }) + { + m_input.ReadInt32(); // skip size + Info.Width = m_input.ReadUInt16() & 0x3FFu; + Info.Height = m_input.ReadUInt16() & 0x3FFu; + } + + protected override ImageData GetImageData () + { + m_input.Position = 8; + int width = Info.iWidth; + int output_size = width * Info.iHeight; + var pixels = new ushort[output_size]; + var prev = new int[8]; + int dst = 0; + while (dst < output_size) + { + int count; + ushort w = m_input.ReadUInt16(); + if ((w & 0x2000) != 0) + { + count = (((w >> 1) & 0x6000 | w & 0x1000) >> 12) + 1; + int idx = prev[count - 1]++ % 19; + int off = w & 0xFFF; + bool step_back = (off & StepBackMask[idx]) != 0; + bool step_vertical = (off & StepVerticalMask[idx]) != 0; + int m = (off & OffsetMask0[idx]) | (off >> 1) & (OffsetMask1[idx] >> 1) | (off >> 2) & (OffsetMask2[idx] >> 2); + int n = 16 - width * ((m + 16) / 32); + int p = m + 16; + int hidword = p >> 31; + p = (p & ~0xFF) | ((hidword & 0xFF) ^ (m + 16)); + int src = dst + n - (hidword ^ ((p - hidword) & 0x1F) - hidword); + count = Math.Min (count, output_size - dst); + if (step_vertical) + { + if (step_back) + { + for (int i = 0; i < count; ++i) + { + pixels[dst+i] = pixels[src]; + src -= width; + } + } + else + { + int step = width; + for (int i = 0; i < count; ++i) + { + pixels[dst+i] = pixels[src]; + src += width; + } + } + } + else if (step_back) + { + for (int i = 0; i < count; ++i) + { + pixels[dst+i] = pixels[src--]; + } + } + else + { + for (int i = 0; i < count; ++i) + { + pixels[dst+i] = pixels[src++]; + } + } + } + else + { + pixels[dst] = (ushort)((w >> 1) & 0x6000 | w & 0x1FFF); + count = 1; + } + dst += count; + } + int stride = width * 2; + return ImageData.Create (Info, PixelFormats.Bgr555, null, pixels, stride); + } + + static readonly ushort[] OffsetMask2 = { + 0, 0x800, 0x0C00, 0x0E00, 0x800, 0x0FC0, 0, 0x0F00, 0x0FF0, 0x0FF0, 0x0C00, 0x0F00, 0x800, 0x0E00, 0x0F00, 0x0C00, 0x0C00, 0x0F80, 0, + }; + static readonly ushort[] OffsetMask1 = { + 0, 0, 0, 0x0F0, 0x200, 0x18, 0x7C0, 0x7E, 0, 0, 0x1FE, 0x7E, 0x3E0, 0x0C0, 0x78, 0x1F0, 0, 0x30, 0x7E0, + }; + static readonly ushort[] OffsetMask0 = { + 0x3FF, 0x1FF, 0x0FF, 7, 0x0FF, 3, 0x1F, 0, 3, 3, 0, 0, 0x0F, 0x1F, 3, 7, 0x0FF, 7, 0x0F, + }; + static readonly ushort[] StepBackMask = { + 0x800, 0x400, 0x100, 8, 0x400, 0x20, 0x20, 0x80, 4, 8, 0x200, 1, 0x10, 0x100, 4, 0x200, 0x100, 0x40, 0x800, + }; + static readonly ushort[] StepVerticalMask = { + 0x400, 0x200, 0x200, 0x100, 0x100, 4, 0x800, 1, 8, 4, 1, 0x80, 0x400, 0x20, 0x80, 8, 0x200, 8, 0x10, + }; + } +} diff --git a/Legacy/Properties/AssemblyInfo.cs b/Legacy/Properties/AssemblyInfo.cs index 99ace8d5..59d342b0 100644 --- a/Legacy/Properties/AssemblyInfo.cs +++ b/Legacy/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion ("1.0.10.210")] -[assembly: AssemblyFileVersion ("1.0.10.210")] +[assembly: AssemblyVersion ("1.0.10.212")] +[assembly: AssemblyFileVersion ("1.0.10.212")] diff --git a/Legacy/System98/ArcLIB.cs b/Legacy/System98/ArcLIB.cs new file mode 100644 index 00000000..e2717ab0 --- /dev/null +++ b/Legacy/System98/ArcLIB.cs @@ -0,0 +1,141 @@ +//! \file ArcLIB.cs +//! \date 2023 Oct 15 +//! \brief System-98 resource archive (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.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes.Formats.System98 +{ + [Export(typeof(ArchiveFormat))] + public class LibOpener : ArchiveFormat + { + public override string Tag => "LIB/SYSTEM98"; + public override string Description => "System-98 engine resource archive"; + public override uint Signature => 0x3062694C; // 'Lib0' + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + var cat_name = Path.ChangeExtension (file.Name, ".CAT"); + if (!VFS.FileExists (cat_name)) + return null; + int count; + byte[] index; + using (var cat = VFS.OpenView (cat_name)) + { + count = cat.View.ReadInt16 (4); + if (!IsSaneCount (count)) + return null; + int index_size = count * 0x16; + if (cat.View.AsciiEqual (0, "Cat0")) + { + index = file.View.ReadBytes (6, (uint)index_size); + } + else if (cat.View.AsciiEqual (0, "Cat1")) + { + index = new byte[index_size]; + using (var input = cat.CreateStream (6)) + LzssUnpack (input, index); + } + else + return null; + } + int pos = 0; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = Binary.GetCString (index, pos, 0xC).TrimEnd(); + var entry = Create (name); + entry.Size = index.ToUInt32 (pos+0xE); + entry.Offset = index.ToUInt32 (pos+0x12); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + entry.IsPacked = index[pos+0xC] != 0; + dir.Add (entry); + pos += 0x16; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var pent = entry as PackedEntry; + if (null == pent || !pent.IsPacked) + return base.OpenEntry (arc, entry); + if (pent.UnpackedSize == 0) + pent.UnpackedSize = arc.File.View.ReadUInt32 (entry.Offset+6); + var data = new byte[pent.UnpackedSize]; + using (var input = arc.File.CreateStream (entry.Offset+10, entry.Size-10)) + { + int length = LzssUnpack (input, data); + return new BinMemoryStream (data, 0, length, entry.Name); + } + } + + internal static int LzssUnpack (IBinaryStream input, byte[] output) + { + var frame = new byte[0x1000]; + int frame_pos = 1; + int ctl = 0; + byte mask = 0; + int dst = 0; + while (dst < output.Length) + { + mask <<= 1; + if (0 == mask) + { + ctl = input.ReadByte(); + if (-1 == ctl) + break; + mask = 1; + } + if (input.PeekByte() == -1) + break; + if ((ctl & mask) != 0) + { + output[dst++] = frame[frame_pos++ & 0xFFF] = input.ReadUInt8(); + } + else + { + int lo = input.ReadByte(); + int hi = input.ReadByte(); + if (-1 == hi) + break; + int count = (lo & 0xF) + 3; + int off = hi << 4 | lo >> 4; + while (count --> 0) + { + byte b = frame[off++ & 0xFFF]; + output[dst++] = frame[frame_pos++ & 0xFFF] = b; + } + } + } + return dst; + } + } +} diff --git a/Legacy/System98/ImageG.cs b/Legacy/System98/ImageG.cs new file mode 100644 index 00000000..d17c2550 --- /dev/null +++ b/Legacy/System98/ImageG.cs @@ -0,0 +1,335 @@ +//! \file ImageG.cs +//! \date 2023 Oct 15 +//! \brief System-98 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; + +// [951216][Four-Nine] Lilith + +namespace GameRes.Formats.System98 +{ + [Export(typeof(ImageFormat))] + public class GFormat : ImageFormat + { + public override string Tag => "G/SYSTEM98"; + public override string Description => "System-98 engine image format"; + public override uint Signature => 0; + + public GFormat () + { + Extensions = new[] { "g", "" }; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + if (file.Length < 61) + return null; + var header = file.ReadHeader (0xA); + ushort width = Binary.BigEndian (header.ToUInt16 (6)); + ushort height = Binary.BigEndian (header.ToUInt16 (8)); + if (0 == width || 0 == height || (width & 7) != 0 || width > 640 || height > 400) + return null; + return new ImageMetaData { + Width = width, + Height = height, + BPP = 4, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + file.Position = 0xA; + var palette = ReadPalette (file.AsStream, 16, PaletteFormat.Rgb); + var reader = new GraBaseReader (file, info); + reader.UnpackBits(); + return ImageData.Create (info, PixelFormats.Indexed4, palette, reader.Pixels, reader.Stride); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("GFormat.Write not implemented"); + } + } + + internal class GraBaseReader + { + protected IBinaryStream m_input; + protected ImageMetaData m_info; + protected int m_output_stride; + protected byte[] m_pixels; + + public byte[] Pixels => m_pixels; + public int Stride => m_output_stride; + + public GraBaseReader (IBinaryStream file, ImageMetaData info) + { + m_input = file; + m_info = info; + m_output_stride = m_info.iWidth >> 1; + m_pixels = new byte[m_output_stride * m_info.iHeight]; + } + + ushort[] m_buffer; + + public void UnpackBits () + { + try + { + UnpackBitsInternal(); + } + catch (EndOfStreamException) + { + FlushBuffer(); + } + } + + void UnpackBitsInternal () + { + int width = m_info.iWidth; + int wTimes2 = width << 1; + int wTimes4 = width << 2; + int buffer_size = wTimes4 + wTimes2; + m_buffer = new ushort[buffer_size >> 1]; + m_dst = 0; + InitFrame(); + InitBitReader(); + ushort p = ReadPair (0); + for (int i = 0; i < width; ++i) + m_buffer[i] = p; + int dst = wTimes2; + int prev_src = 0; + while (m_dst < m_pixels.Length) + { + bool same_line = false; + int src = -width; + if (GetNextBit() != 0) + { + if (GetNextBit() == 0) + src <<= 1; + else if (GetNextBit() == 0) + src += 1; + else + src -= 1; + } + else if (GetNextBit() == 0) + { + src = -4; + p = m_buffer[dst/2-1]; + if ((p & 0xFF) == (p >> 8)) + same_line = src != prev_src; + } + if (src != prev_src) + { + prev_src = src; + if (!same_line) + src += dst; + else + src = dst - 2; + if (GetNextBit() != 0) + { + int bitlength = 0; + do + { + ++bitlength; + } + while (GetNextBit() != 0); + int count = 1; + while (bitlength --> 0) + count = count << 1 | GetNextBit(); + int remaining = (buffer_size - dst) >> 1; + while (count > remaining) + { + count -= remaining; + MovePixels (m_buffer, src, dst, remaining); + src += remaining << 1; + if (FlushBuffer()) + return; + dst = wTimes2; + src -= wTimes4; + remaining = wTimes4 >> 1; + } + MovePixels (m_buffer, src, dst, count); + dst += count << 1; + if (dst == buffer_size) + { + if (FlushBuffer()) + return; + dst = wTimes2; + } + } + else + { + MovePixels (m_buffer, src, dst, 1); + dst += 2; + if (dst == buffer_size) + { + if (FlushBuffer()) + return; + dst = wTimes2; + } + } + } + else + { + p = m_buffer[dst/2-1]; + do + { + byte prev = (byte)(p >> 8); + p = ReadPair (prev); + m_buffer[dst >> 1] = p; + dst += 2; + if (dst == buffer_size) + { + if (FlushBuffer()) + return; + dst = wTimes2; + } + } + while (GetNextBit() != 0); + prev_src = 0; + } + } + } + + int m_dst; + + bool FlushBuffer () + { + MovePixels (m_buffer, m_info.iWidth * 4, 0, m_info.iWidth); + int src = m_info.iWidth; + int count = Math.Min (m_info.iWidth << 1, m_pixels.Length - m_dst); + while (count --> 0) + { + ushort p = m_buffer[src++]; + m_pixels[m_dst++] = (byte)((p & 0xF0) | p >> 12); + } + return m_dst == m_pixels.Length; + } + + ushort ReadPair (int pos) + { + byte al = ReadPixel (pos); + byte ah = ReadPixel (al); + return (ushort)(al | ah << 8); + } + + byte ReadPixel (int pos) + { + byte px = 0; + if (GetNextBit() == 0) + { + int count = 1; + if (GetNextBit() != 0) + { + if (GetNextBit() != 0) + { + count = count << 1 | GetNextBit(); + } + count = count << 1 | GetNextBit(); + } + count = count << 1 | GetNextBit(); + pos += count; + px = m_frame[pos--]; + while (count --> 0) + { + m_frame[pos+1] = m_frame[pos]; + --pos; + } + m_frame[pos+1] = px; + } + else if (GetNextBit() == 0) + { + px = m_frame[pos]; + } + else + { + px = m_frame[pos+1]; + m_frame[pos+1] = m_frame[pos]; + m_frame[pos] = px; + } + return px; + } + + byte[] m_frame; + + void InitFrame () + { + m_frame = new byte[0x100]; + int p = 0; + byte a = 0; + for (int j = 0; j < 0x10; ++j) + { + for (int i = 0; i < 0x10; ++i) + { + m_frame[p++] = a; + a -= 0x10; + } + a += 0x10; + } + } + + void MovePixels (ushort[] pixels, int src, int dst, int count) + { + count <<= 1; + if (dst > src) + { + while (count > 0) + { + int preceding = Math.Min (dst - src, count); + Buffer.BlockCopy (pixels, src, pixels, dst, preceding); + dst += preceding; + count -= preceding; + } + } + else + { + Buffer.BlockCopy (pixels, src, pixels, dst, count); + } + } + + int m_bits; + int m_bit_count; + + void InitBitReader () + { + m_bit_count = 1; + } + + byte GetNextBit () + { + if (--m_bit_count <= 0) + { + m_bits = m_input.ReadUInt8(); + m_bit_count = 8; + } + int bit = (m_bits >> 7) & 1; + m_bits <<= 1; + return (byte)bit; + } + } +} diff --git a/Legacy/Tiare/ImageGRA.cs b/Legacy/Tiare/ImageGRA.cs index 0e78d750..ad4aa379 100644 --- a/Legacy/Tiare/ImageGRA.cs +++ b/Legacy/Tiare/ImageGRA.cs @@ -24,7 +24,6 @@ // using GameRes.Utility; -using System; using System.ComponentModel.Composition; using System.IO; using System.Windows.Media; @@ -82,266 +81,22 @@ namespace GameRes.Formats.Tiare public override ImageData Read (IBinaryStream file, ImageMetaData info) { - var reader = new GraReader (file, (GraMetaData)info); - return reader.Unpack(); + var gra = (GraMetaData)info; + file.Position = gra.DataOffset; + BitmapPalette palette; + if (gra.HasPalette) + palette = ReadPalette (file.AsStream, 16, PaletteFormat.Rgb); + else + palette = DefaultPalette; + var reader = new System98.GraBaseReader (file, info); + reader.UnpackBits(); + return ImageData.Create (gra, PixelFormats.Indexed4, palette, reader.Pixels, reader.Stride); } public override void Write (Stream file, ImageData image) { throw new System.NotImplementedException ("GraFormat.Write not implemented"); } - } - - internal class GraReader - { - IBinaryStream m_input; - GraMetaData m_info; - - public GraReader (IBinaryStream file, GraMetaData info) - { - m_input = file; - m_info = info; - } - - byte[] m_pixels; - ushort[] m_buffer; - - public ImageData Unpack () - { - m_input.Position = m_info.DataOffset; - BitmapPalette palette; - if (m_info.HasPalette) - palette = ImageFormat.ReadPalette (m_input.AsStream, 16, PaletteFormat.Rgb); - else - palette = DefaultPalette; - int width = m_info.iWidth; - int wTimes2 = width << 1; - int wTimes4 = width << 2; - int buffer_size = wTimes4 + wTimes2; - m_buffer = new ushort[buffer_size >> 1]; - int stride = width >> 1; - m_pixels = new byte[stride * m_info.iHeight]; - m_dst = 0; - InitFrame(); // 1BF0:32C - InitBitReader(); - ushort p = ReadPair (0); - for (int i = 0; i < width; ++i) - m_buffer[i] = p; - int dst = wTimes2; - int prev_src = 0; - while (m_dst < m_pixels.Length) - { - bool same_line = false; - int src; - if (GetNextBit() != 0) - { - src = -width << 1; - if (GetNextBit() != 0) - { - src = -width + 1; - if (GetNextBit() != 0) - { - src -= 2; - } - } - } - else - { - src = -width; - if (GetNextBit() == 0) - { - src = -4; - p = m_buffer[dst/2-1]; - if ((p & 0xFF) == (p >> 8)) - same_line = src != prev_src; - } - } - if (src != prev_src) - { - prev_src = src; - if (!same_line) - src += dst; - else - src = dst - 2; - if (GetNextBit() != 0) - { - int bitlength = 0; - do - { - ++bitlength; - } - while (GetNextBit() != 0); - int count = 1; - while (bitlength --> 0) - count = count << 1 | GetNextBit(); - int remaining = (buffer_size - dst) >> 1; - while (count > remaining) - { - count -= remaining; - MovePixels (m_buffer, src, dst, remaining); - src += remaining << 1; - FlushBuffer(); - dst = wTimes2; - src -= wTimes4; - remaining = wTimes4 >> 1; - } - MovePixels (m_buffer, src, dst, count); - dst += count << 1; - if (dst == buffer_size) - { - FlushBuffer(); - dst = wTimes2; - } - } - else - { - MovePixels (m_buffer, src, dst, 1); - dst += 2; - if (dst == buffer_size) - { - FlushBuffer(); - dst = wTimes2; - } - } - } - else - { - p = m_buffer[dst/2-1]; - do - { - byte prev = (byte)(p >> 8); - p = ReadPair (prev); - m_buffer[dst >> 1] = p; - dst += 2; - if (dst == buffer_size) - { - FlushBuffer(); - dst = wTimes2; - } - } - while (GetNextBit() != 0); - prev_src = 0; - } - } - return ImageData.Create (m_info, PixelFormats.Indexed4, palette, m_pixels, stride); - } - - int m_dst; - - void FlushBuffer () - { - MovePixels (m_buffer, m_info.iWidth * 4, 0, m_info.iWidth); - int src = m_info.iWidth; - int count = Math.Min (m_info.iWidth << 1, m_pixels.Length - m_dst); - while (count --> 0) - { - ushort p = m_buffer[src++]; - m_pixels[m_dst++] = (byte)((p & 0xF0) | p >> 12); - } - } - - ushort ReadPair (int pos) - { - byte al = ReadPixel (pos); - byte ah = ReadPixel (al); - return (ushort)(al | ah << 8); - } - - byte ReadPixel (int pos) - { - byte px = 0; - if (GetNextBit() == 0) - { - int count = 1; - if (GetNextBit() != 0) - { - if (GetNextBit() != 0) - { - count = count << 1 | GetNextBit(); - } - count = count << 1 | GetNextBit(); - } - count = count << 1 | GetNextBit(); - pos += count; - px = m_frame[pos--]; - while (count --> 0) - { - m_frame[pos+1] = m_frame[pos]; - --pos; - } - m_frame[pos+1] = px; - } - else if (GetNextBit() == 0) - { - px = m_frame[pos]; - } - else - { - px = m_frame[pos+1]; - m_frame[pos+1] = m_frame[pos]; - m_frame[pos] = px; - } - return px; - } - - byte[] m_frame; - - void InitFrame () - { - m_frame = new byte[0x100]; - int p = 0; - byte a = 0; - for (int j = 0; j < 0x10; ++j) - { - for (int i = 0; i < 0x10; ++i) - { - m_frame[p++] = a; - a -= 0x10; - } - a += 0x10; - } - } - - void MovePixels (ushort[] pixels, int src, int dst, int count) - { - count <<= 1; - if (dst > src) - { - while (count > 0) - { - int preceding = Math.Min (dst - src, count); - Buffer.BlockCopy (pixels, src, pixels, dst, preceding); - dst += preceding; - count -= preceding; - } - } - else - { - Buffer.BlockCopy (pixels, src, pixels, dst, count); - } - } - - int m_bits; - int m_bit_count; - - void InitBitReader () - { - m_bit_count = 1; - } - - byte GetNextBit () - { - if (--m_bit_count <= 0) - { - m_bits = m_input.ReadByte(); - if (-1 == m_bits) - m_bits = 0; - m_bit_count = 8; - } - int bit = (m_bits >> 7) & 1; - m_bits <<= 1; - return (byte)bit; - } static readonly BitmapPalette DefaultPalette = new BitmapPalette (new Color[] { #region Default palette diff --git a/Legacy/Tobe/ImageWBI.cs b/Legacy/Tobe/ImageWBI.cs new file mode 100644 index 00000000..f37d5da8 --- /dev/null +++ b/Legacy/Tobe/ImageWBI.cs @@ -0,0 +1,107 @@ +//! \file ImageWBI.cs +//! \date 2023 Oct 18 +//! \brief TOBE image format. +// +// 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.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; + +// [000915][TOBE] One's Own [or very] + +namespace GameRes.Formats.Tobe +{ + internal class WbiMetaData : ImageMetaData + { + public byte RleCode; + } + + [Export(typeof(ImageFormat))] + public class WbiFormat : ImageFormat + { + public override string Tag => "WBI"; + public override string Description => "TOBE image format"; + public override uint Signature => 0x2D494257; // 'WBI-' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x20); + if (!header.AsciiEqual (4, "V1.00\0")) + return null; + return new WbiMetaData + { + Width = header.ToUInt16 (0xE), + Height = header.ToUInt16 (0x10), + BPP = 24, + RleCode = header[0x1C], + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var wbi = (WbiMetaData)info; + file.Position = 0x20; + int stride = wbi.iWidth * 3; + var pixels = new byte[stride * wbi.iHeight]; + int dst = 0; + bool skip = false; + byte r = 0, g = 0, b = 0; + int count = 0; + while (dst < pixels.Length) + { + if (count <= 0) + { + b = file.ReadUInt8(); + if (skip) + { + file.ReadByte(); + skip = false; + } + g = file.ReadUInt8(); + r = file.ReadUInt8(); + count = 1; + if (wbi.RleCode == file.PeekByte()) + { + count = file.ReadUInt16() >> 8; + if (count <= 0) + { + count = 1; + file.Seek (-2, SeekOrigin.Current); + skip = true; + } + } + } + --count; + pixels[dst++] = b; + pixels[dst++] = g; + pixels[dst++] = r; + } + return ImageData.CreateFlipped (info, PixelFormats.Bgr24, null, pixels, stride); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("WbiFormat.Write not implemented"); + } + } +} diff --git a/Legacy/Ucom/ImageUG.cs b/Legacy/Ucom/ImageUG.cs new file mode 100644 index 00000000..00cf6f9c --- /dev/null +++ b/Legacy/Ucom/ImageUG.cs @@ -0,0 +1,104 @@ +//! \file ImageUG.cs +//! \date 2023 Oct 16 +//! \brief Ucom 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.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +// [961220][Ucom] Bunkasai + +namespace GameRes.Formats.Ucom +{ + [Export(typeof(ImageFormat))] + public class UgFormat : ImageFormat + { + public override string Tag => "UG"; + public override string Description => "Ucom image format"; + public override uint Signature => 0; + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + if (!file.Name.HasExtension (".UG")) + return null; + int left = file.ReadUInt16(); + int top = file.ReadUInt16(); + int right = file.ReadUInt16(); + int bottom = file.ReadUInt16(); + int width = (right - left + 1) << 3; + int height = bottom - top + 1; + if (width <= 0 || height <= 0 || width > 640 || height > 512) + return null; + return new ImageMetaData + { + Width = (uint)width, + Height = (uint)height, + OffsetX = left, + OffsetY = top, + BPP = 4, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new UgReader (file, info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("UgFormat.Write not implemented"); + } + } + + internal class UgReader : System98.GraBaseReader + { + public UgReader (IBinaryStream input, ImageMetaData info) : base (input, info) + { + } + + public ImageData Unpack () + { + m_input.Position = 8; + var palette = ReadPalette(); + UnpackBits(); + return ImageData.Create (m_info, PixelFormats.Indexed4, palette, Pixels, Stride); + } + + BitmapPalette ReadPalette () + { + var colors = new Color[16]; + for (int i = 0; i < 16; ++i) + { + ushort rgb = m_input.ReadUInt16(); + int b = (rgb & 0xF) * 0x11; + int r = ((rgb >> 4) & 0xF) * 0x11; + int g = ((rgb >> 8) & 0xF) * 0x11; + colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b); + } + return new BitmapPalette (colors); + } + } +}