From 3c3f2013ef6febe43a4aa3b87f08e34560a1ee1c Mon Sep 17 00:00:00 2001 From: morkt Date: Mon, 25 Sep 2023 21:01:22 +0400 Subject: [PATCH 01/22] updated formats. (ExeFile): limited support for 16-bit Windows executables. (MbImageFormat): recognize 'MK' signature. (XP3, VF, YPF): added common executable signature. (PICT): improved parser. (Macromedia): improved support, recognize archives within windows executables. (SEEN): fixed decompression. --- ArcFormats/ArcFormats.csproj | 1 - ArcFormats/Cyberworks/ArcP8.cs | 2 +- ArcFormats/ExeFile.cs | 35 ++++ ArcFormats/ImageMB.cs | 5 +- ArcFormats/KiriKiri/ArcXP3.cs | 2 +- ArcFormats/LiveMaker/ArcVF.cs | 2 +- ArcFormats/MAI/ImageMAI.cs | 40 ++-- ArcFormats/Macintosh/ImagePICT.cs | 3 + ArcFormats/Macromedia/ArcCCT.cs | 256 -------------------------- ArcFormats/Macromedia/ArcDXR.cs | 99 ++++++++-- ArcFormats/Macromedia/AudioEDIM.cs | 4 +- ArcFormats/Macromedia/DirectorFile.cs | 15 +- ArcFormats/Macromedia/ImageBITD.cs | 87 ++++----- ArcFormats/RealLive/ArcSEEN.cs | 2 +- ArcFormats/YuRis/ArcYPF.cs | 2 +- ArcFormats/Zyx/ImageSPL.cs | 39 ++-- 16 files changed, 220 insertions(+), 374 deletions(-) delete mode 100644 ArcFormats/Macromedia/ArcCCT.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 1fac11fa..db64baa0 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -729,7 +729,6 @@ - diff --git a/ArcFormats/Cyberworks/ArcP8.cs b/ArcFormats/Cyberworks/ArcP8.cs index f968e81f..83342d86 100644 --- a/ArcFormats/Cyberworks/ArcP8.cs +++ b/ArcFormats/Cyberworks/ArcP8.cs @@ -55,7 +55,7 @@ namespace GameRes.Formats.TinkerBell var entry = FormatCatalog.Instance.Create (name); entry.Offset = file.View.ReadUInt32 (index_offset+0x18); entry.Size = file.View.ReadUInt32 (index_offset+0x10); - if (!entry.CheckPlacement (file.MaxOffset)) + if (entry.Offset <= index_offset || !entry.CheckPlacement (file.MaxOffset)) return null; dir.Add (entry); } diff --git a/ArcFormats/ExeFile.cs b/ArcFormats/ExeFile.cs index 8c73f48f..b57f9b63 100644 --- a/ArcFormats/ExeFile.cs +++ b/ArcFormats/ExeFile.cs @@ -40,6 +40,7 @@ namespace GameRes.Formats Section m_overlay; uint m_image_base = 0; List m_section_list; + bool? m_is_NE; public ExeFile (ArcView file) { @@ -56,9 +57,18 @@ namespace GameRes.Formats /// public Section Whole { get; private set; } + public bool IsWin16 => m_is_NE ?? (m_is_NE = IsNe()).Value; + + private bool IsNe () + { + uint ne_offset = View.ReadUInt32 (0x3C); + return ne_offset < m_file.MaxOffset-2 && View.AsciiEqual (ne_offset, "NE"); + } + /// /// Dictionary of executable file sections. /// + /// public IReadOnlyDictionary Sections { get @@ -255,6 +265,11 @@ namespace GameRes.Formats private void InitSectionTable () { + if (IsWin16) + { + InitNe(); + return; + } long pe_offset = GetHeaderOffset(); int opt_header = View.ReadUInt16 (pe_offset+0x14); // SizeOfOptionalHeader long section_table = pe_offset+opt_header+0x18; @@ -294,6 +309,26 @@ namespace GameRes.Formats m_section_list = list; } + void InitNe () + { + uint ne_offset = m_file.View.ReadUInt32 (0x3C); + int segment_count = m_file.View.ReadUInt16 (ne_offset + 0x1C); + uint seg_table = m_file.View.ReadUInt16 (ne_offset + 0x22) + ne_offset; + int shift = m_file.View.ReadUInt16 (ne_offset + 0x32); + uint last_seg_end = 0; + for (int i = 0; i < segment_count; ++i) + { + uint offset = (uint)m_file.View.ReadUInt16 (seg_table) << shift; + uint size = m_file.View.ReadUInt16 (seg_table+2); + if (offset + size > last_seg_end) + last_seg_end = offset + size; + } + m_overlay.Offset = last_seg_end; + m_overlay.Size = (uint)(m_file.MaxOffset - last_seg_end); + m_section_table = new Dictionary(); // these are empty for 16-bit executables + m_section_list = new List(); // + } + /// /// Helper class for executable file resources access. /// diff --git a/ArcFormats/ImageMB.cs b/ArcFormats/ImageMB.cs index 39925bdc..0dd5da9f 100644 --- a/ArcFormats/ImageMB.cs +++ b/ArcFormats/ImageMB.cs @@ -38,17 +38,18 @@ namespace GameRes.Formats public MbImageFormat () { - Extensions = new[] { "bmp", "gra" }; + Extensions = new[] { "bmp", "gra", "xxx" }; } public override ImageMetaData ReadMetaData (IBinaryStream stream) { int c1 = stream.ReadByte(); int c2 = stream.ReadByte(); + // MB/MC/MK/CL switch (c1) { case 'M': - if ('B' != c2 && 'C' != c2) + if ('B' != c2 && 'C' != c2 && 'K' != c2) return null; break; case 'C': diff --git a/ArcFormats/KiriKiri/ArcXP3.cs b/ArcFormats/KiriKiri/ArcXP3.cs index 29d05082..02114bc3 100644 --- a/ArcFormats/KiriKiri/ArcXP3.cs +++ b/ArcFormats/KiriKiri/ArcXP3.cs @@ -88,7 +88,7 @@ namespace GameRes.Formats.KiriKiri public Xp3Opener () { - Signatures = new uint[] { 0x0d335058, 0 }; + Signatures = new uint[] { 0x0d335058, 0x00905A4D, 0 }; Extensions = new[] { "XP3", "EXE" }; ContainedFormats = new[] { "TLG", "BMP", "PNG", "JPEG", "OGG", "WAV", "TXT" }; } diff --git a/ArcFormats/LiveMaker/ArcVF.cs b/ArcFormats/LiveMaker/ArcVF.cs index ee40f60f..8bb78d2c 100644 --- a/ArcFormats/LiveMaker/ArcVF.cs +++ b/ArcFormats/LiveMaker/ArcVF.cs @@ -45,7 +45,7 @@ namespace GameRes.Formats.LiveMaker public VffOpener () { Extensions = new string[] { "dat", "exe" }; - Signatures = new uint[] { 0x666676, 0 }; + Signatures = new uint[] { 0x666676, 0x00905A4D, 0 }; } public override ArcFile TryOpen (ArcView file) diff --git a/ArcFormats/MAI/ImageMAI.cs b/ArcFormats/MAI/ImageMAI.cs index 2263d9fb..c636667a 100644 --- a/ArcFormats/MAI/ImageMAI.cs +++ b/ArcFormats/MAI/ImageMAI.cs @@ -50,7 +50,7 @@ namespace GameRes.Formats.MAI public CmFormat () { - Extensions = new string[] { "cm" }; + Extensions = new string[] { "cmp" }; } public override void Write (Stream file, ImageData image) @@ -60,24 +60,22 @@ namespace GameRes.Formats.MAI public override ImageMetaData ReadMetaData (IBinaryStream stream) { - if ('C' != stream.ReadByte() || 'M' != stream.ReadByte()) + var header = stream.ReadHeader (0x20); + if ('C' != header[0] || 'M' != header[1]) return null; - var header = stream.ReadBytes (0x1e); - if (header.Length != 0x1e) + if (1 != header[0x0E]) return null; - if (1 != header[0x0c]) - return null; - uint size = LittleEndian.ToUInt32 (header, 0); + uint size = LittleEndian.ToUInt32 (header, 2); if (size != stream.Length) return null; var info = new CmMetaData(); - info.Width = LittleEndian.ToUInt16 (header, 4); - info.Height = LittleEndian.ToUInt16 (header, 6); - info.Colors = LittleEndian.ToUInt16 (header, 8); - info.BPP = header[0x0a]; - info.IsCompressed = 0 != header[0x0b]; - info.DataOffset = LittleEndian.ToUInt32 (header, 0x0e); - info.DataLength = LittleEndian.ToUInt32 (header, 0x12); + info.Width = LittleEndian.ToUInt16 (header, 6); + info.Height = LittleEndian.ToUInt16 (header, 8); + info.Colors = LittleEndian.ToUInt16 (header, 0x0A); + info.BPP = header[0x0C]; + info.IsCompressed = 0 != header[0x0D]; + info.DataOffset = LittleEndian.ToUInt32 (header, 0x10); + info.DataLength = LittleEndian.ToUInt32 (header, 0x14); if (info.DataLength > size) return null; return info; @@ -158,7 +156,7 @@ namespace GameRes.Formats.MAI public AmFormat () { - Extensions = new string[] { "am", "ami" }; + Extensions = new string[] { "amp", "ami" }; } public override void Write (Stream file, ImageData image) @@ -235,6 +233,8 @@ namespace GameRes.Formats.MAI m_pixels = new byte[m_width*m_height*4]; } + static readonly Color Default8bppTransparencyColor = Color.FromRgb (0, 0xFE, 0); + public void Unpack () { if (m_info.Colors > 0) @@ -262,13 +262,23 @@ namespace GameRes.Formats.MAI m_pixels[dst+3] = alpha; }; else + { + const int alphaScale = 0x11; + var alphaColor = Color.FromRgb (0, 0xFE, 0); copy_pixel = (src, dst, alpha) => { var color = Palette.Colors[m_output[src]]; + if (Default8bppTransparencyColor == color) + alpha = 0; + else if (0 == alpha) + alpha = 0xFF; + else + alpha *= alphaScale; m_pixels[dst] = color.B; m_pixels[dst+1] = color.G; m_pixels[dst+2] = color.R; m_pixels[dst+3] = alpha; }; + } int src_stride = m_width * m_pixel_size; for (int y = 0; y < m_height; ++y) { diff --git a/ArcFormats/Macintosh/ImagePICT.cs b/ArcFormats/Macintosh/ImagePICT.cs index d332afa1..98d999d9 100644 --- a/ArcFormats/Macintosh/ImagePICT.cs +++ b/ArcFormats/Macintosh/ImagePICT.cs @@ -190,6 +190,9 @@ namespace GameRes.Formats.Apple break; } + case 0x001E: // DefHilite + break; + case 0x0090: case 0x0091: case 0x0098: diff --git a/ArcFormats/Macromedia/ArcCCT.cs b/ArcFormats/Macromedia/ArcCCT.cs deleted file mode 100644 index 0f31f25c..00000000 --- a/ArcFormats/Macromedia/ArcCCT.cs +++ /dev/null @@ -1,256 +0,0 @@ -//! \file ArcCCT.cs -//! \date Fri Jun 26 01:15:26 2015 -//! \brief Macromedia Director archive access implementation. -// -// Copyright (C) 2015 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.Text; -using GameRes.Compression; -using GameRes.Utility; - -namespace GameRes.Formats.Macromedia -{ - [Export(typeof(ArchiveFormat))] - public class CctOpener : ArchiveFormat - { - public override string Tag { get { return "CCT"; } } - public override string Description { get { return "Macromedia Shockwave resource archive"; } } - public override uint Signature { get { return 0x52494658; } } // 'XFIR' - public override bool IsHierarchic { get { return false; } } - public override bool CanWrite { get { return false; } } - - public CctOpener () - { - Extensions = new string[] { "cct", "dcr" }; - } - - public override ArcFile TryOpen (ArcView file) - { - uint id = file.View.ReadUInt32 (8); - if (id != 0x46474443 && id != 0x4647444D) // 'CDGF' || 'MDGF' - return null; - - var reader = new CctReader (file); - var dir = reader.ReadIndex(); - if (null != dir) - { - var arc = new ArcFile (file, this, dir); - SetEntryTypes (arc); - return arc; - } - return null; - } - - public override Stream OpenEntry (ArcFile arc, Entry entry) - { - var input = arc.File.CreateStream (entry.Offset, entry.Size); - var packed_entry = entry as PackedEntry; - if (null == packed_entry || !packed_entry.IsPacked) - return input; - else - return new ZLibStream (input, CompressionMode.Decompress); - } - - private void SetEntryTypes (ArcFile arc) - { - foreach (var entry in arc.Dir.OrderBy (x => x.Offset)) - { - if (entry.Name.EndsWith (".edim")) - entry.Type = DetectEdimType (arc, entry); - else if (entry.Name.EndsWith (".bitd")) - entry.Type = "image"; - } - } - - private string DetectEdimType (ArcFile arc, Entry entry) - { - using (var input = OpenEntry (arc, entry)) - { - uint signature = (uint)input.ReadByte() << 24; - signature |= (uint)input.ReadByte() << 16; - signature |= (uint)input.ReadByte() << 8; - signature |= (byte)input.ReadByte(); - if (0xffd8ffe0 == signature) - return "image"; - uint real_size = (entry as PackedEntry).UnpackedSize; - if (signature > 0xffff || signature+4 > real_size) - return ""; - var header = new byte[signature+0x10]; - if (header.Length != input.Read (header, 0, header.Length)) - return ""; - if (0xff == header[signature]) - return "audio"; - return ""; - } - } - } - - internal class CctReader - { - ArcView m_file; - long m_offset; - - public CctReader (ArcView file) - { - m_file = file; - m_offset = 0x0C; - } - - byte[] m_size_buffer = new byte[10]; - - public List ReadIndex () - { - uint section_size = ReadSectionSize ("Fver"); - m_offset += section_size; - section_size = ReadSectionSize ("Fcdr"); - /* - int Mcdr_size; - var Mcdr = ZlibUnpack (m_offset, section_size, out Mcdr_size); - */ - m_offset += section_size; - uint abmp_size = ReadSectionSize ("ABMP"); - int max_count = m_file.View.Read (m_offset, m_size_buffer, 0, Math.Min (10, abmp_size)); - int size_offset = 0; - ReadValue (m_size_buffer, ref size_offset, max_count); - max_count -= size_offset; - - int bmp_unpacked_size = (int)ReadValue (m_size_buffer, ref size_offset, max_count); - m_offset += size_offset; - abmp_size -= (uint)size_offset; - int index_size; - var index = ZlibUnpack (m_offset, abmp_size, out index_size, bmp_unpacked_size); - m_offset += abmp_size; - section_size = ReadSectionSize ("FGEI"); - if (0 != section_size) - throw new NotSupportedException(); - - int index_offset = 0; - ReadValue (index, ref index_offset, index_size-index_offset); - ReadValue (index, ref index_offset, index_size-index_offset); - int entry_count = (int)ReadValue (index, ref index_offset, index_size-index_offset); - if (entry_count <= 0 || entry_count > 0xfffff) - return null; - - var type_buf = new char[4]; - var dir = new List (entry_count); - for (int i = 0; i < entry_count; ++i) - { - uint id = ReadValue (index, ref index_offset, index_size-index_offset); - uint offset = ReadValue (index, ref index_offset, index_size-index_offset); - uint size = ReadValue (index, ref index_offset, index_size-index_offset); - uint unpacked_size = ReadValue (index, ref index_offset, index_size-index_offset); - uint flag = ReadValue (index, ref index_offset, index_size-index_offset); - - if (index_size-index_offset < 4) - return null; - uint type_id = LittleEndian.ToUInt32 (index, index_offset); - index_offset += 4; - if (0 == type_id || uint.MaxValue == offset) - continue; - - Encoding.ASCII.GetChars (index, index_offset-4, 4, type_buf, 0); - var entry = new PackedEntry - { - Name = CreateName (id, type_buf), - Offset = (long)m_offset + offset, - Size = size, - UnpackedSize = unpacked_size, - IsPacked = 0 == flag, - }; - if (entry.CheckPlacement (m_file.MaxOffset)) - { - dir.Add (entry); - } - } - return dir; - } - - string CreateName (uint id, char[] type_buf) - { - Array.Reverse (type_buf); - int t = 3; - while (t >= 0 && ' ' == type_buf[t]) - t--; - if (t >= 0) - { - string ext = new string (type_buf, 0, t+1); - return string.Format ("{0:D8}.{1}", id, ext.ToLowerInvariant()); - } - else - return id.ToString ("D8"); - } - - byte[] ZlibUnpack (long offset, uint size, out int actual_size, int unpacked_size_hint = 0) - { - using (var input = m_file.CreateStream (offset, size)) - using (var zstream = new ZLibStream (input, CompressionMode.Decompress)) - using (var mem = new MemoryStream (unpacked_size_hint)) - { - zstream.CopyTo (mem); - actual_size = (int)mem.Length; - return mem.GetBuffer(); - } - } - - uint ReadSectionSize (string id_str) - { - uint id = ConvertId (id_str); - if (id != m_file.View.ReadUInt32 (m_offset)) - throw new InvalidFormatException(); - m_offset += 4; - if (5 != m_file.View.Read (m_offset, m_size_buffer, 0, 5)) - throw new InvalidFormatException(); - int off_count = 0; - uint size = ReadValue (m_size_buffer, ref off_count, 5); - m_offset += off_count; - return size; - } - - static uint ReadValue (byte[] buffer, ref int offset, int length) - { - uint n = 0; - for (int off_count = 0; off_count < length; ++off_count) - { - uint bits = buffer[offset++]; - n = (n << 7) | (bits & 0x7F); - if (0 == (bits & 0x80)) - return n; - } - throw new InvalidFormatException(); - } - - static uint ConvertId (string id) - { - if (id.Length != 4) - throw new ArgumentException ("Invalid section id"); - uint n = 0; - for (int i = 0; i < 4; ++i) - n = (n << 8) | (byte)id[i]; - return n; - } - } -} diff --git a/ArcFormats/Macromedia/ArcDXR.cs b/ArcFormats/Macromedia/ArcDXR.cs index ed6eabc5..31a19e29 100644 --- a/ArcFormats/Macromedia/ArcDXR.cs +++ b/ArcFormats/Macromedia/ArcDXR.cs @@ -38,46 +38,58 @@ namespace GameRes.Formats.Macromedia [Export(typeof(ArchiveFormat))] public class DxrOpener : ArchiveFormat { - public override string Tag { get => "DXR"; } - public override string Description { get => "Macromedia Director resource archive"; } - public override uint Signature { get => 0x52494658; } // 'XFIR' - public override bool IsHierarchic { get => false; } - public override bool CanWrite { get => false; } + public override string Tag => "DXR"; + public override string Description => "Macromedia Director resource archive"; + public override uint Signature => SignatureXFIR; // 'XFIR' + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public const uint SignatureXFIR = 0x52494658u; + public const uint SignatureRIFX = 0x58464952u; public DxrOpener () { - Extensions = new[] { "dxr", "cxt", "cct", "dcr" }; - Signatures = new[] { 0x52494658u, 0x58464952u }; + Extensions = new[] { "dxr", "cxt", "cct", "dcr", "exe" }; + Signatures = new[] { SignatureXFIR, SignatureRIFX, 0x00905A4Du, 0u }; } internal static readonly HashSet RawChunks = new HashSet { - "RTE0", "RTE1", "FXmp", "VWFI", "VWSC", "Lscr", "STXT", "XMED", //"snd " + "RTE0", "RTE1", "FXmp", "VWFI", "VWSC", "Lscr", "STXT", "XMED", "File" }; internal bool ConvertText = true; public override ArcFile TryOpen (ArcView file) { + long base_offset = 0; + if (file.View.AsciiEqual (0, "MZ")) + base_offset = LookForXfir (file); + uint signature = file.View.ReadUInt32 (base_offset); + if (signature != SignatureXFIR && signature != SignatureRIFX) + return null; using (var input = file.CreateStream()) { - ByteOrder ord = input.Signature == 0x52494658u ? ByteOrder.LittleEndian : ByteOrder.BigEndian; + ByteOrder ord = signature == SignatureXFIR ? ByteOrder.LittleEndian : ByteOrder.BigEndian; var reader = new Reader (input, ord); - reader.Position = 4; - uint length = reader.ReadU32(); + reader.Position = base_offset; var context = new SerializationContext(); var dir_file = new DirectorFile(); if (!dir_file.Deserialize (context, reader)) return null; var dir = new List (); - ImportMedia (dir_file, dir); + if (dir_file.Codec != "APPL") + ImportMedia (dir_file, dir); foreach (DirectorEntry entry in dir_file.Directory) { if (entry.Size != 0 && entry.Offset != -1 && RawChunks.Contains (entry.FourCC)) { entry.Name = string.Format ("{0:D6}.{1}", entry.Id, entry.FourCC.Trim()); - if ("snd " == entry.FourCC) - entry.Type = "audio"; + if ("File" == entry.FourCC) + { + entry.Offset -= 8; + entry.Size += 8; + } dir.Add (entry); } } @@ -155,7 +167,7 @@ namespace GameRes.Formats.Macromedia entry = ImportBitmap (piece, dir_file, cast); else if (piece.Type == DataType.Sound) entry = ImportSound (piece, dir_file); - if (entry != null) + if (entry != null && entry.Size > 0) dir.Add (entry); } } @@ -381,6 +393,56 @@ namespace GameRes.Formats.Macromedia zstream.Read (data, 0, data.Length); return new BinMemoryStream (data, entry.Name); } + + static readonly byte[] s_xfir = { (byte)'X', (byte)'F', (byte)'I', (byte)'R' }; + + long LookForXfir (ArcView file) + { + var exe = new ExeFile (file); + long pos; + if (exe.IsWin16) + { + pos = exe.FindString (exe.Overlay, s_xfir); + if (pos < 0) + return 0; + } + else + { + pos = exe.Overlay.Offset; + if (pos >= file.MaxOffset) + return 0; + if (file.View.AsciiEqual (pos, "10JP")) + { + pos = file.View.ReadUInt32 (pos+4); + } + } + if (pos >= file.MaxOffset || !file.View.AsciiEqual (pos, "XFIR")) + return 0; + // TODO threat 'LPPA' archives the normal way, like archives that contain entries. + // the problem is, DXR archives contained within 'LPPA' have their offsets relative to executable file, + // so have to figure out way to handle it. + if (!file.View.AsciiEqual (pos+8, "LPPA")) + return pos; + var appl = new DirectorFile(); + var context = new SerializationContext(); + using (var input = file.CreateStream()) + { + var reader = new Reader (input, ByteOrder.LittleEndian); + input.Position = pos + 12; + if (!appl.ReadMMap (context, reader)) + return 0; + foreach (var entry in appl.Directory) + { + // only the first XFIR entry is matched here, but archive may contain multiple sub-archives. + if (entry.FourCC == "File") + { + if (file.View.AsciiEqual (entry.Offset-8, "XFIR")) + return entry.Offset-8; + } + } + return 0; + } + } } internal class BitmapEntry : PackedEntry @@ -409,8 +471,11 @@ namespace GameRes.Formats.Macromedia Right = reader.ReadI16(); reader.Skip (0x0C); BitDepth = reader.ReadU16() & 0xFF; // ??? - reader.Skip (2); - Palette = reader.ReadI16(); + if (data.Length >= 0x1C) + { + reader.Skip (2); + Palette = reader.ReadI16(); + } } } } diff --git a/ArcFormats/Macromedia/AudioEDIM.cs b/ArcFormats/Macromedia/AudioEDIM.cs index 18925d59..aab5eaca 100644 --- a/ArcFormats/Macromedia/AudioEDIM.cs +++ b/ArcFormats/Macromedia/AudioEDIM.cs @@ -1,6 +1,6 @@ //! \file AudioEDIM.cs //! \date Fri Jun 26 06:52:33 2015 -//! \brief Selen wrapper around MP3 stream. +//! \brief Macromedia Director wrapper around MP3 stream. // // Copyright (C) 2015 by morkt // @@ -33,7 +33,7 @@ namespace GameRes.Formats.Selen public class EdimAudio : Mp3Audio { public override string Tag { get { return "EDIM"; } } - public override string Description { get { return "Selen audio format (MP3)"; } } + public override string Description { get { return "Macromedia Director audio format (MP3)"; } } public override uint Signature { get { return 0x40010000; } } public override bool CanWrite { get { return false; } } diff --git a/ArcFormats/Macromedia/DirectorFile.cs b/ArcFormats/Macromedia/DirectorFile.cs index cca399d5..a1b2232f 100644 --- a/ArcFormats/Macromedia/DirectorFile.cs +++ b/ArcFormats/Macromedia/DirectorFile.cs @@ -75,6 +75,9 @@ namespace GameRes.Formats.Macromedia DirectorConfig m_config = new DirectorConfig(); List m_casts = new List(); Dictionary m_ilsMap = new Dictionary(); + string m_codec; + + public string Codec => m_codec; public bool IsAfterBurned { get; private set; } @@ -96,14 +99,14 @@ namespace GameRes.Formats.Macromedia public bool Deserialize (SerializationContext context, Reader reader) { - reader.Position = 8; - string codec = reader.ReadFourCC(); - if (codec == "MV93" || codec == "MC95") + reader.Skip (8); + m_codec = reader.ReadFourCC(); + if (m_codec == "MV93" || m_codec == "MC95") { if (!ReadMMap (context, reader)) return false; } - else if (codec == "FGDC" || codec == "FGDM") + else if (m_codec == "FGDC" || m_codec == "FGDM") { IsAfterBurned = true; if (!ReadAfterBurner (context, reader)) @@ -111,7 +114,7 @@ namespace GameRes.Formats.Macromedia } else { - Trace.WriteLine (string.Format ("Unknown codec '{0}'", codec), "DXR"); + Trace.WriteLine (string.Format ("Unknown m_codec '{0}'", m_codec), "DXR"); return false; } return ReadKeyTable (context, reader) @@ -119,7 +122,7 @@ namespace GameRes.Formats.Macromedia && ReadCasts (context, reader); } - bool ReadMMap (SerializationContext context, Reader reader) + internal bool ReadMMap (SerializationContext context, Reader reader) { if (reader.ReadFourCC() != "imap") return false; diff --git a/ArcFormats/Macromedia/ImageBITD.cs b/ArcFormats/Macromedia/ImageBITD.cs index fb571e6a..0e1795a6 100644 --- a/ArcFormats/Macromedia/ImageBITD.cs +++ b/ArcFormats/Macromedia/ImageBITD.cs @@ -71,7 +71,7 @@ namespace GameRes.Formats.Macromedia : info.BPP == 8 ? PixelFormats.Indexed8 : info.BPP == 16 ? PixelFormats.Bgr555 : info.DepthType == 0x87 // i have no clue what this is - || info.DepthType == 0x8A ? PixelFormats.Bgra32 // depth type 0x87/0x8A + || info.DepthType == 0x8A ? PixelFormats.Bgra32 // depth type 0x87/0x8A : PixelFormats.Bgra32; // depth type 0x82/84/85/86/8C m_palette = palette; } @@ -151,32 +151,7 @@ namespace GameRes.Formats.Macromedia { for (int line = 0; line < m_output.Length; line += m_stride) { - int x = 0; - while (x < m_stride) - { - int b = m_input.ReadByte(); - if (-1 == b) - throw new InvalidFormatException ("Unexpected end of file"); - int count = b; - if (b > 0x7f) - count = (byte)-(sbyte)b; - ++count; - if (x + count > m_stride) - throw new InvalidFormatException(); - if (b > 0x7f) - { - b = m_input.ReadByte(); - if (-1 == b) - throw new InvalidFormatException ("Unexpected end of file"); - for (int i = 0; i < count; ++i) - m_output[line + x++] = (byte)b; - } - else - { - m_input.Read (m_output, line + x, count); - x += count; - } - } + UnpackScanLine (m_output, line); } return m_output; } @@ -186,33 +161,7 @@ namespace GameRes.Formats.Macromedia var scan_line = new byte[m_stride]; for (int line = 0; line < m_output.Length; line += m_stride) { - int x = 0; - while (x < m_stride) - { - int b = m_input.ReadByte(); - if (-1 == b) - break; // one in 5000 images somehow stumbles here -// throw new InvalidFormatException ("Unexpected end of file"); - int count = b; - if (b > 0x7f) - count = (byte)-(sbyte)b; - ++count; - if (x + count > m_stride) - throw new InvalidFormatException(); - if (b > 0x7f) - { - b = m_input.ReadByte(); - if (-1 == b) - throw new InvalidFormatException ("Unexpected end of file"); - for (int i = 0; i < count; ++i) - scan_line[x++] = (byte)b; - } - else - { - m_input.Read (scan_line, x, count); - x += count; - } - } + UnpackScanLine (scan_line, 0); int dst = line; for (int i = 0; i < m_width; ++i) { @@ -222,6 +171,36 @@ namespace GameRes.Formats.Macromedia } } + void UnpackScanLine (byte[] scan_line, int pos) + { + int x = 0; + while (x < m_stride) + { + int b = m_input.ReadByte(); + if (-1 == b) + break; // one in 5000 images somehow stumbles here + int count = b; + if (b > 0x7f) + count = (byte)-(sbyte)b; + ++count; + if (x + count > m_stride) + throw new InvalidFormatException(); + if (b > 0x7f) + { + b = m_input.ReadByte(); + if (-1 == b) + throw new InvalidFormatException ("Unexpected end of file"); + for (int i = 0; i < count; ++i) + scan_line[pos + x++] = (byte)b; + } + else + { + m_input.Read (scan_line, pos+x, count); + x += count; + } + } + } + #region IDisposable Members bool m_disposed = false; diff --git a/ArcFormats/RealLive/ArcSEEN.cs b/ArcFormats/RealLive/ArcSEEN.cs index 772f1ff6..791c0742 100644 --- a/ArcFormats/RealLive/ArcSEEN.cs +++ b/ArcFormats/RealLive/ArcSEEN.cs @@ -111,7 +111,7 @@ namespace GameRes.Formats.RealLive int offset = input.ReadUInt16(); int count = (offset & 0xF) + 2; offset >>= 4; - Binary.CopyOverlapped (output, dst-offset, dst, count); + Binary.CopyOverlapped (output, dst-offset-1, dst, count); dst += count; } } diff --git a/ArcFormats/YuRis/ArcYPF.cs b/ArcFormats/YuRis/ArcYPF.cs index 36336593..08a2f11e 100644 --- a/ArcFormats/YuRis/ArcYPF.cs +++ b/ArcFormats/YuRis/ArcYPF.cs @@ -105,7 +105,7 @@ namespace GameRes.Formats.YuRis public YpfOpener () { - Signatures = new uint[] { 0x00465059, 0 }; + Signatures = new uint[] { 0x00465059, 0x00905A4D, 0 }; } static public Dictionary KnownSchemes { get { return DefaultScheme.KnownSchemes; } } diff --git a/ArcFormats/Zyx/ImageSPL.cs b/ArcFormats/Zyx/ImageSPL.cs index bd26100b..3d28aa0a 100644 --- a/ArcFormats/Zyx/ImageSPL.cs +++ b/ArcFormats/Zyx/ImageSPL.cs @@ -52,30 +52,37 @@ namespace GameRes.Formats.Zyx public override ImageMetaData ReadMetaData (IBinaryStream file) { int count = file.ReadInt16(); - if (count <= 0) + if (count < 0 || count > 0x100) return null; - var tiles = new Tile[count]; - for (int i = 0; i < count; ++i) + Tile[] tiles = null; + if (count > 0) { - var tile = new Tile(); - tile.Left = file.ReadInt16(); - tile.Top = file.ReadInt16(); - if (tile.Left < 0 || tile.Top < 0) - return null; - tile.Right = file.ReadInt16(); - tile.Bottom = file.ReadInt16(); - if (tile.Right <= tile.Left || tile.Bottom <= tile.Top) - return null; - tiles[i] = tile; + tiles = new Tile[count]; + for (int i = 0; i < count; ++i) + { + var tile = new Tile(); + tile.Left = file.ReadInt16(); + tile.Top = file.ReadInt16(); + if (tile.Left < 0 || tile.Top < 0) + return null; + tile.Right = file.ReadInt16(); + tile.Bottom = file.ReadInt16(); + if (tile.Right <= tile.Left || tile.Bottom <= tile.Top) + return null; + tiles[i] = tile; + } } int width = file.ReadInt16(); int height = file.ReadInt16(); if (width <= 0 || height <= 0) return null; - foreach (var tile in tiles) + if (tiles != null) { - if (tile.Right > width || tile.Bottom > height) - return null; + foreach (var tile in tiles) + { + if (tile.Right > width || tile.Bottom > height) + return null; + } } return new SplMetaData { From 08ab953bffc4f0353f10e0e4f4513fac9555a3bf Mon Sep 17 00:00:00 2001 From: morkt Date: Mon, 25 Sep 2023 21:02:56 +0400 Subject: [PATCH 02/22] (ArcNE): parse VERSION resource. --- Experimental/Microsoft/ArcEXE.cs | 12 ++-- Experimental/Microsoft/ArcNE.cs | 98 +++++++++++++++++++++++++++++--- 2 files changed, 94 insertions(+), 16 deletions(-) diff --git a/Experimental/Microsoft/ArcEXE.cs b/Experimental/Microsoft/ArcEXE.cs index 56ca88ad..4c5b4230 100644 --- a/Experimental/Microsoft/ArcEXE.cs +++ b/Experimental/Microsoft/ArcEXE.cs @@ -37,11 +37,11 @@ namespace GameRes.Formats.Microsoft [ExportMetadata("Priority", -1)] public class ExeOpener : ArchiveFormat { - public override string Tag { get => "EXE"; } - public override string Description { get => "Windows executable resources"; } - public override uint Signature { get => 0; } - public override bool IsHierarchic { get => true; } - public override bool CanWrite { get => false; } + public override string Tag => "EXE"; + public override string Description => "Windows executable resources"; + public override uint Signature => 0; + public override bool IsHierarchic => true; + public override bool CanWrite => false; public ExeOpener () { @@ -158,8 +158,6 @@ namespace GameRes.Formats.Microsoft return id; } - static readonly byte[] VS_VERSION_INFO = Encoding.Unicode.GetBytes ("VS_VERSION_INFO"); - Stream OpenVersion (byte[] data, string name) { var input = new BinMemoryStream (data, name); diff --git a/Experimental/Microsoft/ArcNE.cs b/Experimental/Microsoft/ArcNE.cs index ba795e57..88a8b599 100644 --- a/Experimental/Microsoft/ArcNE.cs +++ b/Experimental/Microsoft/ArcNE.cs @@ -27,17 +27,19 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; +using System.Text; namespace GameRes.Formats.Microsoft { [Export(typeof(ArchiveFormat))] - public class PakOpener : ArchiveFormat + [ExportMetadata("Priority", -2)] + public class NeExeOpener : ArchiveFormat { - public override string Tag { get => "EXE/NE"; } - public override string Description { get => "Windows 16-bit executable resources"; } - public override uint Signature { get => 0; } - public override bool IsHierarchic { get => true; } - public override bool CanWrite { get => false; } + public override string Tag => "EXE/NE"; + public override string Description => "Windows 16-bit executable resources"; + public override uint Signature => 0; + public override bool IsHierarchic => true; + public override bool CanWrite => false; static readonly Dictionary TypeMap = new Dictionary { { 1, "RT_CURSOR" }, @@ -53,10 +55,10 @@ namespace GameRes.Formats.Microsoft public override ArcFile TryOpen (ArcView file) { - if (!file.View.AsciiEqual (0, "MZ")) + if (!file.View.AsciiEqual (0, "MZ") || file.MaxOffset < 0x40) return null; uint ne_offset = file.View.ReadUInt32 (0x3C); - if (!file.View.AsciiEqual (ne_offset, "NE")) + if (ne_offset > file.MaxOffset-2 || !file.View.AsciiEqual (ne_offset, "NE")) return null; uint res_table_offset = file.View.ReadUInt16 (ne_offset+0x24) + ne_offset; if (res_table_offset <= ne_offset || res_table_offset >= file.MaxOffset) @@ -84,13 +86,17 @@ namespace GameRes.Formats.Microsoft int offset = file.View.ReadUInt16 (res_table_offset) << shift; uint size = (uint)file.View.ReadUInt16 (res_table_offset+2) << shift; int res_id = file.View.ReadUInt16 (res_table_offset+6); + if ((res_id & 0x8000) != 0) + res_id &= 0x7FFF; res_table_offset += 12; string name = res_id.ToString ("D5"); name = string.Join ("/", dir_name, name); - var entry = new Entry { + var entry = new NeResourceEntry { Name = name, Offset = offset, Size = size, + NativeName = res_id, + NativeType = type_id, }; dir.Add (entry); } @@ -99,5 +105,79 @@ namespace GameRes.Formats.Microsoft return null; return new ArcFile (file, this, dir); } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var rent = (NeResourceEntry)entry; + if (rent.NativeType == 16) + return OpenVersion (arc, rent); + return base.OpenEntry (arc, entry); + } + + Encoding DefaultEncoding = Encodings.cp932; + + Stream OpenVersion (ArcFile arc, NeResourceEntry entry) + { + uint data_length = arc.File.View.ReadUInt16 (entry.Offset); + var input = arc.File.CreateStream (entry.Offset, data_length); + for (;;) + { + input.ReadUInt16(); + int value_length = input.ReadUInt16(); + if (0 == value_length) + break; + if (input.ReadCString (DefaultEncoding) != "VS_VERSION_INFO") + break; + long pos = (input.Position + 3) & -4L; + input.Position = pos; + if (input.ReadUInt32() != 0xFEEF04BDu) + break; + input.Position = pos + value_length; + int str_info_length = input.ReadUInt16(); + value_length = input.ReadUInt16(); + if (value_length != 0) + break; + if (input.ReadCString (DefaultEncoding) != "StringFileInfo") + break; + pos = (input.Position + 3) & -4L; + input.Position = pos; + int info_length = input.ReadUInt16(); + long end_pos = pos + info_length; + value_length = input.ReadUInt16(); + if (value_length != 0) + break; + var output = new MemoryStream(); + using (var text = new StreamWriter (output, DefaultEncoding, 512, true)) + { + string block_name = input.ReadCString (DefaultEncoding); + text.WriteLine ("BLOCK \"{0}\"\n{{", block_name); + long next_pos = (input.Position + 3) & -4L; + while (next_pos < end_pos) + { + input.Position = next_pos; + info_length = input.ReadUInt16(); + value_length = input.ReadUInt16(); + next_pos = (next_pos + info_length + 3) & -4L; + string key = input.ReadCString (DefaultEncoding); + input.Position = (input.Position + 3) & -4L; + string value = value_length != 0 ? input.ReadCString (value_length, DefaultEncoding) + : String.Empty; + text.WriteLine ("\tVALUE \"{0}\", \"{1}\"", key, value); + } + text.WriteLine ("}"); + } + input.Dispose(); + output.Position = 0; + return output; + } + input.Position = 0; + return input; + } + } + + internal class NeResourceEntry : Entry + { + public int NativeType; + public int NativeName; } } From e4e85fe879af20ae3501e61fe1363734e2ebc01e Mon Sep 17 00:00:00 2001 From: morkt Date: Mon, 25 Sep 2023 21:10:00 +0400 Subject: [PATCH 03/22] (Legacy): added formats. (GPC): Adv98 engine image format. (PIC): Grocer image format. (PL4): Pearl Soft images. (PAK,QDO): Red-Zone resources. --- Legacy/Adv98/ImageGPC.cs | 239 +++++++++++++++++++++ Legacy/Blucky/Aliases.cs | 41 ++++ Legacy/Grocer/ImagePIC.cs | 193 +++++++++++++++++ Legacy/Legacy.csproj | 7 + Legacy/Pearl/ArcARY.cs | 83 ++++++++ Legacy/Pearl/ImagePL4.cs | 382 ++++++++++++++++++++++++++++++++++ Legacy/PlanTech/ArcPAC.cs | 2 - Legacy/PlanTech/ImagePAC.cs | 2 - Legacy/ProjectMyu/ImageGAM.cs | 1 + Legacy/RedZone/ArcPAK.cs | 68 ++++++ Legacy/RedZone/ScriptQDO.cs | 74 +++++++ 11 files changed, 1088 insertions(+), 4 deletions(-) create mode 100644 Legacy/Adv98/ImageGPC.cs create mode 100644 Legacy/Blucky/Aliases.cs create mode 100644 Legacy/Grocer/ImagePIC.cs create mode 100644 Legacy/Pearl/ArcARY.cs create mode 100644 Legacy/Pearl/ImagePL4.cs create mode 100644 Legacy/RedZone/ArcPAK.cs create mode 100644 Legacy/RedZone/ScriptQDO.cs diff --git a/Legacy/Adv98/ImageGPC.cs b/Legacy/Adv98/ImageGPC.cs new file mode 100644 index 00000000..7af92cba --- /dev/null +++ b/Legacy/Adv98/ImageGPC.cs @@ -0,0 +1,239 @@ +//! \file ImageGPC.cs +//! \date 2023 Sep 22 +//! \brief Adv98 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.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace GameRes.Formats.Adv98 +{ + internal class GpcMetaData : ImageMetaData + { + public long PaletteOffset; + public long DataOffset; + public int Interleaving; + } + + [Export(typeof(ImageFormat))] + public class GpcFormat : ImageFormat + { + public override string Tag => "GPC/PC98"; + public override string Description => "Adv98 engine image format"; + public override uint Signature => 0x38394350; // 'PC98' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x20); + if (!header.AsciiEqual (4, ")GPCFILE \0")) + return null; + uint info_pos = header.ToUInt32 (0x18); + var info = new GpcMetaData + { + Interleaving = header.ToUInt16 (0x10), + PaletteOffset = header.ToUInt32 (0x14), + DataOffset = info_pos + 0x10, + BPP = 4, + }; + file.Position = info_pos; + info.Width = file.ReadUInt16(); + info.Height = file.ReadUInt16(); + file.Position = info_pos + 0xA; + info.OffsetX = file.ReadInt16(); + info.OffsetY = file.ReadInt16(); + return info; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new GpcReader (file, (GpcMetaData)info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("GpcFormat.Write not implemented"); + } + } + + internal class GpcReader + { + IBinaryStream m_input; + GpcMetaData m_info; + int m_stride; + + public BitmapPalette Palette { get; private set; } + + public GpcReader (IBinaryStream input, GpcMetaData info) + { + m_input = input; + m_info = info; + } + + public ImageData Unpack () + { + m_input.Position = m_info.PaletteOffset; + Palette = ReadPalette(); + int plane_stride = (m_info.iWidth + 7) >> 3; + int row_size = plane_stride * 4 + 1; + var data = new byte[row_size * m_info.iHeight]; + m_input.Position = m_info.DataOffset; + UnpackData (data); + RestoreData (data, row_size); + m_stride = plane_stride * 4; + var pixels = new byte[m_stride * m_info.iHeight]; + ConvertTo8bpp (data, pixels, plane_stride); + return ImageData.Create (m_info, PixelFormats.Indexed4, Palette, pixels, m_stride); + } + + void ConvertTo8bpp (byte[] input, byte[] output, int plane_stride) + { + int interleaving_step = m_stride * m_info.Interleaving; + int src_row = 1; + int dst_row = 0; + int i = 0; + for (int y = 0; y < m_info.iHeight; ++y) + { + if (dst_row >= output.Length) + { + dst_row = m_stride * ++i; + } + int p0 = src_row; + int p1 = p0 + plane_stride; + int p2 = p1 + plane_stride; + int p3 = p2 + plane_stride; + src_row = p3 + plane_stride + 1; + int dst = dst_row; + for (int x = plane_stride; x > 0; --x) + { + byte b0 = input[p0++]; + byte b1 = input[p1++]; + byte b2 = input[p2++]; + byte b3 = input[p3++]; + 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)); + output[dst++] = px; + } + } + dst_row += interleaving_step; + } + } + + void UnpackData (byte[] output) + { + int dst = 0; + int ctl = 0; + int ctl_mask = 0; + while (dst < output.Length) + { + if (0 == ctl_mask) + { + ctl = m_input.ReadByte(); + if (-1 == ctl) + break; + ctl_mask = 0x80; + } + if ((ctl & ctl_mask) != 0) + { + int cmd = m_input.ReadByte(); + for (int cmd_mask = 0x80; cmd_mask != 0; cmd_mask >>= 1) + { + if ((cmd & cmd_mask) != 0) + output[dst++] = m_input.ReadUInt8(); + else + ++dst; + } + } + else + { + dst += 8; + } + ctl_mask >>= 1; + } + } + + void RestoreData (byte[] data, int stride) + { + int src = 0; + for (int y = 0; y < m_info.iHeight; ++y) + { + int interleave = data[src]; + if (interleave != 0) + { + byte lastValue = 0; + for (int i = 0; i < interleave; ++i) + { + int pos = 1 + i; + while (pos < stride) + { + data[src + pos] ^= lastValue; + lastValue = data[src + pos]; + pos += interleave; + } + } + + } + if (y > 0) + { + int prev = src - stride; + int length = (stride - 1) & -4; + for (int x = 1; x <= length; ++x) + { + data[src + x] ^= data[prev + x]; + + } + } + src += stride; + } + } + + BitmapPalette ReadPalette () + { + int count = m_input.ReadUInt16(); + int elem_size = m_input.ReadUInt16(); + if (elem_size != 2) + throw new InvalidFormatException (string.Format ("Invalid palette element size {0}", elem_size)); + var colors = new Color[count]; + for (int i = 0; i < count; ++i) + { + int v = m_input.ReadUInt16(); + int r = (v >> 4) & 0xF; + int g = (v >> 8) & 0xF; + int b = (v ) & 0xF; + colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11)); + } +// colors[0].A = 0; // force transparency + return new BitmapPalette (colors); + } + } +} diff --git a/Legacy/Blucky/Aliases.cs b/Legacy/Blucky/Aliases.cs new file mode 100644 index 00000000..39d738cb --- /dev/null +++ b/Legacy/Blucky/Aliases.cs @@ -0,0 +1,41 @@ +//! \file Aliases.cs +//! \date 2023 Sep 17 +//! \brief Blucky formats aliases. +// +// 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; + +// [970627][Blucky] Rekiai + +namespace GameRes.Formats.Blucky +{ + [Export(typeof(ResourceAlias))] + [ExportMetadata("Extension", "OSA")] + [ExportMetadata("Target", "BMP")] + public class OsaFormat : ResourceAlias { } + + [Export(typeof(ResourceAlias))] + [ExportMetadata("Extension", "WF")] + [ExportMetadata("Target", "WAV")] + public class WfFormat : ResourceAlias { } +} diff --git a/Legacy/Grocer/ImagePIC.cs b/Legacy/Grocer/ImagePIC.cs new file mode 100644 index 00000000..4da613d0 --- /dev/null +++ b/Legacy/Grocer/ImagePIC.cs @@ -0,0 +1,193 @@ +//! \file ImagePIC.cs +//! \date 2023 Sep 25 +//! \brief Grocer 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; + +// [941209][Grocer] Wedding Errantry -Gyakutama Ou- + +namespace GameRes.Formats.Grocer +{ + [Export(typeof(ImageFormat))] + public class PicFormat : ImageFormat + { + public override string Tag => "PIC/GROCER"; + public override string Description => "Grocer image format"; + public override uint Signature => 1; + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x57); + if (!header.AsciiEqual (0x10, "Actor98")) + return null; + uint width = (uint)header.ToUInt16 (0x53) << 3; + if (width > 640) + return null; + return new ImageMetaData + { + Width = width, + Height = header.ToUInt16 (0x55), + BPP = 4, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new PicReader (file, info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("PicFormat.Write not implemented"); + } + } + + internal class PicReader + { + IBinaryStream m_input; + ImageMetaData m_info; + + public PicReader (IBinaryStream input, ImageMetaData info) + { + m_input = input; + m_info = info; + } + + public ImageData Unpack () + { + m_input.Position = 0x21; + var palette = ReadPalette(); + m_input.Position = 0x57; + int stride = m_info.iWidth / 8; + var pixels = new byte[m_info.iWidth * m_info.iHeight]; + var buffer = new byte[0x3C0]; + int output_pos = 0; + for (int y = 0; y < m_info.iHeight; ++y) + { + int x; + for (int plane = 0; plane < 4; ++plane) + { + x = 0; + while (x < stride) + { + byte cur_byte = m_input.ReadUInt8(); + if (cur_byte > 0 && cur_byte < 6) + { + int count = m_input.ReadUInt8(); + switch (cur_byte) + { + case 1: + { + cur_byte = m_input.ReadUInt8(); + int dst = plane * 0x50 + x + 0x280; + for (int i = 0; i < count; ++i) + { + buffer[dst+i] = cur_byte; + } + break; + } + case 2: + { + int src = plane * 0x50 + x; + int dst = src + 0x280; + Buffer.BlockCopy (buffer, src, buffer, dst, count); + break; + } + case 3: + { + int src = x + 0x280; + int dst = plane * 0x50 + src; + Buffer.BlockCopy (buffer, src, buffer, dst, count); + break; + } + case 4: + { + int src = x + 0x2D0; + int dst = plane * 0x50 + x + 0x280; + Buffer.BlockCopy (buffer, src, buffer, dst, count); + break; + } + case 5: + { + int src = x + 0x320; + int dst = plane * 0x50 + x + 0x280; + Buffer.BlockCopy (buffer, src, buffer, dst, count); + break; + } + } + x += count; + } + else + { + if (6 == cur_byte) + { + cur_byte = m_input.ReadUInt8(); + } + int dst = plane * 0x50 + x + 0x280; + buffer[dst] = cur_byte; + ++x; + } + } + } + for (x = 0; x < stride; ++x) + { + byte mask = 0x80; + for (int i = 0; i < 8; ++i) + { + byte px = 0; + if ((buffer[x + 0x280] & mask) != 0) px |= 0x01; + if ((buffer[x + 0x2D0] & mask) != 0) px |= 0x02; + if ((buffer[x + 0x320] & mask) != 0) px |= 0x04; + if ((buffer[x + 0x370] & mask) != 0) px |= 0x08; + pixels[output_pos + (x << 3) + i] = px; + mask >>= 1; + } + } + Buffer.BlockCopy (buffer, 0x140, buffer, 0, 0x280); + output_pos += m_info.iWidth; + } + return ImageData.Create (m_info, PixelFormats.Indexed8, palette, pixels); + } + + BitmapPalette ReadPalette () + { + 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/Legacy.csproj b/Legacy/Legacy.csproj index 3f5b9ce2..ce3dac7f 100644 --- a/Legacy/Legacy.csproj +++ b/Legacy/Legacy.csproj @@ -89,8 +89,10 @@ + + @@ -144,6 +146,8 @@ + + @@ -156,6 +160,9 @@ + + + diff --git a/Legacy/Pearl/ArcARY.cs b/Legacy/Pearl/ArcARY.cs new file mode 100644 index 00000000..7e7020c4 --- /dev/null +++ b/Legacy/Pearl/ArcARY.cs @@ -0,0 +1,83 @@ +//! \file ArcARY.cs +//! \date 2023 Sep 23 +//! \brief Pearl Soft 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; + +namespace GameRes.Formats.Pearl +{ + // implementation based on BMX/TRIANGLE + // exact same layout, but doesn't have compressed entries. + + [Export(typeof(ArchiveFormat))] + public class AryOpener : ArchiveFormat + { + public override string Tag => "ARY"; + public override string Description => "Pearl Soft resource archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (0); + if (!IsSaneCount (count)) + return null; + uint index_size = (uint)count * 4 + 8; + if (index_size > file.View.Reserve (0, index_size)) + return null; + uint index_offset = 4; + uint offset = file.View.ReadUInt32 (index_offset); + if (offset != index_size) + return null; + uint last_offset = file.View.ReadUInt32 (index_size - 4); + if (last_offset != file.MaxOffset) + return null; + var base_name = Path.GetFileNameWithoutExtension (file.Name); + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + index_offset += 4; + var entry = new Entry { + Name = string.Format ("{0}#{1:D4}", base_name, i), + Offset = offset, + }; + offset = file.View.ReadUInt32 (index_offset); + entry.Size = (uint)(offset - entry.Offset); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + } + foreach (var entry in dir) + { + uint signature = file.View.ReadUInt32 (entry.Offset); + entry.ChangeType (AutoEntry.DetectFileType (signature)); + } + return new ArcFile (file, this, dir); + } + } +} diff --git a/Legacy/Pearl/ImagePL4.cs b/Legacy/Pearl/ImagePL4.cs new file mode 100644 index 00000000..2d6bd0af --- /dev/null +++ b/Legacy/Pearl/ImagePL4.cs @@ -0,0 +1,382 @@ +//! \file ImagePL4.cs +//! \date 2023 Sep 23 +//! \brief Pearl Soft 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; + +// [980424][Pearl Soft] Watashi + +namespace GameRes.Formats.Pearl +{ + internal class Pl4MetaData : ImageMetaData + { + public ushort CompressionMethod; + } + + [Export(typeof(ImageFormat))] + public class Pl4Format : ImageFormat + { + public override string Tag => "PL4"; + public override string Description => "Pearl Soft image format"; + public override uint Signature => 0x20344C50; // 'PL4 ' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x10); + int version = header.ToUInt16 (4); + if (version != 1) + return null; + var info = new Pl4MetaData { + Width = header.ToUInt16 (0xC) * 8u, + Height = header.ToUInt16 (0xE), + CompressionMethod = header.ToUInt16 (6), + BPP = 8, + }; + if (info.CompressionMethod > 1) + return null; + return info; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new Pl4Reader (file, (Pl4MetaData)info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("Pl4Format.Write not implemented"); + } + } + + internal class Pl4Reader + { + IBinaryStream m_input; + Pl4MetaData m_info; + int m_stride; + + public Pl4Reader (IBinaryStream input, Pl4MetaData info) + { + m_input = input; + m_info = info; + m_stride = m_info.iWidth; + } + + public ImageData Unpack () + { + m_input.Position = 0x10; + var palette = ReadPalette (16); + var pixels = new byte[m_stride * m_info.iHeight]; + m_input.Position = 0x40; + if (m_info.CompressionMethod == 0) + UnpackV0 (pixels); + else if (m_info.CompressionMethod == 1) + UnpackV1 (pixels); + return ImageData.Create (m_info, PixelFormats.Indexed8, palette, pixels, m_stride); + } + + BitmapPalette ReadPalette (int colors) + { + var color_data = m_input.ReadBytes (colors * 3); + var color_map = new Color[colors]; + int src = 0; + for (int i = 0; i < colors; ++i) + { + color_map[i] = Color.FromRgb ((byte)(color_data[src ] * 0x11), + (byte)(color_data[src+1] * 0x11), + (byte)(color_data[src+2] * 0x11)); + src += 3; + } + return new BitmapPalette (color_map); + } + + void UnpackV0 (byte[] output) + { + int height = m_info.iHeight; + int x = m_info.iWidth / 4; + int output_size = height * m_stride; + int row = 0; + int dst = 0; + int ctl, word; + while ((ctl = m_input.ReadByte()) != -1) + { + byte next = m_input.ReadUInt8(); + if (0x98 == ctl) + { + ctl = m_input.ReadUInt8(); + if (0 == next) + { + next = m_input.ReadUInt8(); + } + else + { + word = ctl << 8 | next; + int count = ((word >> 1) & 0x1F) + 2; + int src_y; + int src_x = Math.DivRem ((word >> 6) + 1, height, out src_y); + int src = dst - src_y * m_stride - 4 * src_x; + src_y = row - src_y; + if (src_y < 0) + { + src_y += height; + src += output_size - 4; + } + while (count --> 0) + { + output[dst ] = output[src ]; + output[dst+1] = output[src+1]; + output[dst+2] = output[src+2]; + output[dst+3] = output[src+3]; + dst += m_stride; + if (++row >= height) + { + row = 0; + dst -= output_size - 4; + if (--x <= 0) + return; + } + src += m_stride; + if (++src_y >= height) + { + src_y = 0; + src -= output_size - 4; + } + } + continue; + } + } + word = next << 8 | ctl; + int px = 0; + if ((word & 0x1000) != 0) px = 0x01000000; + if ((word & 0x2000) != 0) px |= 0x00010000; + if ((word & 0x4000) != 0) px |= 0x00000100; + if ((word & 0x8000) != 0) px |= 0x00000001; + if ((word & 0x0100) != 0) px |= 0x02000000; + if ((word & 0x0200) != 0) px |= 0x00020000; + if ((word & 0x0400) != 0) px |= 0x00000200; + if ((word & 0x0800) != 0) px |= 0x00000002; + if ((word & 0x0010) != 0) px |= 0x04000000; + if ((word & 0x0020) != 0) px |= 0x00040000; + if ((word & 0x0040) != 0) px |= 0x00000400; + if ((word & 0x0080) != 0) px |= 0x00000004; + if ((word & 0x0001) != 0) px |= 0x08000000; + if ((word & 0x0002) != 0) px |= 0x00080000; + if ((word & 0x0004) != 0) px |= 0x00000800; + if ((word & 0x0008) != 0) px |= 0x00000008; + LittleEndian.Pack (px, output, dst); + dst += m_stride; + if (++row >= height) + { + row = 0; + dst -= output_size - 4; + if (--x <= 0) + break; + } + } + } + + byte[] m_pixelBuffer; + MsbBitStream m_bits; + + void UnpackV1 (byte[] output) + { + m_pixelBuffer = InitLineBuffer(); + int height = m_info.iHeight; + int dst = 0; + int output_size = m_stride * height; + int x = m_info.iWidth / 8; + int y = 0; + using (m_bits = new MsbBitStream (m_input.AsStream, true)) + { + int p1 = 0, p2 = 0, p3 = 0, p4 = 0; + int ctl_bit; + while ((ctl_bit = m_bits.GetNextBit()) != -1) + { + if (ctl_bit != 0) + { + int src = dst; + int src_y = y; + switch (m_bits.GetBits (2)) + { + case 0: + src_y = y - 2; + src = dst - 2 * m_stride; + break; + case 1: + src_y = y - 1; + src = dst - m_stride; + break; + case 2: + src_y = y - 4; + src = dst - 4 * m_stride; + break; + case 3: + src = dst - 8; + break; + } + if (src_y < 0) + { + src_y += height; + src += output_size - 8; + } + int count_length = 0; + while (m_bits.GetNextBit() == 0) + ++count_length; + int count = 1; + if (count_length != 0) + { + count = m_bits.GetBits (count_length) | 1 << count_length; + } + while (count --> 0) + { + Buffer.BlockCopy (output, src, output, dst, 8); + dst += m_stride; + if (++y >= height) + { + y = 0; + dst -= output_size - 8; + if (--x <= 0) + return; + } + src += m_stride; + if (++src_y >= height) + { + src_y = 0; + src -= output_size - 8; + } + } + } + else + { + int px1 = 0; + int px2 = 0; + p1 = UpdatePixel (p1); + p2 = UpdatePixel (p2); + p3 = UpdatePixel (p3); + p4 = UpdatePixel (p4); + if ((p1 & 0x80) != 0) px1 = 0x00000001; + if ((p1 & 0x40) != 0) px1 |= 0x00000100; + if ((p1 & 0x20) != 0) px1 |= 0x00010000; + if ((p1 & 0x10) != 0) px1 |= 0x01000000; + if ((p1 & 0x08) != 0) px2 = 0x00000001; + if ((p1 & 0x04) != 0) px2 |= 0x00000100; + if ((p1 & 0x02) != 0) px2 |= 0x00010000; + if ((p1 & 0x01) != 0) px2 |= 0x01000000; + if ((p2 & 0x80) != 0) px1 |= 0x00000002; + if ((p2 & 0x40) != 0) px1 |= 0x00000200; + if ((p2 & 0x20) != 0) px1 |= 0x00020000; + if ((p2 & 0x10) != 0) px1 |= 0x02000000; + if ((p2 & 0x08) != 0) px2 |= 0x00000002; + if ((p2 & 0x04) != 0) px2 |= 0x00000200; + if ((p2 & 0x02) != 0) px2 |= 0x00020000; + if ((p2 & 0x01) != 0) px2 |= 0x02000000; + if ((p3 & 0x80) != 0) px1 |= 0x00000004; + if ((p3 & 0x40) != 0) px1 |= 0x00000400; + if ((p3 & 0x20) != 0) px1 |= 0x00040000; + if ((p3 & 0x10) != 0) px1 |= 0x04000000; + if ((p3 & 0x08) != 0) px2 |= 0x00000004; + if ((p3 & 0x04) != 0) px2 |= 0x00000400; + if ((p3 & 0x02) != 0) px2 |= 0x00040000; + if ((p3 & 0x01) != 0) px2 |= 0x04000000; + if ((p4 & 0x80) != 0) px1 |= 0x00000008; + if ((p4 & 0x40) != 0) px1 |= 0x00000800; + if ((p4 & 0x20) != 0) px1 |= 0x00080000; + if ((p4 & 0x10) != 0) px1 |= 0x08000000; + if ((p4 & 0x08) != 0) px2 |= 0x00000008; + if ((p4 & 0x04) != 0) px2 |= 0x00000800; + if ((p4 & 0x02) != 0) px2 |= 0x00080000; + if ((p4 & 0x01) != 0) px2 |= 0x08000000; + LittleEndian.Pack (px1, output, dst); + LittleEndian.Pack (px2, output, dst+4); + dst += m_stride; + if (++y >= height) + { + y = 0; + dst -= output_size - 8; + if (--x <= 0) + break; + } + } + } + } + } + + int UpdatePixel (int pixel) + { + byte nibble = GetNextPixel (pixel); + return GetNextPixel (nibble) | nibble << 4; + } + + byte GetNextPixel (int pixel) + { + int bits = GetPixelBits(); + int prior = (pixel & 0xF) << 4; + byte next = m_pixelBuffer[prior+bits]; + int pos = prior + bits; + if (bits == 0) + return next; + while (bits --> 0) + { + m_pixelBuffer[pos] = m_pixelBuffer[pos - 1]; + --pos; + } + return m_pixelBuffer[prior] = next; + } + + int GetPixelBits () + { + if (m_bits.GetNextBit() != 0) + { + return m_bits.GetBits (1); + } + else if (m_bits.GetNextBit() != 0) + { + return m_bits.GetBits (1) + 2; + } + else if (m_bits.GetNextBit() != 0) + { + return m_bits.GetBits (2) + 4; + } + else + { + return m_bits.GetBits (3) + 8; + } + } + + static byte[] InitLineBuffer () + { + var buffer = new byte[256]; + for (int i = 0; i < 256; ++i) + { + buffer[i] = (byte)((i + (i >> 4)) & 0xF); + } + return buffer; + } + } +} diff --git a/Legacy/PlanTech/ArcPAC.cs b/Legacy/PlanTech/ArcPAC.cs index 21374276..f1c8dbc0 100644 --- a/Legacy/PlanTech/ArcPAC.cs +++ b/Legacy/PlanTech/ArcPAC.cs @@ -29,9 +29,7 @@ using System.IO; namespace GameRes.Formats.PlanTech { -#if DEBUG [Export(typeof(ArchiveFormat))] -#endif public class PacOpener : ArchiveFormat { public override string Tag { get { return "PAC/PLANTECH"; } } diff --git a/Legacy/PlanTech/ImagePAC.cs b/Legacy/PlanTech/ImagePAC.cs index 02ce04a3..b2efee40 100644 --- a/Legacy/PlanTech/ImagePAC.cs +++ b/Legacy/PlanTech/ImagePAC.cs @@ -29,9 +29,7 @@ using System.Windows.Media; namespace GameRes.Formats.PlanTech { -#if DEBUG [Export(typeof(ImageFormat))] -#endif public class PacFormat : ImageFormat { public override string Tag { get { return "PAC/PLANTECH"; } } diff --git a/Legacy/ProjectMyu/ImageGAM.cs b/Legacy/ProjectMyu/ImageGAM.cs index 736d546f..49ff4791 100644 --- a/Legacy/ProjectMyu/ImageGAM.cs +++ b/Legacy/ProjectMyu/ImageGAM.cs @@ -28,6 +28,7 @@ using System.ComponentModel.Composition; using System.IO; using GameRes.Compression; +// [031219][Project-μ] Gin no Hebi Kuro no Tsuki // [040528][Lakshmi] Mabuta Tojireba Soko ni... namespace GameRes.Formats.ProjectMu diff --git a/Legacy/RedZone/ArcPAK.cs b/Legacy/RedZone/ArcPAK.cs new file mode 100644 index 00000000..69a82a42 --- /dev/null +++ b/Legacy/RedZone/ArcPAK.cs @@ -0,0 +1,68 @@ +//! \file ArcPAK.cs +//! \date 2023 Sep 18 +//! \brief RED-ZONE 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; + +// [010706][RED-ZONE] Kenkyuu Nisshi + +namespace GameRes.Formats.RedZone +{ + [Export(typeof(ArchiveFormat))] + public class PakOpener : ArchiveFormat + { + public override string Tag => "PAK/REDZONE"; + public override string Description => "RED-ZONE resource archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (0); + if (!IsSaneCount (count)) + return null; + + uint index_offset = 4; + const uint index_entry_size = 0x54; + long min_offset = index_offset + count * index_entry_size; + if (min_offset >= file.MaxOffset) + return null; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = file.View.ReadString (index_offset, 0x44); + var entry = Create (name); + entry.Offset = file.View.ReadUInt32 (index_offset+0x44); + entry.Size = file.View.ReadUInt32 (index_offset+0x48); + if (entry.Offset < min_offset || !entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_offset += index_entry_size; + } + return new ArcFile (file, this, dir); + } + } +} diff --git a/Legacy/RedZone/ScriptQDO.cs b/Legacy/RedZone/ScriptQDO.cs new file mode 100644 index 00000000..9ad1ce32 --- /dev/null +++ b/Legacy/RedZone/ScriptQDO.cs @@ -0,0 +1,74 @@ +//! \file ScriptQDO.cs +//! \date 2023 Sep 21 +//! \brief RED-ZONE binary script. +// +// 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; + +namespace GameRes.Formats.RedZone +{ + [Export(typeof(ScriptFormat))] + public class QdoOpener : GenericScriptFormat + { + public override string Tag => "QDO"; + public override string Description => "Red-Zone script file"; + public override uint Signature => 0x5F4F4451; // 'QDO_SHO' + + public override bool IsScript (IBinaryStream file) + { + var header = file.ReadHeader (8); + return header.AsciiEqual ("QDO_SHO"); + } + + const int ScriptDataPos = 0x0E; + + public override Stream ConvertFrom (IBinaryStream file) + { + var data = file.ReadBytes ((int)file.Length); + if (data[0xC] != 0) + { + for (int i = ScriptDataPos; i < data.Length; ++i) + { + data[i] = (byte)~(data[i] - 13); + } + data[0xC] = 0; + } + return new BinMemoryStream (data, file.Name); + } + + public override Stream ConvertBack (IBinaryStream file) + { + var data = file.ReadBytes ((int)file.Length); + if (data[0xC] == 0) + { + for (int i = ScriptDataPos; i < data.Length; ++i) + { + data[i] = (byte)(~data[i] + 13); + } + data[0xC] = 1; + } + return new BinMemoryStream (data, file.Name); + } + } +} From 1b585ea30ebaf4f2681e474acc0f6878cbb6e253 Mon Sep 17 00:00:00 2001 From: morkt Date: Wed, 11 Oct 2023 18:38:23 +0400 Subject: [PATCH 04/22] updated formats. (PICT): fixed 16bpp images. (GDT): added image format. (DXR): tweaks to recognizing DXR inside exe files. (NSA): recognize mp3 files named as nsa. (TLZ): added ContainedFormats. (WrapSingleFileAchive): class that represents single file as an archive. (DesertCgOPener): Software House Parsley archive. (Triangle.RleReader): utilize UnpackV2, replaced BinaryReader with IBinaryStream. --- ArcFormats/ArcFormats.csproj | 3 + ArcFormats/CsWare/ImageGDT.cs | 569 ++++++++++++++++++++ ArcFormats/Lilim/ArcAOS.cs | 4 + ArcFormats/Macintosh/ImagePICT.cs | 15 +- ArcFormats/Macromedia/ArcDXR.cs | 20 +- ArcFormats/NScripter/ArcNSA.cs | 3 + ArcFormats/Otemoto/ArcTLZ.cs | 12 +- ArcFormats/Otemoto/ImageMAG.cs | 2 +- ArcFormats/SingleFileArchive.cs | 67 +++ ArcFormats/Software House Parsley/ArcCG3.cs | 170 ++++++ ArcFormats/Triangle/ImageIAF.cs | 47 +- 11 files changed, 872 insertions(+), 40 deletions(-) create mode 100644 ArcFormats/CsWare/ImageGDT.cs create mode 100644 ArcFormats/SingleFileArchive.cs create mode 100644 ArcFormats/Software House Parsley/ArcCG3.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index db64baa0..222931c4 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -106,6 +106,7 @@ + @@ -153,6 +154,8 @@ + + diff --git a/ArcFormats/CsWare/ImageGDT.cs b/ArcFormats/CsWare/ImageGDT.cs new file mode 100644 index 00000000..e184874f --- /dev/null +++ b/ArcFormats/CsWare/ImageGDT.cs @@ -0,0 +1,569 @@ +//! \file ImageGDT.cs +//! \date 2023 Sep 29 +//! \brief AGS 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.CsWare +{ + internal class GdtMetaData : ImageMetaData + { + public byte Flags; + + public bool HasPalette => (Flags & 0x80) != 0; + public bool IsDouble => (Flags & 0x40) == 0; + } + + [Export(typeof(ImageFormat))] + public class GdtFormat : ImageFormat + { + public override string Tag => "GDT"; + public override string Description => "AGS engine image format"; + public override uint Signature => 0x314144; // 'DA1' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (16); + var info = new GdtMetaData { + OffsetX = header[8] << 3, + OffsetY = header.ToUInt16 (0xA), + Width = (uint)header[9] << 3, + Height = header.ToUInt16 (0xC), + BPP = 4, + Flags = header[0xF], + }; + return info; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new GdtReader (file, (GdtMetaData)info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("GdtFormat.Write not implemented"); + } + } + + internal class GdtReader + { + IBinaryStream m_input; + GdtMetaData m_info; + int m_stride; + int m_output_stride; + + public BitmapPalette Palette { get; private set; } + + public GdtReader (IBinaryStream file, GdtMetaData info) + { + m_input = file; + m_info = info; + m_stride = info.iWidth >> 3; + m_output_stride = info.iWidth >> 1; + } + + byte[][] m_planes; + + public ImageData Unpack () + { + m_input.Position = 0x10; + if (m_info.HasPalette) + { + Palette = ReadPalette(); + } + var packed_sizes = new ushort[4]; + for (int i = 0; i < 4; ++i) + packed_sizes[i] = m_input.ReadUInt16(); + long plane_pos = m_input.Position; + 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], + }; + Action UnpackPlane = UnpackSingle; + if (m_info.IsDouble) + UnpackPlane = UnpackDouble; + for (int i = 0; i < 4; ++i) + { + m_input.Position = plane_pos; + plane_pos += packed_sizes[i]; + UnpackPlane (i); + } + var pixels = new byte[m_output_stride * m_info.iHeight]; + FlattenPlanes (pixels); + PixelFormat format; + if (null == Palette) + format = PixelFormats.Gray4; + else + format = PixelFormats.Indexed4; + return ImageData.Create (m_info, format, Palette, pixels, m_output_stride); + } + + void UnpackSingle (int plane_index) + { + int h = m_info.iHeight; + int w = m_stride; + int dst = 0; + while (w --> 0) + { + Unpack8Line (plane_index, dst); + dst += h; + } + } + + void UnpackDouble (int plane_index) + { + var output = m_planes[plane_index]; + int h = m_info.iHeight; + int width = m_stride; + int dst = 0; + if ((m_info.OffsetX & 8) != 0) + { + Unpack8Line (plane_index, dst); + --width; + dst += h; + } + if ((m_input.Position & 1) != 0) + m_input.Seek (1, SeekOrigin.Current); + if (1 == width) + { + Unpack8Line (plane_index, dst); + return; + } + while (m_input.PeekByte() != -1) + { + byte op = m_input.ReadUInt8(); + byte ctl = m_input.ReadUInt8(); + if (ctl < 0x80) + { + if (0 == ctl) + continue; + ushort w = (ushort)(((op & 0xF) << 8 | (op & 0xF0) >> 4) * 0x11); + int count = ctl; + Fill (output, dst , count, w); + Fill (output, dst+h, count, w); + dst += count * 2; + } + else if (ctl < 0xC0) + { + int count = ctl & 0x3F; + int w = op & 0xF | (op & 0xF0) << 4; + uint d = (uint)(w | (w & 0x0303) << 18 | (w & 0x0C0C) << 14); + d = Binary.BigEndian (d | d << 4); + Fill (output, dst , count, d); + Fill (output, dst+h, count, d); + dst += count * 4; + } + else if (ctl < 0xD0) + { + byte b = (byte)((ctl & 0xF) | ctl << 4); + int count = op; + Fill (output, dst , count, b); + Fill (output, dst+h, count, b); + dst += count; + } + else if (ctl < 0xD2) + { + int count = (ctl & 1) << 8 | op; + while (count --> 0) + { + output[dst ] = m_input.ReadUInt8(); + output[dst+h] = m_input.ReadUInt8(); + } + } + else if (0xD2 == ctl) + { + dst += op; + } + else if (ctl < 0xF3) + { + int count = op; + int off = 0; + switch (ctl) + { + case 0xD3: off = 16; break; + case 0xD4: off = 12; break; + case 0xD5: off = 8; break; + case 0xD6: off = 4; break; + case 0xD7: off = 2; break; + case 0xD8: off = 1; break; + case 0xD9: off = h * 2 + 8; break; + case 0xDA: off = h * 2 + 4; break; + case 0xDB: off = h * 2 + 2; break; + case 0xDC: off = h * 2 + 1; break; + case 0xDD: off = h * 2; break; + case 0xDE: off = h * 2 - 1; break; + case 0xDF: off = h * 2 - 2; break; + case 0xE0: off = h * 2 - 4; break; + case 0xE1: off = h * 2 - 8; break; + case 0xE2: off = h * 4 + 8; break; + case 0xE3: off = h * 4 + 4; break; + case 0xE4: off = h * 4 + 2; break; + case 0xE5: off = h * 4 + 1; break; + case 0xE6: off = h * 4; break; + case 0xE7: off = h * 4 - 1; break; + case 0xE8: off = h * 4 - 2; break; + case 0xE9: off = h * 4 - 4; break; + case 0xEA: off = h * 4 - 8; break; + case 0xEB: off = h * 6 + 4; break; + case 0xEC: off = h * 6 + 2; break; + case 0xED: off = h * 6 + 1; break; + case 0xEE: off = h * 6; break; + case 0xEF: off = h * 6 - 1; break; + case 0xF0: off = h * 6 - 2; break; + case 0xF1: off = h * 6 - 4; break; + case 0xF2: off = h * 8; break; + } + Binary.CopyOverlapped (output, dst-off, dst, count); + Binary.CopyOverlapped (output, dst-off+h, dst+h, count); + dst += count; + } + else if (ctl < 0xFC) + { + int count = op; + var source = m_planes[(ctl - 0xF3) % 3]; + if (ctl < 0xF6) + { + Buffer.BlockCopy (source, dst, output, dst, count); + Buffer.BlockCopy (source, dst+h, output, dst+h, count); + dst += count; + } + else if (ctl > 0xF8) + { + var source1 = m_planes[ctl & 1]; + var source2 = m_planes[ctl & 2]; + while (count --> 0) + { + output[dst] = (byte)(source1[dst] & source2[dst]); + output[dst+h] = (byte)(source1[dst+h] & source2[dst+h]); + ++dst; + } + } + else + { + while (count --> 0) + { + output[dst] = (byte)~source[dst]; + output[dst+h] = (byte)~source[dst+h]; + ++dst; + } + } + } + else if (0xFC == ctl) + { + int count = op; + byte b = m_input.ReadUInt8(); + Fill (output, dst , count, b); + Fill (output, dst+h, count, b); + dst += count; + } + else if (0xFD == ctl) + { + if (op < 0x80) + { + int count = op; + ushort w = m_input.ReadUInt16(); + Fill (output, dst , count, w); + Fill (output, dst+h, count, w); + dst += count * 2; + } + else + { + int count = op & 0x7F; + ushort w1 = m_input.ReadUInt16(); + ushort w2 = m_input.ReadUInt16(); + Fill (output, dst , count, (ushort)(w1 << 8 | w2 & 0xFF)); + Fill (output, dst+h, count, (ushort)(w1 & 0xFF | w2 >> 8)); + dst += count * 2; + } + } + else if (0xFE == ctl) + { + int count = op & 0x3F; + if (op < 0x80) + { + byte b0 = m_input.ReadUInt8(); + byte b1 = m_input.ReadUInt8(); + int d = b1 | b0 << 16; + d = d & 0x0F000F | (d & 0xF000F0) << 4; + d *= 0x11; + d = Binary.BigEndian (d); + Fill (output, dst , count, (uint)d); + Fill (output, dst+h, count, (uint)d); + } + else + { + uint d0 = m_input.ReadUInt32(); + uint d1 = m_input.ReadUInt32(); + uint p0 = d0 << 24 | d0 & 0xFF0000 | (d1 & 0xFF) << 8 | (d1 & 0xFF0000) >> 16; + uint p1 = (d0 & 0xFF00) << 16 | (d0 & 0xFF000000) >> 8 | d1 & 0xFF00 | (d1 & 0xFF000000) >> 24; + Fill (output, dst , count, p0); + Fill (output, dst+h, count, p1); + } + dst += count * 4; + } + else // 0xFF + { + dst += h; + width -= 2; + if (0 == width) + break; + if (1 == width) + { + Unpack8Line (plane_index, dst); + break; + } + } + } + } + + void Unpack8Line (int plane_index, int dst) + { + var output = m_planes[plane_index]; + int h = m_info.iHeight; + int end_pos = dst + h; + while (m_input.PeekByte() != -1) + { + byte ctl = m_input.ReadUInt8(); + if (ctl < 0x40) + { + byte b = 0; + if (ctl >= 0x20) + b = 0xFF; + int count = ctl & 0x1F; + if (0 == count) + count = m_input.ReadUInt8(); + Fill (output, dst, count, b); + dst += count; + } + else if (ctl < 0xA0) + { + int count = ctl & 0x1F; + if (0 == count) + count = m_input.ReadUInt8(); + int src_plane = (ctl - 0x40) >> 5; + Buffer.BlockCopy (m_planes[src_plane], dst, output, dst, count); + dst += count; + } + else if (ctl < 0xF0) + { + int count = ctl & 0xF; + if (0 == count) + count = m_input.ReadUInt8(); + switch (ctl & 0xF0) + { + case 0xA0: Binary.CopyOverlapped (output, dst-16, dst, count); break; + case 0xB0: Binary.CopyOverlapped (output, dst-8, dst, count); break; + case 0xC0: Binary.CopyOverlapped (output, dst-4, dst, count); break; + case 0xD0: Binary.CopyOverlapped (output, dst-2, dst, count); break; + case 0xE0: Binary.CopyOverlapped (output, dst-h*2, dst, count); break; + } + dst += count; + } + else if (ctl < 0xF9) + { + int count = ctl & 0xF; + if (0 == count) + count = m_input.ReadUInt8(); + m_input.Read (output, dst, count); + dst += count; + } + else if (0xF9 == ctl) + { + dst += m_input.ReadUInt8(); + } + else if (0xFA == ctl) + { + int count = m_input.ReadUInt8(); + byte b = m_input.ReadUInt8(); + Fill (output, dst, count, b); + dst += count; + } + else if (0xFB == ctl) + { + int count = m_input.ReadUInt8(); + int b = count >> 7; + count &= 0x7F; + while (count --> 0) + { + output[dst] = (byte)~m_planes[b][dst]; + ++dst; + } + } + else if (0xFC == ctl) + { + int count = m_input.ReadUInt8(); + if ((count & 0x80) != 0) + { + count &= 0x7F; + byte b = m_input.ReadUInt8(); + ushort d = (ushort)(b & 0xF | (b & 0xF0) << 4); + d |= (ushort)(d << 4); + Fill (output, dst, count, d); + dst += count * 2; + } + else + { + while (count --> 0) + { + output[dst] = (byte)~m_planes[2][dst]; + ++dst; + } + } + } + else if (0xFD == ctl) + { + int count = m_input.ReadUInt8(); + if ((count & 0x80) != 0) + { + byte b = m_input.ReadUInt8(); + uint d = (uint)(b & 0xF | b << 4 | (b & 0xF0) << 8); + if ((count & 0x40) != 0) + { + b = m_input.ReadUInt8(); + d |= (uint)((b & 0xF) << 16 | b << 20 | (b & 0xF0) << 24); + } + else + { + d |= (d & 0x3F3F) << 18 | (d & 0xC0C0) << 10; + } + count &= 0x3F; + Fill (output, dst, count, d); + dst += count * 4; + } + else + { + ushort w = m_input.ReadUInt16(); + Fill (output, dst, count, w); + dst += count * 2; + } + } + else if (0xFE == ctl) + { + int count = m_input.ReadUInt8(); + int b = count & 0xC0; + if (b != 0) + { + count &= 0x3F; + b >>= 6; + while (count --> 0) + { + output[dst] = (byte)(m_planes[b & 1][dst] & m_planes[b & 2][dst]); + ++dst; + } + } + else + { + uint u = m_input.ReadUInt32(); + Fill (output, dst, count, u); + dst += count * 4; + } + } + else // 0xFF + { + break; + } + } + } + + void FlattenPlanes (byte[] output) + { + int plane_size = m_planes[0].Length; + int src = 0; + for (int x = 0; x < m_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 += m_output_stride; + } + } + } + + static void Fill (byte[] output, int dst, int count, byte pixel) + { + while (count --> 0) + { + output[dst++] = pixel; + } + } + + static void Fill (byte[] output, int dst, int count, ushort pixel) + { + count <<= 1; + for (int i = 0; i < count; i += 2) + { + LittleEndian.Pack (pixel, output, dst+i); + } + } + + static void Fill (byte[] output, int dst, int count, uint pixel) + { + count <<= 2; + for (int i = 0; i < count; i += 4) + { + LittleEndian.Pack (pixel, output, dst+i); + } + } + + BitmapPalette ReadPalette () + { + using (var bits = new MsbBitStream (m_input.AsStream, true)) + { + var colors = new Color[16]; + for (int i = 0; i < 16; ++i) + { + int b = bits.GetBits (4) * 0x11; + int r = bits.GetBits (4) * 0x11; + int g = bits.GetBits (4) * 0x11; + colors[i] = Color.FromRgb ((byte)r, (byte)g, (byte)b); + } + return new BitmapPalette (colors); + } + } + } +} diff --git a/ArcFormats/Lilim/ArcAOS.cs b/ArcFormats/Lilim/ArcAOS.cs index 5c4e030d..64cf39a4 100644 --- a/ArcFormats/Lilim/ArcAOS.cs +++ b/ArcFormats/Lilim/ArcAOS.cs @@ -60,6 +60,7 @@ namespace GameRes.Formats.Lilim if (!name_buf.SequenceEqual (IndexLink) && !name_buf.SequenceEqual (IndexEnd)) return null; + string last_name = null; long current_offset = 0; var dir = new List (0x3E); while (current_offset < file.MaxOffset) @@ -79,6 +80,9 @@ namespace GameRes.Formats.Lilim if (-1 == name_length) name_length = name_buf.Length; var name = Encodings.cp932.GetString (name_buf, 0, name_length); + if (last_name == name || string.IsNullOrWhiteSpace (name)) + return null; + last_name = name; var entry = FormatCatalog.Instance.Create (name); entry.Offset = file.View.ReadUInt32 (current_offset+0x10); entry.Size = file.View.ReadUInt32 (current_offset+0x14); diff --git a/ArcFormats/Macintosh/ImagePICT.cs b/ArcFormats/Macintosh/ImagePICT.cs index 98d999d9..95c9c55c 100644 --- a/ArcFormats/Macintosh/ImagePICT.cs +++ b/ArcFormats/Macintosh/ImagePICT.cs @@ -294,8 +294,10 @@ namespace GameRes.Formats.Apple byte[] RepackPixels (Pixmap pixmap) { int bpp = m_info.BPP; - if (bpp <= 16) + if (bpp < 16) return m_buffer; + else if (16 == bpp) + return Repack16bpp(); int bytes_per_pixel = bpp / 8; int stride = m_info.iWidth * bytes_per_pixel; var pixels = new byte[stride * m_info.iHeight]; @@ -326,6 +328,17 @@ namespace GameRes.Formats.Apple return pixels; } + byte[] Repack16bpp () // swap 16bit pixels to little-endian order + { + for (int p = 1; p < m_buffer.Length; p += 2) + { + byte b = m_buffer[p-1]; + m_buffer[p-1] = m_buffer[p]; + m_buffer[p] = b; + } + return m_buffer; + } + void SetFormat (Pixmap pixmap) { int bpp = null == pixmap ? 8 : pixmap.BPP; diff --git a/ArcFormats/Macromedia/ArcDXR.cs b/ArcFormats/Macromedia/ArcDXR.cs index 31a19e29..1fa780da 100644 --- a/ArcFormats/Macromedia/ArcDXR.cs +++ b/ArcFormats/Macromedia/ArcDXR.cs @@ -49,7 +49,7 @@ namespace GameRes.Formats.Macromedia public DxrOpener () { - Extensions = new[] { "dxr", "cxt", "cct", "dcr", "exe" }; + Extensions = new[] { "dxr", "cxt", "cct", "dcr", "dir", "exe" }; Signatures = new[] { SignatureXFIR, SignatureRIFX, 0x00905A4Du, 0u }; } @@ -411,7 +411,7 @@ namespace GameRes.Formats.Macromedia pos = exe.Overlay.Offset; if (pos >= file.MaxOffset) return 0; - if (file.View.AsciiEqual (pos, "10JP")) + if (file.View.AsciiEqual (pos, "10JP") || file.View.AsciiEqual (pos, "59JP")) { pos = file.View.ReadUInt32 (pos+4); } @@ -436,7 +436,8 @@ namespace GameRes.Formats.Macromedia // only the first XFIR entry is matched here, but archive may contain multiple sub-archives. if (entry.FourCC == "File") { - if (file.View.AsciiEqual (entry.Offset-8, "XFIR")) + if (file.View.AsciiEqual (entry.Offset-8, "XFIR") + && !file.View.AsciiEqual (entry.Offset, "artX")) return entry.Offset-8; } } @@ -469,12 +470,15 @@ namespace GameRes.Formats.Macromedia Left = reader.ReadI16(); Bottom = reader.ReadI16(); Right = reader.ReadI16(); - reader.Skip (0x0C); - BitDepth = reader.ReadU16() & 0xFF; // ??? - if (data.Length >= 0x1C) + if (data.Length > 0x16) { - reader.Skip (2); - Palette = reader.ReadI16(); + reader.Skip (0x0C); + BitDepth = reader.ReadU16() & 0xFF; // ??? + if (data.Length >= 0x1C) + { + reader.Skip (2); + Palette = reader.ReadI16(); + } } } } diff --git a/ArcFormats/NScripter/ArcNSA.cs b/ArcFormats/NScripter/ArcNSA.cs index 1e95d140..f1d4431a 100644 --- a/ArcFormats/NScripter/ArcNSA.cs +++ b/ArcFormats/NScripter/ArcNSA.cs @@ -107,6 +107,9 @@ namespace GameRes.Formats.NScripter catch { /* ignore parse errors */ } if (zero_signature || !file.Name.HasExtension (".nsa")) return null; + uint signature = file.View.ReadUInt32 (0); + if ((signature & 0xFFFFFF) == 0x90FBFF) // looks like mp3 file + return new WrapSingleFileArchive (file, Path.GetFileNameWithoutExtension (file.Name)+".mp3"); var password = QueryPassword(); if (string.IsNullOrEmpty (password)) diff --git a/ArcFormats/Otemoto/ArcTLZ.cs b/ArcFormats/Otemoto/ArcTLZ.cs index 585bc48d..bbc2b8ff 100644 --- a/ArcFormats/Otemoto/ArcTLZ.cs +++ b/ArcFormats/Otemoto/ArcTLZ.cs @@ -39,6 +39,11 @@ namespace GameRes.Formats.Otemoto public override bool IsHierarchic { get { return false; } } public override bool CanWrite { get { return false; } } + public TlzOpener () + { + ContainedFormats = new[] { "BMP", "SCR" }; + } + public override ArcFile TryOpen (ArcView file) { int count = file.View.ReadInt32 (0xC); @@ -61,7 +66,7 @@ namespace GameRes.Formats.Otemoto if (0 == name_length || name_length > 0x100) return null; entry.Name = file.View.ReadString (index_offset+0x10, name_length); - entry.Type = FormatCatalog.Instance.GetTypeFromName (entry.Name); + entry.Type = FormatCatalog.Instance.GetTypeFromName (entry.Name, ContainedFormats); entry.IsPacked = entry.UnpackedSize != entry.Size; dir.Add (entry); index_offset += 0x10 + name_length; @@ -78,4 +83,9 @@ namespace GameRes.Formats.Otemoto return new LzssStream (input); } } + + [Export(typeof(ResourceAlias))] + [ExportMetadata("Extension", "SNR")] + [ExportMetadata("Target", "SCR")] + internal class SnrFormat : ResourceAlias { } } diff --git a/ArcFormats/Otemoto/ImageMAG.cs b/ArcFormats/Otemoto/ImageMAG.cs index 8d222fa5..e68f3f21 100644 --- a/ArcFormats/Otemoto/ImageMAG.cs +++ b/ArcFormats/Otemoto/ImageMAG.cs @@ -45,7 +45,7 @@ namespace GameRes.Formats.Otemoto [Export(typeof(ImageFormat))] public class MagFormat : ImageFormat { - public override string Tag { get { return "MAG"; } } + public override string Tag { get { return "MAG/MAKI02"; } } public override string Description { get { return "Otemoto image format"; } } public override uint Signature { get { return 0x494B414D; } } // 'MAKI02' diff --git a/ArcFormats/SingleFileArchive.cs b/ArcFormats/SingleFileArchive.cs new file mode 100644 index 00000000..a070b9ed --- /dev/null +++ b/ArcFormats/SingleFileArchive.cs @@ -0,0 +1,67 @@ +//! \file SingleFileArchive.cs +//! \date 2023 Oct 05 +//! \brief represent single file as an archive for convenience. +// +// 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; + +namespace GameRes.Formats +{ + public class WrapSingleFileArchive : ArcFile + { + internal static readonly ArchiveFormat Format = new SingleFileArchiveFormat(); + + public WrapSingleFileArchive (ArcView file, Entry entry) + : base (file, Format, new List { entry }) + { + } + + public WrapSingleFileArchive (ArcView file, string entry_name) + : base (file, Format, new List { CreateEntry (file, entry_name) }) + { + } + + private static Entry CreateEntry (ArcView file, string name) + { + var entry = FormatCatalog.Instance.Create (name); + entry.Offset = 0; + entry.Size = (uint)file.MaxOffset; + return entry; + } + + /// this format is not registered in catalog and only accessible via WrapSingleFileArchive.Format singleton. + private class SingleFileArchiveFormat : ArchiveFormat + { + public override string Tag => "DAT/BOGUS"; + public override string Description => "Not an archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + return new WrapSingleFileArchive (file, System.IO.Path.GetFileName (file.Name)); + } + } + } +} diff --git a/ArcFormats/Software House Parsley/ArcCG3.cs b/ArcFormats/Software House Parsley/ArcCG3.cs new file mode 100644 index 00000000..43a5aa45 --- /dev/null +++ b/ArcFormats/Software House Parsley/ArcCG3.cs @@ -0,0 +1,170 @@ +//! \file ArcCG3.cs +//! \date 2023 Oct 10 +//! \brief Software House Parsley CG 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.Windows.Media; + +// [050610][Software House Parsley] Desert Time Mugen no Meikyuu PE + +namespace GameRes.Formats.Parsley +{ + [Export(typeof(ArchiveFormat))] + public class DesertCgOpener : ArchiveFormat + { + public override string Tag { get { return "CG/DESERT"; } } + public override string Description { get { return "Software House Parsley CG archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + public DesertCgOpener () + { + Extensions = new string[] { "" }; + } + + public override ArcFile TryOpen (ArcView file) + { + if (!VFS.IsPathEqualsToFileName (file.Name, "CG")) + return null; + int count = file.View.ReadInt32 (0); + if (!IsSaneCount (count)) + return null; + uint index_pos = 4; + var filename_table = LookupFileNameTable (file, count); + Func get_entry_name; + if (filename_table != null) + get_entry_name = n => filename_table[n]; + else + get_entry_name = n => string.Format ("CG#{0:D4}"); + long last_offset = count * 4 + 4; + var dir = new List (count); + for (int i = 0; i < count; ++ i) + { + uint offset = file.View.ReadUInt32 (index_pos); + if (0 == offset) + break; + if (offset <= last_offset || offset >= file.MaxOffset) + return null; + var entry = new Entry { + Name = get_entry_name (i), + Type = "image", + Offset = offset, + }; + dir.Add (entry); + last_offset = offset; + index_pos += 4; + } + if (0 == dir.Count) + return null; + last_offset = file.MaxOffset; + for (int i = dir.Count-1; i >= 0; --i) + { + dir[i].Size = (uint)(last_offset - dir[i].Offset); + last_offset = dir[i].Offset; + } + return new ArcFile (file, this, dir); + } + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var input = arc.File.CreateStream (entry.Offset, entry.Size); + try + { + return new DesertCgDecoder (input); + } + catch + { + input.Dispose(); + throw; + } + } + + internal static Dictionary FileNameTableMap = new Dictionary { + { @"..\DTime.exe", 0x49E348 }, + }; + + List LookupFileNameTable (ArcView file, int count) + { + try + { + var dir_name = Path.GetDirectoryName (file.Name); + foreach (var source in FileNameTableMap.Keys) + { + var src_name = Path.Combine (dir_name, source); + if (File.Exists (src_name)) + { + using (var src = new ArcView (src_name)) + { + var exe = new ExeFile (src); + long offset = exe.GetAddressOffset (FileNameTableMap[source]); + if (offset >= src.MaxOffset || offset + 0x104 * count > src.MaxOffset) + return null; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = src.View.ReadString (offset, 0x104); + dir.Add (name); + offset += 0x104; + } + return dir; + } + } + } + } + catch { } // ignore errors + return null; + } + } + + internal class DesertCgDecoder : BinaryImageDecoder + { + public DesertCgDecoder (IBinaryStream input) : base (input, ReadMetaData (input)) + { + } + + static ImageMetaData ReadMetaData (IBinaryStream input) + { + return new ImageMetaData { + Width = input.ReadUInt32(), + Height = input.ReadUInt32(), + BPP = 8, + }; + } + + protected override ImageData GetImageData () + { + m_input.Position = 8; + var palette = ImageFormat.ReadPalette (m_input.AsStream, 0x100, PaletteFormat.RgbX); + int stride = (Info.iWidth + 3) & ~3; + var pixels = m_input.ReadBytes (stride * Info.iHeight); + return ImageData.Create (Info, PixelFormats.Indexed8, palette, pixels, stride); + } + } +} + + diff --git a/ArcFormats/Triangle/ImageIAF.cs b/ArcFormats/Triangle/ImageIAF.cs index 486c1dd7..df16d275 100644 --- a/ArcFormats/Triangle/ImageIAF.cs +++ b/ArcFormats/Triangle/ImageIAF.cs @@ -100,7 +100,7 @@ namespace GameRes.Formats.Triangle return null; unpacked_size &= (int)~0xC0000000; stream.Position = data_offset; - byte[] bmp = UnpackBitmap (stream.AsStream, pack_type, packed_size, 0x26); + byte[] bmp = UnpackBitmap (stream, pack_type, packed_size, 0x26); if (bmp[0] != 'B' && bmp[0] != 'C' || bmp[1] != 'M') return null; return new IafMetaData @@ -121,7 +121,7 @@ namespace GameRes.Formats.Triangle { var meta = (IafMetaData)info; stream.Position = meta.DataOffset; - var bitmap = UnpackBitmap (stream.AsStream, meta.PackType, meta.PackedSize, meta.UnpackedSize); + var bitmap = UnpackBitmap (stream, meta.PackType, meta.PackedSize, meta.UnpackedSize); if ('C' == bitmap[0]) { bitmap[0] = (byte)'B'; @@ -155,19 +155,24 @@ namespace GameRes.Formats.Triangle return Bmp.Read (bmp, info); } - internal static byte[] UnpackBitmap (Stream stream, int pack_type, int packed_size, int unpacked_size) + internal static byte[] UnpackBitmap (IBinaryStream stream, int pack_type, int packed_size, int unpacked_size) { if (2 == pack_type) { + uint signature = stream.ReadUInt32(); + stream.Seek (-4, SeekOrigin.Current); using (var reader = new RleReader (stream, packed_size, unpacked_size)) { - reader.Unpack(); + if (0x014D0142 == signature) + reader.UnpackV2(); + else + reader.Unpack(); return reader.Data; } } else if (0 == pack_type) { - using (var reader = new LzssReader (stream, packed_size, unpacked_size)) + using (var reader = new LzssReader (stream.AsStream, packed_size, unpacked_size)) { reader.Unpack(); return reader.Data; @@ -261,15 +266,15 @@ namespace GameRes.Formats.Triangle internal class RleReader : IDataUnpacker, IDisposable { - BinaryReader m_input; + IBinaryStream m_input; byte[] m_output; int m_size; public byte[] Data { get { return m_output; } } - public RleReader (Stream input, int input_length, int output_length) + public RleReader (IBinaryStream input, int input_length, int output_length) { - m_input = new ArcView.Reader (input); + m_input = input; m_output = new byte[output_length]; m_size = input_length; } @@ -280,8 +285,8 @@ namespace GameRes.Formats.Triangle int dst = 0; while (dst < m_output.Length && src < m_size) { - byte b = m_input.ReadByte(); - int count = m_input.ReadByte(); + byte b = m_input.ReadUInt8(); + int count = m_input.ReadUInt8(); src += 2; count = Math.Min (count, m_output.Length - dst); for (int i = 0; i < count; i++) @@ -295,11 +300,11 @@ namespace GameRes.Formats.Triangle int dst = 0; while (dst < m_output.Length && src < m_size) { - byte ctl = m_input.ReadByte(); + byte ctl = m_input.ReadUInt8(); ++src; if (0 == ctl) { - int count = m_input.ReadByte(); + int count = m_input.ReadUInt8(); ++src; count = Math.Min (count, m_output.Length - dst); int read = m_input.Read (m_output, dst, count); @@ -309,7 +314,7 @@ namespace GameRes.Formats.Triangle else { int count = ctl; - byte b = m_input.ReadByte(); + byte b = m_input.ReadUInt8(); ++src; count = Math.Min (count, m_output.Length - dst); @@ -320,24 +325,8 @@ namespace GameRes.Formats.Triangle } #region IDisposable Members - bool disposed = false; - public void Dispose () { - Dispose (true); - GC.SuppressFinalize (this); - } - - protected virtual void Dispose (bool disposing) - { - if (!disposed) - { - if (disposing) - { - m_input.Dispose(); - } - disposed = true; - } } #endregion } From 196f0ea318875cfe6b874ac76d90fb7ba2c6c7c4 Mon Sep 17 00:00:00 2001 From: morkt Date: Wed, 11 Oct 2023 18:38:51 +0400 Subject: [PATCH 05/22] (ArcEXE): fixed new line. --- Experimental/Microsoft/ArcEXE.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Experimental/Microsoft/ArcEXE.cs b/Experimental/Microsoft/ArcEXE.cs index 4c5b4230..f32a14a7 100644 --- a/Experimental/Microsoft/ArcEXE.cs +++ b/Experimental/Microsoft/ArcEXE.cs @@ -195,7 +195,7 @@ namespace GameRes.Formats.Microsoft using (var text = new StreamWriter (output, new UTF8Encoding (false), 512, true)) { string block_name = input.ReadCString (Encoding.Unicode); - text.WriteLine ("BLOCK \"{0}\"\n{{", block_name); + text.WriteLine ("BLOCK \"{0}\"\r\n{{", block_name); long next_pos = (input.Position + 3) & -4L; while (next_pos < end_pos) { From 4d26cdcff686ce50106b167706986c34af30dbcd Mon Sep 17 00:00:00 2001 From: morkt Date: Wed, 11 Oct 2023 18:53:12 +0400 Subject: [PATCH 06/22] (GameRes): use ResourceInstance instead of Lazy. --- GameRes/Audio.cs | 4 ++-- GameRes/Image.cs | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/GameRes/Audio.cs b/GameRes/Audio.cs index 00f59f9d..25ae4e3f 100644 --- a/GameRes/Audio.cs +++ b/GameRes/Audio.cs @@ -197,8 +197,8 @@ namespace GameRes return null; } - public static AudioFormat Wav { get { return s_WavFormat.Value; } } + public static AudioFormat Wav => s_WavFormat.Value; - static readonly Lazy s_WavFormat = new Lazy (() => FormatCatalog.Instance.AudioFormats.FirstOrDefault (x => x.Tag == "WAV")); + static readonly ResourceInstance s_WavFormat = new ResourceInstance ("WAV"); } } diff --git a/GameRes/Image.cs b/GameRes/Image.cs index aab3b796..f52ce64c 100644 --- a/GameRes/Image.cs +++ b/GameRes/Image.cs @@ -189,15 +189,15 @@ namespace GameRes return FormatCatalog.Instance.ImageFormats.FirstOrDefault (x => x.Tag == tag); } - static readonly Lazy s_JpegFormat = new Lazy (() => FindByTag ("JPEG")); - static readonly Lazy s_PngFormat = new Lazy (() => FindByTag ("PNG")); - static readonly Lazy s_BmpFormat = new Lazy (() => FindByTag ("BMP")); - static readonly Lazy s_TgaFormat = new Lazy (() => FindByTag ("TGA")); + static readonly ResourceInstance s_JpegFormat = new ResourceInstance ("JPEG"); + static readonly ResourceInstance s_PngFormat = new ResourceInstance ("PNG"); + static readonly ResourceInstance s_BmpFormat = new ResourceInstance ("BMP"); + static readonly ResourceInstance s_TgaFormat = new ResourceInstance ("TGA"); - public static ImageFormat Jpeg { get { return s_JpegFormat.Value; } } - public static ImageFormat Png { get { return s_PngFormat.Value; } } - public static ImageFormat Bmp { get { return s_BmpFormat.Value; } } - public static ImageFormat Tga { get { return s_TgaFormat.Value; } } + public static ImageFormat Jpeg => s_JpegFormat.Value; + public static ImageFormat Png => s_PngFormat.Value; + public static ImageFormat Bmp => s_BmpFormat.Value; + public static ImageFormat Tga => s_TgaFormat.Value; /// /// Desereialize color map from stream, consisting of specified number of From f90e2e851cfcf56396480caba2cf4c7ebbac6190 Mon Sep 17 00:00:00 2001 From: morkt Date: Wed, 11 Oct 2023 19:03:45 +0400 Subject: [PATCH 07/22] updated legacy formats. (BIZ): moved to Adviz folder. (GIZ, GIZ2, BIZ2, PR1): new PC-98 image formats. (DATA, BGM, SED): MyHarvest resource formats. (HTF): compressed image format. (PAK): Mina resource archives. (GCmp): read palette from external resource. (NCG): Nekotaro image format. (BND, TCZ, TSZ): Ponytail Soft PC-98 formats. (NOR): Sophia resource archive. (SDA, PLA): Squadra D resource archives. (UCA): added checks to avoid false positives. --- Legacy/Adviz/ImageBIZ.cs | 366 ++++++++++++++++ .../ImageBIZ.cs => Adviz/ImageBIZ2.cs} | 44 +- Legacy/Adviz/ImageGIZ.cs | 309 ++++++++++++++ Legacy/Adviz/ImageGIZ2.cs | 231 ++++++++++ Legacy/Discovery/ImageAN1.cs | 80 ++++ Legacy/Discovery/ImagePR1.cs | 343 +++++++++++++++ Legacy/Harvest/ArcDAT.cs | 65 +++ Legacy/Harvest/AudioBGM.cs | 56 +++ Legacy/Harvest/AudioSED.cs | 61 +++ Legacy/Harvest/ImageUNH.cs | 96 +++++ Legacy/Jam/ImageHTF.cs | 69 +++ Legacy/Legacy.csproj | 20 +- Legacy/Mina/ArcPAK.cs | 299 +++++++++++++ Legacy/Nekotaro/ArcNSC.cs | 1 + Legacy/Nekotaro/ImageGCmp.cs | 394 ++++++------------ Legacy/Nekotaro/ImageNCG.cs | 293 +++++++++++++ Legacy/Ponytail/ArcBND.cs | 125 ++++++ Legacy/Ponytail/ImageTCZ.cs | 212 ++++++++++ Legacy/Ponytail/ImageTSZ.cs | 330 +++++++++++++++ Legacy/Properties/AssemblyInfo.cs | 4 +- Legacy/Sophia/ArcNOR.cs | 146 +++++++ Legacy/SquadraD/ArcPLA.cs | 114 +++++ Legacy/SquadraD/ArcSDA.cs | 121 ++++++ Legacy/WestGate/ArcUCA.cs | 4 +- 24 files changed, 3504 insertions(+), 279 deletions(-) create mode 100644 Legacy/Adviz/ImageBIZ.cs rename Legacy/{Sorciere/ImageBIZ.cs => Adviz/ImageBIZ2.cs} (56%) create mode 100644 Legacy/Adviz/ImageGIZ.cs create mode 100644 Legacy/Adviz/ImageGIZ2.cs create mode 100644 Legacy/Discovery/ImageAN1.cs create mode 100644 Legacy/Discovery/ImagePR1.cs create mode 100644 Legacy/Harvest/ArcDAT.cs create mode 100644 Legacy/Harvest/AudioBGM.cs create mode 100644 Legacy/Harvest/AudioSED.cs create mode 100644 Legacy/Harvest/ImageUNH.cs create mode 100644 Legacy/Jam/ImageHTF.cs create mode 100644 Legacy/Mina/ArcPAK.cs create mode 100644 Legacy/Nekotaro/ImageNCG.cs create mode 100644 Legacy/Ponytail/ArcBND.cs create mode 100644 Legacy/Ponytail/ImageTCZ.cs create mode 100644 Legacy/Ponytail/ImageTSZ.cs create mode 100644 Legacy/Sophia/ArcNOR.cs create mode 100644 Legacy/SquadraD/ArcPLA.cs create mode 100644 Legacy/SquadraD/ArcSDA.cs diff --git a/Legacy/Adviz/ImageBIZ.cs b/Legacy/Adviz/ImageBIZ.cs new file mode 100644 index 00000000..ed6c8bae --- /dev/null +++ b/Legacy/Adviz/ImageBIZ.cs @@ -0,0 +1,366 @@ +//! \file ImageBIZ.cs +//! \date 2023 Sep 30 +//! \brief ADVIZ 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 System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Text.RegularExpressions; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +// [970829][Ange] Coin + +namespace GameRes.Formats.Adviz +{ + [Export(typeof(ImageFormat))] + public class BizFormat : ImageFormat + { + public override string Tag => "BIZ"; + public override string Description => "ADVIZ engine image format"; + public override uint Signature => 0; + + const byte DefaultKey = 0x39; + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + if (!file.Name.HasExtension (".BIZ")) + return null; + var header = file.ReadHeader (4); + uint width = header.ToUInt16 (0); + uint height = header.ToUInt16 (2); + if (width * height + 4 != file.Length) + return null; + return new ImageMetaData { + Width = width, + Height = height, + BPP = 8, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var palette = ReadPalette (file.Name, 0x300, (pal, off) => ReadPalette (pal, off, 0x100, PaletteFormat.Rgb)); + if (null == palette) + throw new FileNotFoundException ("Unable to retrieve palette."); + file.Position = 4; + var pixels = file.ReadBytes (info.iWidth * info.iHeight); + byte key = DefaultKey; + for (int i = 0; i < pixels.Length; ++i) + { + pixels[i] ^= key; + key += pixels[i]; + } + return ImageData.CreateFlipped (info, PixelFormats.Indexed8, palette, pixels, info.iWidth); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("BizFormat.Write not implemented"); + } + + internal delegate BitmapPalette PaletteReader (ArcView file, int offset); + + static readonly Regex TachieRe = new Regex (@"^(T[^._]+_)[2-9][^.]*\.GIZ$", RegexOptions.Compiled); + + internal static BitmapPalette ReadPalette (string base_name, int pal_size, PaletteReader read_pal) + { + var dir_name = Path.GetDirectoryName (base_name); + var grp_tbl_name = Path.Combine (dir_name, @"..\GRP_TBL.SYS"); + var plt_tbl_name = Path.Combine (dir_name, @"..\PLT_TBL.SYS"); + if (!File.Exists (grp_tbl_name) || !File.Exists (plt_tbl_name)) + return null; + int index = 0; + uint grp_size = 0; + base_name = Path.GetFileName (base_name).ToUpperInvariant(); + var name = base_name; + var ext = Path.GetExtension (name).TrimStart('.'); + var match = TachieRe.Match (name); + if (match.Success) + name = match.Groups[1].Value + "1"; + else + name = Path.GetFileNameWithoutExtension (name); + if (name.Length < 8) + name += ' '; + using (var grp = new ArcView (grp_tbl_name)) + { + grp_size = (uint)grp.MaxOffset; + int pos = 0; + while (pos + 12 <= grp.MaxOffset) + { + if (grp.View.AsciiEqual (pos, name) && + grp.View.AsciiEqual (pos+8, ext)) + { + break; + } + ++index; + pos += 12; + } + if (pos >= grp.MaxOffset) + return null; + } + using (var pal = new ArcView (plt_tbl_name)) + { + uint plt_size = (uint)pal.MaxOffset; + var id = new GrpIdentifier (grp_size, plt_size); + IGrpMapper mapper; + if (!GrpMap.TryGetValue (id, out mapper)) + mapper = new DirectMapper(); + index = mapper.GetPaletteIndex (index, base_name); + int pal_offset = index * pal_size; + if (pal_offset + pal_size > pal.MaxOffset) + { + int count = (int)(pal.MaxOffset / pal_size) - 1; + pal_offset = count * pal_size; + } + return read_pal (pal, pal_offset); + } + } + + static readonly Dictionary GrpMap = new Dictionary { + { new GrpIdentifier (1584, 139008), new GrpShiftMapper (52) }, + { new GrpIdentifier (2160, 12288), + new GrpNameMapper { NameMap = new Dictionary { + { "BG01.BIZ", 14 }, + { "BG02.BIZ", 8 }, + { "BG03.BIZ", 8 }, + { "BG04.BIZ", 8 }, + { "BG05.BIZ", 8 }, + { "BG06.BIZ", 8 }, + { "BG07.BIZ", 8 }, + { "BG08.BIZ", 8 }, + { "BG09.BIZ", 8 }, + { "BG10.BIZ", 8 }, + { "BG11.BIZ", 8 }, + { "BG12.BIZ", 8 }, + { "BG13.BIZ", 8 }, + { "BG14.BIZ", 8 }, + { "BG15.BIZ", 8 }, + { "BG16.BIZ", 8 }, + { "BG17.BIZ", 8 }, + { "BG18.BIZ", 8 }, + { "BG19.BIZ", 8 }, + { "CA01.BIZ", 12 }, + { "CA02.BIZ", 12 }, + { "CA03.BIZ", 4 }, + { "CA04.BIZ", 8 }, + { "CA05.BIZ", 8 }, + { "CA06.BIZ", 8 }, + { "CA07.BIZ", 8 }, + { "CA08.BIZ", 8 }, + { "CA09.BIZ", 8 }, + { "CA10.BIZ", 8 }, + { "CA11.BIZ", 8 }, + { "CA12.BIZ", 8 }, + { "CA13.BIZ", 8 }, + { "CA14.BIZ", 8 }, + { "CA15.BIZ", 8 }, + { "CA16.BIZ", 8 }, + { "CA17.BIZ", 8 }, + { "CA18.BIZ", 8 }, + { "CA19.BIZ", 8 }, + { "CA20.BIZ", 8 }, + { "CA21.BIZ", 8 }, + { "CA22.BIZ", 8 }, + { "CA23.BIZ", 8 }, + { "CA24.BIZ", 8 }, + { "CA25.BIZ", 8 }, + { "CA26.BIZ", 8 }, + { "CA27.BIZ", 8 }, + { "CA28.BIZ", 8 }, + { "CA29.BIZ", 8 }, + { "CA30.BIZ", 8 }, + { "CA31.BIZ", 8 }, + { "CA32.BIZ", 8 }, + { "CA33.BIZ", 8 }, + { "CA34.BIZ", 8 }, + { "CA35.BIZ", 8 }, + { "CA36.BIZ", 8 }, + { "CA37.BIZ", 8 }, + { "CA38.BIZ", 8 }, + { "CA39.BIZ", 8 }, + { "CA40.BIZ", 8 }, + { "CA41.BIZ", 8 }, + { "CA42.BIZ", 8 }, + { "CA43.BIZ", 8 }, + { "CA44.BIZ", 8 }, + { "CA45.BIZ", 8 }, + { "CA46.BIZ", 8 }, + { "CA47.BIZ", 8 }, + { "CA48.BIZ", 8 }, + { "CA49.BIZ", 8 }, + { "CA50.BIZ", 8 }, + { "CA51.BIZ", 8 }, + { "CA52.BIZ", 8 }, + { "CA53.BIZ", 8 }, + { "CA54.BIZ", 8 }, + { "CA55.BIZ", 8 }, + { "CA56.BIZ", 8 }, + { "CA57.BIZ", 8 }, + { "CA58.BIZ", 8 }, + { "CA59.BIZ", 8 }, + { "CA60.BIZ", 8 }, + { "E02.BIZ", 8 }, + { "E03.BIZ", 8 }, + { "E04.BIZ", 8 }, + { "E05.BIZ", 8 }, + { "E06.BIZ", 8 }, + { "E07.BIZ", 8 }, + { "E08.BIZ", 8 }, + { "E09.BIZ", 8 }, + { "E10.BIZ", 8 }, + { "E11.BIZ", 8 }, + { "E12.BIZ", 8 }, + { "E13.BIZ", 8 }, + { "E14.BIZ", 8 }, + { "E15.BIZ", 8 }, + { "E16.BIZ", 8 }, + { "E17.BIZ", 8 }, + { "E18.BIZ", 8 }, + { "END.BIZ", 6 }, + { "IPL.BIZ", 12 }, + { "S01.BIZ", 8 }, + { "S02.BIZ", 8 }, + { "S03.BIZ", 8 }, + { "S04.BIZ", 8 }, + { "S05.BIZ", 8 }, + { "S06.BIZ", 8 }, + { "S07.BIZ", 8 }, + { "S08.BIZ", 8 }, + { "S09.BIZ", 8 }, + { "S10.BIZ", 8 }, + { "S11.BIZ", 8 }, + { "S12.BIZ", 8 }, + { "S13.BIZ", 8 }, + { "S14.BIZ", 8 }, + { "S15.BIZ", 8 }, + { "S16.BIZ", 8 }, + { "S17.BIZ", 8 }, + { "S18.BIZ", 8 }, + { "S19.BIZ", 8 }, + { "S20.BIZ", 8 }, + { "S21.BIZ", 8 }, + { "S22.BIZ", 8 }, + { "S23.BIZ", 8 }, + { "S24.BIZ", 8 }, + { "S25.BIZ", 8 }, + { "S26.BIZ", 8 }, + { "S27.BIZ", 8 }, + { "S28.BIZ", 8 }, + { "S29.BIZ", 8 }, + { "S30.BIZ", 8 }, + { "S31.BIZ", 8 }, + { "S32.BIZ", 8 }, + { "S33.BIZ", 8 }, + { "S34.BIZ", 8 }, + { "S35.BIZ", 8 }, + { "S36.BIZ", 8 }, + { "S37.BIZ", 8 }, + { "S38.BIZ", 8 }, + { "S39.BIZ", 8 }, + { "S40.BIZ", 8 }, + { "S41.BIZ", 8 }, + { "S42.BIZ", 8 }, + { "S43.BIZ", 8 }, + { "S44.BIZ", 8 }, + { "S45.BIZ", 8 }, + { "S46.BIZ", 8 }, + { "S47.BIZ", 8 }, + { "S48.BIZ", 8 }, + { "S49.BIZ", 8 }, + { "T01.BIZ", 8 }, + { "T02.BIZ", 8 }, + { "T03.BIZ", 8 }, + { "WAKU1.BIZ", 8 }, + { "WAKU2.BIZ", 8 }, + } } }, + }; + } + + public struct GrpIdentifier + { + public uint GrpSize; + public uint PltSize; + + public GrpIdentifier (uint grp_size, uint plt_size) + { + GrpSize = grp_size; + PltSize = plt_size; + } + + public override int GetHashCode () + { + return (int)((GrpSize + 1) * (PltSize + 1)); + } + + public override bool Equals (object obj) + { + if (null == obj) + return false; + var other = (GrpIdentifier)obj; + return this.GrpSize == other.GrpSize && this.PltSize == other.PltSize; + } + } + + internal interface IGrpMapper + { + int GetPaletteIndex (int id, string name); + } + + internal class DirectMapper : IGrpMapper + { + public int GetPaletteIndex (int id, string name) + { + return id; + } + } + + internal class GrpShiftMapper : IGrpMapper + { + int m_shift; + + public GrpShiftMapper (int shift) + { + m_shift = shift; + } + + public int GetPaletteIndex (int id, string name) + { + return id + m_shift; + } + } + + internal class GrpNameMapper : IGrpMapper + { + public Dictionary NameMap; + + public int GetPaletteIndex (int id, string name) + { + int index; + if (NameMap.TryGetValue (name, out index)) + return index; + return id; + } + } +} diff --git a/Legacy/Sorciere/ImageBIZ.cs b/Legacy/Adviz/ImageBIZ2.cs similarity index 56% rename from Legacy/Sorciere/ImageBIZ.cs rename to Legacy/Adviz/ImageBIZ2.cs index 83260b74..a6a86fcc 100644 --- a/Legacy/Sorciere/ImageBIZ.cs +++ b/Legacy/Adviz/ImageBIZ2.cs @@ -1,6 +1,6 @@ -//! \file ImageBIZ.cs +//! \file ImageBIZ2.cs //! \date 2018 Feb 11 -//! \brief Sorciere compressed image. +//! \brief ADVIZ engine compressed image. // // Copyright (C) 2018 by morkt // @@ -29,14 +29,15 @@ using System.Windows.Media; using GameRes.Compression; // [000225][Sorciere] Karei +// [011012][Ange] Nyuunyuu -namespace GameRes.Formats.Sorciere +namespace GameRes.Formats.Adviz { [Export(typeof(ImageFormat))] - public class BizFormat : ImageFormat + public class Biz2Format : ImageFormat { - public override string Tag { get { return "BIZ"; } } - public override string Description { get { return "Sorciere compressed image"; } } + public override string Tag { get { return "BIZ/2"; } } + public override string Description { get { return "ADVIZ engine compressed image"; } } public override uint Signature { get { return 0x325A4942; } } // 'BIZ2' public override ImageMetaData ReadMetaData (IBinaryStream file) @@ -52,13 +53,34 @@ namespace GameRes.Formats.Sorciere public override ImageData Read (IBinaryStream file, ImageMetaData info) { file.Position = 8; - using (var input = new LzssStream (file.AsStream, LzssMode.Decompress, true)) + using (var lzss = new LzssStream (file.AsStream, LzssMode.Decompress, true)) + using (var input = new BinaryStream (lzss, file.Name)) { - int stride = (int)info.Width * 3; - var pixels = new byte[stride * (int)info.Height]; - if (pixels.Length != input.Read (pixels, 0, pixels.Length)) + int stride = info.iWidth * 3; + var rgb = new byte[stride * info.Height]; + if (rgb.Length != input.Read (rgb, 0, rgb.Length)) throw new InvalidFormatException(); - return ImageData.CreateFlipped (info, PixelFormats.Bgr24, null, pixels, stride); + if (input.PeekByte() != -1) // possible alpha channel + { + var alpha = input.ReadBytes (rgb.Length); + if (alpha.Length == rgb.Length) + { + int stride32bpp = info.iWidth * 4; + var rgba = new byte[stride32bpp * info.iHeight]; + int src = 0; + int dst = 0; + while (src < rgb.Length) + { + rgba[dst++] = rgb[src ]; + rgba[dst++] = rgb[src+1]; + rgba[dst++] = rgb[src+2]; + rgba[dst++] = alpha[src]; // presumably it's grayscale and R/G/B values are equal + src += 3; + } + return ImageData.CreateFlipped (info, PixelFormats.Bgra32, null, rgba, stride32bpp); + } + } + return ImageData.CreateFlipped (info, PixelFormats.Bgr24, null, rgb, stride); } } diff --git a/Legacy/Adviz/ImageGIZ.cs b/Legacy/Adviz/ImageGIZ.cs new file mode 100644 index 00000000..8e4da430 --- /dev/null +++ b/Legacy/Adviz/ImageGIZ.cs @@ -0,0 +1,309 @@ +//! \file ImageGIZ.cs +//! \date 2023 Oct 02 +//! \brief ADVIZ 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; +using System.Windows.Media.Imaging; + +// [960830][Ange] Coin + +namespace GameRes.Formats.Adviz +{ + internal class GizMetaData : ImageMetaData + { + public byte RleCode; + public byte PlaneMap; + public bool HasPalette; + } + + [Export(typeof(ImageFormat))] + public class Giz3Format : ImageFormat + { + public override string Tag => "GIZ"; + public override string Description => "ADVIZ engine image format"; + public override uint Signature => 0x335A4947; // 'GIZ3' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x10); + int xy = header.ToUInt16 (4); + return new GizMetaData { + Width = (uint)header.ToUInt16 (6) << 3, + Height = header.ToUInt16 (8), + OffsetX = (xy % 0x50) << 3, + OffsetY = xy / 0x50, + HasPalette = header[0xC] != 0, + PlaneMap = header[0xE], + BPP = 4, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new Giz3Reader (file, (GizMetaData)info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("Giz3Format.Write not implemented"); + } + } + + internal class Giz3Reader + { + IBinaryStream m_input; + GizMetaData m_info; + BitmapPalette m_palette; + int m_stride; + int m_output_stride; + + public Giz3Reader (IBinaryStream input, GizMetaData info) + { + m_input = input; + m_info = info; + } + + byte[] m_buffer; + byte[] m_output; + + public ImageData Unpack () + { + m_input.Position = 0x10; + if (m_info.HasPalette) + m_palette = ReadPalette(); + else + m_palette = BitmapPalettes.Gray16; // palette is stored somewhere else + long data_pos = m_input.Position; + ReadHuffmanTree(); + data_pos += m_dataOffset; + + m_bitCount = 1; + m_stride = m_info.iWidth >> 3; + m_buffer = new byte[0x2000]; + m_output_stride = m_info.iWidth >> 1; + m_output = new byte[m_output_stride * m_info.iHeight]; + m_input.Position = data_pos; + UnpackBits(); + return ImageData.Create (m_info, PixelFormats.Indexed4, m_palette, m_output, m_output_stride); + } + + int m_dataOffset; + int m_gizColumn; + int m_outputPos1; + int m_outputPos2; + + void UnpackBits () // sub_15426 + { + m_gizColumn = 0; + m_outputPos1 = 0; + m_outputPos2 = 0; + int dst = 0; + for (int x = 0; x < m_stride; ++x) + { + int src1 = m_outputPos1; + int src2 = 0; + for (int j = 0; j < 2; ++j) + { + src2 = m_outputPos1; + int plane_mask = 1; + for (int i = 0; i < 4; ++i) + { + if ((m_info.PlaneMap & plane_mask) == 0) + { + UnpackPlane (m_outputPos1); + } + plane_mask <<= 1; + m_outputPos1 += 0x800; + m_outputPos2 += 0x800; + } + m_gizColumn = (m_gizColumn + 1) & 3; + m_outputPos1 = m_gizColumn << 9; + m_outputPos2 = 0; + } + CopyPlanes (src1, src2, dst); + dst += 4; + } + } + + void CopyPlanes (int src1, int src2, int dst) + { + for (int y = 0; y < m_info.iHeight; ++y) + { + int b0 = m_buffer[src1+y ] << 4 | m_buffer[src2+y ]; + int b1 = m_buffer[src1+y+0x0800] << 4 | m_buffer[src2+y+0x0800]; + int b2 = m_buffer[src1+y+0x1000] << 4 | m_buffer[src2+y+0x1000]; + int b3 = m_buffer[src1+y+0x1800] << 4 | m_buffer[src2+y+0x1800]; + 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/2] = px; + } + dst += m_output_stride; + } + } + + int m_root; + ushort[] m_treeTable; + + void ReadHuffmanTree () + { + m_dataOffset = m_input.ReadUInt16(); + m_treeTable = new ushort[(m_dataOffset-2) * 2 / 3 + 1]; + int di = 0; + for (int si = 2; si + 2 < m_dataOffset; si += 3) + { + ushort bx = m_input.ReadUInt16(); + int ax = bx & 0xFFF; + if ((ax & 0x800) == 0) + ax = (ax - 2) >> 1; + m_treeTable[di++] = (ushort)ax; + ax = m_input.ReadUInt8() << 4; + ax |= bx >> 12; + if ((ax & 0x800) == 0) + ax = (ax - 2) >> 1; + m_treeTable[di++] = (ushort)ax; + } + m_root = di - 2; + } + + byte ReadToken () + { + int token = m_root; + do + { + if (GetNextBit()) + ++token; + token = m_treeTable[token]; + } + while ((token & 0x800) == 0); + return (byte)token; + } + + void UnpackPlane (int dst) + { + int y = 0; + while (y < m_info.iHeight) + { + byte ctl = ReadToken(); + if (ctl < 0x10) + { + m_buffer[dst++] = ctl; + ++y; + } + else + { + int count = ReadToken() + 2; + ctl -= 0x10; + switch (ctl) + { + case 0: + for (int i = 0; i < count; ++i) + m_buffer[dst+i] = 0; + break; + + case 1: + for (int i = 0; i < count; ++i) + m_buffer[dst+i] = 0xF; + break; + + case 2: + Binary.CopyOverlapped (m_buffer, dst-1, dst, count); + break; + + case 3: + Binary.CopyOverlapped (m_buffer, dst-2, dst, count); + break; + + case 4: + case 5: + case 6: + { + int off = (ctl - 3) << 11; + Binary.CopyOverlapped (m_buffer, dst-off, dst, count); + break; + } + case 7: + { + int src = dst - m_outputPos1; + int ax = (m_gizColumn - 1) & 3; + src += (ax << 9) + m_outputPos2; + Binary.CopyOverlapped (m_buffer, src, dst, count); + break; + } + case 8: + { + int src = dst - m_outputPos1; + int ax = (m_gizColumn - 2) & 3; + src += (ax << 9) + m_outputPos2; + Binary.CopyOverlapped (m_buffer, src, dst, count); + break; + } + } + dst += count; + y += count; + } + } + } + + BitmapPalette ReadPalette () + { + const int count = 16; + var colors = new Color[count]; + for (int i = 0; i < count; ++i) + { + byte b = m_input.ReadUInt8(); + byte r = m_input.ReadUInt8(); + byte g = m_input.ReadUInt8(); + colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11)); + } + return new BitmapPalette (colors); + } + + int m_bitCount; + int m_bits; + + bool GetNextBit () + { + if (--m_bitCount == 0) + { + m_bits = m_input.ReadUInt16(); + m_bitCount = 16; + } + bool bit = (m_bits & 0x8000) != 0; + m_bits <<= 1; + return bit; + } + } +} diff --git a/Legacy/Adviz/ImageGIZ2.cs b/Legacy/Adviz/ImageGIZ2.cs new file mode 100644 index 00000000..7ceb287c --- /dev/null +++ b/Legacy/Adviz/ImageGIZ2.cs @@ -0,0 +1,231 @@ +//! \file ImageGIZ2.cs +//! \date 2023 Oct 02 +//! \brief ADVIZ 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; +using System.Windows.Media.Imaging; + +// [951027][Ange] Leap Toki ni Sarawareta Shoujo + +namespace GameRes.Formats.Adviz +{ + [Export(typeof(ImageFormat))] + public class GizFormat : ImageFormat + { + public override string Tag => "GIZ/2"; + public override string Description => "ADVIZ engine image format"; + public override uint Signature => 0x325A4947; // 'GIZ2' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x10); + int xy = header.ToUInt16 (4); + return new GizMetaData { + Width = (uint)header.ToUInt16 (6) << 3, + Height = header.ToUInt16 (8), + OffsetX = (xy % 0x50) << 3, + OffsetY = xy / 0x50, + RleCode = header[0xC], + PlaneMap = header[0xE], + BPP = 4, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new Giz2Reader (file, (GizMetaData)info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("GizFormat.Write not implemented"); + } + } + + internal class Giz2Reader + { + IBinaryStream m_input; + GizMetaData m_info; + BitmapPalette m_palette; + + public BitmapPalette Palette => m_palette; + + public Giz2Reader (IBinaryStream input, GizMetaData info) + { + m_input = input; + m_info = info; + } + + int m_stride; + byte[][] m_planes; + int m_output_stride; + byte[] m_output; + + public ImageData Unpack () + { + m_palette = BizFormat.ReadPalette (m_input.Name, 0x30, (pal, off) => ReadPalette (pal, off)); + if (null == m_palette) + { +// m_palette = BitmapPalettes.Gray16; + throw new FileNotFoundException ("Unable to retrieve palette."); + } + m_input.Position = 0x10; + m_stride = m_info.iWidth >> 3; + int plane_size = m_info.iHeight; + m_planes = new byte[][] { + new byte[plane_size], new byte[plane_size], new byte[plane_size], new byte[plane_size], + }; + m_output_stride = m_info.iWidth >> 1; + m_output = new byte[m_output_stride * m_info.iHeight]; + int dst = 0; + for (int x = 0; x < m_stride; ++x) + { + int plane_mask = 1; + for (int i = 0; i < 4; ++i) + { + if ((m_info.PlaneMap & plane_mask) == 0) + UnpackPlane (m_planes[i], 0); + plane_mask <<= 1; + } + CopyPlanes (dst); + dst += 4; + } + return ImageData.Create (m_info, PixelFormats.Indexed4, Palette, m_output, m_output_stride); + } + + bool UnpackPlane (byte[] output, int dst) + { + for (int y = 0; y < m_info.iHeight; ) + { + byte b = m_input.ReadUInt8(); + int ctl = (b - m_info.RleCode) & 0xFF; + if (2 == ctl) + { + output[dst++] = m_input.ReadUInt8(); + } + else if (ctl < 4) + { + if (0 == ctl) + b = 0; + else if (1 == ctl) + b = 0xFF; + else + b = m_input.ReadUInt8(); + int count = ((m_input.ReadUInt8() - 1) & 0xFF) + 1; + y += count; + while (count --> 0) + { + output[dst++] = b; + } + continue; + } + else if (ctl < 7) + { + byte b0 = m_input.ReadUInt8(); + byte b1 = m_input.ReadUInt8(); + int count; + if (4 == ctl) + { + count = ((b1 - 1) & 0x7F) + 1; + if (b1 < 0x80) + b1 = Binary.RotByteL (b0, 1); + else + b1 = Binary.RotByteR (b0, 1); + } + else if (5 == ctl) + { + count = ((b1 - 1) & 0x7F) + 1; + if (b1 < 0x80) + b1 = Binary.RotByteL (b0, 2); + else + b1 = Binary.RotByteR (b0, 2); + } + else + { + count = ((m_input.ReadUInt8() - 1) & 0xFF) + 1; + count *= 2; + } + y += count; + do + { + output[dst++] = b0; + if (--count <= 0) + break; + output[dst++] = b1; + } + while (--count > 0); + continue; + } + else + { + output[dst++] = b; + } + ++y; + } + return true; + } + + void CopyPlanes (int dst) + { + for (int y = 0; y < m_info.iHeight; ++y) + { + int b0 = m_planes[0][y]; + int b1 = m_planes[1][y]; + int b2 = m_planes[2][y]; + int b3 = m_planes[3][y]; + 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/2] = px; + } + dst += m_output_stride; + } + } + + BitmapPalette ReadPalette (ArcView file, int offset) + { + const int count = 16; + var colors = new Color[count]; + for (int i = 0; i < count; ++i) + { + byte b = file.View.ReadByte (offset++); + byte r = file.View.ReadByte (offset++); + byte g = file.View.ReadByte (offset++); + colors[i] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11)); + } + return new BitmapPalette (colors); + } + } +} diff --git a/Legacy/Discovery/ImageAN1.cs b/Legacy/Discovery/ImageAN1.cs new file mode 100644 index 00000000..ff6673c7 --- /dev/null +++ b/Legacy/Discovery/ImageAN1.cs @@ -0,0 +1,80 @@ +//! \file ImageAN1.cs +//! \date 2023 Oct 05 +//! \brief Discovery animation resource (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; + +namespace GameRes.Formats.Discovery +{ + //[Export(typeof(ImageFormat))] + public class An1Format : Pr1Format + { + public override string Tag => "AN1"; + public override string Description => "Discovery animation resource"; + public override uint Signature => 0; + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + if (!file.Name.HasExtension (".AN1")) + return null; + return base.ReadMetaData (file); + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new AnReader (file, (PrMetaData)info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("An1Format.Write not implemented"); + } + } + + internal class AnReader : PrReader + { + public AnReader (IBinaryStream file, PrMetaData info) : base (file, info) + { + } + + public new ImageData Unpack () + { + UnpackPlanes(); + int frame_count = m_planes[0].ToUInt16 (2); + int frame_width = 0x20; + int frame_height = frame_count * 0x20; + int output_stride = frame_width >> 1; + var output = new byte[output_stride * frame_height]; + int src = frame_count * 0x16 + 6; + m_plane_size = (output_stride >> 2) * frame_height; + FlattenPlanes (src, output); + Info.Width = (uint)frame_width; + Info.Height = (uint)frame_height; + return ImageData.Create (Info, PixelFormats.Indexed4, m_palette, output, output_stride); + } + } +} diff --git a/Legacy/Discovery/ImagePR1.cs b/Legacy/Discovery/ImagePR1.cs new file mode 100644 index 00000000..027689a2 --- /dev/null +++ b/Legacy/Discovery/ImagePR1.cs @@ -0,0 +1,343 @@ +//! \file ImagePR1.cs +//! \date 2023 Oct 04 +//! \brief Discovery 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.Discovery +{ + internal class PrMetaData : ImageMetaData + { + public byte Flags; + public byte Mask; + + public bool IsLeftToRight => (Flags & 1) != 0; + } + + [Export(typeof(ImageFormat))] + public class Pr1Format : ImageFormat + { + public override string Tag => "PR1"; + public override string Description => "Discovery image format"; + public override uint Signature => 0; + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + if (!file.Name.HasAnyOfExtensions (".PR1", ".AN1")) + return null; + var header = file.ReadHeader (12); + return new PrMetaData { + Width = (uint)header.ToUInt16 (8) << 3, + Height = header.ToUInt16 (0xA), + OffsetX = header.ToUInt16 (2), + OffsetY = header.ToUInt16 (4), + Flags = header[0], + Mask = header[1], + BPP = 4, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new PrReader (file, (PrMetaData)info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("Pr1Format.Write not implemented"); + } + } + + internal class PrReader + { + IBinaryStream m_input; + PrMetaData m_info; + + Action IncrementDest; + Func IsDone; + + public PrMetaData Info => m_info; + + public PrReader (IBinaryStream file, PrMetaData info) + { + m_input = file; + m_info = info; + if (m_info.IsLeftToRight) + { + IncrementDest = IncLeftToRight; + IsDone = () => m_dst >= m_plane_size; + } + else + { + IncrementDest = IncTopToBottom; + IsDone = () => m_x >= m_stride; + } + } + + protected BitmapPalette m_palette; + protected int m_stride; + protected int m_plane_size; + protected byte[][] m_planes; + int m_dst; + int m_x; + + protected void UnpackPlanes () + { + const int buffer_slice = 0x410; + m_input.Position = 0xC; + m_palette = ReadPalette(); + m_stride = m_info.iWidth >> 3; + m_plane_size = m_stride * m_info.iHeight; + m_planes = new byte[][] { + new byte[m_plane_size], new byte[m_plane_size], new byte[m_plane_size], new byte[m_plane_size], + }; + var buffer = new byte[buffer_slice * 4]; + var buf_count = new byte[4]; + var offsets = new int[] { 0, buffer_slice, buffer_slice*2, buffer_slice*3 }; + m_dst = 0; + m_x = 0; + while (!IsDone()) + { + int ctl = m_input.ReadByte(); + if (-1 == ctl) + break; + int count = (ctl & 0x1F) + 1; + bool bit = (ctl & 0x20) != 0; + ctl >>= 6; + if (!bit) + { + if (ctl != 0) + { + int src_pos = ctl; + int src_count2 = 1 << (ctl - 1); + int pos = offsets[ctl]; + int count2 = src_count2; + do + { + byte p0 = m_input.ReadUInt8(); + byte p1 = m_input.ReadUInt8(); + byte p2 = m_input.ReadUInt8(); + byte p3 = m_input.ReadUInt8(); + PutPixels (p0, p1, p2, p3); + buffer[pos++] = p0; + buffer[pos++] = p1; + buffer[pos++] = p2; + buffer[pos++] = p3; + } + while (--count > 0 && --count2 > 0); + while (count > 0) + { + int si = offsets[src_pos]; + for (int i = 0; i < src_count2; ++i) + { + byte p0 = buffer[si++]; + byte p1 = buffer[si++]; + byte p2 = buffer[si++]; + byte p3 = buffer[si++]; + PutPixels (p0, p1, p2, p3); + if (--count <= 0) + break; + } + } + offsets[src_pos] += src_count2 * 4; + buf_count[src_pos] += (byte)src_count2; + if (buf_count[src_pos] == 0) + offsets[src_pos] = src_pos * buffer_slice; + } + else + { + while (count --> 0) + { + byte p0 = m_input.ReadUInt8(); + byte p1 = m_input.ReadUInt8(); + byte p2 = m_input.ReadUInt8(); + byte p3 = m_input.ReadUInt8(); + PutPixels (p0, p1, p2, p3); + int pos = offsets[0]; + buffer[pos++] = p0; + buffer[pos++] = p1; + buffer[pos++] = p2; + buffer[pos++] = p3; + offsets[0] += 4; + buf_count[0]++; + if (0 == buf_count[0]) + offsets[0] = 0; + } + } + } + else if (ctl != 0) + { + int count2 = 1 << (ctl - 1); + int off_diff = count2 << 2; + int off_mask = off_diff - 1; + int off = m_input.ReadUInt8() << 2;; + int base_pos = ctl * buffer_slice; + off += base_pos; + int src = off; + while (count > 0) + { + off = src; + for (int i = 0; i < count2; ++i) + { + byte p0 = buffer[off]; + byte p1 = buffer[off+1]; + byte p2 = buffer[off+2]; + byte p3 = buffer[off+3]; + PutPixels (p0, p1, p2, p3); + off += 4; + int pos = off - base_pos; + if ((pos & off_mask) == 0) + off -= off_diff; + if (--count <= 0) + break; + } + } + } + else + { + while (count --> 0) + { + int off = m_input.ReadUInt8() << 2; + byte p0 = buffer[off]; + byte p1 = buffer[off+1]; + byte p2 = buffer[off+2]; + byte p3 = buffer[off+3]; + PutPixels (p0, p1, p2, p3); + } + } + } + } + + public ImageData Unpack () + { + UnpackPlanes(); + int output_stride = m_info.iWidth >> 1; + var output = new byte[output_stride * m_info.iHeight]; + FlattenPlanes (0, output); + return ImageData.Create (m_info, PixelFormats.Indexed4, m_palette, output, output_stride); + } + + void PutPixels (byte p0, byte p1, byte p2, byte p3) + { + if (0xFF == m_info.Mask || true) // we don't do overlaying here, just single image decoding + { + m_planes[0][m_dst] = p0; + m_planes[1][m_dst] = p1; + m_planes[2][m_dst] = p2; + m_planes[3][m_dst] = p3; + } + else + { + byte v = m_info.Mask; + byte mask = p0; + if ((v & 1) != 0) + mask = (byte)~mask; + if ((v & 2) != 0) + mask |= (byte)~p1; + else + mask |= p1; + if ((v & 4) != 0) + mask |= (byte)~p2; + else + mask |= p2; + if ((v & 8) != 0) + mask |= (byte)~p3; + else + mask |= p3; + p0 &= mask; + p1 &= mask; + p2 &= mask; + p3 &= mask; + mask = (byte)~mask; + m_planes[0][m_dst] &= mask; + m_planes[0][m_dst] |= p0; + m_planes[1][m_dst] &= mask; + m_planes[1][m_dst] |= p1; + m_planes[2][m_dst] &= mask; + m_planes[2][m_dst] |= p2; + m_planes[3][m_dst] &= mask; + m_planes[3][m_dst] |= p3; + } + IncrementDest(); + } + + void IncLeftToRight () + { + ++m_dst; + ++m_x; + if (m_x > m_info.iWidth) + m_x = 0; + } + + void IncTopToBottom () + { + m_dst += m_stride; + if (m_dst >= m_plane_size) + m_dst = ++m_x; + } + + internal void FlattenPlanes (int src, byte[] output) + { + int m_dst = 0; + for (; src < m_plane_size; ++src) + { + int b0 = m_planes[0][src]; + int b1 = m_planes[1][src]; + int b2 = m_planes[2][src]; + int b3 = m_planes[3][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) )); + px |= (byte)((((b0 << j) & 0x40) >> 6) + | (((b1 << j) & 0x40) >> 5) + | (((b2 << j) & 0x40) >> 4) + | (((b3 << j) & 0x40) >> 3)); + output[m_dst++] = px; + } + } + } + + BitmapPalette ReadPalette () + { + 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/Harvest/ArcDAT.cs b/Legacy/Harvest/ArcDAT.cs new file mode 100644 index 00000000..a9908fd5 --- /dev/null +++ b/Legacy/Harvest/ArcDAT.cs @@ -0,0 +1,65 @@ +//! \file ArcDAT.cs +//! \date 2023 Sep 26 +//! \brief MyHarvest 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; + +namespace GameRes.Formats.MyHarvest +{ + [Export(typeof(ArchiveFormat))] + public class DatOpener : ArchiveFormat + { + public override string Tag => "DAT/UNA"; + public override string Description => "MyHarvest resource archive"; + public override uint Signature => 0x414E55; // 'UNA' + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (4, "001\0")) + return null; + int count = file.View.ReadInt32 (8); + if (!IsSaneCount (count)) + return null; + uint index = 0x20; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = file.View.ReadString (index, 0x20); + var entry = Create (name); + entry.Offset = file.View.ReadUInt32 (index+0x20); + entry.Size = file.View.ReadUInt32 (index+0x24); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index += 0x30; + } + return new ArcFile (file, this, dir); + } + } +} diff --git a/Legacy/Harvest/AudioBGM.cs b/Legacy/Harvest/AudioBGM.cs new file mode 100644 index 00000000..f3221782 --- /dev/null +++ b/Legacy/Harvest/AudioBGM.cs @@ -0,0 +1,56 @@ +//! \file AudioBGM.cs +//! \date 2023 Sep 26 +//! \brief MyHarvest audio 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; + +namespace GameRes.Formats.MyHarvest +{ + [Export(typeof(AudioFormat))] + public class BgmAudio : AudioFormat + { + public override string Tag => "BGM/HARVEST"; + public override string Description => "MyHarvest audio resource"; + public override uint Signature => 0x304D4742; // 'BMG0' + public override bool CanWrite => false; + + public override SoundInput TryOpen (IBinaryStream file) + { + var header = file.ReadHeader (0x1C); + if (!header.AsciiEqual (0x14, "dar\0")) + return null; + var format = new WaveFormat { + FormatTag = header.ToUInt16 (4), + Channels = header.ToUInt16 (6), + SamplesPerSecond = header.ToUInt32 (8), + AverageBytesPerSecond = header.ToUInt32 (0xC), + BlockAlign = header.ToUInt16 (0x10), + BitsPerSample = header.ToUInt16 (0x12), + }; + uint pcm_size = header.ToUInt32 (0x18); + var region = new StreamRegion (file.AsStream, 0x1C, pcm_size); + return new RawPcmInput (region, format); + } + } +} diff --git a/Legacy/Harvest/AudioSED.cs b/Legacy/Harvest/AudioSED.cs new file mode 100644 index 00000000..5543d225 --- /dev/null +++ b/Legacy/Harvest/AudioSED.cs @@ -0,0 +1,61 @@ +//! \file AudioSED.cs +//! \date 2023 Sep 26 +//! \brief MyHarvest audio 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; + +namespace GameRes.Formats.MyHarvest +{ + [Export(typeof(AudioFormat))] + public class SedAudio : AudioFormat + { + public override string Tag => "SED/HARVEST"; + public override string Description => "MyHarvest audio resource"; + public override uint Signature => 0x14553; // 'SE' + public override bool CanWrite => false; + + public SedAudio () + { + Signatures = new[] { 0x14553u, 0u }; + } + + public override SoundInput TryOpen (IBinaryStream file) + { + var header = file.ReadHeader (0x18); + if (!header.AsciiEqual (0, "SE") || !header.AsciiEqual (0x12, "da")) + return null; + var format = new WaveFormat { + FormatTag = header.ToUInt16 (2), + Channels = header.ToUInt16 (4), + SamplesPerSecond = header.ToUInt32 (6), + AverageBytesPerSecond = header.ToUInt32 (0xA), + BlockAlign = header.ToUInt16 (0xE), + BitsPerSample = header.ToUInt16 (0x10), + }; + uint pcm_size = header.ToUInt32 (0x14); + var region = new StreamRegion (file.AsStream, 0x18, pcm_size); + return new RawPcmInput (region, format); + } + } +} diff --git a/Legacy/Harvest/ImageUNH.cs b/Legacy/Harvest/ImageUNH.cs new file mode 100644 index 00000000..993adcbf --- /dev/null +++ b/Legacy/Harvest/ImageUNH.cs @@ -0,0 +1,96 @@ +//! \file ImageUNH.cs +//! \date 2023 Sep 26 +//! \brief MyHarvest 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; + +// [021206][MyHarvest] Idol Mahjong Final Romance 4 + +namespace GameRes.Formats.MyHarvest +{ + [Export(typeof(ImageFormat))] + public class UnhFormat : ImageFormat + { + public override string Tag => "UNH"; + public override string Description => "MyHarvest image format"; + public override uint Signature => 0x30484E55; // 'UNH0' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x18); + if (header.ToInt32 (4) != 1) + return null; + return new ImageMetaData { + Width = header.ToUInt32 (0x10), + Height = header.ToUInt32 (0x14), + BPP = 16, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + file.Position = 0x44; + var pixels = new ushort[info.iWidth * info.iHeight]; + var frame = new ushort[0x1000]; + int frame_pos = 0; + int dst = 0; + byte mask = 0; + int ctl = 0; + while (dst < pixels.Length) + { + mask <<= 1; + if (0 == mask) + { + ctl = file.ReadByte(); + if (-1 == ctl) + break; + mask = 1; + } + ushort word = file.ReadUInt16(); + if ((ctl & mask) == 0) + { + pixels[dst++] = frame[frame_pos++ & 0xFFF] = word; + } + else + { + int offset = word >> 4; + int count = (word & 0xF) + 2; + while (count --> 0) + { + ushort u = frame[offset++ & 0xFFF]; + pixels[dst++] = frame[frame_pos++ & 0xFFF] = u; + } + } + } + return ImageData.Create (info, PixelFormats.Bgr565, null, pixels); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("UnhFormat.Write not implemented"); + } + } +} diff --git a/Legacy/Jam/ImageHTF.cs b/Legacy/Jam/ImageHTF.cs new file mode 100644 index 00000000..3355d9fa --- /dev/null +++ b/Legacy/Jam/ImageHTF.cs @@ -0,0 +1,69 @@ +//! \file ImageHTF.cs +//! \date 2023 Oct 07 +//! \brief Huffman-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.Compression; +using System.ComponentModel.Composition; +using System.IO; +using System.Windows.Media.Imaging; + +namespace GameRes.Formats.Jam +{ + [Export(typeof(ImageFormat))] + public class HtfFormat : ImageFormat + { + public override string Tag => "HTF"; + public override string Description => "Huffman-compressed bitmap"; + public override uint Signature => 0; + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + if (!file.Name.HasExtension (".HTF")) + return null; + int unpacked_size = file.ReadInt32(); + if (unpacked_size <= 0 || unpacked_size > 0x1000000) + return null; + using (var huff = new HuffmanStream (file.AsStream, true)) + using (var input = new BinaryStream (huff, file.Name)) + { + return Bmp.ReadMetaData (input); + } + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + file.Position = 4; + using (var input = new HuffmanStream (file.AsStream, true)) + { + var decoder = new BmpBitmapDecoder (input, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + return new ImageData (decoder.Frames[0], info); + } + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("HtfFormat.Write not implemented"); + } + } +} diff --git a/Legacy/Legacy.csproj b/Legacy/Legacy.csproj index ce3dac7f..eb4da4e8 100644 --- a/Legacy/Legacy.csproj +++ b/Legacy/Legacy.csproj @@ -78,6 +78,9 @@ + + + @@ -92,9 +95,15 @@ + + + + + + @@ -118,6 +127,7 @@ + @@ -138,9 +148,14 @@ + + + + + @@ -209,8 +224,11 @@ + + + @@ -251,7 +269,7 @@ - + diff --git a/Legacy/Mina/ArcPAK.cs b/Legacy/Mina/ArcPAK.cs new file mode 100644 index 00000000..bc7a74cd --- /dev/null +++ b/Legacy/Mina/ArcPAK.cs @@ -0,0 +1,299 @@ +//! \file ArcPAK.cs +//! \date 2023 Oct 09 +//! \brief Mina 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 GameRes.Utility; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Text; +using System.Windows.Media; + +// [010223][Mina] Storia ~Ouma no Mori no Himegimi-tachi~ + +namespace GameRes.Formats.Mina +{ + [Export(typeof(ArchiveFormat))] + public class BmpPakOpener : ArchiveFormat + { + public override string Tag => "PAK/MINA/BMP"; + public override string Description => "Mina bitmap 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.HasExtension (".PAK")) + return null; + int pos; + for (pos = 0; pos < 0x10; ++pos) + { + if (0 == file.View.ReadByte (pos)) + break; + } + if (pos >= 0x10 || pos <= 4 || !file.View.AsciiEqual (pos-4, ".BMP")) + return null; + using (var input = file.CreateStream()) + { + var dir = new List(); + while (input.PeekByte() != -1) + { + var name = input.ReadCString(); + if (name.Length > 0x10) + return null; + var entry = Create (name); + entry.Offset = input.Position; + input.Seek (5, SeekOrigin.Current); + uint size = input.ReadUInt32(); + entry.Size = size + 9; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + input.Seek (size, SeekOrigin.Current); + } + return new ArcFile (file, this, dir); + } + } + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var input = arc.File.CreateStream (entry.Offset, entry.Size); + return new BitmapDecoder (input); + } + } + + [Export(typeof(ArchiveFormat))] + public class WavPakOpener : ArchiveFormat + { + public override string Tag => "PAK/MINA/WAV"; + public override string Description => "Mina audio 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.HasExtension (".PAK")) + return null; + int pos; + for (pos = 4; pos < 0x14; ++pos) + { + if (0 == file.View.ReadByte (pos)) + break; + } + if (pos >= 0x14 || pos <= 8 || !file.View.AsciiEqual (pos-4, ".WAV")) + return null; + using (var input = file.CreateStream()) + { + var dir = new List(); + while (input.PeekByte() != -1) + { + uint data_size = input.ReadUInt32(); + var name = input.ReadCString(); + if (name.Length > 0x10) + return null; + var entry = Create (name); + entry.Offset = input.Position; + uint fmt_size = input.ReadUInt32(); + if (fmt_size < 0x10) + return null; + entry.Size = data_size + fmt_size + 4; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + input.Seek (data_size + fmt_size, SeekOrigin.Current); + } + return new ArcFile (file, this, dir); + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + uint fmt_size = arc.File.View.ReadUInt32 (entry.Offset); + uint pcm_size = entry.Size - 4 - fmt_size; + using (var mem = new MemoryStream ((int)fmt_size)) + { + using (var buffer = new BinaryWriter (mem, Encoding.ASCII, true)) + { + buffer.Write (AudioFormat.Wav.Signature); + buffer.Write (entry.Size+0x10); + buffer.Write (0x45564157); // 'WAVE' + buffer.Write (0x20746d66); // 'fmt ' + buffer.Write (fmt_size); + var fmt = arc.File.View.ReadBytes (entry.Offset+4, fmt_size); + buffer.Write (fmt, 0, fmt.Length); + buffer.Write (0x61746164); // 'data' + buffer.Write (pcm_size); + } + var header = mem.ToArray(); + var data = arc.File.CreateStream (entry.Offset+4+fmt_size, pcm_size); + return new PrefixStream (header, data); + } + } + } + + [Export(typeof(ArchiveFormat))] + public class ScriptPakOpener : ArchiveFormat + { + public override string Tag => "PAK/MINA/SPT"; + public override string Description => "Mina scripts archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public ScriptPakOpener () + { + ContainedFormats = new[] { "SCR" }; + } + + public override ArcFile TryOpen (ArcView file) + { + if (!VFS.IsPathEqualsToFileName (file.Name, "SCRIPT.PAK")) + return null; + using (var input = file.CreateStream()) + { + var dir = new List(); + while (input.PeekByte() != -1) + { + var name = input.ReadCString(); + if (name.Length > 0x10) + return null; + var entry = Create (name); + entry.Size = input.ReadUInt32(); + entry.Offset = input.Position; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + input.Seek (entry.Size, SeekOrigin.Current); + } + return new ArcFile (file, this, dir); + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var data = arc.File.View.ReadBytes (entry.Offset, entry.Size); + var mem = new MemoryStream (data.Length); + int pos = 0; + while (pos < data.Length) + { + int len = data[pos]+1; + int num = data.ToUInt16 (1); + pos += 3; + for (int j = 0; j < len; ++j) + data[pos+j] = Binary.RotByteR (data[pos+j], 4); + mem.Write (data, pos, len); + mem.WriteByte (0xD); + mem.WriteByte (0xA); + pos += len; + } + mem.Position = 0; + return mem; + } + } + + internal class BmpMetaData : ImageMetaData + { + public byte Flags; + public bool IsCompressed => (Flags & 1) != 0; + } + + internal class BitmapDecoder : IImageDecoder + { + IBinaryStream m_input; + BmpMetaData m_info; + ImageData m_image; + + public Stream Source => m_input.AsStream; + public ImageFormat SourceFormat => null; + public ImageMetaData Info => m_info; + public ImageData Image => m_image ?? (m_image = Unpack()); + + public BitmapDecoder (IBinaryStream input) + { + m_input = input; + m_info = new BmpMetaData { + Width = input.ReadUInt16(), + Height = input.ReadUInt16(), + Flags = input.ReadUInt8(), + }; + m_info.BPP = m_info.IsCompressed ? 32 : 24; + } + + ImageData Unpack () + { + m_input.Position = 9; + if (m_info.IsCompressed) + { + return RleUnpack(); + } + else + { + int bitmap_size = m_info.iWidth * 3 * m_info.iHeight; + var pixels = m_input.ReadBytes (bitmap_size); + return ImageData.Create (m_info, PixelFormats.Rgb24, null, pixels); + } + } + + ImageData RleUnpack () + { + int stride = m_info.iWidth * 4; + var output = new byte[stride * m_info.iHeight]; + byte alpha = 0; + int count = 0; + int dst = 0; + while (dst < output.Length) + { + if (--count <= 0) + { + alpha = m_input.ReadUInt8(); + count = m_input.ReadUInt8(); + } + if (alpha != 0) + { + output[dst+2] = m_input.ReadUInt8(); + output[dst+1] = m_input.ReadUInt8(); + output[dst ] = m_input.ReadUInt8(); + output[dst+3] = alpha; + } + dst += 4; + } + return ImageData.Create (m_info, PixelFormats.Bgra32, null, output, stride); + } + + #region IDisposable members + bool m_disposed = false; + public void Dispose () + { + if (!m_disposed) + { + m_input.Dispose(); + m_disposed = true; + } + } + #endregion + } +} diff --git a/Legacy/Nekotaro/ArcNSC.cs b/Legacy/Nekotaro/ArcNSC.cs index 1eebc879..deeb89ef 100644 --- a/Legacy/Nekotaro/ArcNSC.cs +++ b/Legacy/Nekotaro/ArcNSC.cs @@ -27,6 +27,7 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; +// [991231][Jam] Kakuyuugou Shoujo Ripple-chan // [000331][Jam] Zetsumetsu King // [000630][STONE HEADS] Sei Cosplay Gakuen ~Game Bunkou~ diff --git a/Legacy/Nekotaro/ImageGCmp.cs b/Legacy/Nekotaro/ImageGCmp.cs index f825a7c7..bf4b7f93 100644 --- a/Legacy/Nekotaro/ImageGCmp.cs +++ b/Legacy/Nekotaro/ImageGCmp.cs @@ -24,8 +24,10 @@ // using System; +using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; +using System.Text.RegularExpressions; using System.Windows.Media; using System.Windows.Media.Imaging; using GameRes.Utility; @@ -44,6 +46,11 @@ namespace GameRes.Formats.Nekotaro public override string Description { get { return "Nekotaro Game System image format"; } } public override uint Signature { get { return 0x706D4347; } } // 'GCmp' + public GCmpFormat () + { + Extensions = new[] { "GCMP", "AIG" }; + } + public override ImageMetaData ReadMetaData (IBinaryStream file) { var header = file.ReadHeader (0x10); @@ -98,6 +105,8 @@ namespace GameRes.Formats.Nekotaro Stride = (info.iWidth + 7) / 8; } + static BitmapPalette LastUsedPalette = null; + public ImageData Unpack () { m_input.Position = 0x10; @@ -106,6 +115,8 @@ namespace GameRes.Formats.Nekotaro pixels = Unpack24bpp(); else pixels = Unpack8bpp(); + if (8 == Info.BPP) + Palette = LastUsedPalette ?? (LastUsedPalette = RetrievePalette() ?? DefaultPalette); return ImageData.CreateFlipped (Info, Format, Palette, pixels, Stride); } @@ -190,14 +201,11 @@ namespace GameRes.Formats.Nekotaro byte[] Unpack8bpp () { if (8 == Info.BPP) - { Format = PixelFormats.Indexed8; - Palette = DefaultPalette; - } else Format = PixelFormats.BlackWhite; int pixel_count = Info.iHeight * Stride; - if (m_info.IsCompressed) + if (!m_info.IsCompressed) return m_input.ReadBytes (pixel_count); var output = new byte[pixel_count]; @@ -272,268 +280,125 @@ namespace GameRes.Formats.Nekotaro return output; } - static readonly BitmapPalette DefaultPalette = new BitmapPalette ( - /* - new Color[] { - Color.FromRgb (0x00, 0x00, 0x00), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0x22, 0x22, 0x22), - Color.FromRgb (0x44, 0x44, 0x44), - Color.FromRgb (0x55, 0x55, 0x55), - Color.FromRgb (0x66, 0x66, 0x66), - Color.FromRgb (0x77, 0x77, 0x77), - Color.FromRgb (0x88, 0x88, 0x88), - Color.FromRgb (0x99, 0x99, 0x99), - Color.FromRgb (0xAA, 0xAA, 0xAA), - Color.FromRgb (0xBB, 0xBB, 0xBB), - Color.FromRgb (0xCC, 0xCC, 0xCC), - Color.FromRgb (0xDD, 0xDD, 0xDD), - Color.FromRgb (0xEE, 0xEE, 0xEE), - Color.FromRgb (0x00, 0xFF, 0x00), - Color.FromRgb (0x1C, 0x09, 0x05), - Color.FromRgb (0x2F, 0x0A, 0x05), - Color.FromRgb (0x4E, 0x04, 0x02), - Color.FromRgb (0x41, 0x0C, 0x05), - Color.FromRgb (0x29, 0x15, 0x36), - Color.FromRgb (0x24, 0x22, 0x21), - Color.FromRgb (0x6C, 0x07, 0x0D), - Color.FromRgb (0x1F, 0x2D, 0x36), - Color.FromRgb (0x4B, 0x21, 0x18), - Color.FromRgb (0x5D, 0x1B, 0x0D), - Color.FromRgb (0x8B, 0x00, 0x36), - Color.FromRgb (0x8E, 0x06, 0x16), - Color.FromRgb (0x7E, 0x11, 0x0F), - Color.FromRgb (0x09, 0x44, 0x64), - Color.FromRgb (0x48, 0x2C, 0x4B), - Color.FromRgb (0x38, 0x37, 0x3A), - Color.FromRgb (0x3A, 0x24, 0x88), - Color.FromRgb (0x74, 0x23, 0x12), - Color.FromRgb (0x0D, 0x53, 0x29), - Color.FromRgb (0x22, 0x34, 0x86), - Color.FromRgb (0xB1, 0x03, 0x2A), - Color.FromRgb (0x4B, 0x37, 0x28), - Color.FromRgb (0x64, 0x30, 0x28), - Color.FromRgb (0x32, 0x4A, 0x2D), - Color.FromRgb (0x9B, 0x17, 0x20), - Color.FromRgb (0xB0, 0x10, 0x10), - Color.FromRgb (0x3D, 0x19, 0xCC), - Color.FromRgb (0x1B, 0x38, 0xB2), - Color.FromRgb (0x97, 0x25, 0x13), - Color.FromRgb (0x30, 0x4C, 0x5E), - Color.FromRgb (0x77, 0x38, 0x22), - Color.FromRgb (0xD3, 0x0B, 0x1F), - Color.FromRgb (0x01, 0x69, 0x65), - Color.FromRgb (0x5F, 0x46, 0x33), - Color.FromRgb (0x4B, 0x4D, 0x4F), - Color.FromRgb (0xB6, 0x1B, 0x34), - Color.FromRgb (0x0A, 0x74, 0x34), - Color.FromRgb (0xBB, 0x26, 0x11), - Color.FromRgb (0xED, 0x0B, 0x26), - Color.FromRgb (0x2F, 0x52, 0x97), - Color.FromRgb (0x49, 0x20, 0xFB), - Color.FromRgb (0x89, 0x44, 0x15), - Color.FromRgb (0x67, 0x46, 0x65), - Color.FromRgb (0x06, 0x76, 0x72), - Color.FromRgb (0x93, 0x3F, 0x2D), - Color.FromRgb (0x3F, 0x65, 0x49), - Color.FromRgb (0x6D, 0x52, 0x3B), - Color.FromRgb (0x88, 0x4C, 0x38), - Color.FromRgb (0xE5, 0x26, 0x17), - Color.FromRgb (0xA6, 0x47, 0x1D), - Color.FromRgb (0x43, 0x68, 0x7D), - Color.FromRgb (0x23, 0x50, 0xE8), - Color.FromRgb (0xE3, 0x24, 0x43), - Color.FromRgb (0x94, 0x56, 0x1C), - Color.FromRgb (0x60, 0x63, 0x64), - Color.FromRgb (0xBC, 0x3E, 0x49), - Color.FromRgb (0x06, 0x9C, 0x45), - Color.FromRgb (0xC4, 0x44, 0x24), - Color.FromRgb (0xB1, 0x55, 0x2B), - Color.FromRgb (0x8D, 0x60, 0x53), - Color.FromRgb (0x63, 0x46, 0xFB), - Color.FromRgb (0x7B, 0x6C, 0x61), - Color.FromRgb (0x91, 0x57, 0x97), - Color.FromRgb (0xAA, 0x5A, 0x4C), - Color.FromRgb (0x49, 0x7E, 0xA0), - Color.FromRgb (0xF8, 0x3C, 0x29), - Color.FromRgb (0xA9, 0x67, 0x20), - Color.FromRgb (0xC9, 0x56, 0x36), - Color.FromRgb (0xA2, 0x6A, 0x3E), - Color.FromRgb (0xBF, 0x56, 0x6C), - Color.FromRgb (0x77, 0x7A, 0x7B), - Color.FromRgb (0x5D, 0x79, 0xD2), - Color.FromRgb (0xCC, 0x62, 0x44), - Color.FromRgb (0xA3, 0x75, 0x63), - Color.FromRgb (0xDE, 0x60, 0x31), - Color.FromRgb (0xB5, 0x79, 0x23), - Color.FromRgb (0x45, 0x80, 0xF5), - Color.FromRgb (0xFD, 0x56, 0x29), - Color.FromRgb (0xEE, 0x52, 0x64), - Color.FromRgb (0x8C, 0x83, 0x6E), - Color.FromRgb (0xCD, 0x70, 0x2A), - Color.FromRgb (0xC4, 0x6E, 0x53), - Color.FromRgb (0x86, 0x87, 0x87), - Color.FromRgb (0x5E, 0x95, 0xAC), - Color.FromRgb (0x7D, 0x6C, 0xFD), - Color.FromRgb (0x36, 0xC5, 0x22), - Color.FromRgb (0xAC, 0x6F, 0xB2), - Color.FromRgb (0xD9, 0x6E, 0x4E), - Color.FromRgb (0xC1, 0x84, 0x2D), - Color.FromRgb (0xDB, 0x6C, 0x72), - Color.FromRgb (0xEB, 0x6F, 0x42), - Color.FromRgb (0x9F, 0x8B, 0x81), - Color.FromRgb (0x92, 0x94, 0x93), - Color.FromRgb (0x76, 0x90, 0xDB), - Color.FromRgb (0x85, 0x9A, 0x99), - Color.FromRgb (0xE0, 0x79, 0x58), - Color.FromRgb (0xBE, 0x87, 0x6D), - Color.FromRgb (0xD5, 0x7E, 0x62), - Color.FromRgb (0x5B, 0xA7, 0xDF), - Color.FromRgb (0xCC, 0x91, 0x2A), - Color.FromRgb (0xF5, 0x6F, 0x76), - Color.FromRgb (0x7B, 0xA7, 0xA7), - Color.FromRgb (0xF1, 0x7C, 0x54), - Color.FromRgb (0xA1, 0x9C, 0x87), - Color.FromRgb (0xE5, 0x81, 0x61), - Color.FromRgb (0xF2, 0x8A, 0x47), - Color.FromRgb (0xEE, 0x88, 0x67), - Color.FromRgb (0xA1, 0xA3, 0xA3), - Color.FromRgb (0x8A, 0xA0, 0xE5), - Color.FromRgb (0xC4, 0x9A, 0x7F), - Color.FromRgb (0xD9, 0x9F, 0x36), - Color.FromRgb (0x95, 0xAC, 0xAA), - Color.FromRgb (0xEC, 0x88, 0x8B), - Color.FromRgb (0xAE, 0xA7, 0x92), - Color.FromRgb (0xE8, 0x90, 0x70), - Color.FromRgb (0xF5, 0x8F, 0x6F), - Color.FromRgb (0xD5, 0x8B, 0xDC), - Color.FromRgb (0x6A, 0xC2, 0xF7), - Color.FromRgb (0xEE, 0x9A, 0x7A), - Color.FromRgb (0xF7, 0x98, 0x74), - Color.FromRgb (0x8D, 0xBA, 0xDB), - Color.FromRgb (0xBA, 0xB1, 0x9C), - Color.FromRgb (0xB2, 0xB3, 0xB1), - Color.FromRgb (0xD2, 0xA8, 0x9B), - Color.FromRgb (0xA6, 0xBA, 0xBD), - Color.FromRgb (0xEC, 0xB4, 0x3A), - Color.FromRgb (0xFC, 0x98, 0x9F), - Color.FromRgb (0xF7, 0xA1, 0x80), - Color.FromRgb (0xED, 0xA7, 0x85), - Color.FromRgb (0xFA, 0xA9, 0x83), - Color.FromRgb (0xDD, 0xB3, 0xAF), - Color.FromRgb (0xFA, 0xA6, 0xA7), - Color.FromRgb (0xC8, 0xC0, 0xAD), - Color.FromRgb (0xFA, 0xB0, 0x8F), - Color.FromRgb (0x89, 0xD9, 0xFC), - Color.FromRgb (0xA9, 0xCF, 0xE8), - Color.FromRgb (0xBB, 0xCC, 0xCB), - Color.FromRgb (0xFB, 0xB2, 0xB2), - Color.FromRgb (0xFB, 0xB9, 0x97), - Color.FromRgb (0xE2, 0xC2, 0xAF), - Color.FromRgb (0xFC, 0xCA, 0x40), - Color.FromRgb (0xFA, 0xBF, 0x82), - Color.FromRgb (0xC9, 0xCA, 0xC9), - Color.FromRgb (0xF8, 0xAC, 0xF8), - Color.FromRgb (0xD4, 0xCD, 0xC2), - Color.FromRgb (0xFC, 0xC2, 0x9D), - Color.FromRgb (0xFC, 0xBE, 0xBA), - Color.FromRgb (0xD2, 0xD3, 0xD0), - Color.FromRgb (0xEC, 0xC9, 0xC6), - Color.FromRgb (0xCA, 0xD9, 0xD7), - Color.FromRgb (0xFD, 0xCA, 0xA5), - Color.FromRgb (0xFE, 0xDB, 0x5B), - Color.FromRgb (0xD8, 0xD8, 0xD4), - Color.FromRgb (0xFD, 0xCA, 0xC9), - Color.FromRgb (0xC3, 0xDF, 0xF1), - Color.FromRgb (0xFE, 0xD2, 0xB1), - Color.FromRgb (0xFD, 0xD6, 0xA1), - Color.FromRgb (0xEE, 0xD7, 0xCA), - Color.FromRgb (0xFB, 0xCB, 0xF7), - Color.FromRgb (0xFE, 0xDB, 0xB6), - Color.FromRgb (0xFE, 0xF5, 0x2C), - Color.FromRgb (0xFD, 0xD6, 0xD4), - Color.FromRgb (0xE2, 0xE2, 0xDC), - Color.FromRgb (0xFE, 0xEC, 0x74), - Color.FromRgb (0xFE, 0xE1, 0xBE), - Color.FromRgb (0xED, 0xE5, 0xDC), - Color.FromRgb (0xD9, 0xEC, 0xF8), - Color.FromRgb (0xFB, 0xE3, 0xD4), - Color.FromRgb (0xFD, 0xDD, 0xFA), - Color.FromRgb (0xFE, 0xE7, 0xC6), - Color.FromRgb (0xFE, 0xFA, 0x91), - Color.FromRgb (0xFE, 0xEF, 0xCD), - Color.FromRgb (0xFC, 0xEB, 0xEA), - Color.FromRgb (0xFE, 0xF6, 0xDC), - Color.FromRgb (0xFE, 0xFD, 0xE4), - Color.FromRgb (0x35, 0x29, 0x24), - Color.FromRgb (0x1A, 0x43, 0x25), - Color.FromRgb (0x01, 0x49, 0x96), - Color.FromRgb (0x86, 0x27, 0x16), - Color.FromRgb (0x4D, 0x52, 0x3F), - Color.FromRgb (0xEB, 0x0E, 0x0A), - Color.FromRgb (0x00, 0x6A, 0xCC), - Color.FromRgb (0x80, 0x34, 0xC1), - Color.FromRgb (0xFD, 0x00, 0xFF), - Color.FromRgb (0x08, 0x87, 0xEF), - Color.FromRgb (0x76, 0x70, 0x56), - Color.FromRgb (0xB8, 0x55, 0x3F), - Color.FromRgb (0x35, 0x9F, 0xE1), - Color.FromRgb (0xAA, 0x7E, 0x60), - Color.FromRgb (0x01, 0xFD, 0x00), - Color.FromRgb (0xAB, 0x93, 0x8A), - Color.FromRgb (0xD3, 0x8C, 0x56), - Color.FromRgb (0x77, 0xC0, 0xAC), - Color.FromRgb (0xB9, 0xA6, 0x9E), - Color.FromRgb (0xE6, 0xAB, 0x63), - Color.FromRgb (0x9D, 0xCC, 0xA5), - Color.FromRgb (0xD1, 0xB6, 0x91), - Color.FromRgb (0xA6, 0xD9, 0xCF), - Color.FromRgb (0xEA, 0xC9, 0x9D), - Color.FromRgb (0xDF, 0xE2, 0xBC), - Color.FromRgb (0xFC, 0xE8, 0xA2), - Color.FromRgb (0xF9, 0xF2, 0xDE), - Color.FromRgb (0x23, 0x0D, 0x1A), - Color.FromRgb (0x02, 0x58, 0x1A), - Color.FromRgb (0x66, 0x39, 0x13), - Color.FromRgb (0x36, 0x6D, 0x66), - Color.FromRgb (0x90, 0x5F, 0x2A), - Color.FromRgb (0x51, 0x9E, 0x7E), - Color.FromRgb (0xC0, 0x91, 0x52), - Color.FromRgb (0x7F, 0xC3, 0xAE), - Color.FromRgb (0xE0, 0xBF, 0x78), - Color.FromRgb (0xDC, 0xE8, 0xD4), - Color.FromRgb (0x65, 0x39, 0x12), - Color.FromRgb (0x22, 0x69, 0x49), - Color.FromRgb (0x90, 0x5E, 0x2B), - Color.FromRgb (0x36, 0x87, 0x5F), - Color.FromRgb (0x53, 0x9F, 0x81), - Color.FromRgb (0xBB, 0x85, 0x4C), - Color.FromRgb (0xD9, 0xB9, 0x6F), - Color.FromRgb (0x9A, 0xCE, 0xC2), - Color.FromRgb (0xDF, 0xEF, 0xDE), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), - Color.FromRgb (0xFF, 0xFF, 0xFF), + static void LzssUnpack (IBinaryStream input, byte[] output) + { + int dst = 0; + int mask = 0; + int ctl = 0; + while (dst < output.Length) + { + mask >>= 1; + if (0 == mask) + { + ctl = input.ReadUInt8(); + mask = 0x80; + } + if ((ctl & mask) != 0) + { + int off = input.ReadUInt16(); + int count = (off & 0xF) + 3; + off >>= 4; + int src = dst - off - 1; + Binary.CopyOverlapped (output, src, dst, count); + dst += count; + } + else + { + output[dst++] = input.ReadUInt8(); + } } - */ + } + + BitmapPalette RetrievePalette () + { + // find SYSTEM.LZS file, decompress and read it as text file + // find 'P' line that denotes archive name and entry number + // if entry number is zero, then it's just a file (possibly compressed) + // open referenced file and retrieve palette + try + { + string system_name = "SYSTEM.LZS"; + if (!File.Exists (system_name)) + { + system_name = @"..\SYSTEM.LZS"; + if (!File.Exists (system_name)) + return null; + } + byte[] system_bin; + using (var input = BinaryStream.FromFile (system_name)) + { + int unpacked_size = input.ReadUInt16(); + input.ReadUInt16(); + system_bin = new byte[unpacked_size]; + LzssUnpack (input, system_bin); + } + string line; + using (var mem = new MemoryStream (system_bin)) + using (var text = new StreamReader (mem, Encodings.cp932)) + { + while ((line = text.ReadLine()) != null) + { + if (line.Length > 3 && line.StartsWith ("P:")) + break; + } + if (null == line) + return null; + } + var match = PLineRe.Match (line); + if (!match.Success) + return null; + int id; + if (!Int32.TryParse (match.Groups[2].Value, out id)) + return null; + var arc_name = Path.Combine (Path.GetDirectoryName (system_name), match.Groups[1].Value); + if (0 == id) + { + using (var file = BinaryStream.FromFile (arc_name)) + { + Stream pal_stream; + int unpacked_size = file.ReadUInt16(); + int packed_size = file.ReadUInt16(); + if (packed_size + 4 == file.Length) + { + var pal_data = new byte[unpacked_size]; + LzssUnpack (file, pal_data); + pal_stream = new MemoryStream (pal_data); + } + else + { + file.Position = 0; + pal_stream = file.AsStream; + } + int colors = (int)pal_stream.Length / 3; + using (pal_stream) + return ImageFormat.ReadPalette (pal_stream, colors, PaletteFormat.Rgb); + } + } + else + { + using (var file = new ArcView (arc_name)) + { + var arc = Nsc.Value.TryOpen (file); + if (null == arc) + return null; + var entry = ((List)arc.Dir)[id-1]; + using (var input = arc.OpenEntry (entry)) + return ImageFormat.ReadPalette (input, 0x100, PaletteFormat.Rgb); + } + } + } + catch + { + return null; + } + } + + static readonly Regex PLineRe = new Regex (@"^P:([^,]+),(\d+),(\d+)", RegexOptions.Compiled); + static readonly ResourceInstance Nsc = new ResourceInstance ("NSC"); + + static readonly BitmapPalette DefaultPalette = new BitmapPalette ( // [000317][PIL] Seek -remasters- +#region colors new Color[] { Color.FromRgb (0x00, 0x00, 0x00), Color.FromRgb (0xFF, 0xFF, 0xFF), @@ -792,6 +657,7 @@ namespace GameRes.Formats.Nekotaro Color.FromRgb (0x00, 0x00, 0x00), Color.FromRgb (0x00, 0x00, 0x00), } +#endregion ); bool m_disposed = false; diff --git a/Legacy/Nekotaro/ImageNCG.cs b/Legacy/Nekotaro/ImageNCG.cs new file mode 100644 index 00000000..7b0f5281 --- /dev/null +++ b/Legacy/Nekotaro/ImageNCG.cs @@ -0,0 +1,293 @@ +//! \file ImageNCG.cs +//! \date 2023 Oct 10 +//! \brief Nekotaro Game System 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; + +namespace GameRes.Formats.Nekotaro +{ + [Export(typeof(ImageFormat))] + public class NcgFormat : ImageFormat + { + public override string Tag => "NCG"; + public override string Description => "Nekotaro Game System image format"; + public override uint Signature => 0; + + public NcgFormat () + { + Signatures = new[] { 0xC8500000u, 0u }; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (4); + int left = header[0] << 3; + int top = header[1] << 1; + int width = header[2] << 3; + int height = header[3] << 1; + int right = left + width; + int bottom = top + height; + if (right > 640 || bottom > 400 || 0 == width || 0 == height) + 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 NcgReader (file, info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("NcgFormat.Write not implemented"); + } + } + + internal class NcgReader + { + IBinaryStream m_input; + ImageMetaData m_info; + + public NcgReader (IBinaryStream input, ImageMetaData info) + { + m_input = input; + m_info = info; + } + + public ImageData Unpack () + { + m_input.Position = 4; + var palette = ReadPalette(); + int width = m_info.iWidth; + int height = m_info.iHeight; + int output_stride = width; + var pixels = new byte[output_stride * height]; + int quart_width = width / 4; + int half_height = height / 2; + var blockmap = new bool[quart_width * half_height]; + var bits1 = new byte[8]; + var bits2 = new byte[8]; + byte ctl; + int dst, pblk; + do + { + for (int i = 0; i < 8; ++i) + bits1[i] = bits2[i] = 0; + for (int shift = 0; shift < 4; ++shift) + { + byte bit = (byte)(1 << shift); + FillBits (bits1, bit); + FillBits (bits2, bit); + } + for (;;) + { + ctl = m_input.ReadUInt8(); + if (0xFF == ctl || 0x7F == ctl) + break; + int pos = (ctl & 0x3F) << 8 | m_input.ReadUInt8(); + int x = (pos % 80) << 3; + int y = (pos / 80) << 1; + dst = width * y + x; + pblk = x / 4 + quart_width * (y / 2); + switch (ctl >> 6) + { + case 0: + { + int w_count = m_input.ReadUInt8(); + int h_count = m_input.ReadUInt8(); + int gap = quart_width - 2 * w_count; + while (h_count --> 0) + { + for (int i = 0; i < w_count; ++i) + { + for (int j = 0; j < 8; ++j) + { + pixels[dst+width] = bits2[j]; + pixels[dst++] = bits1[j]; + } + blockmap[pblk++] = true; + blockmap[pblk++] = true; + } + pblk += gap; + dst += 2 * width - 8 * w_count; + } + break; + } + case 1: + { + int count = m_input.ReadUInt8(); + while (count --> 0) + { + for (int j = 0; j < 8; ++j) + { + pixels[dst+width] = bits2[j]; + pixels[dst++] = bits1[j]; + } + blockmap[pblk++] = true; + blockmap[pblk++] = true; + } + break; + } + case 2: + { + int count = m_input.ReadUInt8(); + while (count --> 0) + { + for (int j = 0; j < 8; ++j) + { + pixels[dst+width] = bits2[j]; + pixels[dst++] = bits1[j]; + } + blockmap[pblk ] = true; + blockmap[pblk+1] = true; + dst += 2 * width - 8; + pblk += quart_width; + } + break; + } + case 3: + { + for (int j = 0; j < 8; ++j) + { + pixels[dst+width] = bits2[j]; + pixels[dst++] = bits1[j]; + } + blockmap[pblk ] = true; + blockmap[pblk+1] = true; + break; + } + } + } + } + while (ctl != 0xFF); + do + { + for (int i = 0; i < 8; ++i) + bits1[i] = 0; + for (int shift = 0; shift < 4; ++shift) + FillBits (bits1, (byte)(1 << shift)); + for (;;) + { + ctl = m_input.ReadUInt8(); + if (0xFF == ctl || 0xFE == ctl) + break; + int pos = (ctl & 0x7F) << 8 | m_input.ReadUInt8(); + dst = 4 * (pos % 160) + width * 2 * (pos / 160); + pblk = (pos % 160) + quart_width * (pos / 160); + if ((ctl & 0x80) == 0) + { + int count = m_input.ReadUInt8(); + while (count --> 0) + { + for (int j = 0; j < 4; ++j) + { + pixels[dst+width] = bits1[j+4]; + pixels[dst++] = bits1[j]; + } + blockmap[pblk] = true; + pblk += quart_width; + dst += 2 * width - 4; + } + } + else + { + for (int j = 0; j < 4; ++j) + { + pixels[dst+width] = bits1[j+4]; + pixels[dst++] = bits1[j]; + } + blockmap[pblk] = true; + } + } + } + while (ctl != 0xFF); + dst = 0; + pblk = 0; + for (int y = 0; y < half_height; ++y) + { + for (int x = 0; x < quart_width; ++x) + { + if (blockmap[pblk++]) + { + dst += 4; + } + else + { + for (int i = 0; i < 8; ++i) + bits1[i] = 0; + for (int shift = 0; shift < 4; ++shift) + FillBits (bits1, (byte)(1 << shift)); + for (int j = 0; j < 4; ++j) + { + pixels[dst+width] = bits1[j+4]; + pixels[dst++] = bits1[j]; + } + } + } + dst += width; + } + return ImageData.Create (m_info, PixelFormats.Indexed8, palette, pixels, output_stride); + } + + void FillBits (byte[] bits, byte bit) + { + sbyte s = m_input.ReadInt8(); + for (int i = 0; i < 8; ++i) + { + if (s < 0) + bits[i] |= bit; + s <<= 1; + } + } + + static readonly string PaletteKey = "NEKOTARO"; + + BitmapPalette ReadPalette () + { + int k = 0; + var colors = new Color[16]; + for (int c = 0; c < 16; ++c) + { + int g = m_input.ReadUInt8(); + int r = m_input.ReadUInt8(); + int b = m_input.ReadUInt8(); + b = (~b - PaletteKey[k++ & 7]) & 0xFF; + r = (~r - PaletteKey[k++ & 7]) & 0xFF; + g = (~g - PaletteKey[k++ & 7]) & 0xFF; + colors[c] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11)); + } + return new BitmapPalette (colors); + } + } +} diff --git a/Legacy/Ponytail/ArcBND.cs b/Legacy/Ponytail/ArcBND.cs new file mode 100644 index 00000000..d92ba2b3 --- /dev/null +++ b/Legacy/Ponytail/ArcBND.cs @@ -0,0 +1,125 @@ +//! \file ArcBND.cs +//! \date 2023 Sep 28 +//! \brief Ponytail Adventure System 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 GameRes.Utility; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; + +// [951115][Ponytail Soft] Masuzume Yume + +namespace GameRes.Formats.Ponytail +{ + [Export(typeof(ArchiveFormat))] + public class BndOpener : ArchiveFormat + { + public override string Tag => "BND/NMI"; + public override string Description => "Ponytail Soft resource archive"; + public override uint Signature => 0x646E6942; // 'Bind' + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (4, " ver.0")) + return null; + int count = file.View.ReadInt16 (0xD); + if (!IsSaneCount (count)) + return null; + uint index_offset = file.View.ReadUInt32 (0xF); + if (index_offset >= file.MaxOffset) + return null; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = file.View.ReadString (index_offset, 8).Trim(); + var ext = file.View.ReadString (index_offset+8, 3); + name = name + '.' + ext; + var entry = Create (name); + entry.Size = file.View.ReadUInt32 (index_offset+0x0C); + entry.Offset = file.View.ReadUInt32 (index_offset+0x14); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_offset += 0x18; + } + foreach (PackedEntry entry in dir.Where (e => e.Name.EndsWith ("Z") && e.Type != "image")) + { + if (file.View.AsciiEqual (entry.Offset, "lz1_")) + { + entry.IsPacked = true; + char last_chr =(char)file.View.ReadByte (entry.Offset+4); + entry.UnpackedSize = file.View.ReadUInt32 (entry.Offset+5); + string name = entry.Name.Remove (entry.Name.Length-1); + entry.Name = name + char.ToUpperInvariant (last_chr); + } + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var pent = (PackedEntry)entry; + if (!pent.IsPacked) + return base.OpenEntry (arc, entry); + var output = new byte[pent.UnpackedSize]; + using (var input = arc.File.CreateStream (pent.Offset+9, pent.Size-9)) + Lz1Unpack (input, output); + return new BinMemoryStream (output, pent.Name); + } + + internal static void Lz1Unpack (IBinaryStream input, byte[] output) + { + byte mask = 0; + int ctl = 0; + int dst = 0; + while (dst < output.Length) + { + mask <<= 1; + if (0 == mask) + { + ctl = input.ReadUInt8(); + if (ctl < 0) + break; + mask = 1; + } + if ((ctl & mask) != 0) + { + output[dst++] = input.ReadUInt8(); + } + else + { + int code = input.ReadUInt16(); + int offset = (code >> 5) + 1; + int count = Math.Min (3 + (code & 0x1F), output.Length - dst); + Binary.CopyOverlapped (output, dst - offset, dst, count); + dst += count; + } + } + } + } +} diff --git a/Legacy/Ponytail/ImageTCZ.cs b/Legacy/Ponytail/ImageTCZ.cs new file mode 100644 index 00000000..fc79a3ff --- /dev/null +++ b/Legacy/Ponytail/ImageTCZ.cs @@ -0,0 +1,212 @@ +//! \file ImageTCZ.cs +//! \date 2023 Sep 28 +//! \brief Ponytail NMI 2.5 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; + +// Graphics driver version 0.3 95/10/30 +// by Y.Nakamura / NARIMI.A + +namespace GameRes.Formats.Ponytail +{ + [Export(typeof(ImageFormat))] + public class TczFormat : ImageFormat + { + public override string Tag => "TCZ"; + public override string Description => "Ponytail Soft NMI image format"; + public override uint Signature => 0x20494D4E; // 'NMI ' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x10); + if (!header.AsciiEqual (4, "2.5\0")) + return null; + var info = new ImageMetaData { + Width = header.ToUInt16 (0xC), + Height = header.ToUInt16 (0xE), + OffsetX = header.ToInt16 (0x8), + OffsetY = header.ToInt16 (0xA), + BPP = 4, + }; + if (info.Height > TczReader.MaxHeight) + return null; + return info; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new TczReader (file, info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("TczFormat.Write not implemented"); + } + } + + internal class TczReader : TszReader + { + internal const int MaxHeight = 0x190; + const int BufferSize = MaxHeight * 0x10; + + public TczReader (IBinaryStream input, ImageMetaData info) + { + m_input = input; + m_info = info; + m_stride = m_info.iWidth >> 1; + } + + byte[] m_buffer; + + static short[] s_offTable0 = { -4, -3, -2, -1, 0, 1, 2, 3 }; + static short[] s_offTable1 = { -16, -8, -6, -4, -3, -2, -1, 0, 1, 2, 3, 4, 6, 8, 10, 16 }; + + public new ImageData Unpack () + { + m_input.Position = 0x10; + var palette = ReadPalette(); + var output = new byte[m_stride * m_info.iHeight]; + FillBuffers(); + ResetBitReader(); + int dst = 2; + int output_pos = 0; + int x = 0; + while (x < m_stride) + { + int y = 0; + while (y < m_info.iHeight) + { + if (GetNextBit()) // @1@ + { + int src = dst; + if (!GetNextBit()) // @2@ + { + src += m_lineOffsets1[1]; + int off = GetBits (4); + src += s_offTable1[off]; + } + else if (!GetNextBit()) // @3@ + { + src += GetBits (2) - 4; + } + else + { + if (!GetNextBit()) // @4@ + { + src += m_lineOffsets1[2]; + } + else if (!GetNextBit()) // @5@ + { + src += m_lineOffsets1[4]; + } + else + { + src += m_lineOffsets1[8]; + } + int off = GetBits (3); + src += s_offTable0[off]; + } + src &= 0xFFFF; + int count = GetBitLength() + 1; + Binary.CopyOverlapped (m_buffer, src, dst, count); + y += count; + dst += count; + } + else + { + int bx = m_buffer[dst-2]; + bx = (bx << 8 | bx) & 0xF00F; + if (GetNextBit()) // @8@ + { + int ax = GetBits (4); + bx = (bx & 0xFF) | ax << 12; + } + if (GetNextBit()) // @9@ + { + int ax = GetBits (4); + bx = (bx & 0xFF00) | ax; + } + bx = (bx & 0xFF) | (bx >> 8); + m_buffer[dst++] = (byte)bx; + ++y; + } + } + ++x; + if ((x & 3) == 0) + { + CopyScanline (m_lineOffsets0[(x - 1) & 0xC], output, output_pos); + output_pos += 4; + } + int z = x & 0xF; + dst = m_lineOffsets0[z]; + if (z != 0) + { + m_lineOffsets1[z] -= BufferSize; + } + else + { + for (int i = 1; i < 16; ++i) + { + m_lineOffsets1[i] += BufferSize; + } + } + } + return ImageData.Create (m_info, PixelFormats.Indexed4, palette, output, m_stride); + } + + ushort[] m_lineOffsets0 = new ushort[16]; + ushort[] m_lineOffsets1 = new ushort[16]; + + void FillBuffers () + { + m_buffer = new byte[BufferSize + MaxHeight]; + m_buffer[0] = m_buffer[1] = 3; + ushort p = 2; + for (int i = 0; i < 16; ++i) + { + m_lineOffsets1[i] = (ushort)(((16 - i) & 0xF) * MaxHeight); + m_lineOffsets0[i] = p; + p += MaxHeight; + } + } + + void CopyScanline (int src, byte[] output, int dst) + { + for (int i = 0; i < m_info.iHeight; ++i) + { + output[dst ] = m_buffer[src + i ]; + output[dst+1] = m_buffer[src + i + MaxHeight ]; + output[dst+2] = m_buffer[src + i + MaxHeight*2]; + output[dst+3] = m_buffer[src + i + MaxHeight*3]; + dst += m_stride; + } + } + } +} diff --git a/Legacy/Ponytail/ImageTSZ.cs b/Legacy/Ponytail/ImageTSZ.cs new file mode 100644 index 00000000..57cea061 --- /dev/null +++ b/Legacy/Ponytail/ImageTSZ.cs @@ -0,0 +1,330 @@ +//! \file ImageTSZ.cs +//! \date 2023 Sep 27 +//! \brief Ponytail NMI 2.05 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; + +// [930413][Ponytail Soft] Yougen Doumu + +namespace GameRes.Formats.Ponytail +{ + [Export(typeof(ImageFormat))] + public class TszFormat : ImageFormat + { + public override string Tag => "TSZ"; + public override string Description => "Ponytail Soft NMI image format"; + public override uint Signature => 0x20494D4E; // 'NMI ' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x10); + if (!header.AsciiEqual (4, "2.05")) + return null; + return new ImageMetaData { + Width = (uint)header.ToUInt16 (0xC) << 2, + Height = header.ToUInt16 (0xE), + BPP = 4, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new TszReader (file, info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("TszFormat.Write not implemented"); + } + } + + internal class TszReader + { + protected IBinaryStream m_input; + protected ImageMetaData m_info; + protected int m_stride; + + protected TszReader () { } + + public TszReader (IBinaryStream input, ImageMetaData info) + { + m_input = input; + m_info = info; + m_stride = m_info.iWidth >> 1; + } + + private int m_previous_row; + private ushort[] m_linebuffer; + private int m_dst; + + public ImageData Unpack () + { + m_input.Position = 0x10; + var palette = ReadPalette(); + var output = new byte[m_stride * m_info.iHeight]; + m_linebuffer = new ushort[m_info.iHeight * 2]; + m_previous_row = m_info.iHeight; + int width = m_info.iWidth >> 2; + int dst_x = 0; + int x = 0; + ResetBitReader(); + while (x < width) + { + int y = 0; + m_dst = 0; + if ((x & 1) != 0) + m_dst += m_info.iHeight; + while (y < m_info.iHeight) + { + int ctl = 0; + while (GetNextBit()) + { + ++ctl; + } + int count; // bx + switch (ctl) + { + case 0: count = CopyMethod0(); break; + case 1: count = CopyMethod1(); break; + case 2: + m_linebuffer[m_dst] = m_input.ReadUInt16(); + count = 1; + break; + case 3: count = CopyMethod3(); break; + case 4: count = CopyMethod4(); break; + default: throw new InvalidFormatException(); + } + m_dst += count; + y += count; + } + if ((x & 1) != 0) + { + CopyScanline (output, dst_x); + dst_x += 4; + } + m_previous_row = -m_previous_row; + ++x; + } + return ImageData.Create (m_info, PixelFormats.Indexed4, palette, output, m_stride); + } + + void CopyScanline (byte[] output, int dst) + { + for (int i = 0; i < m_info.iHeight; ++i) + { + ushort px1 = m_linebuffer[i]; + ushort px2 = m_linebuffer[i + m_info.iHeight]; + // these bytes contain 8 pixels that are being put into 4 planes + int b0 = (px1 << 4) & 0xF0 | (px2 ) & 0x0F; + int b1 = (px1 ) & 0xF0 | (px2 >> 4) & 0x0F; + int b2 = (px1 >> 4) & 0xF0 | (px2 >> 8) & 0x0F; + int b3 = (px1 >> 8) & 0xF0 | (px2 >> 12) & 0x0F; + // repack pixels into flat surface, 2 pixels per byte + 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)); + output[dst+j/2] = px; + } + dst += m_stride; + } + } + + int CopyMethod0 () + { + int count = GetBitLength(); + int offset = GetBits (4); + offset += m_previous_row - 8; + CopyOverlapped (m_linebuffer, m_dst + offset, m_dst, count); + return count; + } + + int CopyMethod1 () + { + int count = GetBitLength(); + int offset = m_input.ReadUInt8(); + offset += m_previous_row - 0x80; + CopyOverlapped (m_linebuffer, m_dst + offset, m_dst, count); + return count; + } + + int CopyMethod3 () + { + int count = GetBitLength(); + int offset = GetBits (4); + offset -= 0x10; + CopyOverlapped (m_linebuffer, m_dst + offset, m_dst, count); + return count; + } + + int CopyMethod4 () + { + byte al = m_input.ReadUInt8(); + int nibble = al >> 4; + ushort mask1 = s_pattern1[al >> 4]; + ushort mask2 = s_pattern2[al & 0xF]; + ushort pixel = m_linebuffer[m_dst + m_previous_row]; + pixel &= mask1; + for (int i = 0; i < 4; ++i) + { + short carry = (short)(nibble & 1); + nibble >>= 1; + pixel |= (ushort)(-carry & mask2); + pixel = RotU16R (pixel, 1); + } + pixel = RotU16L (pixel, 4); + m_linebuffer[m_dst] = pixel; + return 1; + } + + static readonly ushort[] s_pattern1 = new ushort[] { + 0xFFFF, 0xEEEE, 0xDDDD, 0xCCCC, 0xBBBB, 0xAAAA, 0x9999, 0x8888, + 0x7777, 0x6666, 0x5555, 0x4444, 0x3333, 0x2222, 0x1111, 0x0000, + }; + static readonly ushort[] s_pattern2 = new ushort[] { + 0x0000, 0x0001, 0x0010, 0x0011, 0x0100, 0x0101, 0x0110, 0x0111, + 0x1000, 0x1001, 0x1010, 0x1011, 0x1100, 0x1101, 0x1110, 0x1111, + }; + + ushort m_bits; + int m_bit_count; + + protected void ResetBitReader () + { + m_bits = 0; + m_bit_count = 0; + } + + protected int GetBitLength () + { + if (!GetNextBit()) + return 1; + int count = 1; + while (GetNextBit()) + { + ++count; + } + return GetBits (count) | 1 << count; + } + + protected bool GetNextBit () + { + if (--m_bit_count < 0) + { + m_bits = m_input.ReadUInt16(); + m_bit_count = 15; + } + bool bit = (m_bits & 0x8000) != 0; + m_bits <<= 1; + return bit; + } + + static readonly ushort[] s_bit_mask = new ushort[] { + 0, 1, 3, 7, 0xF, 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF, + }; + + protected int GetBits (int count) + { + m_bit_count -= count; + if (m_bit_count < 0) + { + m_bits = RotU16L (m_bits, count); + int cl = -m_bit_count; + ushort bits = m_input.ReadUInt16(); + bits = RotU16L (bits, cl); + ushort mask = s_bit_mask[cl]; + ushort new_bits = (ushort)(bits & ~mask); + bits &= mask; + bits |= m_bits; + m_bits = new_bits; + m_bit_count = 16 - cl; + return bits; + } + else + { + m_bits = RotU16L (m_bits, count); + ushort mask = s_bit_mask[count]; + int bits = m_bits & mask; + m_bits &= (ushort)~mask; + return bits; + } + } + + protected BitmapPalette ReadPalette () + { + const int count = 16; + var colors = new Color[count]; + for (int i = 0; i < count; ++i) + { + byte r = m_input.ReadUInt8(); + byte g = 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); + } + + static internal ushort RotU16L (ushort val, int count) + { + return (ushort)(val << count | val >> (16 - count)); + } + + static internal ushort RotU16R (ushort val, int count) + { + return (ushort)(val >> count | val << (16 - count)); + } + + static internal void CopyOverlapped (ushort[] data, int src, int dst, int count) + { + src <<= 1; + dst <<= 1; + count <<= 1; + if (dst > src) + { + while (count > 0) + { + int preceding = Math.Min (dst - src, count); + Buffer.BlockCopy (data, src, data, dst, preceding); + dst += preceding; + count -= preceding; + } + } + else + { + Buffer.BlockCopy (data, src, data, dst, count); + } + } + } +} diff --git a/Legacy/Properties/AssemblyInfo.cs b/Legacy/Properties/AssemblyInfo.cs index d63b355a..99ace8d5 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.208")] -[assembly: AssemblyFileVersion ("1.0.10.208")] +[assembly: AssemblyVersion ("1.0.10.210")] +[assembly: AssemblyFileVersion ("1.0.10.210")] diff --git a/Legacy/Sophia/ArcNOR.cs b/Legacy/Sophia/ArcNOR.cs new file mode 100644 index 00000000..003243c3 --- /dev/null +++ b/Legacy/Sophia/ArcNOR.cs @@ -0,0 +1,146 @@ +//! \file ArcNOR.cs +//! \date 2023 Sep 26 +//! \brief Sophia 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; +using System.IO; + +// [991210][Sophia] Film Noir + +namespace GameRes.Formats.Sophia +{ + internal class NorEntry : PackedEntry + { + public int Method; + } + + [Export(typeof(ArchiveFormat))] + public class NorOpener : ArchiveFormat + { + public override string Tag => "NOR"; + public override string Description => "Sophia resource archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (0); + if (!file.View.AsciiEqual (4, "NRCOMB01\0") || !IsSaneCount (count)) + return null; + var dir = new List (count); + using (var index = file.CreateStream()) + { + index.Position = 0x10; + for (int i = 0; i < count; ++i) + { + uint offset = index.ReadUInt32(); + uint size = index.ReadUInt32(); + string name = index.ReadCString(); + var entry = Create (name); + entry.Offset = offset; + entry.Size = size; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + } + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var nent = (NorEntry)entry; + if (!nent.IsPacked) + { + if (nent.Method == 0x1F4 || nent.Method == 0x67 + || !arc.File.View.AsciiEqual (nent.Offset, "NCMB01")) + return base.OpenEntry (arc, nent); + nent.Method = arc.File.View.ReadInt32 (nent.Offset+0x28); + if (nent.Method == 0x1F4 || nent.Method == 0x67) + { + nent.Size = arc.File.View.ReadUInt32 (nent.Offset+0x24); + nent.Offset += 0x2C; + return base.OpenEntry (arc, nent); + } + nent.IsPacked = true; + nent.UnpackedSize = arc.File.View.ReadUInt32 (nent.Offset+0x24); + nent.Size = arc.File.View.ReadUInt32 (nent.Offset+0x10); + nent.Offset += 0x2C; + } + using (var input = arc.File.CreateStream (nent.Offset, nent.Size)) + { + var output = new byte[nent.UnpackedSize]; + NcmbDecompress (input, output); + return new BinMemoryStream (output, nent.Name); + } + } + + internal static void NcmbDecompress (IBinaryStream input, byte[] output) + { + var dict = new int[0xC00]; + int root = input.ReadInt32(); + int tree_size = input.ReadInt32(); + int unpacked_size = input.ReadInt32(); + int count = root + tree_size - 0xFF; + while (count --> 0) + { + int token = 6 * input.ReadInt32(); + dict[token ] = input.ReadInt32(); + dict[token + 1] = input.ReadInt32(); + } + if (unpacked_size > 0) + { + int cur_byte = 0; + int mask = 0; + for (int dst = 0; dst < unpacked_size; ++dst) + { + int token = root; + do + { + if (0 == mask) + { + cur_byte = input.ReadUInt8(); + mask = 0x80; + } + if ((cur_byte & mask) != 0) + token = dict[6 * token + 1]; + else + token = dict[6 * token]; + mask >>= 1; + } + while (dict[6 * token] != -1); + output[dst] = (byte)token; + } + } + } + } + + [Export(typeof(ResourceAlias))] + [ExportMetadata("Extension", "M")] + [ExportMetadata("Target", "MP3")] + [ExportMetadata("Type", "audio")] + public class MFormat : ResourceAlias { } +} diff --git a/Legacy/SquadraD/ArcPLA.cs b/Legacy/SquadraD/ArcPLA.cs new file mode 100644 index 00000000..c9ac5e84 --- /dev/null +++ b/Legacy/SquadraD/ArcPLA.cs @@ -0,0 +1,114 @@ +//! \file ArcPLA.cs +//! \date 2023 Sep 26 +//! \brief Squadra D audio 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; + +namespace GameRes.Formats.SquadraD +{ + internal class PlaEntry : PackedEntry + { + public int Id; + public int n1; + public uint SampleRate; + public int Channels; + public byte n2; + public byte n3; + public int[] Data; + } + + [Export(typeof(ArchiveFormat))] + public class PlaOpener : ArchiveFormat + { + public override string Tag => "PLA"; + public override string Description => "Squadra D audio archive"; + public override uint Signature => 0x2E616C50; // 'Pla.' + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + uint arc_size = file.View.ReadUInt32 (4); + if (arc_size != file.MaxOffset || file.View.ReadUInt32 (0x10) != 2) + return null; + uint check = (arc_size & 0xD5555555u) << 1 | arc_size & 0xAAAAAAAAu; + if (check != file.View.ReadUInt32 (8)) + return null; + int count = file.View.ReadUInt16 (0xE); + if (!IsSaneCount (count)) + return null; + + var dir = new List (count); + using (var index = file.CreateStream()) + { + index.Position = 0x14; + for (int i = 0; i < count; ++i) + { + var entry = new PlaEntry { + Id = index.ReadInt32() + }; + entry.Name = entry.Id.ToString ("D5"); + dir.Add (entry); + } + foreach (PlaEntry entry in dir) + { + entry.n1 = index.ReadInt32(); + entry.SampleRate = index.ReadUInt32(); + entry.Channels = index.ReadInt32(); + entry.n2 = index.ReadUInt8(); + entry.n3 = index.ReadUInt8(); + index.ReadInt16(); + } + foreach (PlaEntry entry in dir) + { + entry.Offset = index.ReadUInt32(); + } + foreach (PlaEntry entry in dir) + { + int n = entry.Channels * 2; + entry.Data = new int[n]; + for (int j = 0; j < n; ++j) + entry.Data[j] = index.ReadInt32(); + } + } + long last_offset = file.MaxOffset; + for (int i = dir.Count - 1; i >= 0; --i) + { + dir[i].Size = (uint)(last_offset - dir[i].Offset); + last_offset = dir[i].Offset; + } + return new ArcFile (file, this, dir); + } + + /* + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + return base.OpenEntry (arc, entry); + } + */ + } +} diff --git a/Legacy/SquadraD/ArcSDA.cs b/Legacy/SquadraD/ArcSDA.cs new file mode 100644 index 00000000..dabf18bc --- /dev/null +++ b/Legacy/SquadraD/ArcSDA.cs @@ -0,0 +1,121 @@ +//! \file ArcSDA.cs +//! \date 2023 Sep 26 +//! \brief Squadra D resource archive 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; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes.Formats.SquadraD +{ + [Export(typeof(ArchiveFormat))] + public class SdaOpener : ArchiveFormat + { + public override string Tag => "SDA/SD"; + public override string Description => "Squadra D resource archive"; + public override uint Signature => 0x4153; // 'SA' + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public SdaOpener () + { + Signatures = new[] { 0x4153u, 0xCC004153u, 0u }; + } + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (0, "SA\0")) + return null; + int data_offset = file.View.ReadInt32 (4); + if (data_offset <= 8 || data_offset >= file.MaxOffset) + return null; + int count = (data_offset - 8) / 0x14; + if (!IsSaneCount (count)) + return null; + string arc_name = Path.GetFileNameWithoutExtension (file.Name); + bool is_cg = arc_name == "g"; + uint index = 8; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = file.View.ReadString (index, 0x10).Trim();; + var entry = Create (name); + entry.Offset = file.View.ReadUInt32 (index+0xC) + data_offset; + entry.Size = file.View.ReadUInt32 (index+0x10); + if (is_cg) + entry.Type = "image"; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index += 0x14; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + using (var input = arc.File.CreateStream (entry.Offset, entry.Size)) + { + var data = LzssDecompress (input); + return new BinMemoryStream (data, entry.Name); + } + } + + internal static byte[] LzssDecompress (IBinaryStream input) + { + int unpacked_size = input.ReadInt32(); + var output = new byte[unpacked_size]; + using (var bits = new LsbBitStream (input.AsStream, true)) + { + var frame = new byte[0x1000]; + int frame_pos = 0xFC0; + int dst = 0; + while (dst < unpacked_size) + { + if (bits.GetNextBit() == 0) + { + byte b = (byte)bits.GetBits (8); + output[dst++] = frame[frame_pos++ & 0xFFF] = b; + } + else + { + int count_len = 4; + if (bits.GetNextBit() != 0) + count_len = 6; + int offset = bits.GetBits (12); + int count = bits.GetBits (count_len); + count = Math.Min (count + 3, unpacked_size - dst); + while (count --> 0) + { + byte b = frame[offset++ & 0xFFF]; + output[dst++] = frame[frame_pos++ & 0xFFF] = b; + } + } + } + return output; + } + } + } +} diff --git a/Legacy/WestGate/ArcUCA.cs b/Legacy/WestGate/ArcUCA.cs index 7b9f66cd..821870a2 100644 --- a/Legacy/WestGate/ArcUCA.cs +++ b/Legacy/WestGate/ArcUCA.cs @@ -74,13 +74,15 @@ namespace GameRes.Formats.WestGate uint next_offset = file.View.ReadUInt32 (index_offset+0xC); if (next_offset < data_offset) return null; + string last_name = null; var invalid_chars = Path.GetInvalidFileNameChars(); var dir = new List (count); for (int i = 0; i < count; ++i) { var name = file.View.ReadString (index_offset, 0xC); - if (string.IsNullOrWhiteSpace (name) || name.IndexOfAny (invalid_chars) != -1) + if (last_name == name || string.IsNullOrWhiteSpace (name) || name.IndexOfAny (invalid_chars) != -1) return null; + last_name = name; index_offset += 0x10; var entry = new Entry { Name = name, Type = entry_type }; entry.Offset = next_offset; From 90cca06742e95f9e8dd9744af9b88e839a5f9639 Mon Sep 17 00:00:00 2001 From: morkt Date: Thu, 12 Oct 2023 23:18:13 +0400 Subject: [PATCH 08/22] (ImageMB): forgor deault branch in switch. --- ArcFormats/ImageMB.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ArcFormats/ImageMB.cs b/ArcFormats/ImageMB.cs index 0dd5da9f..4677b31d 100644 --- a/ArcFormats/ImageMB.cs +++ b/ArcFormats/ImageMB.cs @@ -56,6 +56,8 @@ namespace GameRes.Formats if ('L' != c2) return null; break; + default: + return null; } using (var bmp = OpenAsBitmap (stream)) return Bmp.ReadMetaData (bmp); From add9ae0a1fcd157951da6c12580890b804bce400 Mon Sep 17 00:00:00 2001 From: morkt Date: Thu, 12 Oct 2023 23:18:35 +0400 Subject: [PATCH 09/22] (Xp3Opener.Extensions): lower case. --- ArcFormats/KiriKiri/ArcXP3.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ArcFormats/KiriKiri/ArcXP3.cs b/ArcFormats/KiriKiri/ArcXP3.cs index 02114bc3..118d46bc 100644 --- a/ArcFormats/KiriKiri/ArcXP3.cs +++ b/ArcFormats/KiriKiri/ArcXP3.cs @@ -89,7 +89,7 @@ namespace GameRes.Formats.KiriKiri public Xp3Opener () { Signatures = new uint[] { 0x0d335058, 0x00905A4D, 0 }; - Extensions = new[] { "XP3", "EXE" }; + Extensions = new[] { "xp3", "exe" }; ContainedFormats = new[] { "TLG", "BMP", "PNG", "JPEG", "OGG", "WAV", "TXT" }; } From 74f8754be91ff7dde6e952480656619e00aacd94 Mon Sep 17 00:00:00 2001 From: morkt Date: Thu, 12 Oct 2023 23:19:55 +0400 Subject: [PATCH 10/22] (IsPathEqualsToFileName): if not needed. --- GameRes/FileSystem.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/GameRes/FileSystem.cs b/GameRes/FileSystem.cs index 0fa33e6d..d65ea995 100644 --- a/GameRes/FileSystem.cs +++ b/GameRes/FileSystem.cs @@ -833,10 +833,7 @@ namespace GameRes return false; // now, compare length of filename portion of the path int filename_index = path.LastIndexOfAny (PathSeparatorChars); - if (-1 == filename_index) - filename_index = 0; - else - filename_index++; + filename_index++; int filename_portion_length = path.Length - filename_index; return filename.Length == filename_portion_length; } From b004a24c0e28b316bf67f608953ef6802d9fd4db Mon Sep 17 00:00:00 2001 From: morkt Date: Sat, 14 Oct 2023 23:32:52 +0400 Subject: [PATCH 11/22] added some PC-98 formats. (AyPio): DLB archivesand PDT images. (Tiare): GRA images. --- Legacy/AyPio/ArcDLB.cs | 63 +++++++ Legacy/AyPio/ImagePDT.cs | 204 ++++++++++++++++++++++ Legacy/Legacy.csproj | 3 + Legacy/Tiare/ImageGRA.cs | 367 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 637 insertions(+) create mode 100644 Legacy/AyPio/ArcDLB.cs create mode 100644 Legacy/AyPio/ImagePDT.cs create mode 100644 Legacy/Tiare/ImageGRA.cs diff --git a/Legacy/AyPio/ArcDLB.cs b/Legacy/AyPio/ArcDLB.cs new file mode 100644 index 00000000..e35ffb03 --- /dev/null +++ b/Legacy/AyPio/ArcDLB.cs @@ -0,0 +1,63 @@ +//! \file ArcDLB.cs +//! \date 2023 Oct 13 +//! \brief AyPio 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.Collections.Generic; +using System.ComponentModel.Composition; + +namespace GameRes.Formats.AyPio +{ + [Export(typeof(ArchiveFormat))] + public class DlbOpener : ArchiveFormat + { + public override string Tag => "DLB"; + public override string Description => "UK2 engine resource archive"; + public override uint Signature => 0x64203C3C; // '<< d' + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (3, "dlb file Ver1.00>>\0")) + return null; + int count = file.View.ReadInt16 (0x16); + if (!IsSaneCount (count)) + return null; + uint index_pos = 0x18; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = file.View.ReadString (index_pos, 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)) + return null; + dir.Add (entry); + index_pos += 0x15; + } + return new ArcFile (file, this, dir); + } + } +} diff --git a/Legacy/AyPio/ImagePDT.cs b/Legacy/AyPio/ImagePDT.cs new file mode 100644 index 00000000..c7f62250 --- /dev/null +++ b/Legacy/AyPio/ImagePDT.cs @@ -0,0 +1,204 @@ +//! \file ImagePDT.cs +//! \date 2023 Oct 13 +//! \brief AyPio 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; + +// [960726][AyPio] Chuushaki +// [970314][AyPio] Stars + +namespace GameRes.Formats.AyPio +{ + internal class PdtMetaData : ImageMetaData + { + public byte Rle1; + public byte Rle2; + } + + [Export(typeof(ImageFormat))] + public class PdtFormat : ImageFormat + { + public override string Tag => "PDT/UK2"; + public override string Description => "UK2 engine image format"; + public override uint Signature => 0; + + public PdtFormat () + { + Extensions = new[] { "pdt", "anm" }; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + if (file.ReadByte() != 0x34) + return null; + file.Position = 0x21; + byte rle1 = file.ReadUInt8(); + byte rle2 = file.ReadUInt8(); + 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) + 1) << 1; + if (width <= 0 || height <= 0 || width > 2048 || height > 512) + return null; + return new PdtMetaData { + Width = (uint)width, + Height = (uint)height, + OffsetX = left << 3, + OffsetY = top, + BPP = 4, + Rle1 = rle1, + Rle2 = rle2, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new PdtReader (file, (PdtMetaData)info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("PdtFormat.Write not implemented"); + } + } + + internal class PdtReader + { + IBinaryStream m_input; + PdtMetaData m_info; + + public PdtReader (IBinaryStream input, PdtMetaData info) + { + m_input = input; + m_info = info; + } + + int m_stride; + int m_rows; + + public ImageData Unpack () + { + m_input.Position = 1; + var palette = ReadPalette(); + m_input.Position = 0x2B; + int output_stride = m_info.iWidth >> 1; + int height = m_info.iHeight; + m_stride = m_info.iWidth >> 3; + m_rows = height >> 1; + int plane_size = m_stride * height; + var planes = new byte[][] { + new byte[plane_size], new byte[plane_size], new byte[plane_size], new byte[plane_size] + }; + for (int i = 0; i < 4; ++i) + UnpackPlane (planes[i]); + var pixels = new byte[output_stride * height]; + FlattenPlanes (planes, pixels); + return ImageData.Create (m_info, PixelFormats.Indexed4, palette, pixels, output_stride); + } + + void UnpackPlane (byte[] output) + { + for (int x = 0; x < m_stride; ++x) + { + int dst = x; + int y = 0; + while (y < m_rows) + { + int count = 1; + byte p0, p1; + byte ctl = m_input.ReadUInt8(); + if (ctl == m_info.Rle1) + { + count = m_input.ReadUInt8(); + p0 = m_input.ReadUInt8(); + p1 = m_input.ReadUInt8(); + } + else if (ctl == m_info.Rle2) + { + count = m_input.ReadUInt8(); + p1 = p0 = m_input.ReadUInt8(); + } + else + { + p0 = ctl; + p1 = m_input.ReadUInt8(); + } + while (count --> 0) + { + output[dst] = p0; + dst += m_stride; + output[dst] = p1; + dst += m_stride; + ++y; + } + } + } + } + + void FlattenPlanes (byte[][] planes, byte[] output) + { + int plane_size = planes[0].Length; + int dst = 0; + for (int src = 0; src < plane_size; ++src) + { + int b0 = planes[0][src]; + int b1 = planes[1][src]; + int b2 = planes[2][src]; + int b3 = planes[3][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) )); + px |= (byte)((((b0 << j) & 0x40) >> 6) + | (((b1 << j) & 0x40) >> 5) + | (((b2 << j) & 0x40) >> 4) + | (((b3 << j) & 0x40) >> 3)); + output[dst++] = px; + } + } + } + + 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/Legacy.csproj b/Legacy/Legacy.csproj index eb4da4e8..14f41585 100644 --- a/Legacy/Legacy.csproj +++ b/Legacy/Legacy.csproj @@ -91,6 +91,8 @@ + + @@ -234,6 +236,7 @@ + diff --git a/Legacy/Tiare/ImageGRA.cs b/Legacy/Tiare/ImageGRA.cs new file mode 100644 index 00000000..0e78d750 --- /dev/null +++ b/Legacy/Tiare/ImageGRA.cs @@ -0,0 +1,367 @@ +//! \file ImageGRA.cs +//! \date 2023 Oct 11 +//! \brief Tiare 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; + +// [950616][JAST] Tenshi-tachi no Gogo ~Tenkousei~ +// [950922][Tiare] Vanishing Point -Tenshi no Kieta Machi- + +namespace GameRes.Formats.Tiare +{ + internal class GraMetaData : ImageMetaData + { + public byte Flags; + public long DataOffset; + + public bool HasPalette => (Flags & 0x80) == 0; + } + + [Export(typeof(ImageFormat))] + public class GraFormat : ImageFormat + { + public override string Tag => "GRA/TIARE"; + public override string Description => "Tiare image format"; + public override uint Signature => 0; + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x30); + int pos = header.IndexOf (0x1A); + if (-1 == pos) + return null; + ++pos; + while (pos < header.Length && header[pos++] != 0) + ; + if (pos + 3 >= header.Length || header[pos+3] != 4) + return null; + byte flags = header[pos]; + file.Position = pos + 8; + int skip = Binary.BigEndian (file.ReadUInt16()); + if (skip != 0) + file.Seek (skip, SeekOrigin.Current); + uint width = Binary.BigEndian (file.ReadUInt16()); + uint height = Binary.BigEndian (file.ReadUInt16()); + if (width == 0 || height == 0) + return null; + return new GraMetaData + { + Width = width, + Height = height, + BPP = 4, + Flags = flags, + DataOffset = file.Position, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new GraReader (file, (GraMetaData)info); + return reader.Unpack(); + } + + 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 + Color.FromRgb (0x00, 0x00, 0x00), + Color.FromRgb (0x00, 0x00, 0x77), + Color.FromRgb (0x77, 0x00, 0x00), + Color.FromRgb (0x77, 0x00, 0x77), + Color.FromRgb (0x00, 0x77, 0x00), + Color.FromRgb (0x00, 0x77, 0x77), + Color.FromRgb (0x77, 0x77, 0x00), + Color.FromRgb (0x77, 0x77, 0x77), + Color.FromRgb (0x00, 0x00, 0x00), + Color.FromRgb (0x00, 0x00, 0xFF), + Color.FromRgb (0xFF, 0x00, 0x00), + Color.FromRgb (0xFF, 0x00, 0xFF), + Color.FromRgb (0x00, 0xFF, 0x00), + Color.FromRgb (0x00, 0xFF, 0xFF), + Color.FromRgb (0xFF, 0xFF, 0x00), + Color.FromRgb (0xFF, 0xFF, 0xFF), + #endregion + }); + } +} From a7f6862c495baf6403344806954fa791244b8a89 Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 20 Oct 2023 18:12:32 +0400 Subject: [PATCH 12/22] (WVB, bg00, sbg): Xuse resource archives. --- ArcFormats/ArcFormats.csproj | 2 + ArcFormats/Xuse/ArcNT.cs | 189 +++++++++++++++++++++++++++++++++++ ArcFormats/Xuse/ArcWVB.cs | 91 +++++++++++++++++ 3 files changed, 282 insertions(+) create mode 100644 ArcFormats/Xuse/ArcNT.cs create mode 100644 ArcFormats/Xuse/ArcWVB.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 222931c4..1ffebee6 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -159,6 +159,8 @@ + + diff --git a/ArcFormats/Xuse/ArcNT.cs b/ArcFormats/Xuse/ArcNT.cs new file mode 100644 index 00000000..06990a91 --- /dev/null +++ b/ArcFormats/Xuse/ArcNT.cs @@ -0,0 +1,189 @@ +//! \file ArcNT.cs +//! \date 2023 Oct 17 +//! \brief Xuse 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.Windows.Media; + +namespace GameRes.Formats.Xuse +{ + [Export(typeof(ArchiveFormat))] + public class BgOpener : ArchiveFormat + { + public override string Tag => "BG/Xuse"; + public override string Description => "Xuse bitmap archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public BgOpener () + { + Extensions = new[] { "" }; + } + + public override ArcFile TryOpen (ArcView file) + { + var arc_name = Path.GetFileName (file.Name); + if (!arc_name.StartsWith ("bg00", StringComparison.OrdinalIgnoreCase) && + !arc_name.StartsWith ("sbg", StringComparison.OrdinalIgnoreCase)) + return null; + long rem; + uint entry_size = arc_name[0] == 's' ? 0x96400u : 0x4B400u; + int count = (int)Math.DivRem (file.MaxOffset, entry_size, out rem); + if (rem != 0 || !IsSaneCount (count)) + return null; + uint offset = 0; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var entry = new Entry { + Name = string.Format ("{0}#{1:D4}", arc_name, i), + Type = "image", + Offset = offset, + Size = entry_size, + }; + dir.Add (entry); + offset += entry_size; + } + return new ArcFile (file, this, dir); + } + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var input = arc.File.CreateStream (entry.Offset, entry.Size); + uint height = entry.Size == 0x4B400u ? 480u : 960u; + var info = new ImageMetaData { Width = 640, Height = height, BPP = 8 }; + return new BgImageDecoder (input, info); + } + } + + internal class BgImageDecoder : BinaryImageDecoder + { + public BgImageDecoder (IBinaryStream input, ImageMetaData info) : base (input, info) + { + } + + protected override ImageData GetImageData () + { + m_input.Position = 0; + var pixels = m_input.ReadBytes (Info.iWidth * Info.iHeight); + var palette = ImageFormat.ReadPalette (m_input.AsStream); + return ImageData.CreateFlipped (Info, PixelFormats.Indexed8, palette, pixels, Info.iWidth); + } + } + + [Export(typeof(ArchiveFormat))] + public class HOpener : ArchiveFormat + { + public override string Tag => "H/Xuse"; + public override string Description => "Xuse bitmap archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public HOpener () + { + Extensions = new[] { "" }; + } + + public override ArcFile TryOpen (ArcView file) + { + var arc_name = Path.GetFileName (file.Name); + if (!arc_name.StartsWith ("H", StringComparison.OrdinalIgnoreCase)) + return null; + long rem; + uint entry_size = arc_name.EndsWith ("W") ? 0x2A700u : 0x25480u; + int count = (int)Math.DivRem (file.MaxOffset, entry_size, out rem); + if (!IsSaneCount (count) || rem != 0) + return null; + uint offset = 0; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var entry = new Entry { + Name = string.Format ("{0}#{1:D4}", arc_name, i), + Type = "image", + Offset = offset, + Size = entry_size, + }; + dir.Add (entry); + offset += entry_size; + } + return new ArcFile (file, this, dir); + } + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var input = arc.File.CreateStream (entry.Offset, entry.Size); + uint width = entry.Size == 0x25480u ? 280u : 320u; + var info = new ImageMetaData { Width = width, Height = 480, BPP = 8 }; + return new HImageDecoder (input, info); + } + } + + internal class HImageDecoder : BinaryImageDecoder + { + public HImageDecoder (IBinaryStream input, ImageMetaData info) : base (input, info) + { + } + + protected override ImageData GetImageData () + { + m_input.Position = 0; + int stride8bpp = Info.iWidth; + int alpha_stride = (Info.iWidth / 8 + 1) & -2; + var pixels = m_input.ReadBytes (stride8bpp * Info.iHeight); + var alpha = m_input.ReadBytes (alpha_stride * Info.iHeight); + m_input.Seek (-0x400, SeekOrigin.End); + var palette = ImageFormat.ReadPalette (m_input.AsStream); + int stride32bpp = Info.iWidth * 4; + var bgra = new byte[stride32bpp * Info.iHeight]; + var colors = palette.Colors; + int src = pixels.Length - stride8bpp; + int asrc = alpha.Length - alpha_stride; + int dst_row = 0; + for (int y = 0; y < Info.iHeight; ++y) + { + int dst = dst_row; + for (int x = 0; x < Info.iWidth; ++x) + { + var c = colors[pixels[src+x]]; + bgra[dst ] = c.B; + bgra[dst+1] = c.G; + bgra[dst+2] = c.R; + int A = (alpha[asrc + (x >> 3)] << (x & 7)) & 0x80; + bgra[dst+3] = (byte)(A == 0 ? 0xFF : 0); + dst += 4; + } + src -= stride8bpp; + asrc -= alpha_stride; + dst_row += stride32bpp; + } + return ImageData.Create (Info, PixelFormats.Bgra32, null, bgra, stride32bpp); + } + } +} diff --git a/ArcFormats/Xuse/ArcWVB.cs b/ArcFormats/Xuse/ArcWVB.cs new file mode 100644 index 00000000..1bc5e894 --- /dev/null +++ b/ArcFormats/Xuse/ArcWVB.cs @@ -0,0 +1,91 @@ +//! \file ArcWVB.cs +//! \date 2023 Oct 17 +//! \brief Xuse audio 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 GameRes.Utility; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes.Formats.Xuse +{ + [Export(typeof(ArchiveFormat))] + public class WvbOpener : ArchiveFormat + { + public override string Tag => "WVB"; + public override string Description => "Xuse audio resource archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + int first_offset = file.View.ReadInt32 (4) - 1; + if (first_offset < 8 || first_offset >= file.MaxOffset) + return null; + int count = first_offset / 8; + if ((first_offset & 7) != 0 || !IsSaneCount (count)) + return null; + uint fmt_size = file.View.ReadUInt32 (first_offset); + if (!file.View.AsciiEqual (first_offset+fmt_size+4, "data")) + return null; + + var base_name = Path.GetFileNameWithoutExtension (file.Name); + uint index_pos = 0; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + uint offset = file.View.ReadUInt32 (index_pos+4); + if (0 == offset) + break; + var entry = new Entry { + Name = string.Format ("{0}#{1:D2}", base_name, i), + Type = "audio", + Size = file.View.ReadUInt32 (index_pos) - 8, + Offset = offset - 1, + }; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + entry.Size += 16; + dir.Add (entry); + index_pos += 8; + } + if (0 == dir.Count) + return null; + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var header = new byte[0x10]; + LittleEndian.Pack (AudioFormat.Wav.Signature, header, 0); + LittleEndian.Pack (entry.Size - 8u, header, 4); + LittleEndian.Pack (0x45564157, header, 8); // 'WAVE' + LittleEndian.Pack (0x20746d66, header, 12); // 'fmt ' + var data = arc.File.CreateStream (entry.Offset, entry.Size - 16); + return new PrefixStream (header, data); + } + } +} From 5f9340c32bf9d3653d13ffd8b2f7c02746cacda6 Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 20 Oct 2023 18:13:19 +0400 Subject: [PATCH 13/22] (VF): addtional check for exe files. --- ArcFormats/LiveMaker/ArcVF.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ArcFormats/LiveMaker/ArcVF.cs b/ArcFormats/LiveMaker/ArcVF.cs index 8bb78d2c..fd8e505d 100644 --- a/ArcFormats/LiveMaker/ArcVF.cs +++ b/ArcFormats/LiveMaker/ArcVF.cs @@ -66,6 +66,8 @@ namespace GameRes.Formats.LiveMaker && (0x5A4D == (signature & 0xFFFF))) // 'MZ' { base_offset = SkipExeData (index_file); + if (base_offset >= file.MaxOffset) + return null; signature = index_file.View.ReadUInt32 (base_offset); } else if (!file.Name.HasExtension (".dat")) From 67811651d4265b1257f8a1f4623344a74ffad91a Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 20 Oct 2023 18:14:14 +0400 Subject: [PATCH 14/22] (EXE): imporved RT_VERSION parser. --- Experimental/Microsoft/ArcEXE.cs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Experimental/Microsoft/ArcEXE.cs b/Experimental/Microsoft/ArcEXE.cs index f32a14a7..3555c950 100644 --- a/Experimental/Microsoft/ArcEXE.cs +++ b/Experimental/Microsoft/ArcEXE.cs @@ -175,17 +175,23 @@ namespace GameRes.Formats.Microsoft input.Position = pos; if (input.ReadUInt32() != 0xFEEF04BDu) break; - input.Position = pos + value_length; - int str_info_length = input.ReadUInt16(); - value_length = input.ReadUInt16(); - type = input.ReadUInt16(); - if (value_length != 0) - break; - if (input.ReadCString (Encoding.Unicode) != "StringFileInfo") + int info_length = value_length; + bool found_string_info = false; + do + { + pos += info_length; + input.Position = pos; + info_length = input.ReadUInt16(); + value_length = input.ReadUInt16(); + type = input.ReadUInt16(); + found_string_info = input.ReadCString (Encoding.Unicode) == "StringFileInfo"; + } + while (!found_string_info && input.PeekByte() != -1); + if (!found_string_info) break; pos = (input.Position + 3) & -4L; input.Position = pos; - int info_length = input.ReadUInt16(); + info_length = input.ReadUInt16(); long end_pos = pos + info_length; value_length = input.ReadUInt16(); type = input.ReadUInt16(); From 1aac79f7b55fb3680236e6cf10fd4781bb3fca7c Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 20 Oct 2023 18:21:55 +0400 Subject: [PATCH 15/22] (Legacy): added formats, mostly PC-98. --- Legacy/AyPio/ArcDLB.cs | 50 +++- Legacy/AyPio/AudioVOC.cs | 192 +++++++++++++ Legacy/AyPio/ImagePDT.cs | 6 +- Legacy/AyPio/ImagePDT5.cs | 252 +++++++++++++++++ Legacy/AyPio/PdtBitmap.cs | 221 +++++++++++++++ Legacy/Desire/ArcDSV.cs | 85 ++++++ Legacy/Desire/ImageDES.cs | 74 +++++ Legacy/Desire/ImageDPC.cs | 93 +++++++ Legacy/Legacy.csproj | 13 + Legacy/Mapl/ImageMI2.cs | 438 ++++++++++++++++++++++++++++++ Legacy/Neon/ArcAR2.cs | 90 ++++++ Legacy/Pias/ArcDAT.cs | 186 ++++++++----- Legacy/Pias/EncryptedGraphDat.cs | 377 +++++++++++++++++++++++++ Legacy/Properties/AssemblyInfo.cs | 4 +- Legacy/System98/ArcLIB.cs | 141 ++++++++++ Legacy/System98/ImageG.cs | 335 +++++++++++++++++++++++ Legacy/Tiare/ImageGRA.cs | 265 +----------------- Legacy/Tobe/ImageWBI.cs | 107 ++++++++ Legacy/Ucom/ImageUG.cs | 104 +++++++ 19 files changed, 2705 insertions(+), 328 deletions(-) create mode 100644 Legacy/AyPio/AudioVOC.cs create mode 100644 Legacy/AyPio/ImagePDT5.cs create mode 100644 Legacy/AyPio/PdtBitmap.cs create mode 100644 Legacy/Desire/ArcDSV.cs create mode 100644 Legacy/Desire/ImageDES.cs create mode 100644 Legacy/Desire/ImageDPC.cs create mode 100644 Legacy/Mapl/ImageMI2.cs create mode 100644 Legacy/Neon/ArcAR2.cs create mode 100644 Legacy/Pias/EncryptedGraphDat.cs create mode 100644 Legacy/System98/ArcLIB.cs create mode 100644 Legacy/System98/ImageG.cs create mode 100644 Legacy/Tobe/ImageWBI.cs create mode 100644 Legacy/Ucom/ImageUG.cs 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); + } + } +} From 05ab3e260043e80c1d483c1a9cc74237fb66325b Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 27 Oct 2023 04:06:23 +0400 Subject: [PATCH 16/22] (GRP): added 'zfd' compression. --- ArcFormats/Ankh/ArcGRP.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ArcFormats/Ankh/ArcGRP.cs b/ArcFormats/Ankh/ArcGRP.cs index 4dc09bed..a594f541 100644 --- a/ArcFormats/Ankh/ArcGRP.cs +++ b/ArcFormats/Ankh/ArcGRP.cs @@ -118,6 +118,12 @@ namespace GameRes.Formats.Ankh entry.UnpackedSize = header.ToUInt32 (0); entry.IsPacked = true; } + else if (header.AsciiEqual (0, "zfd ")) + { + entry.ChangeType (ImageFormat.Tga); + entry.UnpackedSize = header.ToUInt32 (4); + entry.IsPacked = true; + } else if (header.AsciiEqual (4, "OggS")) { entry.ChangeType (OggAudio.Instance); @@ -177,6 +183,8 @@ namespace GameRes.Formats.Ankh return OpenTpw (arc, pent); if (arc.File.View.AsciiEqual (entry.Offset+4, "HDJ\0")) return OpenImage (arc, pent); + if (arc.File.View.AsciiEqual (entry.Offset, "zfd ")) + return OpenZfd (arc, pent); if (entry.Size > 12) { byte type = arc.File.View.ReadByte (entry.Offset+4); @@ -243,6 +251,12 @@ namespace GameRes.Formats.Ankh } } + Stream OpenZfd (ArcFile arc, PackedEntry entry) + { + var input = arc.File.CreateStream (entry.Offset+8, entry.Size-8); + return new ZLibStream (input, CompressionMode.Decompress); + } + internal static void UnpackTpw (IBinaryStream input, byte[] output) { input.Position = 8; From 849fb5508cacf0cd0217ac0b27398aabebba0a4a Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 27 Oct 2023 04:06:59 +0400 Subject: [PATCH 17/22] (MB): added 'XX' signature. --- ArcFormats/ImageMB.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ArcFormats/ImageMB.cs b/ArcFormats/ImageMB.cs index 4677b31d..70dc4117 100644 --- a/ArcFormats/ImageMB.cs +++ b/ArcFormats/ImageMB.cs @@ -45,7 +45,7 @@ namespace GameRes.Formats { int c1 = stream.ReadByte(); int c2 = stream.ReadByte(); - // MB/MC/MK/CL + // MB/MC/MK/CL/XX switch (c1) { case 'M': @@ -56,6 +56,10 @@ namespace GameRes.Formats if ('L' != c2) return null; break; + case 'X': + if ('X' != c2) + return null; + break; default: return null; } From e852ea5a8247880c46d0acd07c90ce72460b7e09 Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 27 Oct 2023 04:12:32 +0400 Subject: [PATCH 18/22] (WAV): some wave audio encoding. --- ArcFormats/ArcFormats.csproj | 1 + ArcFormats/CsWare/AudioWAV.cs | 92 +++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 ArcFormats/CsWare/AudioWAV.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 1ffebee6..eabac13c 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -106,6 +106,7 @@ + diff --git a/ArcFormats/CsWare/AudioWAV.cs b/ArcFormats/CsWare/AudioWAV.cs new file mode 100644 index 00000000..5cff2cf5 --- /dev/null +++ b/ArcFormats/CsWare/AudioWAV.cs @@ -0,0 +1,92 @@ +//! \file AudioWAV.cs +//! \date 2023 Oct 26 +//! \brief Obscure C's ware WAVE file encoding. +// +// 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; + +// [960405][C's Ware] GLO-RI-A ~Kindan no Ketsuzoku~ + +namespace GameRes.Formats.CsWare +{ + [Export(typeof(AudioFormat))] + [ExportMetadata("Priority", 1)] // should be tried before generic WAVE format + public class WavAudio : AudioFormat + { + public override string Tag => "WAV/CSWARE"; + public override string Description => "C's ware encoded audio"; + public override uint Signature => 0x46464952; // 'RIFF' + public override bool CanWrite => false; + + public override SoundInput TryOpen (IBinaryStream file) + { + var header = file.ReadHeader (0x2E); + if (header[0x14] != 1 || header[0x15] != 0xFF + || !header.AsciiEqual (8, "WAVEfmt ") + || !header.AsciiEqual (0x26, "data")) + return null; + var format = new WaveFormat { + FormatTag = 1, + Channels = header.ToUInt16 (0x16), + SamplesPerSecond = header.ToUInt32 (0x18), + AverageBytesPerSecond = header.ToUInt32 (0x1C) * 2, + BlockAlign = (ushort)(header.ToUInt16 (0x20) * 2), + BitsPerSample = 16, + }; + uint input_size = header.ToUInt32 (0x2A); + var samples = new byte[input_size * 2]; + Decode (file, samples); + var decoded = new BinMemoryStream (samples, file.Name); + file.Dispose(); + return new RawPcmInput (decoded, format); + } + + void Decode (IBinaryStream input, byte[] output) + { + int dst = 0; + while (input.PeekByte() != -1) + { + sbyte sample = input.ReadInt8(); + LittleEndian.Pack (SampleMap[sample + 128], output, dst); + dst += 2; + } + } + + static readonly short[] SampleMap = InitSampleMap(); + + static short[] InitSampleMap () + { + var map = new short[256]; + for (int i = 1; i <= 127; ++i) + { + map[128 + i] = (short)(Math.Pow (10.0, ((double)i + 44.8637) / 38.0597) - 14.5342); + map[128 - i] = (short)-map[i + 128]; + } + map[0] = -0x8000; + return map; + } + } +} From 0758496dd8f7d201113f887055d8bc9087a70b35 Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 27 Oct 2023 04:13:30 +0400 Subject: [PATCH 19/22] (Jpeg, Bmp): increase priority. --- GameRes/ImageBMP.cs | 1 + GameRes/ImageJPEG.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/GameRes/ImageBMP.cs b/GameRes/ImageBMP.cs index 8ee0c054..0fc5416c 100644 --- a/GameRes/ImageBMP.cs +++ b/GameRes/ImageBMP.cs @@ -47,6 +47,7 @@ namespace GameRes } [Export(typeof(ImageFormat))] + [ExportMetadata("Priority", 10)] public sealed class BmpFormat : ImageFormat { public override string Tag { get { return "BMP"; } } diff --git a/GameRes/ImageJPEG.cs b/GameRes/ImageJPEG.cs index 3de37066..41ddd25f 100644 --- a/GameRes/ImageJPEG.cs +++ b/GameRes/ImageJPEG.cs @@ -34,6 +34,7 @@ using GameRes.Utility; namespace GameRes { [Export(typeof(ImageFormat))] + [ExportMetadata("Priority", 10)] public class JpegFormat : ImageFormat { public override string Tag { get { return "JPEG"; } } From 6981d3bf8263999b00e5f04c8ba16bded0609059 Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 27 Oct 2023 04:14:57 +0400 Subject: [PATCH 20/22] (UG): properly implemented format. --- Legacy/Ucom/ImageUG.cs | 132 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 1 deletion(-) diff --git a/Legacy/Ucom/ImageUG.cs b/Legacy/Ucom/ImageUG.cs index 00cf6f9c..ce8a27b8 100644 --- a/Legacy/Ucom/ImageUG.cs +++ b/Legacy/Ucom/ImageUG.cs @@ -23,6 +23,7 @@ // IN THE SOFTWARE. // +using System; using System.ComponentModel.Composition; using System.IO; using System.Windows.Media; @@ -73,6 +74,9 @@ namespace GameRes.Formats.Ucom } } + /// + /// Same compression algorithm as the base, but scanlines are vertical + /// internal class UgReader : System98.GraBaseReader { public UgReader (IBinaryStream input, ImageMetaData info) : base (input, info) @@ -83,10 +87,136 @@ namespace GameRes.Formats.Ucom { m_input.Position = 8; var palette = ReadPalette(); - UnpackBits(); + m_input.Position = 0x28; + try + { + UnpackBitsInternal(); + } + catch (EndOfStreamException) + { + FlushBuffer(); + } return ImageData.Create (m_info, PixelFormats.Indexed4, palette, Pixels, Stride); } + void UnpackBitsInternal () + { + int height = m_info.iHeight; + int hTimes2 = height << 1; + int hTimes4 = height << 2; + int buffer_size = hTimes4 * 3; + m_buffer = new ushort[buffer_size >> 1]; + m_dst = 0; + InitFrame(); + InitBitReader(); + ushort p = ReadPair (0); + for (int i = 0; i < hTimes2+1; ++i) + m_buffer[i] = p; + int dst = hTimes4; + int prev_src = 0; + while (m_dst < m_pixels.Length) + { + bool same_line = false; + int src = -hTimes2; + if (GetNextBit() != 0) // @1@ + { + if (GetNextBit() == 0) // @4@ + src += 4; + else if (GetNextBit() == 0) // @5@ + src -= 4; + else + src <<= 1; + } + else if (GetNextBit() == 0) // @2@ + { + src = -4; + p = m_buffer[dst/2-1]; + if ((p & 0xFF) == (p >> 8)) + same_line = src != prev_src; + } + if (src != prev_src) // @6@ + { + prev_src = src; + if (!same_line) + src += dst; + else + src = dst - 2; + if (GetNextBit() != 0) // @3@ + { + int bitlength = 0; + do + { + ++bitlength; + } + while (GetNextBit() != 0); + int count = 1; + while (bitlength --> 0) + count = count << 1 | GetNextBit(); + MovePixels (m_buffer, src, dst, count); + dst += count << 1; + if (dst == buffer_size) + { + if (FlushBuffer()) + return; + dst = hTimes4; + } + } + else + { + MovePixels (m_buffer, src, dst, 1); + dst += 2; + if (dst == buffer_size) + { + if (FlushBuffer()) + return; + dst = hTimes4; + } + } + } + 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 = hTimes4; + } + } + while (GetNextBit() != 0); + prev_src = 0; + } + } + } + + bool FlushBuffer () + { + int height = m_info.iHeight; + int src_line = height << 1; + int dst = m_dst; + for (int i = 0; i < height; ++i) + { + int src = src_line; + for (int j = 0; j < 4; ++j) + { + ushort p = m_buffer[src]; + m_pixels[dst+j] = (byte)((p & 0xF0) | p >> 12); + src += height; + } + src_line++; + dst += m_output_stride; + } + m_dst += 4; + MovePixels (m_buffer, height << 3, 0, height << 1); + return m_dst >= m_output_stride; + } + BitmapPalette ReadPalette () { var colors = new Color[16]; From 1521532bf483dc882a0fad0dc1fd1c16c837cb90 Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 27 Oct 2023 04:17:21 +0400 Subject: [PATCH 21/22] (Pias): use decimal number for entry names. --- Legacy/Pias/ArcDAT.cs | 9 +++++++-- Legacy/Pias/EncryptedGraphDat.cs | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Legacy/Pias/ArcDAT.cs b/Legacy/Pias/ArcDAT.cs index 231e58a9..34c999fe 100644 --- a/Legacy/Pias/ArcDAT.cs +++ b/Legacy/Pias/ArcDAT.cs @@ -45,7 +45,7 @@ namespace GameRes.Formats.Pias internal class IndexReader { - internal const bool NamesAsHexOffset = true; + internal const bool UseOffsetAsName = true; protected ArcView m_arc; protected ResourceType m_res; @@ -118,7 +118,7 @@ namespace GameRes.Formats.Pias if (!known_offsets.Contains (offset)) { var entry = new Entry { - Name = NamesAsHexOffset ? offset.ToString ("X8") : m_dir.Count.ToString("D4"), + Name = GetName (offset, m_dir.Count), Type = entry_type, Offset = offset, Size = entry_size, @@ -132,6 +132,11 @@ namespace GameRes.Formats.Pias } return true; } + + internal string GetName (long offset, int num) + { + return UseOffsetAsName ? offset.ToString ("D8") : num.ToString("D4"); + } } [Export(typeof(ArchiveFormat))] diff --git a/Legacy/Pias/EncryptedGraphDat.cs b/Legacy/Pias/EncryptedGraphDat.cs index 560755a2..b54a6578 100644 --- a/Legacy/Pias/EncryptedGraphDat.cs +++ b/Legacy/Pias/EncryptedGraphDat.cs @@ -96,7 +96,7 @@ namespace GameRes.Formats.Pias 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.Name = GetName (entry.Offset, i); entry.Type = "image"; } var known_offsets = new HashSet (m_dir.Select (e => e.Offset)); @@ -111,7 +111,7 @@ namespace GameRes.Formats.Pias if (!known_offsets.Contains (offset)) { var entry = new Entry { - Name = (NamesAsHexOffset ? offset.ToString ("X8") : m_dir.Count.ToString("D4")) + "_", + Name = GetName (offset, m_dir.Count) + "_", Type = "image", Offset = offset, Size = entry_size, From b09ee4570ccb1daf6ac56710ee8934dc0b8baeb0 Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 27 Oct 2023 04:20:15 +0400 Subject: [PATCH 22/22] added more PC-98 formats. MIA, MAI2, MAI3 images, D-Motion archives. --- Legacy/DMotion/ArcDM.cs | 89 ++++++++ Legacy/Izumi/ImageMAI2.cs | 436 ++++++++++++++++++++++++++++++++++++++ Legacy/Izumi/ImageMAI3.cs | 332 +++++++++++++++++++++++++++++ Legacy/Legacy.csproj | 4 + Legacy/Miami/ImageMIA.cs | 318 +++++++++++++++++++++++++++ Legacy/System98/ImageG.cs | 20 +- 6 files changed, 1190 insertions(+), 9 deletions(-) create mode 100644 Legacy/DMotion/ArcDM.cs create mode 100644 Legacy/Izumi/ImageMAI2.cs create mode 100644 Legacy/Izumi/ImageMAI3.cs create mode 100644 Legacy/Miami/ImageMIA.cs 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 @@ namespace GameRes.Formats.System98 } } + /// + /// 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 @@ namespace GameRes.Formats.System98 m_pixels = new byte[m_output_stride * m_info.iHeight]; } - ushort[] m_buffer; + protected ushort[] m_buffer; public void UnpackBits () { @@ -216,8 +220,6 @@ namespace GameRes.Formats.System98 } } - int m_dst; - bool FlushBuffer () { MovePixels (m_buffer, m_info.iWidth * 4, 0, m_info.iWidth); @@ -231,14 +233,14 @@ namespace GameRes.Formats.System98 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 @@ namespace GameRes.Formats.System98 byte[] m_frame; - void InitFrame () + protected void InitFrame () { m_frame = new byte[0x100]; int p = 0; @@ -293,7 +295,7 @@ namespace GameRes.Formats.System98 } } - 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 @@ namespace GameRes.Formats.System98 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) {