From d312da7de56d20480ad0b8daabac09b7863bd7bb Mon Sep 17 00:00:00 2001 From: morkt Date: Tue, 14 Jul 2015 05:15:26 +0400 Subject: [PATCH] implemented F&C resource formats. *.mrg archives *.mcg and *.acd images --- ArcFormats/ArcFormats.csproj | 3 + ArcFormats/ArcMRG.cs | 332 ++++++++++++++++++++++++++ ArcFormats/ImageACD.cs | 158 ++++++++++++ ArcFormats/ImageMCG.cs | 195 +++++++++++++++ ArcFormats/Properties/AssemblyInfo.cs | 4 +- supported.html | 6 +- 6 files changed, 695 insertions(+), 3 deletions(-) create mode 100644 ArcFormats/ArcMRG.cs create mode 100644 ArcFormats/ImageACD.cs create mode 100644 ArcFormats/ImageMCG.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index be8f985d..4c8e7e6b 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -109,6 +109,7 @@ + @@ -172,6 +173,7 @@ CreateYPFWidget.xaml + @@ -204,6 +206,7 @@ + diff --git a/ArcFormats/ArcMRG.cs b/ArcFormats/ArcMRG.cs new file mode 100644 index 00000000..8db4a161 --- /dev/null +++ b/ArcFormats/ArcMRG.cs @@ -0,0 +1,332 @@ +//! \file ArcMRG.cs +//! \date Mon Jul 13 03:20:13 2015 +//! \brief F&C Co. MGR archive format. +// +// 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.Text; +using GameRes.Utility; + +namespace GameRes.Formats.FC01 +{ + internal class MrgEntry : PackedEntry + { + public int Method; + } + + [Export(typeof(ArchiveFormat))] + public class MrgOpener : ArchiveFormat + { + public override string Tag { get { return "MRG"; } } + public override string Description { get { return "F&C Co. engine resource archive"; } } + public override uint Signature { get { return 0x0047524D; } } // 'MRG' + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public static readonly Tuple KnownKey = Tuple.Create ( + // Konata yori Kanata made + new byte[] { 0, 0x68, 0x5F }, new byte[] { 0, 0x37 } + ); + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (12); + if (!IsSaneCount (count)) + return null; + int key1index = file.View.ReadUInt16 (4); + int key2index = file.View.ReadUInt16 (6); + if (key2index != 0 && key1index == 0) + return null; + uint index_size = file.View.ReadUInt32 (8) - 0x10; + if (0 == index_size || index_size >= file.MaxOffset) + return null; + var index = new byte[index_size]; + if (index.Length != file.View.Read (0x10, index, 0, index_size)) + return null; + var key_src = KnownKey; + if (key1index >= key_src.Item1.Length || key2index >= key_src.Item2.Length) + return null; + byte index_key = (byte)(key_src.Item1[key1index] + key_src.Item2[key2index]); + int remaining = index.Length; + for (int i = 0; i < index.Length; ++i) + { + byte v = index[i]; + v = (byte)(v << 1 | v >> 7); + index[i] = (byte)(v ^ index_key); + index_key += (byte)remaining--; + } + int current_offset = 0; + uint next_offset = LittleEndian.ToUInt32 (index, current_offset+0x1C); + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + string name = Binary.GetCString (index, current_offset, 0x0E); + var entry = new MrgEntry + { + Name = name, + Type = FormatCatalog.Instance.GetTypeFromName (name), + Offset = next_offset, + Method = index[current_offset+0x12], + }; + next_offset = LittleEndian.ToUInt32 (index, current_offset+0x3C); + entry.Size = next_offset - (uint)entry.Offset; + if (entry.Offset < index_size || !entry.CheckPlacement (file.MaxOffset)) + return null; + entry.IsPacked = entry.Method != 0; + entry.UnpackedSize = LittleEndian.ToUInt32 (index, current_offset+0x0E); + dir.Add (entry); + current_offset += 0x20; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var packed_entry = entry as MrgEntry; + if (null == packed_entry || !packed_entry.IsPacked || packed_entry.Method > 3) + return arc.File.CreateStream (entry.Offset, entry.Size); + Stream input; + if (packed_entry.Method >= 2) + { + if (entry.Size < 0x108) + return arc.File.CreateStream (entry.Offset, entry.Size); + var data = new byte[entry.Size]; + arc.File.View.Read (entry.Offset, data, 0, entry.Size); + var reader = new MrgDecoder (data); + reader.Unpack(); + input = new MemoryStream (reader.Data); + } + else + input = arc.File.CreateStream (entry.Offset, entry.Size); + if (packed_entry.Method < 3) + { + using (input) + using (var reader = new MrgLzssReader (input, (int)input.Length, (int)packed_entry.UnpackedSize)) + { + reader.Unpack(); + return new MemoryStream (reader.Data); + } + } + return input; + } + } + + /// + /// LZSS decompression with slightly modified offset/count values encoding. + /// + internal sealed class MrgLzssReader : IDisposable + { + BinaryReader m_input; + byte[] m_output; + int m_size; + + public byte[] Data { get { return m_output; } } + + public MrgLzssReader (Stream input, int input_length, int output_length) + { + m_input = new BinaryReader (input, Encoding.ASCII, true); + m_output = new byte[output_length]; + m_size = input_length; + } + + public void Unpack () + { + int dst = 0; + var frame = new byte[0x1000]; + int frame_pos = 0xfee; + int frame_mask = 0xfff; + int remaining = m_size; + while (remaining > 0) + { + int ctl = m_input.ReadByte(); + --remaining; + for (int bit = 1; remaining > 0 && bit != 0x100; bit <<= 1) + { + if (dst >= m_output.Length) + return; + if (0 != (ctl & bit)) + { + byte b = m_input.ReadByte(); + --remaining; + frame[frame_pos++] = b; + frame_pos &= frame_mask; + m_output[dst++] = b; + } + else + { + if (remaining < 2) + return; + int offset = m_input.ReadUInt16(); + remaining -= 2; + int count = (offset >> 12) + 3; + for ( ; count != 0; --count) + { + if (dst >= m_output.Length) + break; + offset &= frame_mask; + byte v = frame[offset++]; + frame[frame_pos++] = v; + frame_pos &= frame_mask; + m_output[dst++] = v; + } + } + } + } + } + + #region IDisposable Members + bool disposed = false; + + public void Dispose () + { + if (!disposed) + { + m_input.Dispose(); + disposed = true; + } + GC.SuppressFinalize (this); + } + #endregion + } + + internal class MrgDecoder + { + byte[] m_input; + byte[] m_output; + int m_start_index; + int m_src; + + public byte[] Data { get { return m_output; } } + public byte Key { get; set; } + + public MrgDecoder (byte[] data, int index = 0) + { + m_input = data; + m_src = index; + uint unpacked_size = LittleEndian.ToUInt32 (data, m_src); + unpacked_size ^= LittleEndian.ToUInt32 (data, m_src+0x104); + m_src += 4; + m_start_index = m_src; + m_output = new byte[unpacked_size]; + } + + public MrgDecoder (byte[] data, int index, uint unpacked_size) + { + m_input = data; + m_start_index = index; + m_src = index; + m_output = new byte[unpacked_size]; + } + + public void ResetKey (byte key) + { + m_src = m_start_index; + Key = key; + } + + ushort[] word_10036650 = new ushort[0x200]; + byte[] byte_table = new byte[0xff00]; + + public int Unpack () // sub_10026EB0 + { + uint quant = InitTable(); + if (0 == quant) + throw new InvalidFormatException(); + uint mask = GetMask (quant); + uint scale = 0x10000 / quant; + uint b = 0; + uint c = 0xffffffff; + int dst = 0; + uint a = BigEndian.ToUInt32 (m_input, m_src); + m_src += 4; + while (dst < m_output.Length) + { + c = ((c >> 8) * scale) >> 8; + uint v = (a - b) / c; + if (v > quant) + throw new InvalidFormatException(); + v = byte_table[v]; + m_output[dst++] = (byte)v; + b += word_10036650[v*2] * c; + c *= word_10036650[v*2+1]; + while (0 == (((c + b) ^ b) & 0xFF000000)) + { + if (m_src >= m_input.Length) + return dst; + a <<= 8; + b <<= 8; + c <<= 8; + a |= m_input[m_src++]; + } + while (c <= mask) + { + if (m_src >= m_input.Length) + return dst; + c = (~b & mask) << 8; + a <<= 8; + b <<= 8; + a |= m_input[m_src++]; + } + } + return dst; + } + + ushort InitTable () // sub_10026E30 + { + ushort d = 0; + int t = 0; + byte key = Key; + for (int i = 0; i < 0x100; i++) + { + byte c = m_input[m_src++]; + if (0 != Key) + { + c = (byte)(((c << 1) | (c >> 7)) ^ key); + key -= (byte)i; + } + word_10036650[i*2] = d; + word_10036650[i*2+1] = c; + d += c; + for (int j = 0; j < c; ++j) + byte_table[t++] = (byte)i; + } + return d; + } + + uint GetMask (uint d) // sub_10026DC0 + { + d--; + d >>= 8; + uint result = 0xff; + while (d > 0) + { + d >>= 1; + result = (result << 1) | 1; + } + return result; + } + } +} diff --git a/ArcFormats/ImageACD.cs b/ArcFormats/ImageACD.cs new file mode 100644 index 00000000..ade96d95 --- /dev/null +++ b/ArcFormats/ImageACD.cs @@ -0,0 +1,158 @@ +//! \file ImageACD.cs +//! \date Mon Jul 13 16:13:36 2015 +//! \brief F&C Co. image format. +// +// 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.ComponentModel.Composition; +using System.IO; +using System.Windows.Media; +using GameRes.Utility; + +namespace GameRes.Formats.FC01 +{ + internal class AcdMetaData : ImageMetaData + { + public int DataOffset; + public int PackedSize; + public int UnpackedSize; + } + + [Export(typeof(ImageFormat))] + public class AcdFormat : ImageFormat + { + public override string Tag { get { return "ACD"; } } + public override string Description { get { return "F&C Co. image format"; } } + public override uint Signature { get { return 0x20444341; } } // 'ACD' + + public override ImageMetaData ReadMetaData (Stream stream) + { + var header = new byte[0x1c]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + int header_size = LittleEndian.ToInt32 (header, 8); + if (!Binary.AsciiEqual (header, 4, "1.00") || header_size < 0x1c) + throw new NotSupportedException ("Not supported ACD image version"); + int packed_size = LittleEndian.ToInt32 (header, 0x0C); + int unpacked_size = LittleEndian.ToInt32 (header, 0x10); + return new AcdMetaData + { + Width = LittleEndian.ToUInt32 (header, 0x14), + Height = LittleEndian.ToUInt32 (header, 0x18), + BPP = 24, + DataOffset = header_size, + PackedSize = packed_size, + UnpackedSize = unpacked_size, + }; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = info as AcdMetaData; + if (null == meta) + throw new ArgumentException ("AcdFormat.Read should be supplied with AcdMetaData", "info"); + + stream.Position = meta.DataOffset; + using (var reader = new MrgLzssReader (stream, meta.PackedSize, meta.UnpackedSize)) + { + reader.Unpack(); + var decoder = new AcdDecoder (reader.Data, meta); + decoder.Unpack(); + return ImageData.Create (info, PixelFormats.Gray8, null, decoder.Data); + } + throw new InvalidFormatException(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("AcdFormat.Write not implemented"); + } + } + + internal class AcdDecoder + { + byte[] m_input; + byte[] m_output; + + public byte[] Data { get { return m_output; } } + + public AcdDecoder (byte[] input, AcdMetaData info) + { + m_input = input; + m_output = new byte[info.Width*info.Height]; + } + + int m_src; + int m_bits; + + public byte[] Unpack () + { + m_src = 0; // @@SB + m_bits = 0; + for (int dst = 0; dst < m_output.Length; dst++) + { + int pixel = 0; + if (0 != GetBit()) + { + --pixel; + if (0 == GetBit()) + { + pixel += 3; + int bit; + do + { + bit = GetBit(); + pixel += pixel + bit; + bit = (pixel >> 8) & 1; + pixel &= 0xff; + } + while (0 == bit); + if (0 != pixel) + { + ++pixel; + pixel *= 0x28CCCCD; + pixel = (int)((uint)pixel >> 24); + } + } + } + m_output[dst] = (byte)pixel; + } + return m_output; + } + + int GetBit () + { + int bit = m_bits >> 7; + m_bits = (m_bits << 1) & 0xff; + if (0 == m_bits) + { + if (m_src >= m_input.Length) + throw new InvalidFormatException(); + m_bits = m_input[m_src++]; + bit = m_bits >> 7; + m_bits = (m_bits << 1) & 0xff | 1; + } + return bit; + } + } +} diff --git a/ArcFormats/ImageMCG.cs b/ArcFormats/ImageMCG.cs new file mode 100644 index 00000000..3feb3c50 --- /dev/null +++ b/ArcFormats/ImageMCG.cs @@ -0,0 +1,195 @@ +//! \file ImageMCG.cs +//! \date Mon Jul 13 17:58:33 2015 +//! \brief F&C Co. image format. +// +// 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.Diagnostics; +using System.IO; +using System.Windows.Media; +using GameRes.Utility; + +namespace GameRes.Formats.FC01 +{ + internal class McgMetaData : ImageMetaData + { + public int DataOffset; + public int PackedSize; + } + + [Export(typeof(ImageFormat))] + public class McgFormat : ImageFormat + { + public override string Tag { get { return "MCG"; } } + public override string Description { get { return "F&C Co. image format"; } } + public override uint Signature { get { return 0x2047434D; } } // 'MCG' + + public override ImageMetaData ReadMetaData (Stream stream) + { + byte[] header = new byte[0x40]; + if (header.Length != stream.Read (header, 0, header.Length)) + return null; + if (!Binary.AsciiEqual (header, 4, "2.00")) + throw new NotSupportedException ("Not supported MCG format version"); + int header_size = LittleEndian.ToInt32 (header, 0x10); + if (header_size < 0x40) + return null; + int bpp = LittleEndian.ToInt32 (header, 0x24); + if (24 != bpp) + throw new NotSupportedException ("Not supported MCG image bitdepth"); + return new McgMetaData + { + Width = LittleEndian.ToUInt32 (header, 0x1c), + Height = LittleEndian.ToUInt32 (header, 0x20), + OffsetX = LittleEndian.ToInt32 (header, 0x14), + OffsetY = LittleEndian.ToInt32 (header, 0x18), + BPP = bpp, + DataOffset = header_size, + PackedSize = LittleEndian.ToInt32 (header, 0x38) - header_size, + }; + } + + public override ImageData Read (Stream stream, ImageMetaData info) + { + var meta = info as McgMetaData; + if (null == meta) + throw new ArgumentException ("McgFormat.Read should be supplied with McgMetaData", "info"); + + var reader = new McgDecoder (stream, meta); + reader.Unpack(); + return ImageData.Create (info, PixelFormats.Bgr24, null, reader.Data); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("McgFormat.Write not implemented"); + } + + public static readonly IReadOnlyDictionary KnownKeys = new Dictionary() + { + { "Konata yori Kanata made", 0xD5 }, + }; + } + + // mcg decompression // graphic.unt @ 100047B0 + + internal class McgDecoder + { + byte[] m_input; + byte[] m_output; + uint m_width; + uint m_height; + uint m_pixels; + + public byte[] Data { get { return m_output; } } + + public McgDecoder (Stream input, McgMetaData info) + { + input.Position = info.DataOffset; + m_input = new byte[info.PackedSize]; + if (m_input.Length != input.Read (m_input, 0, m_input.Length)) + throw new InvalidFormatException ("Unexpected end of file"); + m_width = info.Width; + m_height = info.Height; + m_pixels = m_width*m_height; + m_output = new byte[m_pixels*3]; + } + + static readonly byte[] ChannelOrder = { 1, 0, 2 }; + + public void Unpack () + { + var reader = new MrgDecoder (m_input, 0, m_pixels); + for (int key = 0; key < 0x100; ++key) + { + reader.ResetKey ((byte)key); + try + { + for (int i = 0; i < 3; ++i) + { + reader.Unpack(); + var plane = reader.Data; + int src = 0; + for (int j = ChannelOrder[i]; j < m_output.Length; j += 3) + { + m_output[j] = plane[src++]; + } + } +// Trace.WriteLine (string.Format ("Found matching key {0:X2}", key), "[MCG]"); + } + catch (InvalidFormatException) + { + continue; + } + Transform(); + return; + } + throw new UnknownEncryptionScheme(); + } + + void Transform () + { + // esi = m_input + // dst = m_output + uint dst = 0; + uint stride = (m_width - 1) * 3; + for (uint y = m_height-1; y > 0; --y) // @@1a + { + for (uint x = stride; x > 0; --x) // @@1b + { + int p0 = m_output[dst]; + int py = m_output[dst+stride+3] - p0; + int px = m_output[dst+3] - p0; + p0 = Math.Abs (px + py); + py = Math.Abs (py); + px = Math.Abs (px); + byte pv; + if (p0 >= px && py >= px) + pv = m_output[dst+stride+3]; + else if (p0 < py) + pv = m_output[dst]; + else + pv = m_output[dst+3]; + + m_output[dst+stride+6] += (byte)(pv + 0x80); + ++dst; + } + dst += 3; + } + dst = 0; + for (uint i = 0; i < m_pixels; ++i) + { + sbyte b = -128; + sbyte r = -128; + b += (sbyte)m_output[dst]; + r += (sbyte)m_output[dst+2]; + int g = m_output[dst+1] - ((b + r) >> 2); + m_output[dst++] = (byte)(b + g); + m_output[dst++] = (byte)g; + m_output[dst++] = (byte)(r + g); + } + } + } +} diff --git a/ArcFormats/Properties/AssemblyInfo.cs b/ArcFormats/Properties/AssemblyInfo.cs index b9cb3147..fc62dbc4 100644 --- a/ArcFormats/Properties/AssemblyInfo.cs +++ b/ArcFormats/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.7.78")] -[assembly: AssemblyFileVersion ("1.0.7.78")] +[assembly: AssemblyVersion ("1.0.7.79")] +[assembly: AssemblyFileVersion ("1.0.7.79")] diff --git a/supported.html b/supported.html index 26e665cd..9fe50e8d 100644 --- a/supported.html +++ b/supported.html @@ -245,7 +245,11 @@ Jokei Kazoku ~Inbou~
*.gccG24n
G24m
R24n
R24mNo *.arc-NoTumugiKimi no Omoi, Sono Negai *.iksNPSRNoX[iks]Shikkan ~Hazukashimerareta Karada, Oreta Kokoro~ -*.wbpARCFORM3 WBUGNoWild BugYuukyou Gangu 2 +*.wbpARCFORM3 WBUGNoWild BugYuukyou Gangu 2 +*.wbmWPXNo +*.mrgMRGNoF&CKonata yori Kanata made +*.mcgMCG 2.00No +*.acdACD 1.00No

[1] Non-encrypted only