diff --git a/ArcFormats/ImagePSD.cs b/ArcFormats/Adobe/ImagePSD.cs similarity index 100% rename from ArcFormats/ImagePSD.cs rename to ArcFormats/Adobe/ImagePSD.cs diff --git a/ArcFormats/Ankh/ArcGRP.cs b/ArcFormats/Ankh/ArcGRP.cs index b4bb0ed2..4dc09bed 100644 --- a/ArcFormats/Ankh/ArcGRP.cs +++ b/ArcFormats/Ankh/ArcGRP.cs @@ -235,76 +235,85 @@ namespace GameRes.Formats.Ankh Stream OpenTpw (ArcFile arc, PackedEntry entry) { - var output = new byte[entry.UnpackedSize]; using (var input = arc.File.CreateStream (entry.Offset, entry.Size)) { - input.Position = 8; - var offsets = new int[4]; - offsets[0] = input.ReadUInt16(); - offsets[1] = offsets[0] * 2; - offsets[2] = offsets[0] * 3; - offsets[3] = offsets[0] * 4; - int dst = 0; - while (dst < output.Length) + var output = new byte[entry.UnpackedSize]; + UnpackTpw (input, output); + return new BinMemoryStream (output, entry.Name); + } + } + + internal static void UnpackTpw (IBinaryStream input, byte[] output) + { + input.Position = 8; + var offsets = new int[4]; + offsets[0] = input.ReadUInt16(); + offsets[1] = offsets[0] * 2; + offsets[2] = offsets[0] * 3; + offsets[3] = offsets[0] * 4; + int dst = 0; + while (dst < output.Length) + { + byte ctl = input.ReadUInt8(); + if (0 == ctl) + break; + int remaining = output.Length - dst; + int count; + if (ctl < 0x40) { - byte ctl = input.ReadUInt8(); - if (0 == ctl) - break; - int count; - if (ctl < 0x40) - { - count = Math.Min (ctl, output.Length - dst); - input.Read (output, dst, count); - dst += count; - } - else if (ctl <= 0x6F) - { - if (0x6F == ctl) - count = input.ReadUInt16(); - else - count = ctl - 0x3D; - byte v = input.ReadUInt8(); - while (count --> 0) - output[dst++] = v; - } - else if (ctl <= 0x9F) - { - if (ctl == 0x9F) - count = input.ReadUInt16(); - else - count = ctl - 0x6E; - byte v1 = input.ReadUInt8(); - byte v2 = input.ReadUInt8(); - while (count --> 0) - { - output[dst++] = v1; - output[dst++] = v2; - } - } - else if (ctl <= 0xBF) - { - if (ctl == 0xBF) - count = input.ReadUInt16(); - else - count = ctl - 0x9E; - input.Read (output, dst, 3); - if (count > 0) - { - count *= 3; - Binary.CopyOverlapped (output, dst, dst+3, count-3); - dst += count; - } - } + count = Math.Min (ctl, remaining); + input.Read (output, dst, count); + dst += count; + } + else if (ctl <= 0x6F) + { + if (0x6F == ctl) + count = input.ReadUInt16(); else + count = (byte)(ctl - 0x3D); + count = Math.Min (count, remaining); + byte v = input.ReadUInt8(); + while (count --> 0) + output[dst++] = v; + } + else if (ctl <= 0x9F) + { + if (ctl == 0x9F) + count = input.ReadUInt16(); + else + count = (byte)(ctl - 0x6E); + dst += input.Read (output, dst, Math.Min (2, remaining)); + --count; + if (count > 0 && remaining > 2) { - count = (ctl & 0x3F) + 3; - int offset = input.ReadUInt8(); - offset = (offset & 0x3F) - offsets[offset >> 6]; - Binary.CopyOverlapped (output, dst+offset, dst, count); + count = Math.Min (count * 2, remaining - 2); + Binary.CopyOverlapped (output, dst-2, dst, count); dst += count; } } - return new BinMemoryStream (output, entry.Name); + else if (ctl <= 0xBF) + { + if (ctl == 0xBF) + count = input.ReadUInt16(); + else + count = (byte)(ctl - 0x9E); + dst += input.Read (output, dst, Math.Min (3, remaining)); + --count; + if (count > 0 && remaining > 3) + { + count = Math.Min (count * 3, remaining - 3); + Binary.CopyOverlapped (output, dst-3, dst, count); + dst += count; + } + } + else + { + count = Math.Min ((ctl & 0x3F) + 3, remaining); + int offset = input.ReadUInt8(); + offset = (offset & 0x3F) - offsets[offset >> 6]; + Binary.CopyOverlapped (output, dst+offset, dst, count); + dst += count; + } } } } diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index a0618316..3424057f 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -123,6 +123,11 @@ + + + + + @@ -154,13 +159,19 @@ + + + + + + - + @@ -624,7 +635,7 @@ - + @@ -768,7 +779,7 @@ - + @@ -849,6 +860,10 @@ + + + + Code @@ -986,7 +1001,7 @@ - + @@ -1059,11 +1074,11 @@ - - - - - + + + + + diff --git a/ArcFormats/ExeFile.cs b/ArcFormats/ExeFile.cs index 2e44ef4c..8c73f48f 100644 --- a/ArcFormats/ExeFile.cs +++ b/ArcFormats/ExeFile.cs @@ -26,6 +26,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Runtime.InteropServices; using System.Text; using GameRes.Utility; @@ -302,33 +303,99 @@ namespace GameRes.Formats public ResourceAccessor (string filename) { + const uint LOAD_LIBRARY_AS_DATAFILE = 0x02; const uint LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x20; - m_exe = NativeMethods.LoadLibraryEx (filename, IntPtr.Zero, LOAD_LIBRARY_AS_IMAGE_RESOURCE); + m_exe = NativeMethods.LoadLibraryEx (filename, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE|LOAD_LIBRARY_AS_IMAGE_RESOURCE); if (IntPtr.Zero == m_exe) throw new Win32Exception (Marshal.GetLastWin32Error()); } public byte[] GetResource (string name, string type) { - if (m_disposed) - throw new ObjectDisposedException ("Access to disposed ResourceAccessor object failed."); - var res = NativeMethods.FindResource (m_exe, name, type); + var res = FindResource (name, type); if (IntPtr.Zero == res) return null; - var glob = NativeMethods.LoadResource (m_exe, res); - if (IntPtr.Zero == glob) - return null; - uint size = NativeMethods.SizeofResource (m_exe, res); - var src = NativeMethods.LockResource (glob); + var src = LockResource (res); if (IntPtr.Zero == src) return null; - + uint size = NativeMethods.SizeofResource (m_exe, res); var dst = new byte[size]; Marshal.Copy (src, dst, 0, dst.Length); return dst; } + public int ReadResource (string name, string type, byte[] dest, int pos) + { + var res = FindResource (name, type); + if (IntPtr.Zero == res) + return 0; + var src = LockResource (res); + if (IntPtr.Zero == src) + return 0; + int length = (int)NativeMethods.SizeofResource (m_exe, res); + length = Math.Min (dest.Length - pos, length); + Marshal.Copy (src, dest, pos, length); + return length; + } + + public uint GetResourceSize (string name, string type) + { + var res = FindResource (name, type); + if (IntPtr.Zero == res) + return 0; + return NativeMethods.SizeofResource (m_exe, res); + } + + private IntPtr FindResource (string name, string type) + { + if (m_disposed) + throw new ObjectDisposedException ("Access to disposed ResourceAccessor object failed."); + return NativeMethods.FindResource (m_exe, name, type); + } + + private IntPtr LockResource (IntPtr res) + { + var glob = NativeMethods.LoadResource (m_exe, res); + if (IntPtr.Zero == glob) + return IntPtr.Zero; + return NativeMethods.LockResource (glob); + } + + public IEnumerable EnumTypes () + { + var types = new List(); + if (!NativeMethods.EnumResourceTypes (m_exe, (m, t, p) => AddResourceName (types, t), IntPtr.Zero)) + return Enumerable.Empty(); + return types; + } + + public IEnumerable EnumNames (string type) + { + var names = new List(); + if (!NativeMethods.EnumResourceNames (m_exe, type, (m, t, n, p) => AddResourceName (names, n), IntPtr.Zero)) + return Enumerable.Empty(); + return names; + } + + private static bool AddResourceName (List list, IntPtr name) + { + list.Add (ResourceNameToString (name)); + return true; + } + + private static string ResourceNameToString (IntPtr resName) + { + if ((resName.ToInt64() >> 16) == 0) + { + return "#" + resName.ToString(); + } + else + { + return Marshal.PtrToStringUni (resName); + } + } + #region IDisposable implementation bool m_disposed = false; public void Dispose () @@ -363,7 +430,7 @@ namespace GameRes.Formats [return: MarshalAs(UnmanagedType.Bool)] static internal extern bool FreeLibrary (IntPtr hModule); - [DllImport( "kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] static internal extern IntPtr FindResource (IntPtr hModule, string lpName, string lpType); [DllImport("Kernel32.dll", SetLastError = true)] @@ -374,5 +441,22 @@ namespace GameRes.Formats [DllImport("kernel32.dll")] static internal extern IntPtr LockResource (IntPtr hResData); + + internal delegate bool EnumResTypeProc (IntPtr hModule, IntPtr lpszType, IntPtr lParam); + internal delegate bool EnumResNameProc (IntPtr hModule, IntPtr lpszType, IntPtr lpszName, IntPtr lParam); + internal delegate bool EnumResLangProc (IntPtr hModule, IntPtr lpszType, IntPtr lpszName, ushort wIDLanguage, IntPtr lParam); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + static internal extern bool EnumResourceTypes (IntPtr hModule, [MarshalAs(UnmanagedType.FunctionPtr)] EnumResTypeProc lpEnumFunc, IntPtr lParam); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + static internal extern bool EnumResourceNames (IntPtr hModule, IntPtr lpszType, EnumResNameProc lpEnumFunc, IntPtr lParam); + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + static internal extern bool EnumResourceNames (IntPtr hModule, string lpszType, EnumResNameProc lpEnumFunc, IntPtr lParam); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + static internal extern bool EnumResourceLanguages (IntPtr hModule, IntPtr lpszType, string lpName, EnumResLangProc lpEnumFunc, IntPtr lParam); + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + static internal extern bool EnumResourceLanguages (IntPtr hModule, string lpszType, string lpName, EnumResLangProc lpEnumFunc, IntPtr lParam); } } diff --git a/ArcFormats/FamilyAdvSystem/ArcCSAF.cs b/ArcFormats/FamilyAdvSystem/ArcCSAF.cs index fefd232a..1af19564 100644 --- a/ArcFormats/FamilyAdvSystem/ArcCSAF.cs +++ b/ArcFormats/FamilyAdvSystem/ArcCSAF.cs @@ -2,7 +2,7 @@ //! \date 2019 Jan 01 //! \brief Family Adv System resource archive. // -// Copyright (C) 2019 by morkt +// Copyright (C) 2019-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 @@ -34,6 +34,11 @@ using GameRes.Utility; namespace GameRes.Formats.FamilyAdvSystem { + [Serializable] + public class FamilyAdvScheme : ResourceScheme + { + public IDictionary KnownKeys; + } [Export(typeof(ArchiveFormat))] public class CsafOpener : ArchiveFormat @@ -70,8 +75,9 @@ namespace GameRes.Formats.FamilyAdvSystem { if (is_encrypted) { + var key = QueryEncryptionKey (file); file.View.Read (0x20, index, 0, index_size); - enc = new CsafEncryption (DefaultKey, DefaultIV); + enc = new CsafEncryption (key, DefaultIV); using (var decryptor = enc.CreateDecryptor (0)) using (var enc_names = file.CreateStream (0x20 + index_size, names_size)) using (var dec_names = new InputCryptoStream (enc_names, decryptor)) @@ -136,6 +142,27 @@ namespace GameRes.Formats.FamilyAdvSystem var input = new CsafStream (carc); return new StreamRegion (input, entry.Offset, entry.Size); } + + internal string QueryEncryptionKey (ArcView file) + { + var title = FormatCatalog.Instance.LookupGame (file.Name); + if (string.IsNullOrEmpty (title)) + return DefaultKey; + string key; + if (!KnownKeys.TryGetValue (title, out key)) + return DefaultKey; + return key; + } + + FamilyAdvScheme DefaultScheme = new FamilyAdvScheme { KnownKeys = new Dictionary() }; + + internal IDictionary KnownKeys { get { return DefaultScheme.KnownKeys; } } + + public override ResourceScheme Scheme + { + get { return DefaultScheme; } + set { DefaultScheme = (FamilyAdvScheme)value; } + } } internal class CsafEncryption : IDisposable diff --git a/ArcFormats/GScripter/ArcDATA.cs b/ArcFormats/GScripter/ArcDATA.cs new file mode 100644 index 00000000..cf5065c6 --- /dev/null +++ b/ArcFormats/GScripter/ArcDATA.cs @@ -0,0 +1,80 @@ +//! \file ArcDATA.cs +//! \date 2023 Sep 06 +//! \brief GScripter 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.GScripter +{ + [Export(typeof(ArchiveFormat))] + public class DataOpener : ArchiveFormat + { + public override string Tag { get => "DAT/GScripter"; } + public override string Description { get => "GScripter engine resource archive"; } + public override uint Signature { get => 0; } + public override bool IsHierarchic { get => false; } + public override bool CanWrite { get => false; } + + public DataOpener () + { + Extensions = new[] { "" }; + } + + public override ArcFile TryOpen (ArcView file) + { + var info_name = file.Name + ".info"; + if (!VFS.FileExists (info_name)) + return null; + using (var index = VFS.OpenView (info_name)) + { + int count = (int)index.MaxOffset / 0x28; + if (!IsSaneCount (count) || count * 0x28 != index.MaxOffset) + return null; + var arc_name = Path.GetFileName (file.Name); + bool is_cg = arc_name.StartsWith ("CG"); + bool is_sound = !is_cg && arc_name.StartsWith ("SOUND"); + uint index_pos = 0; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = index.View.ReadString (index_pos, 0x20); + var entry = new Entry { + Name = name, + Type = is_cg ? "image" : is_sound ? "audio" : "", + Offset = index.View.ReadUInt32 (index_pos+0x20), + Size = index.View.ReadUInt32 (index_pos+0x24), + }; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_pos += 0x28; + } + return new ArcFile (file, this, dir); + } + } + } +} diff --git a/ArcFormats/Ice/ImageIBM.cs b/ArcFormats/Ice/ImageIBM.cs new file mode 100644 index 00000000..efd7e976 --- /dev/null +++ b/ArcFormats/Ice/ImageIBM.cs @@ -0,0 +1,88 @@ +//! \file ImageIBM.cs +//! \date 2023 Aug 25 +//! \brief Ice Soft compressed bitmap 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.Imaging; +using GameRes.Formats.Ankh; + +// [000324][Juice] Orgel ~Kesenai Melody~ + +namespace GameRes.Formats.Ice +{ + internal class IbmMetaData : ImageMetaData + { + public int UnpackedSize; + } + + [Export(typeof(ImageFormat))] + public class IbmFormat : ImageFormat + { + public override string Tag { get => "IBM/ICE"; } + public override string Description { get => "Ice Soft compressed bitmap"; } + public override uint Signature { get => 0x01575054; } // 'TPW' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + file.Position = 4; + int unpacked_size = file.ReadInt32(); + if (unpacked_size <= 0) + return null; + var bmp_header = new byte[56]; + GrpOpener.UnpackTpw (file, bmp_header); + using (var bmp = new BinMemoryStream (bmp_header, file.Name)) + { + var bmp_info = Bmp.ReadMetaData (bmp); + if (null == bmp_info) + return null; + return new IbmMetaData { + Width = bmp_info.Width, + Height = bmp_info.Height, + BPP = bmp_info.BPP, + UnpackedSize = unpacked_size, + }; + } + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var meta = (IbmMetaData)info; + var output = new byte[meta.UnpackedSize]; + GrpOpener.UnpackTpw (file, output); + using (var bmp = new BinMemoryStream (output, file.Name)) + { + var decoder = new BmpBitmapDecoder (bmp, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + var frame = decoder.Frames[0]; + frame.Freeze(); + return new ImageData (frame, info); + } + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("IbmFormat.Write not implemented"); + } + } +} diff --git a/ArcFormats/Ice/ScriptISD.cs b/ArcFormats/Ice/ScriptISD.cs new file mode 100644 index 00000000..49eaf1f0 --- /dev/null +++ b/ArcFormats/Ice/ScriptISD.cs @@ -0,0 +1,58 @@ +//! \file ScriptISD.cs +//! \date 2023 Aug 25 +//! \brief Ice Soft compressed 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 GameRes.Formats.Ankh; +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes.Formats.Ice +{ + [Export(typeof(ScriptFormat))] + public class IsdScript : GenericScriptFormat + { + public override string Tag { get => "ISD"; } + public override string Description { get => "Ice Soft binary script"; } + public override uint Signature { get => 0x01575054; } // 'TPW' + + public override bool IsScript (IBinaryStream file) + { + return file.Signature == Signature; + } + + public override Stream ConvertFrom (IBinaryStream file) + { + file.Position = 4; + int unpacked_size = file.ReadInt32(); + var data = new byte[unpacked_size]; + GrpOpener.UnpackTpw (file, data); + return new BinMemoryStream (data, file.Name); + } + + public override Stream ConvertBack (IBinaryStream file) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/ArcFormats/ImageMB.cs b/ArcFormats/ImageMB.cs index b4539992..39925bdc 100644 --- a/ArcFormats/ImageMB.cs +++ b/ArcFormats/ImageMB.cs @@ -36,12 +36,26 @@ namespace GameRes.Formats public override uint Signature { get { return 0; } } public override bool CanWrite { get { return true; } } + public MbImageFormat () + { + Extensions = new[] { "bmp", "gra" }; + } + public override ImageMetaData ReadMetaData (IBinaryStream stream) { int c1 = stream.ReadByte(); int c2 = stream.ReadByte(); - if ('M' != c1 || ('B' != c2 && 'C' != c2)) - return null; + switch (c1) + { + case 'M': + if ('B' != c2 && 'C' != c2) + return null; + break; + case 'C': + if ('L' != c2) + return null; + break; + } using (var bmp = OpenAsBitmap (stream)) return Bmp.ReadMetaData (bmp); } diff --git a/ArcFormats/Interheart/ImageCandy.cs b/ArcFormats/Interheart/ImageCandy.cs index 6cab7fbe..b4e10d60 100644 --- a/ArcFormats/Interheart/ImageCandy.cs +++ b/ArcFormats/Interheart/ImageCandy.cs @@ -48,7 +48,7 @@ namespace GameRes.Formats.Interheart public CandyFormat () { - Signatures = new uint[] { 0x0E00, 0 }; + Signatures = new uint[] { 0x0E00, 0x80020A00, 0 }; } public override ImageMetaData ReadMetaData (IBinaryStream file) @@ -82,7 +82,7 @@ namespace GameRes.Formats.Interheart } else return null; - if (info.Version != 1 && info.Version != 2 || info.BPP < 8 || info.BPP > 32) + if (info.Version != 1 && info.Version != 2 || info.BPP < 1 || info.BPP > 32) return null; return info; } @@ -91,7 +91,7 @@ namespace GameRes.Formats.Interheart { var reader = new CandyDecoder (file, (CandyMetaData)info); var pixels = reader.Unpack(); - return ImageData.Create (info, reader.Format, reader.Palette, pixels); + return ImageData.Create (info, reader.Format, reader.Palette, pixels, reader.Stride); } public override void Write (Stream file, ImageData image) @@ -113,6 +113,7 @@ namespace GameRes.Formats.Interheart public PixelFormat Format { get; private set; } public BitmapPalette Palette { get; private set; } + public int Stride { get { return m_stride; } } public CandyDecoder (IBinaryStream input, CandyMetaData info) { @@ -133,6 +134,12 @@ namespace GameRes.Formats.Interheart m_stride = m_width * 4; Format = PixelFormats.Bgra32; } + else if (1 == info.BPP) + { + m_pixel_size = 1; + m_stride = (m_width + 7) / 8; + Format = PixelFormats.Indexed1; + } else { m_pixel_size = 1; @@ -148,6 +155,8 @@ namespace GameRes.Formats.Interheart if (m_colors > 0) Palette = ReadPalette(); LzUnpack(); + if (1 == m_info.BPP) + return m_output; return RestorePixels(); } diff --git a/ArcFormats/ArcISA.cs b/ArcFormats/Ism/ArcISA.cs similarity index 90% rename from ArcFormats/ArcISA.cs rename to ArcFormats/Ism/ArcISA.cs index 86f44f7b..40f1d764 100644 --- a/ArcFormats/ArcISA.cs +++ b/ArcFormats/Ism/ArcISA.cs @@ -37,6 +37,11 @@ namespace GameRes.Formats.ISM public override bool IsHierarchic { get { return false; } } public override bool CanWrite { get { return false; } } + public IsaOpener () + { + ContainedFormats = new[] { "ISG", "PNG/ISM", "OGG", }; + } + public override ArcFile TryOpen (ArcView file) { if (!file.View.AsciiEqual (4, "ARCHIVED")) @@ -87,6 +92,13 @@ namespace GameRes.Formats.ISM entry.Size = m_file.View.ReadUInt32 (index_offset+8); if (!entry.CheckPlacement (m_file.MaxOffset)) return null; + if (string.IsNullOrEmpty (entry.Type) && name_length < 0x20) // try to fix truncated extension + { + if (name.EndsWith (".OG")) + entry.Type = "audio"; + else if (name.EndsWith (".PN")) + entry.Type = "image"; + } m_dir.Add (entry); index_offset += record_length; } diff --git a/ArcFormats/ImageISG.cs b/ArcFormats/Ism/ImageISG.cs similarity index 58% rename from ArcFormats/ImageISG.cs rename to ArcFormats/Ism/ImageISG.cs index 1fef6d27..420a9f16 100644 --- a/ArcFormats/ImageISG.cs +++ b/ArcFormats/Ism/ImageISG.cs @@ -41,6 +41,7 @@ namespace GameRes.Formats.ISM public int Colors; public uint Packed; public uint Unpacked; + public int RecursionDepth; } [Export(typeof(ImageFormat))] @@ -78,88 +79,46 @@ namespace GameRes.Formats.ISM public override ImageData Read (IBinaryStream stream, ImageMetaData info) { var meta = (IsgMetaData)info; - if (0x21 != meta.Type && 0x10 != meta.Type) + if (0x21 != meta.Type && 0x10 != meta.Type && 0x34 != meta.Type) throw new InvalidFormatException ("Unsupported ISM image type"); - stream.Position = 0x30; - using (var input = new Reader (stream, meta)) - { - if (0x21 == meta.Type) - input.Unpack21(); - else - input.Unpack10(); - var palette = new BitmapPalette (input.Palette); - return ImageData.CreateFlipped (info, PixelFormats.Indexed8, palette, input.Data, (int)info.Width); - } + var input = new Reader (stream, meta); + var pixels = input.Unpack (this); + var palette = new BitmapPalette (input.Palette); + return ImageData.CreateFlipped (info, PixelFormats.Indexed8, palette, input.Data, info.iWidth); } - internal class Reader : IDisposable + internal class Reader { IBinaryStream m_input; byte[] m_data; - Color[] m_palette; int m_input_size; + IsgMetaData m_info; - public Color[] Palette { get { return m_palette; } } + public const int RecursionLimit = 32; // have seen 18 deep + + public Color[] Palette { get; private set; } public byte[] Data { get { return m_data; } } public Reader (IBinaryStream file, IsgMetaData info) { - int palette_size = (int)info.Colors*4; - var palette_data = new byte[Math.Max (0x400, palette_size)]; - if (palette_size != file.Read (palette_data, 0, palette_size)) - throw new InvalidFormatException(); - m_palette = new Color[0x100]; - for (int i = 0; i < m_palette.Length; ++i) - { - m_palette[i] = Color.FromRgb (palette_data[i*4+2], palette_data[i*4+1], palette_data[i*4]); - } m_input = file; m_input_size = (int)info.Packed; - m_data = new byte[info.Width * info.Height]; + m_info = info; } - public void Unpack21 () + public byte[] Unpack (IsgFormat isg) { - int dst = 0; - var frame = new byte[2048]; - int frame_pos = 2039; - int remaining = m_input_size; - byte ctl = m_input.ReadUInt8(); - --remaining; - int bit = 0x80; - while (remaining > 0) - { - if (0 != (ctl & bit)) - { - byte hi = m_input.ReadUInt8(); - byte lo = m_input.ReadUInt8(); - remaining -= 2; - int offset = (hi & 7) << 8 | lo; - for (int count = (hi >> 3) + 3; count > 0; --count) - { - byte p = frame[offset]; - frame[frame_pos] = p; - m_data[dst++] = p; - offset = (offset + 1) & 0x7ff; - frame_pos = (frame_pos + 1) & 0x7ff; - } - } - else - { - byte p = m_input.ReadUInt8(); - --remaining; - m_data[dst++] = p; - frame[frame_pos] = p; - frame_pos = (frame_pos + 1) & 0x7ff; - } - if (0 == (bit >>= 1)) - { - ctl = m_input.ReadUInt8(); - --remaining; - bit = 0x80; - } - } + m_input.Position = 0x30; + if (0x34 == m_info.Type) + return Unpack34(isg); + Palette = ImageFormat.ReadColorMap (m_input.AsStream); + m_data = new byte[m_info.Width * m_info.Height]; + if (0x21 == m_info.Type) + DecompressLzss (m_input_size, m_data); + else + Unpack10(); + return m_data; } public void Unpack10 () @@ -192,12 +151,112 @@ namespace GameRes.Formats.ISM } } - #region IDisposable Members - public void Dispose () + public byte[] Unpack34 (IsgFormat isg) { - GC.SuppressFinalize (this); + if (m_info.RecursionDepth >= RecursionLimit) + throw new InvalidFormatException ("Recursion limit reached for ISG image."); + var base_name = m_input.ReadCString (0x10); + var base_image = ReadBaseImage (base_name, isg); + if (null == base_image) + throw new InvalidFormatException ("Unable to read baseline ISG image."); + int count = m_input.ReadInt32(); + int packed_size = m_input.ReadInt32(); + int ovl_ctl_size = m_info.iWidth * m_info.iHeight / 128; + var overlay_info = m_input.ReadBytes (ovl_ctl_size); + var overlay_data = new byte[count * 32 + 8]; + DecompressLzss (packed_size, overlay_data); + int bit_count = 0; + int ctl_src = 0; + int data_src = 0; + for (int y = 0; y < m_info.iHeight; y += 4) + for (int x = 0; x < m_info.iWidth; x += 4) + { + if (((1 << bit_count) & overlay_info[ctl_src]) != 0) + { + int dst = y * m_info.iWidth + x; + for (int r = 0; r < 4; ++r) + { + base_image[dst ] = overlay_data[data_src++]; + base_image[dst+1] = overlay_data[data_src++]; + base_image[dst+2] = overlay_data[data_src++]; + base_image[dst+3] = overlay_data[data_src++]; + dst += m_info.iWidth; + } + } + if (++bit_count == 8) + { + bit_count = 0; + ++ctl_src; + } + } + return m_data = base_image; + } + + internal void DecompressLzss (int remaining, byte[] output) + { + int dst = 0; + var frame = new byte[2048]; + int frame_pos = 2039; + byte ctl = m_input.ReadUInt8(); + --remaining; + int bit = 0x80; + while (remaining > 0) + { + if (0 != (ctl & bit)) + { + byte hi = m_input.ReadUInt8(); + byte lo = m_input.ReadUInt8(); + remaining -= 2; + int offset = (hi & 7) << 8 | lo; + for (int count = (hi >> 3) + 3; count > 0; --count) + { + byte p = frame[offset]; + frame[frame_pos] = p; + output[dst++] = p; + offset = (offset + 1) & 0x7ff; + frame_pos = (frame_pos + 1) & 0x7ff; + } + } + else + { + byte p = m_input.ReadUInt8(); + --remaining; + output[dst++] = p; + frame[frame_pos] = p; + frame_pos = (frame_pos + 1) & 0x7ff; + } + if (0 == (bit >>= 1)) + { + ctl = m_input.ReadUInt8(); + --remaining; + bit = 0x80; + } + } + } + + internal byte[] ReadBaseImage (string name, IsgFormat isg) + { + if (!VFS.FileExists (name)) + { + if (name.Length <= 12) + return null; + name = name.Substring (0, 12); + if (!VFS.FileExists (name)) + return null; + } + using (var base_file = VFS.OpenBinaryStream (name)) + { + var base_info = isg.ReadMetaData (base_file) as IsgMetaData; + if (null == base_info || base_info.Width != m_info.Width || base_info.Height != m_info.Height) + throw new InvalidFormatException ("Invalid baseline ISG image."); + base_info.RecursionDepth = m_info.RecursionDepth + 1; + base_info.FileName = name; + var reader = new Reader (base_file, base_info); + var pixels = reader.Unpack (isg); + this.Palette = reader.Palette; + return pixels; + } } - #endregion } } } diff --git a/ArcFormats/Ism/ImagePNG.cs b/ArcFormats/Ism/ImagePNG.cs new file mode 100644 index 00000000..cd2c3fcb --- /dev/null +++ b/ArcFormats/Ism/ImagePNG.cs @@ -0,0 +1,71 @@ +//! \file ImagePNG.cs +//! \date 2023 Sep 02 +//! \brief PNG image with inverted alpha-channel. +// +// 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.Ism +{ + [Export(typeof(ImageFormat))] + [ExportMetadata("Priority", -1)] // deprioritize + public class PngIsmFormat : ImageFormat + { + public override string Tag { get => "PNG/ISM"; } + public override string Description { get => "ISM engine PNG image"; } + public override uint Signature { get => 0x474e5089; } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + // format only applied when extracting from related archive + if (!VFS.IsVirtual || VFS.CurrentArchive.Tag != "ISA") + return null; + return Png.ReadMetaData (file); + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var decoder = new PngBitmapDecoder (file.AsStream, + BitmapCreateOptions.None, BitmapCacheOption.OnLoad); + BitmapSource bitmap = decoder.Frames[0]; + if (bitmap.Format != PixelFormats.Bgra32) + return new ImageData (bitmap, info); + int stride = bitmap.PixelWidth * 4; + var pixels = new byte[stride * bitmap.PixelHeight]; + bitmap.CopyPixels (pixels, stride, 0); + for (int i = 3; i < pixels.Length; i += 4) + { + pixels[i] ^= 0xFF; + } + return ImageData.Create (info, bitmap.Format, bitmap.Palette, pixels); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("PngIsmFormat.Write not implemented"); + } + } +} diff --git a/ArcFormats/Kaguya/ArcPLT.cs b/ArcFormats/Kaguya/ArcPLT.cs index bc2bcdc5..be74a4ee 100644 --- a/ArcFormats/Kaguya/ArcPLT.cs +++ b/ArcFormats/Kaguya/ArcPLT.cs @@ -23,8 +23,11 @@ // IN THE SOFTWARE. // +using System; using System.Collections.Generic; using System.ComponentModel.Composition; +using System.IO; +using System.Linq; using System.Windows.Media; namespace GameRes.Formats.Kaguya @@ -98,4 +101,101 @@ namespace GameRes.Formats.Kaguya return ImageData.CreateFlipped (Info, format, null, pixels, stride); } } + + internal class Pl10Entry : An21Entry + { + public ImageMetaData Info; + } + + [Export(typeof(ArchiveFormat))] + public class Pl10Opener : An21Opener + { + public override string Tag { get => "PL10"; } + public override uint Signature { get => 0x30314C50; } // 'PL10' + + public Pl10Opener () + { + Extensions = new string[] { "plt" }; + } + + public override ArcFile TryOpen (ArcView file) + { + using (var input = file.CreateStream()) + { + var base_info = GetBaseInfo (input); + var dir = GetFramesList (input); + if (null == dir) + return null; + string base_name = Path.GetFileNameWithoutExtension (file.Name); + foreach (Pl10Entry entry in dir) + { + entry.Name = string.Format ("{0}#{1:D2}", base_name, entry.FrameIndex); + entry.Type = "image"; + } + var first = (Pl10Entry)dir[0]; + base_info.BPP = first.Info.BPP; + return new An21Archive (file, this, dir, base_info); + } + } + + internal ImageMetaData GetBaseInfo (IBinaryStream input) + { + input.Position = 6; + return new ImageMetaData + { + OffsetX = input.ReadInt32(), + OffsetY = input.ReadInt32(), + Width = input.ReadUInt32(), + Height = input.ReadUInt32(), + }; + } + + internal List GetFramesList (IBinaryStream file) + { + file.Position = 4; + int count = file.ReadInt16(); + if (!IsSaneCount (count)) + return null; + var dir = new List (count); + long current_offset = 0x16; + file.Position = current_offset; + var frame_info = new ImageMetaData { + OffsetX = file.ReadInt32(), + OffsetY = file.ReadInt32(), + Width = file.ReadUInt32(), + Height = file.ReadUInt32(), + BPP = file.ReadInt32() * 8, + }; + uint depth = (uint)frame_info.BPP / 8; + uint image_size = depth * frame_info.Width * frame_info.Height; + var entry = new Pl10Entry + { + Offset = current_offset + 0x14, + Size = image_size, + FrameIndex = 0, + RleStep = 0, + Info = frame_info, + }; + dir.Add (entry); + for (int i = 1; i < count; ++i) + { + current_offset = entry.Offset + entry.Size; + file.Position = current_offset; + byte rle_step = file.ReadUInt8(); + uint packed_size = file.ReadUInt32(); + entry = new Pl10Entry + { + Offset = current_offset + 5, + Size = packed_size, + UnpackedSize = image_size, + IsPacked = true, + FrameIndex = i, + RleStep = rle_step, + Info = frame_info, + }; + dir.Add (entry); + } + return dir; + } + } } diff --git a/ArcFormats/Kogado/ArcARC.cs b/ArcFormats/Kogado/ArcARC.cs new file mode 100644 index 00000000..cceca363 --- /dev/null +++ b/ArcFormats/Kogado/ArcARC.cs @@ -0,0 +1,308 @@ +//! \file ArcARC.cs +//! \date 2023 Aug 26 +//! \brief Kogado 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.Compression; +using GameRes.Formats.DirectDraw; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Text; +using System.Windows.Media; + +// [070302][G-mode] Keitai Shoujo + +namespace GameRes.Formats.Kogado +{ + [Export(typeof(ArchiveFormat))] + public class ArcOpener : ArchiveFormat + { + public override string Tag { get => "ARC/KOGADO"; } + public override string Description { get => "Kogado engine resource archive"; } + public override uint Signature { get => 0xA8BCADBE; } // 'ARCW' ^ 0xFFFFFFFF + public override bool IsHierarchic { get => true; } + public override bool CanWrite { get => false; } + + public override ArcFile TryOpen (ArcView file) + { + using (var reader = new ArcIndexReader (file)) + { + var dir = reader.ReadIndex(); + return new ArcFile(file, this, dir); + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + Stream input = arc.File.CreateStream (entry.Offset, entry.Size); + input = new XoredStream (input, 0xFF); + var ova = entry as OvaEntry; + if (ova != null) + return new PrefixStream (ova.Header, input); + var pent = entry as PackedEntry; + if (null == pent || !pent.IsPacked) + return input; + return new LimitStream (new LzssStream (input), pent.UnpackedSize); + } + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var dds = entry as DdsEntry; + if (null == dds) + return base.OpenImage (arc, entry); + var input = this.OpenEntry (arc, entry); + return new ImageDecoder (input, dds.Info); + } + } + + internal class OvaEntry : PackedEntry + { + public byte[] Header; + } + + internal class DdsEntry : PackedEntry + { + public DdsInfo Info; + } + + internal class DdsInfo : ImageMetaData + { + public DdsPF Flags; + } + + internal sealed class ArcIndexReader : IDisposable + { + IBinaryStream m_input; + uint m_base_offset; + long m_max_offset; + + public ArcIndexReader (ArcView arc) + { + m_base_offset = arc.View.ReadUInt32 (0xC); + m_max_offset = arc.MaxOffset; + m_input = arc.CreateStream(); + } + + byte[] m_filenames; + byte[] m_index; + List m_dir; + + public List ReadIndex () + { + m_input.Position = 0x10; + m_filenames = ReadChunk(); + m_index = ReadChunk(); + m_dir = new List(); + int section_count = m_index.ToInt32 (0); + int pos = 4; + for (int i = 0; i < section_count; ++i) + { + int count = m_index.ToInt32 (pos+8); + if (m_dir.Capacity < m_dir.Count + count) + m_dir.Capacity = m_dir.Count + count; + int section_size = m_index.ToInt32 (pos+0xC); + int name_pos = pos + 0x10; + int layout_pos = name_pos + 4 * count; + if (m_index.AsciiEqual (pos, "DDS\0")) + ReadDdsSection (name_pos, layout_pos, count); + else if (m_index.AsciiEqual (pos, "OVA\0")) + ReadOvaSection (name_pos, layout_pos, count); + else + ReadSection (name_pos, layout_pos, count); + pos += 0x10 + section_size; + } + return m_dir; + } + + void ReadSection (int name_pos, int layout_pos, int count) + { + for (int j = 0; j < count; ++j) + { + var name = ReadFileName (m_index.ToInt32 (name_pos)); + var entry = FormatCatalog.Instance.Create (name); + + entry.Offset = m_index.ToUInt32 (layout_pos) + m_base_offset; + entry.Size = m_index.ToUInt32 (layout_pos+4); + entry.UnpackedSize = m_index.ToUInt32 (layout_pos+0xC); + entry.IsPacked = true; + if (!entry.CheckPlacement (m_max_offset+0x14)) + throw new InvalidFormatException(); + + m_dir.Add (entry); + name_pos += 4; + layout_pos += 0x10; + } + } + + void ReadDdsSection (int name_pos, int layout_pos, int count) + { + int header_count = m_index.ToInt32 (layout_pos); + var headers = new DdsInfo[header_count]; + layout_pos += 4; + for (int i = 0; i < header_count; ++i) + { + headers[i] = new DdsInfo { + Width = m_index.ToUInt32 (layout_pos+4), + Height = m_index.ToUInt32 (layout_pos+8), + BPP = 32, + Flags = (DdsPF)m_index.ToUInt32 (layout_pos), + }; + layout_pos += 0xC; + } + for (int j = 0; j < count; ++j) + { + var name = ReadFileName (m_index.ToInt32 (name_pos)); + var entry = FormatCatalog.Instance.Create (name); + + entry.Offset = m_index.ToUInt32 (layout_pos) + m_base_offset; + entry.Size = m_index.ToUInt32 (layout_pos+4); + entry.UnpackedSize = m_index.ToUInt32 (layout_pos+0xC); + int header_id = m_index.ToInt32 (layout_pos+0x10); + entry.Info = headers[header_id]; + entry.IsPacked = true; + if (!entry.CheckPlacement (m_max_offset+0x14)) + throw new InvalidFormatException(); + + m_dir.Add (entry); + name_pos += 4; + layout_pos += 0x14; + } + } + + void ReadOvaSection (int name_pos, int layout_pos, int count) + { + int header_count = m_index.ToInt32 (layout_pos+4); + var headers = new byte[header_count][]; + layout_pos += 8; + for (int i = 0; i < header_count; ++i) + { + int header_len = m_index.ToInt32 (layout_pos+8); + int header_pos = layout_pos + 12; + headers[i] = new CowArray (m_index, header_pos, header_len).ToArray(); + layout_pos = header_pos + header_len; + } + for (int j = 0; j < count; ++j) + { + var name = ReadFileName (m_index.ToInt32 (name_pos)); + var entry = FormatCatalog.Instance.Create (name); + + entry.Offset = m_index.ToUInt32 (layout_pos) + m_base_offset; + entry.UnpackedSize = m_index.ToUInt32 (layout_pos+4); + int header_id = m_index.ToInt32 (layout_pos+8); + entry.Header = headers[header_id]; + entry.Size = entry.UnpackedSize - (uint)entry.Header.Length; + entry.IsPacked = true; + if (!entry.CheckPlacement (m_max_offset)) + throw new InvalidFormatException(); + + m_dir.Add (entry); + name_pos += 4; + layout_pos += 0xC; + } + } + + string ReadFileName (int pos) + { + int i; + for (i = pos; i+1 < m_filenames.Length; i += 2) + { + if (m_filenames[i] == 0 && m_filenames[i+1] == 0) + break; + } + return Encoding.Unicode.GetString (m_filenames, pos, i - pos); + } + + byte[] ReadChunk () + { + long start_pos = m_input.Position; + int size = m_input.ReadInt32(); + int type = m_input.ReadInt32(); + int unpacked_size = m_input.ReadInt32(); + if (size <= 0 || unpacked_size <= 0) + throw new InvalidFormatException(); + var data = new byte[unpacked_size]; + using (var decrypted = new XoredStream (m_input.AsStream, 0xFF, true)) + using (var lzss = new LzssStream (decrypted)) + { + lzss.Read (data, 0, data.Length); + } + m_input.Position = start_pos + size; + return data; + } + + #region IDisposable Members + bool _disposed = false; + + public void Dispose () + { + if (!_disposed) + { + m_input.Dispose(); + _disposed = true; + } + GC.SuppressFinalize (this); + } + #endregion + } + + internal sealed class ImageDecoder : IImageDecoder + { + Stream m_input; + ImageData m_image; + + public Stream Source => m_input; + public ImageFormat SourceFormat => null; + public PixelFormat Format { get; private set; } + public ImageMetaData Info { get; private set; } + public ImageData Image => m_image ?? (m_image = GetImageData()); + + public ImageDecoder (Stream input, DdsInfo info) + { + m_input = input; + Info = info; + Format = info.Flags.HasFlag (DdsPF.AlphaPixels) ? PixelFormats.Bgra32 : PixelFormats.Bgr32; + } + + private ImageData GetImageData () + { + var pixels = new byte[Info.iWidth * Info.iHeight * 4]; + m_input.Read (pixels, 0, pixels.Length); + return ImageData.Create (Info, Format, null, pixels); + } + + #region IDisposable members + bool m_disposed = false; + public void Dispose () + { + if (!m_disposed) + { + m_input.Dispose(); + m_disposed = true; + } + GC.SuppressFinalize (this); + } + #endregion + } +} diff --git a/ArcFormats/Macintosh/ImagePICT.cs b/ArcFormats/Macintosh/ImagePICT.cs new file mode 100644 index 00000000..d332afa1 --- /dev/null +++ b/ArcFormats/Macintosh/ImagePICT.cs @@ -0,0 +1,436 @@ +//! \file ImagePICT.cs +//! \date 2023 Aug 24 +//! \brief Macintosh picture 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; + +namespace GameRes.Formats.Apple +{ + internal class PictMetaData : ImageMetaData + { + public uint DataOffset; + } + + [Export(typeof(ImageFormat))] + public class PictFormat : ImageFormat + { + public override string Tag { get => "PICT/MAC"; } + public override string Description { get => "Apple Macintosh image format"; } + public override uint Signature { get => 0; } + + public PictFormat () + { + Signatures = new[] { 0u, 0x54434950u }; + Extensions = new[] { "pct", "pict", "pic" }; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + int header_pos = 0x200; + if (file.Signature == 0x54434950) // 'PICT' + header_pos = 4; + if (file.Length < header_pos + 0x10) + return null; + file.Position = header_pos + 2; + short top = file.ReadI16BE(); + short left = file.ReadI16BE(); + short bottom = file.ReadI16BE(); + short right = file.ReadI16BE(); + if (file.ReadU16BE() != 0x11) + return null; + int version = file.ReadU16BE(); + if (version != 0x2FF) + return null; + return new PictMetaData { + Width = (uint)(right - left), + Height = (uint)(bottom - top), + OffsetX = left, + OffsetY = top, + BPP = 32, + DataOffset = (uint)file.Position, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var decoder = new PictReader (file, (PictMetaData)info); + var pixels = decoder.Unpack(); + return ImageData.Create (info, decoder.Format, decoder.Palette, pixels); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("PictFormat.Write not implemented"); + } + } + + internal static class BinaryStreamExtension + { + static public short ReadI16BE (this IBinaryStream file) + { + return Binary.BigEndian (file.ReadInt16()); + } + + static public ushort ReadU16BE (this IBinaryStream file) + { + return Binary.BigEndian (file.ReadUInt16()); + } + + static public int ReadI32BE (this IBinaryStream file) + { + return Binary.BigEndian (file.ReadInt32()); + } + + static public uint ReadU32BE (this IBinaryStream file) + { + return Binary.BigEndian (file.ReadUInt32()); + } + } + + internal class Pixmap + { + public short Version; + public short PackType; + public int PackSize; + public int HorizRes; + public int VertRes; + public short PixelType; + public short BPP; + public short CompCount; + public short CompSize; + public int PlaneBytes; + public int Table; + + public void Deserialize (IBinaryStream input) + { + Version = input.ReadI16BE(); + PackType = input.ReadI16BE(); + PackSize = input.ReadI32BE(); + HorizRes = input.ReadI32BE() >> 16; // read 2 bytes and skip next 2 + VertRes = input.ReadI32BE() >> 16; + PixelType = input.ReadI16BE(); + BPP = input.ReadI16BE(); + CompCount = input.ReadI16BE(); + CompSize = input.ReadI16BE(); + PlaneBytes = input.ReadI32BE(); + Table = input.ReadI32BE(); + input.Seek (4, SeekOrigin.Current); + if (BPP <= 0 || BPP > 32 || CompCount <= 0 || CompCount > 4 || CompSize <= 0) + throw new InvalidFormatException(); + } + } + + internal class PictReader + { + IBinaryStream m_input; + PictMetaData m_info; + + public PixelFormat Format { get; private set; } + public BitmapPalette Palette { get; private set; } + + public PictReader (IBinaryStream input, PictMetaData info) + { + m_input = input; + m_info = info; + } + + bool HasAlpha = false; + byte[] m_buffer; + + public byte[] Unpack () + { + Color[] colormap = null; + Pixmap pixmap = null; + m_input.Position = m_info.DataOffset; + + while (m_input.PeekByte() != -1) + { + if ((m_input.Position & 1) != 0) + Skip (1); + int code = m_input.ReadU16BE(); + if (0x00FF == code || 0xFFFF == code) // EOF + break; + switch (code) + { + case 0x0000: // NOP + continue; + + case 0x0001: // Clip + { + int length = m_input.ReadU16BE(); + if (length < 2) + throw new InvalidFormatException(); + Skip (length-2); + break; + } + + case 0x0090: + case 0x0091: + case 0x0098: + case 0x0099: + case 0x009A: + case 0x009B: // BitsRect + { + int stride = 0; + if (code != 0x9A && code != 0x9B) + stride = m_input.ReadU16BE(); + else + Skip (6); + // FIXME we just read the first bitmap and override an existing frame + // TODO place bitmap into frame according to its RECT + m_info.OffsetY = m_input.ReadI16BE(); + m_info.OffsetX = m_input.ReadI16BE(); + m_info.Height = (uint)(m_input.ReadI16BE() - m_info.OffsetY); + m_info.Width = (uint)(m_input.ReadI16BE() - m_info.OffsetX); + if (0x9A == code || 0x9B == code || (stride & 0x8000) != 0) + { + pixmap = new Pixmap(); + pixmap.Deserialize (m_input); + HasAlpha = pixmap.CompCount == 4; + } + if (code != 0x9A && code != 0x9B) + { + int colors = 2; + int flags = 0; + if ((stride & 0x8000) != 0) + { + Skip (4); + flags = m_input.ReadU16BE(); + colors = m_input.ReadU16BE() + 1; + } + if (null == colormap) + colormap = new Color[colors]; + if ((stride & 0x8000) != 0) + { + for (int i = 0; i < colors; i++) + { + int c = m_input.ReadU16BE() % colors; + if ((flags & 0x8000) != 0) + c = i; + int r = m_input.ReadU16BE() / 0x101; + int g = m_input.ReadU16BE() / 0x101; + int b = m_input.ReadU16BE() / 0x101; + colormap[c] = Color.FromRgb ((byte)r, (byte)g, (byte)b); + } + } + else + { + var White = Color.FromRgb (0xFF, 0xFF, 0xFF); + for (int i = 0; i < colors; i++) + { + colormap[i] = Color.Subtract (White, colormap[i]); + } + } + } + Skip (8+8+2); + // -> Skip (8); // source RECT + // Skip (8); // destination RECT + // Skip (2); // transfer mode + if (code == 0x91 || code == 0x99 || code == 0x9b) + { + int length = m_input.ReadU16BE(); + if (length > 2) + Skip (length - 2); + } + if (code != 0x9A && code != 0x9B && (stride & 0x8000) == 0) + DecodeRleBitmap (stride, 1); + else + DecodeRleBitmap (stride, pixmap.BPP); + break; + } + + case 0x00A1: // LongComment + { + m_input.ReadU16BE(); // comment type + int length = m_input.ReadU16BE(); + Skip (length); + break; + } + case 0x0C00: // Header + Skip (0x18); + break; + + default: + throw new NotSupportedException (string.Format ("Unknown code 0x{0:X4} in PICT stream.", code)); + } + } + if (colormap != null) + Palette = new BitmapPalette (colormap); + if (null == m_buffer) + throw new InvalidFormatException(); + SetFormat (pixmap); + return RepackPixels (pixmap); + } + + byte[] RepackPixels (Pixmap pixmap) + { + int bpp = m_info.BPP; + if (bpp <= 16) + return m_buffer; + int bytes_per_pixel = bpp / 8; + int stride = m_info.iWidth * bytes_per_pixel; + var pixels = new byte[stride * m_info.iHeight]; + int src = 0; + for (int y = 0; y < m_info.iHeight; ++y) + { + int dst = y * stride; + for (int x = 0; x < m_info.iWidth; ++x) + { + if (HasAlpha) + { + pixels[dst+3] = m_buffer[src]; + pixels[dst+2] = m_buffer[src+m_info.iWidth]; + pixels[dst+1] = m_buffer[src+m_info.iWidth*2]; + pixels[dst] = m_buffer[src+m_info.iWidth*3]; + } + else + { + pixels[dst+2] = m_buffer[src]; + pixels[dst+1] = m_buffer[src+m_info.iWidth]; + pixels[dst] = m_buffer[src+m_info.iWidth*2]; + } + ++src; + dst += bytes_per_pixel; + } + src += (pixmap.CompCount - 1) * m_info.iWidth; + } + return pixels; + } + + void SetFormat (Pixmap pixmap) + { + int bpp = null == pixmap ? 8 : pixmap.BPP; + if (32 == bpp) + { + if (4 == pixmap.CompCount) + Format = PixelFormats.Bgra32; + else + Format = PixelFormats.Bgr32; + } + else if (24 == bpp) + Format = PixelFormats.Bgr24; + else if (16 == bpp) + Format = PixelFormats.Bgr555; + else if (8 == bpp) + { + if (Palette != null) + Format = PixelFormats.Indexed8; + else + Format = PixelFormats.Gray8; + } + else + throw new NotSupportedException (string.Format ("Not supported PICT bitdepth -- {0}bpp", bpp)); + m_info.BPP = bpp; + } + + void Skip (int amount) + { + m_input.Seek (amount, SeekOrigin.Current); + } + + byte[] m_unpack_buffer = new byte[0x800]; + byte[] m_scanline; + + void DecodeRleBitmap (int stride, int bpp) + { + if (bpp < 8) + throw new NotSupportedException(); + if (bpp <= 8) + stride &= 0x7fff; + int width = m_info.iWidth; + int bytes_per_pixel = 1; + if (16 == bpp) + { + bytes_per_pixel = 2; + width *= 2; + } + else if (32 == bpp) + width *= HasAlpha ? 4 : 3; + if (stride == 0) + stride = width; + int stride_32bpp = m_info.iWidth * 4; + + int total_bytes = stride_32bpp * m_info.iHeight; + if (null == m_buffer || m_buffer.Length < total_bytes) + m_buffer = new byte[total_bytes]; + int scanline_length = stride_32bpp * 2; + if (null == m_scanline || m_scanline.Length < scanline_length) + { + m_scanline = new byte[scanline_length]; + } + if (stride < 8) + { + int dst = 0; + int row_size = width * (bpp / 8); + for (int y = 0; y < m_info.iHeight; ++y) + { + m_input.Read (m_buffer, dst, stride); + dst += row_size; + } + return; + } + for (int y = 0; y < m_info.iHeight; ++y) + { + int dst = y * width; + if (stride > 200) + scanline_length = m_input.ReadU16BE(); + else + scanline_length = m_input.ReadUInt8(); + if (scanline_length >= m_scanline.Length || scanline_length == 0) + throw new InvalidFormatException(); + m_input.Read (m_scanline, 0, scanline_length); + for (int j = 0; j < scanline_length; ) + { + if ((m_scanline[j] & 0x80) == 0) + { + int pixel_count = m_scanline[j] + 1; + int count = pixel_count * bytes_per_pixel; + int src = j + 1; + if ((dst + count) <= total_bytes) + Buffer.BlockCopy (m_scanline, src, m_buffer, dst, count); + dst += count; + j += count + 1; + } + else + { + int count = (m_scanline[j] ^ 0xFF) + 2; + int src = j + 1; + while (count --> 0) + { + if ((dst + bytes_per_pixel) <= total_bytes) + Buffer.BlockCopy (m_scanline, src, m_buffer, dst, bytes_per_pixel); + dst += bytes_per_pixel; + } + j += bytes_per_pixel + 1; + } + } + } + } + } +} diff --git a/ArcFormats/Macromedia/ArcDXR.cs b/ArcFormats/Macromedia/ArcDXR.cs index ae043c3d..38e7af2d 100644 --- a/ArcFormats/Macromedia/ArcDXR.cs +++ b/ArcFormats/Macromedia/ArcDXR.cs @@ -51,7 +51,7 @@ namespace GameRes.Formats.Macromedia } internal static readonly HashSet RawChunks = new HashSet { - "RTE0", "RTE1", "FXmp", "VWFI", "VWSC", "Lscr", "STXT", + "RTE0", "RTE1", "FXmp", "VWFI", "VWSC", "Lscr", "STXT", "XMED", "snd " }; internal bool ConvertText = true; @@ -73,9 +73,11 @@ namespace GameRes.Formats.Macromedia ImportMedia (dir_file, dir); foreach (MemoryMapEntry entry in dir_file.MMap.Dir) { - if (RawChunks.Contains (entry.FourCC)) + if (entry.Size != 0 && RawChunks.Contains (entry.FourCC)) { entry.Name = string.Format ("{0:D6}.{1}", entry.Id, entry.FourCC.Trim()); + if ("snd " == entry.FourCC) + entry.Type = "audio"; dir.Add (entry); } } @@ -98,6 +100,8 @@ namespace GameRes.Formats.Macromedia internal Stream OpenSound (ArcFile arc, SoundEntry entry) { + if (null == entry.Header) + return base.OpenEntry (arc, entry); var header = arc.File.View.ReadBytes (entry.Header.Offset, entry.Header.Size); var format = entry.DeserializeHeader (header); var riff = new MemoryStream (0x2C); @@ -147,11 +151,30 @@ namespace GameRes.Formats.Macromedia Entry ImportSound (CastMember sound, DirectorFile dir_file) { - var sndHrec = dir_file.KeyTable.FindByCast (sound.Id, "sndH"); - var sndSrec = dir_file.KeyTable.FindByCast (sound.Id, "sndS"); + var name = sound.Info.Name; + KeyTableEntry sndHrec = null, sndSrec = null; + foreach (var elem in dir_file.KeyTable.Table.Where (e => e.CastId == sound.Id)) + { + if ("ediM" == elem.FourCC) + { + var ediM = dir_file.MMap[elem.Id]; + if (string.IsNullOrEmpty (name)) + name = ediM.Id.ToString ("D6"); + return new Entry + { + Name = name + ".mp3", + Type = "audio", + Offset = ediM.Offset, + Size = ediM.Size, + }; + } + if (null == sndHrec && "sndH" == elem.FourCC) + sndHrec = elem; + else if (null == sndSrec && "sndS" == elem.FourCC) + sndSrec = elem; + } if (sndHrec == null || sndSrec == null) return null; - var name = sound.Info.Name; var sndH = dir_file.MMap[sndHrec.Id]; var sndS = dir_file.MMap[sndSrec.Id]; if (string.IsNullOrEmpty (name)) @@ -168,7 +191,15 @@ namespace GameRes.Formats.Macromedia Entry ImportBitmap (CastMember bitmap, DirectorFile dir_file, Cast cast) { - var bitd = dir_file.KeyTable.FindByCast (bitmap.Id, "BITD"); +// var bitd = dir_file.KeyTable.FindByCast (bitmap.Id, "BITD"); + KeyTableEntry bitd = null, alfa = null; + foreach (var elem in dir_file.KeyTable.Table.Where (e => e.CastId == bitmap.Id)) + { + if (null == bitd && "BITD" == elem.FourCC) + bitd = elem; + else if (null == alfa && "ALFA" == elem.FourCC) + alfa = elem; + } if (bitd == null) return null; var entry = new BitmapEntry(); @@ -188,6 +219,8 @@ namespace GameRes.Formats.Macromedia if (clut != null) entry.PaletteRef = dir_file.MMap[clut.Id]; } + if (alfa != null) + entry.AlphaRef = dir_file.MMap[alfa.Id]; return entry; } @@ -214,13 +247,27 @@ namespace GameRes.Formats.Macromedia case -101: palette = Palettes.SystemWindows; break; } } - var input = arc.File.CreateStream (entry.Offset, entry.Size); var info = new ImageMetaData { Width = (uint)(bent.Right - bent.Left), Height = (uint)(bent.Bottom - bent.Top), BPP = bent.BitDepth }; - return new BitdDecoder (input.AsStream, info, palette); + byte[] alpha_channel = null; + if (bent.AlphaRef != null) + { + using (var alpha = arc.File.CreateStream (bent.AlphaRef.Offset, bent.AlphaRef.Size)) + { + var alpha_info = new ImageMetaData { + Width = info.Width, + Height = info.Height, + BPP = 8, + }; + var decoder = new BitdDecoder (alpha, alpha_info, null); + alpha_channel = decoder.Unpack8bpp(); + } + } + var input = arc.File.CreateStream (entry.Offset, entry.Size); + return new BitdDecoder (input.AsStream, info, palette) { AlphaChannel = alpha_channel }; } BitmapPalette ReadPalette (byte[] data) @@ -246,6 +293,7 @@ namespace GameRes.Formats.Macromedia public int BitDepth; public int Palette; public Entry PaletteRef; + public Entry AlphaRef; public void DeserializeHeader (byte[] data) { diff --git a/ArcFormats/Macromedia/ArcSWF.cs b/ArcFormats/Macromedia/ArcSWF.cs index 3e9dc59d..abba37d1 100644 --- a/ArcFormats/Macromedia/ArcSWF.cs +++ b/ArcFormats/Macromedia/ArcSWF.cs @@ -330,8 +330,8 @@ namespace GameRes.Formats.Macromedia { int rsize = m_bits.GetBits (5); m_dim.X = GetSignedBits (rsize); + m_dim.Width = GetSignedBits (rsize) - m_dim.X; m_dim.Y = GetSignedBits (rsize); - m_dim.Width = GetSignedBits (rsize) - m_dim.X; m_dim.Height = GetSignedBits (rsize) - m_dim.Y; } diff --git a/ArcFormats/Macromedia/AudioSND.cs b/ArcFormats/Macromedia/AudioSND.cs new file mode 100644 index 00000000..f8ba395f --- /dev/null +++ b/ArcFormats/Macromedia/AudioSND.cs @@ -0,0 +1,112 @@ +//! \file AudioSND.cs +//! \date 2023 Aug 30 +//! \brief Macromedia Director WAVE audio resource. +// +// 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; + +namespace GameRes.Formats.Macromedia +{ + [Export(typeof(AudioFormat))] + public class SndAudio : AudioFormat + { + public override string Tag { get => "SND"; } + public override string Description { get => "Macromedia Director audio resource"; } + public override uint Signature { get => 0; } + public override bool CanWrite { get => false; } + + public override SoundInput TryOpen (IBinaryStream file) + { + if (!file.Name.HasExtension (".snd")) + return null; + int type = file.ReadUInt16(); + if (type != 0x0200) + return null; + var reader = new Reader (file.AsStream, ByteOrder.BigEndian); + reader.Skip (2); + int count = reader.ReadU16(); + if (0 == count) + return null; + int command = reader.ReadU16(); + if (command != 0x8051) + return null; + reader.ReadI16(); + int pos = reader.ReadI32(); + if (pos != reader.Position) + return null; + ushort channels = 1; + ushort bps = 8; + reader.Skip (4); + int param = reader.ReadI32(); + ushort sample_rate = reader.ReadU16(); + reader.Skip (10); + byte encoding = reader.ReadU8(); + byte freq = reader.ReadU8(); + if (freq != 0x3C) + return null; + int frames_count = 0; + if (0 == encoding) + { + frames_count = param / channels; + } + else if (0xFF == encoding) + { + channels = (ushort)param; + frames_count = reader.ReadI32(); + reader.Skip (22); + bps = reader.ReadU16(); + reader.Skip (14); + } + else + throw new NotSupportedException (string.Format ("Not supported 'snd' encoding {0:X2}", encoding)); + if (bps != 16 && bps != 8) + return null; + + var format = new WaveFormat { + FormatTag = 1, + Channels = channels, + SamplesPerSecond = sample_rate, + BlockAlign = (ushort)(bps / 8), + BitsPerSample = bps, + }; + format.SetBPS(); + if (8 == bps) + { + var data = new StreamRegion (file.AsStream, file.Position); + return new RawPcmInput (data, format); + } + int sample_count = frames_count * channels; + var samples = file.ReadBytes (sample_count); + for (int i = 1; i < samples.Length; i += 2) + { + byte s = samples[i-1]; + samples[i-1] = samples[i]; + samples[i] = s; + } + var raw = new BinMemoryStream (samples); + file.Dispose(); + return new RawPcmInput (raw, format); + } + } +} diff --git a/ArcFormats/Macromedia/ImageBITD.cs b/ArcFormats/Macromedia/ImageBITD.cs index 2212f3be..e75828b9 100644 --- a/ArcFormats/Macromedia/ImageBITD.cs +++ b/ArcFormats/Macromedia/ImageBITD.cs @@ -215,11 +215,12 @@ namespace GameRes.Formats.Macromedia ImageData m_image; BitmapPalette m_palette; - public Stream Source { get => m_input; } - public ImageFormat SourceFormat { get => null; } - public ImageMetaData Info { get => m_info; } - public ImageData Image { get => m_image ?? (m_image = GetImageData()); } + public Stream Source => m_input; + public ImageFormat SourceFormat => null; + public ImageMetaData Info => m_info; + public ImageData Image => m_image ?? (m_image = GetImageData()); public PixelFormat Format { get; private set; } + public byte[] AlphaChannel { get; set; } public BitdDecoder (Stream input, ImageMetaData info, BitmapPalette palette) { @@ -230,7 +231,8 @@ namespace GameRes.Formats.Macromedia m_stride = (m_width * m_info.BPP + 7) / 8; m_stride = (m_stride + 1) & ~1; m_output = new byte[m_stride * m_height]; - Format = info.BPP == 4 ? PixelFormats.Indexed4 + Format = info.BPP == 2 ? PixelFormats.Indexed2 + : info.BPP == 4 ? PixelFormats.Indexed4 : info.BPP == 8 ? PixelFormats.Indexed8 : info.BPP == 16 ? PixelFormats.Bgr555 : PixelFormats.Bgr32; @@ -239,14 +241,46 @@ namespace GameRes.Formats.Macromedia protected ImageData GetImageData () { + m_input.Position = 0; if (Info.BPP <= 8) Unpack8bpp(); else UnpackChannels (Info.BPP / 8); + if (AlphaChannel != null) + { + if (Info.BPP != 32) + { + BitmapSource bitmap = BitmapSource.Create (Info.iWidth, Info.iHeight, ImageData.DefaultDpiX, ImageData.DefaultDpiY, Format, m_palette, m_output, m_stride); + bitmap = new FormatConvertedBitmap (bitmap, PixelFormats.Bgr32, null, 0); + m_stride = bitmap.PixelWidth * 4; + m_output = new byte[bitmap.PixelHeight * m_stride]; + bitmap.CopyPixels (m_output, m_stride, 0); + } + ApplyAlphaChannel (AlphaChannel); + Format = PixelFormats.Bgra32; + } return ImageData.Create (m_info, Format, m_palette, m_output, m_stride); } - void Unpack8bpp () + void ApplyAlphaChannel (byte[] alpha) + { + int alpha_stride = (m_width + 1) & ~1; + int src = 0; + int pdst = 3; + for (int y = 0; y < m_height; ++y) + { + int dst = pdst; + for (int x = 0; x < m_width; ++x) + { + m_output[dst] = alpha[src+x]; + dst += 4; + } + src += alpha_stride; + pdst += m_stride; + } + } + + public byte[] Unpack8bpp () { for (int line = 0; line < m_output.Length; line += m_stride) { @@ -277,6 +311,7 @@ namespace GameRes.Formats.Macromedia } } } + return m_output; } public void UnpackChannels (int channels) diff --git a/ArcFormats/Mugi/ArcBIN.cs b/ArcFormats/Mugi/ArcBIN.cs new file mode 100644 index 00000000..0b789208 --- /dev/null +++ b/ArcFormats/Mugi/ArcBIN.cs @@ -0,0 +1,94 @@ +//! \file ArcBIN.cs +//! \date 2023 Sep 03 +//! \brief Mugi's 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.Compression; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +// [070323][schoolzone] Cosplay! Kyonyuu Mahjong + +namespace GameRes.Formats.Mugi +{ + [Export(typeof(ArchiveFormat))] + public class BinOpener : ArchiveFormat + { + public override string Tag { get => "BIN/MUGI"; } + public override string Description { get => "Mugi's resource archive"; } + public override uint Signature { get => 0; } + public override bool IsHierarchic { get => false; } + public override bool CanWrite { get => false; } + + public override ArcFile TryOpen (ArcView file) + { + const uint index_size = 0x8000 + 0x4000; + if (file.MaxOffset <= index_size) + return null; + file.View.Reserve (0, index_size); + uint index_pos = 0x8000; + uint offset = file.View.ReadUInt32 (index_pos); + if (offset != index_size) + return null; + uint[] offsets = new uint[0x800]; + int count = 0; + while (offset != file.MaxOffset) + { + if (count == offsets.Length) + return null; + offsets[count++] = offset; + index_pos += 4; + offset = file.View.ReadUInt32 (index_pos); + if (offset < offsets[count-1] || offset > file.MaxOffset) + return null; + } + offsets[count--] = offset; + uint name_pos = 0; + uint size_pos = 0xA000; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = file.View.ReadString (name_pos, 0x10); + var entry = Create (name); + entry.Offset = offsets[i]; + entry.Size = (uint)(offsets[i+1] - offsets[i]); + entry.UnpackedSize = file.View.ReadUInt32 (size_pos); + entry.IsPacked = entry.Size != entry.UnpackedSize; + dir.Add (entry); + name_pos += 0x10; + size_pos += 4; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var pent = (PackedEntry)entry; + Stream input = arc.File.CreateStream (entry.Offset, entry.Size); + if (pent.IsPacked) + input = new LzssStream (input); + return input; + } + } +} diff --git a/ArcFormats/Qlie/ArcQLIE.cs b/ArcFormats/Qlie/ArcQLIE.cs index ae263489..42fc771b 100644 --- a/ArcFormats/Qlie/ArcQLIE.cs +++ b/ArcFormats/Qlie/ArcQLIE.cs @@ -126,16 +126,32 @@ namespace GameRes.Formats.Qlie } var enc = QlieEncryption.Create (file, index.PackVersion, arc_key); List dir = null; - try + if (index.PackVersion.Major > 1) { dir = index.Read (enc, key_file, use_pack_keyfile); } - catch + else { - if (index.PackVersion.Major == 1) + // PackVer1.0 is a total mess -- it could either use + // • V1 index layout and V1 encryption + // • V1 index layout and V2 encryption + // • V2 index layout and V2 encryption + // all with the same 'FilePackVer1.0' signature + var possibleEncs = new IEncryption[] { + enc, new EncryptionV2 (IndexLayout.WithoutHash), new EncryptionV2() + }; + foreach (var v1enc in possibleEncs) { - enc = new EncryptionV2(); - dir = index.Read (enc, key_file, use_pack_keyfile); + try + { + dir = index.Read (v1enc, key_file, use_pack_keyfile); + if (dir != null) + { + enc = v1enc; + break; + } + } + catch { } } } if (null == dir) @@ -400,6 +416,8 @@ namespace GameRes.Formats.Qlie for (int i = 0; i < m_count; ++i) { int name_length = m_index.ReadUInt16(); + if (name_length > 0x100) // invalid encryption version + return null; if (enc.IsUnicode) name_length *= 2; if (name_length > m_name_buffer.Length) @@ -418,8 +436,8 @@ namespace GameRes.Formats.Qlie entry.UnpackedSize = m_index.ReadUInt32(); // [+0C] entry.IsPacked = 0 != m_index.ReadInt32(); // [+10] entry.EncryptionMethod = m_index.ReadInt32(); // [+14] - if (m_pack_version.Major > 1) - entry.Hash = m_index.ReadUInt32(); // [+18] + if (enc.IndexLayout == IndexLayout.WithHash) + entry.Hash = m_index.ReadUInt32(); // [+18] entry.KeyFile = key_file; if (read_pack_keyfile && entry.Name.Contains ("pack_keyfile")) { diff --git a/ArcFormats/Qlie/Encryption.cs b/ArcFormats/Qlie/Encryption.cs index ff7ee8d3..dbdfcc8b 100644 --- a/ArcFormats/Qlie/Encryption.cs +++ b/ArcFormats/Qlie/Encryption.cs @@ -33,6 +33,8 @@ namespace GameRes.Formats.Qlie { bool IsUnicode { get; } + IndexLayout IndexLayout { get; } + uint CalculateHash (byte[] data, int length); string DecryptName (byte[] name, int name_length); @@ -40,9 +42,17 @@ namespace GameRes.Formats.Qlie void DecryptEntry (byte[] data, int offset, int length, QlieEntry entry); } + internal enum IndexLayout + { + WithoutHash, + WithHash + } + internal abstract class QlieEncryption : IEncryption { - public virtual bool IsUnicode { get { return false; } } + public virtual bool IsUnicode => false; + + public virtual IndexLayout IndexLayout => IndexLayout.WithHash; /// /// Hash generated from the key data contained within archive index. @@ -77,6 +87,8 @@ namespace GameRes.Formats.Qlie internal class EncryptionV1 : QlieEncryption { + public override IndexLayout IndexLayout => IndexLayout.WithoutHash; + public EncryptionV1 () { NameKey = 0xC4; @@ -126,10 +138,15 @@ namespace GameRes.Formats.Qlie internal class EncryptionV2 : QlieEncryption { - public EncryptionV2 () + public override IndexLayout IndexLayout => m_layout; + + private IndexLayout m_layout; + + public EncryptionV2 (IndexLayout layout = IndexLayout.WithHash) { NameKey = 0xC4; ArcKey = 0; + m_layout = layout; } public override uint CalculateHash (byte[] data, int length) diff --git a/ArcFormats/ScrPlayer/ColorBitsTable1 b/ArcFormats/ScrPlayer/IColorBitsTable1 similarity index 100% rename from ArcFormats/ScrPlayer/ColorBitsTable1 rename to ArcFormats/ScrPlayer/IColorBitsTable1 diff --git a/ArcFormats/ScrPlayer/ColorBitsTable2 b/ArcFormats/ScrPlayer/IColorBitsTable2 similarity index 100% rename from ArcFormats/ScrPlayer/ColorBitsTable2 rename to ArcFormats/ScrPlayer/IColorBitsTable2 diff --git a/ArcFormats/ScrPlayer/ControlTable1 b/ArcFormats/ScrPlayer/IControlTable1 similarity index 100% rename from ArcFormats/ScrPlayer/ControlTable1 rename to ArcFormats/ScrPlayer/IControlTable1 diff --git a/ArcFormats/ScrPlayer/ControlTable2 b/ArcFormats/ScrPlayer/IControlTable2 similarity index 100% rename from ArcFormats/ScrPlayer/ControlTable2 rename to ArcFormats/ScrPlayer/IControlTable2 diff --git a/ArcFormats/ScrPlayer/ControlTable32 b/ArcFormats/ScrPlayer/IControlTable32 similarity index 100% rename from ArcFormats/ScrPlayer/ControlTable32 rename to ArcFormats/ScrPlayer/IControlTable32 diff --git a/ArcFormats/ScrPlayer/ImageI.cs b/ArcFormats/ScrPlayer/ImageI.cs index 16979c98..2de675a9 100644 --- a/ArcFormats/ScrPlayer/ImageI.cs +++ b/ArcFormats/ScrPlayer/ImageI.cs @@ -278,11 +278,11 @@ namespace GameRes.Formats.ScrPlayer #endregion #region Bitmap tables - static readonly Lazy s_control_table1 = new Lazy (() => LoadResource ("ControlTable1")); - static readonly Lazy s_control_table2 = new Lazy (() => LoadResource ("ControlTable2")); - static readonly Lazy s_control_table32 = new Lazy (() => LoadResource ("ControlTable32")); - static readonly Lazy s_color_bits1 = new Lazy (() => LoadResource ("ColorBitsTable1")); - static readonly Lazy s_color_bits2 = new Lazy (() => LoadResource ("ColorBitsTable2")); + static readonly Lazy s_control_table1 = new Lazy (() => LoadResource ("IControlTable1")); + static readonly Lazy s_control_table2 = new Lazy (() => LoadResource ("IControlTable2")); + static readonly Lazy s_control_table32 = new Lazy (() => LoadResource ("IControlTable32")); + static readonly Lazy s_color_bits1 = new Lazy (() => LoadResource ("IColorBitsTable1")); + static readonly Lazy s_color_bits2 = new Lazy (() => LoadResource ("IColorBitsTable2")); static byte[] ControlTable1 { get { return s_control_table1.Value; } } static byte[] ControlTable2 { get { return s_control_table2.Value; } } @@ -390,7 +390,7 @@ namespace GameRes.Formats.ScrPlayer 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, } }; - static readonly int[] OffsetTable = { + internal static readonly int[] OffsetTable = { -1, 0, 0, 1, 1, 1, -1, 1, 2, 1, -2, 1, -2, 0, 0, 2, 1, 2, -1, 2, -3, 0, }; #endregion diff --git a/ArcFormats/ScrPlayer/ImageIMG.cs b/ArcFormats/ScrPlayer/ImageIMG.cs new file mode 100644 index 00000000..b1a444ec --- /dev/null +++ b/ArcFormats/ScrPlayer/ImageIMG.cs @@ -0,0 +1,490 @@ +//! \file ImageIMG.cs +//! \date 2023 Aug 30 +//! \brief ScrPlayer 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; + +// [990709][Love Gun] Koi no Sweet Tart wa Ikaga + +namespace GameRes.Formats.ScrPlayer +{ + [Export(typeof(ImageFormat))] + public class ImgFormat : ImageFormat + { + public override string Tag { get => "IMG"; } + public override string Description { get => "ScrPlayer image format"; } + public override uint Signature { get => 0x20474D49; } // 'IMG ' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x18); + int bpp = header.ToUInt16 (0x10); + if (bpp != 24 && bpp != 32) + return null; + return new ImageMetaData { + Width = header.ToUInt16 (0xC), + Height = header.ToUInt16 (0xE), + BPP = bpp, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + using (var reader = new ImgReader (file, info)) + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("ImgFormat.Write not implemented"); + } + } + + internal sealed class ImgReader : IDisposable + { + ImgBitStream m_input; + ImageMetaData m_info; + int m_stride; + byte[] m_output; + + public PixelFormat Format { get; private set; } + + public ImgReader (IBinaryStream input, ImageMetaData info) + { + m_input = new ImgBitStream (input.AsStream, true); + m_info = info; + m_stride = m_info.iWidth * 4; + m_output = new byte[m_stride * (m_info.iHeight + 1)]; + Format = m_info.BPP == 32 ? PixelFormats.Bgra32 : PixelFormats.Bgr32; + } + + public ImageData Unpack () + { + m_input.Input.Position = 0x18; + if (32 == m_info.BPP) + Unpack32bpp(); + else + Unpack24bpp(); + return ImageData.Create (m_info, Format, null, m_output); + } + + byte[] m_row_buffer = new byte[0x2580]; + int[] m_rows_ptr = new int[3] { 0, 0xC80, 0xC80 * 2 }; + + void Unpack24bpp () + { + var offset_table = Img2Reader.OffsetTable.Clone() as int[]; + int dst = 0; + for (int y = 0; y < m_info.iHeight; ++y) + { + int row_pos = m_rows_ptr[2]; + m_rows_ptr[2] = m_rows_ptr[1]; + m_rows_ptr[1] = m_rows_ptr[0]; + m_rows_ptr[0] = row_pos; + for (int x = 0; x < m_info.iWidth; ) + { + int ctl = m_input.GetBits (ControlTable1, 13); + if (0xEF == ctl) + ctl += m_input.GetBits (ControlTable2, 11); + + int pos = m_input.GetBits (PosTable24, 5) * 2; + int x_offset = offset_table[pos]; + int y_offset = offset_table[pos + 1]; + if (pos > 0) + { + offset_table[pos] = offset_table[pos-2]; + offset_table[pos+1] = offset_table[pos-1]; + offset_table[pos-2] = x_offset; + offset_table[pos-1] = y_offset; + } + int src = m_rows_ptr[y_offset] + (x + x_offset) * 4; + if (ctl >= 0xD8) + { + int count = ctl - 0xD6; + Binary.CopyOverlapped (m_row_buffer, src, row_pos, count * 4); + row_pos += count * 4; + x += count; + } + else + { + switch (ColorCode[ctl]) + { + case 0: + m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - MapRed[ctl]); + m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - MapGreen[ctl]); + m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - MapBlue[ctl]); + break; + case 1: + m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - GetDelta()); + m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - MapGreen[ctl]); + m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - MapBlue[ctl]); + break; + case 2: + m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - MapRed[ctl]); + m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - GetDelta()); + m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - MapBlue[ctl]); + break; + case 3: + m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - GetDelta()); + m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - GetDelta()); + m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - MapBlue[ctl]); + break; + case 4: + m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - MapRed[ctl]); + m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - MapGreen[ctl]); + m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - GetDelta()); + break; + case 5: + m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - GetDelta()); + m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - MapGreen[ctl]); + m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - GetDelta()); + break; + case 6: + m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - MapRed[ctl]); + m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - GetDelta()); + m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - GetDelta()); + break; + case 7: + m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - GetDelta()); + m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - GetDelta()); + m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - GetDelta()); + break; + } + row_pos += 4; + ++x; + } + } + Buffer.BlockCopy (m_row_buffer, m_rows_ptr[0], m_output, dst, m_stride); + dst += m_stride; + } + } + + void Unpack32bpp () + { + for (int i = 3; i < m_row_buffer.Length; i += 4) + m_row_buffer[i] = 0xFF; + var offset_table = Img2Reader.OffsetTable.Clone() as int[]; + int dst = 0; + for (int y = 0; y < m_info.iHeight; ++y) + { + int row_pos = m_rows_ptr[2]; + m_rows_ptr[2] = m_rows_ptr[1]; + m_rows_ptr[1] = m_rows_ptr[0]; + m_rows_ptr[0] = row_pos; + for (int x = 0; x < m_info.iWidth; ) + { + int ctl = m_input.GetBits (ControlTable1, 13); + if (0xEF == ctl) + ctl += m_input.GetBits (ControlTable2, 11); + + int t = m_input.GetBits (ControlTable32, 13) * 2; + int pos = PosTable32[t] * 2; + int alpha = PosTable32[t+1]; + int x_offset = offset_table[pos]; + int y_offset = offset_table[pos + 1]; + if (pos > 0) + { + offset_table[pos] = offset_table[pos-2]; + offset_table[pos+1] = offset_table[pos-1]; + offset_table[pos-2] = x_offset; + offset_table[pos-1] = y_offset; + } + int src = m_rows_ptr[y_offset] + (x + x_offset) * 4; + if (ctl >= 0xD8) + { + int count = ctl - 0xD6; + Binary.CopyOverlapped (m_row_buffer, src, row_pos, count * 4); + row_pos += count * 4; + x += count; + } + else + { + switch (ColorCode[ctl]) + { + case 0: + m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - MapRed[ctl]); + m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - MapGreen[ctl]); + m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - MapBlue[ctl]); + break; + case 1: + m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - GetDelta()); + m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - MapGreen[ctl]); + m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - MapBlue[ctl]); + break; + case 2: + m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - MapRed[ctl]); + m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - GetDelta()); + m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - MapBlue[ctl]); + break; + case 3: + m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - GetDelta()); + m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - GetDelta()); + m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - MapBlue[ctl]); + break; + case 4: + m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - MapRed[ctl]); + m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - MapGreen[ctl]); + m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - GetDelta()); + break; + case 5: + m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - GetDelta()); + m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - MapGreen[ctl]); + m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - GetDelta()); + break; + case 6: + m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - MapRed[ctl]); + m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - GetDelta()); + m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - GetDelta()); + break; + case 7: + m_row_buffer[row_pos + 2] = (byte)(m_row_buffer[src + 2] - GetDelta()); + m_row_buffer[row_pos + 1] = (byte)(m_row_buffer[src + 1] - GetDelta()); + m_row_buffer[row_pos ] = (byte)(m_row_buffer[src ] - GetDelta()); + break; + } + if (-3 == alpha) + { + alpha = GetDelta(); + } + m_row_buffer[row_pos + 3] = (byte)(m_row_buffer[src + 3] - alpha); + row_pos += 4; + ++x; + } + } + Buffer.BlockCopy (m_row_buffer, m_rows_ptr[0], m_output, dst, m_stride); + dst += m_stride; + } + } + + int GetDelta () + { + int d = m_input.GetBits (DeltaTable1, 8); + if (0x2B == d) + d += m_input.GetBits (DeltaTable2, 13); + return DeltaTable3[d]; + } + + static byte[] LoadResource (string name) => EmbeddedResource.Load (name, typeof(ImgReader)); + + #region IDisposable Members + bool _disposed = false; + public void Dispose () + { + if (!_disposed) + { + m_input.Dispose(); + _disposed = true; + } + } + #endregion + + #region Bitmap Tables + static readonly Lazy s_control_table1 = new Lazy (() => LoadResource ("ImgControlTable1")); + static readonly Lazy s_control_table2 = new Lazy (() => LoadResource ("ImgControlTable2")); + static readonly Lazy s_control_table32 = new Lazy (() => LoadResource ("ImgControlTable32")); + static readonly Lazy s_delta_table2 = new Lazy (() => LoadResource ("ImgDeltaTable2")); + + static byte[] ControlTable1 { get { return s_control_table1.Value; } } + static byte[] ControlTable2 { get { return s_control_table2.Value; } } + static byte[] ControlTable32 { get { return s_control_table32.Value; } } + static byte[] DeltaTable2 { get { return s_delta_table2.Value; } } + + static readonly byte[] PosTable24 = { + 4, 2, 1, 0, 4, 3, 1, 0, 3, 1, 1, 0, 4, 4, 1, 0, 5, 5, 1, 0, 5, 7, 1, 0, 3, 1, 1, 0, 5, 9, 1, 0, 4, + 2, 1, 0, 4, 3, 1, 0, 3, 1, 1, 0, 4, 4, 1, 0, 5, 6, 1, 0, 5, 8, 1, 0, 3, 1, 1, 0, 5, 10, 1, 0, + }; + static readonly sbyte[] PosTable32 = { + 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0, 0, -3, 1, -3, 2, -3, 3, -3, 4, + -3, 5, -3, 6, -3, 7, -3, 8, -3, 9, -3, 10, -3, 0, -1, 1, -1, 2, -1, 3, -1, 4, -1, 5, -1, 6, -1, 7, + -1, 8, -1, 9, -1, 10, -1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8, 1, 9, 1, 10, 1, 0, + -2, 1, -2, 2, -2, 3, -2, 4, -2, 5, -2, 6, -2, 7, -2, 8, -2, 9, -2, 10, -2, 0, 2, 1, 2, 2, 2, 3, 2, + 4, 2, 5, 2, 6, 2, 7, 2, 8, 2, 9, 2, 10, 2, + }; + static readonly byte[] ColorCode = { + 7, 3, 3, 3, 3, 3, 5, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, + 5, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 6, 2, 2, 2, 2, 2, 4, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, + 6, 2, 2, 2, 2, 2, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 6, 2, 2, 2, 2, 2, 4, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, + 6, 2, 2, 2, 2, 2, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 6, 2, 2, 2, 2, 2, 4, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, + }; + + static readonly byte[] DeltaTable1 = { + 7, 0x13, 3, 0, 6, 0x0D, 4, 4, 5, 7, 3, 1, 5, 8, 4, 5, 4, 2, 3, 0, 4, 0x2B, 6, 0x10, 4, 3, 3, 1, 5, + 9, 5, 0x0B, 5, 6, 3, 0, 6, 0x0F, 4, 4, 6, 0x0C, 3, 1, 8, 0x23, 4, 5, 4, 2, 3, 0, 4, 0x2B, 6, 0x11, + 4, 3, 3, 1, 5, 0x0A, 8, 0x27, 7, 0x15, 3, 0, 6, 0x0E, 4, 4, 5, 7, 3, 1, 5, 8, 4, 5, 4, 2, 3, 0, 4, + 0x2B, 7, 0x1B, 4, 3, 3, 1, 5, 9, 5, 0x0B, 5, 6, 3, 0, 7, 0x18, 4, 4, 8, 0x22, 3, 1, 7, 0x19, 4, 5, + 4, 2, 3, 0, 4, 0x2B, 6, 0x12, 4, 3, 3, 1, 5, 0x0A, 6, 0x14, 8, 0x1D, 3, 0, 6, 0x0D, 4, 4, 5, 7, 3, + 1, 5, 8, 4, 5, 4, 2, 3, 0, 4, 0x2B, 6, 0x10, 4, 3, 3, 1, 5, 9, 5, 0x0B, 5, 6, 3, 0, 6, 0x0F, 4, 4, + 6, 0x0C, 3, 1, 7, 0x1A, 4, 5, 4, 2, 3, 0, 4, 0x2B, 6, 0x11, 4, 3, 3, 1, 5, 0x0A, 7, 0x1C, 8, 0x20, + 3, 0, 6, 0x0E, 4, 4, 5, 7, 3, 1, 5, 8, 4, 5, 4, 2, 3, 0, 4, 0x2B, 8, 0x28, 4, 3, 3, 1, 5, 9, 5, + 0x0B, 5, 6, 3, 0, 7, 0x17, 4, 4, 7, 0x16, 3, 1, 8, 0x26, 4, 5, 4, 2, 3, 0, 4, 0x2B, 6, 0x12, 4, 3, + 3, 1, 5, 0x0A, 6, 0x14, 7, 0x13, 3, 0, 6, 0x0D, 4, 4, 5, 7, 3, 1, 5, 8, 4, 5, 4, 2, 3, 0, 4, 0x2B, + 6, 0x10, 4, 3, 3, 1, 5, 9, 5, 0x0B, 5, 6, 3, 0, 6, 0x0F, 4, 4, 6, 0x0C, 3, 1, 8, 0x24, 4, 5, 4, 2, + 3, 0, 4, 0x2B, 6, 0x11, 4, 3, 3, 1, 5, 0x0A, 8, 0x29, 7, 0x15, 3, 0, 6, 0x0E, 4, 4, 5, 7, 3, 1, 5, + 8, 4, 5, 4, 2, 3, 0, 4, 0x2B, 7, 0x1B, 4, 3, 3, 1, 5, 9, 5, 0x0B, 5, 6, 3, 0, 7, 0x18, 4, 4, 8, + 0x21, 3, 1, 7, 0x19, 4, 5, 4, 2, 3, 0, 4, 0x2B, 6, 0x12, 4, 3, 3, 1, 5, 0x0A, 6, 0x14, 8, 0x1E, 3, + 0, 6, 0x0D, 4, 4, 5, 7, 3, 1, 5, 8, 4, 5, 4, 2, 3, 0, 4, 0x2B, 6, 0x10, 4, 3, 3, 1, 5, 9, 5, 0x0B, + 5, 6, 3, 0, 6, 0x0F, 4, 4, 6, 0x0C, 3, 1, 7, 0x1A, 4, 5, 4, 2, 3, 0, 4, 0x2B, 6, 0x11, 4, 3, 3, 1, + 5, 0x0A, 7, 0x1C, 8, 0x1F, 3, 0, 6, 0x0E, 4, 4, 5, 7, 3, 1, 5, 8, 4, 5, 4, 2, 3, 0, 4, 0x2B, 8, + 0x2A, 4, 3, 3, 1, 5, 9, 5, 0x0B, 5, 6, 3, 0, 7, 0x17, 4, 4, 7, 0x16, 3, 1, 8, 0x25, 4, 5, 4, 2, 3, + 0, 4, 0x2B, 6, 0x12, 4, 3, 3, 1, 5, 0x0A, 6, 0x14, + }; + static readonly byte[] DeltaTable3 = { + 0x03, 0xFD, 0x04, 0xFC, 0x05, 0xFB, 0x06, 0xFA, 0x07, 0xF9, 0x08, 0xF8, 0x09, 0xF7, 0x0A, 0xF6, + 0x0B, 0xF5, 0x0C, 0xF4, 0x0D, 0xF3, 0x0E, 0xF2, 0x0F, 0xF1, 0x10, 0xF0, 0x11, 0xEF, 0x12, 0xEE, + 0x13, 0xED, 0x14, 0xEC, 0x15, 0xEB, 0x16, 0xEA, 0x17, 0xE9, 0x18, 0xE8, 0x19, 0xE7, 0x1A, 0xE6, + 0x1B, 0xE5, 0x1C, 0xE4, 0x1D, 0xE3, 0x1E, 0xE2, 0x1F, 0xE1, 0x20, 0xE0, 0x21, 0xDF, 0x22, 0xDE, + 0x23, 0xDD, 0x24, 0xDC, 0x25, 0xDB, 0x26, 0xDA, 0x27, 0xD9, 0x28, 0xD8, 0x29, 0xD7, 0x2A, 0xD6, + 0x2B, 0xD5, 0x2C, 0xD4, 0x2D, 0xD3, 0x2E, 0xD2, 0x2F, 0xD1, 0x30, 0xD0, 0x31, 0xCF, 0x32, 0xCE, + 0x33, 0xCD, 0x34, 0xCC, 0x35, 0xCB, 0x36, 0xCA, 0x37, 0xC9, 0x38, 0xC8, 0x39, 0xC7, 0x3A, 0xC6, + 0x3B, 0xC5, 0x3C, 0xC4, 0x3D, 0xC3, 0x3E, 0xC2, 0x3F, 0xC1, 0x40, 0xC0, 0x41, 0xBF, 0x42, 0xBE, + 0x43, 0xBD, 0x44, 0xBC, 0x45, 0xBB, 0x46, 0xBA, 0x47, 0xB9, 0x48, 0xB8, 0x49, 0xB7, 0x4A, 0xB6, + 0x4B, 0xB5, 0x4C, 0xB4, 0x4D, 0xB3, 0x4E, 0xB2, 0x4F, 0xB1, 0x50, 0xB0, 0x51, 0xAF, 0x52, 0xAE, + 0x53, 0xAD, 0x54, 0xAC, 0x55, 0xAB, 0x56, 0xAA, 0x57, 0xA9, 0x58, 0xA8, 0x59, 0xA7, 0x5A, 0xA6, + 0x5B, 0xA5, 0x5C, 0xA4, 0x5D, 0xA3, 0x5E, 0xA2, 0x5F, 0xA1, 0x60, 0xA0, 0x61, 0x9F, 0x62, 0x9E, + 0x63, 0x9D, 0x64, 0x9C, 0x65, 0x9B, 0x66, 0x9A, 0x67, 0x99, 0x68, 0x98, 0x69, 0x97, 0x6A, 0x96, + 0x6B, 0x95, 0x6C, 0x94, 0x6D, 0x93, 0x6E, 0x92, 0x6F, 0x91, 0x70, 0x90, 0x71, 0x8F, 0x72, 0x8E, + 0x73, 0x8D, 0x74, 0x8C, 0x75, 0x8B, 0x76, 0x8A, 0x77, 0x89, 0x78, 0x88, 0x79, 0x87, 0x7A, 0x86, + 0x7B, 0x85, 0x7C, 0x84, 0x7D, 0x83, 0x7E, 0x82, 0x7F, 0x81, 0x80, 0, + }; + static readonly byte[] MapRed = { + 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, + 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, + 0xFD, 0xFD, 0xFD, 0xFD, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + }; + static readonly byte[] MapGreen = { + 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FF, 0x0FF, + 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0x0FD, 0x0FD, + 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FF, 0x0FF, 0x0FF, 0x0FF, + 0x0FF, 0x0FF, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0x0FD, 0x0FD, 0x0FD, 0x0FD, + 0x0FD, 0x0FD, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0x0FF, + 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FD, + 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FE, 0x0FE, + 0x0FE, 0x0FE, 0x0FE, 0x0FE, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 1, 1, 1, 2, 2, 2, 2, 2, 2, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FD, 0x0FE, 0x0FE, 0x0FE, 0x0FE, + 0x0FE, 0x0FE, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0x0FF, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, + 2, 2, 2, 2, + }; + static readonly byte[] MapBlue = { + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, 0x0FD, 0x0FE, 0x0FF, 0, 1, 2, + }; + #endregion + } + + internal class ImgBitStream : BitStream + { + public ImgBitStream (Stream file, bool leave_open = false) : base (file, leave_open) + { + } + + public int GetBits (byte[] table, int count) + { + int n = PeekBits (count) * 2; + count = table[n]; + m_bits >>= count; + m_cached_bits -= count; + return table[n + 1]; + } + + public int PeekBits (int count) + { + if (m_cached_bits < count) + { + int b = m_input.ReadByte(); + if (-1 == b) + b = 0; // throw new EndOfStreamException(); + m_bits |= ByteMap[b] << m_cached_bits; + m_cached_bits += 8; + if (m_cached_bits < count) + { + b = m_input.ReadByte(); + if (-1 == b) + b = 0; // throw new EndOfStreamException(); + m_bits |= ByteMap[b] << m_cached_bits; + m_cached_bits += 8; + } + } + return m_bits & g_bitMask[count]; + } + + static readonly int[] g_bitMask = { + 0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000F, 0x0000001F, 0x0000003F, 0x0000007F, + 0x000000FF, 0x000001FF, 0x000003FF, 0x000007FF, 0x00000FFF, 0x00001FFF, 0x00003FFF, 0x00007FFF, + 0x0000FFFF, 0x0001FFFF, 0x0003FFFF, 0x0007FFFF, 0x000FFFFF, 0x001FFFFF, 0x003FFFFF, 0x007FFFFF, + 0x00FFFFFF, 0x01FFFFFF, 0x03FFFFFF, 0x07FFFFFF, 0x0FFFFFFF, 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, + }; + + internal static readonly byte[] ByteMap = { + 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, + 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, + 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, + 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, + 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, + 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, + 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, + 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, + 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, + 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, + 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, + 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, + 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, + 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, + 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, + 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF, + }; + } +} diff --git a/ArcFormats/ScrPlayer/ImgControlTable1 b/ArcFormats/ScrPlayer/ImgControlTable1 new file mode 100644 index 00000000..844a20ca Binary files /dev/null and b/ArcFormats/ScrPlayer/ImgControlTable1 differ diff --git a/ArcFormats/ScrPlayer/ImgControlTable2 b/ArcFormats/ScrPlayer/ImgControlTable2 new file mode 100644 index 00000000..e6e94738 Binary files /dev/null and b/ArcFormats/ScrPlayer/ImgControlTable2 differ diff --git a/ArcFormats/ScrPlayer/ImgControlTable32 b/ArcFormats/ScrPlayer/ImgControlTable32 new file mode 100644 index 00000000..e46f629e Binary files /dev/null and b/ArcFormats/ScrPlayer/ImgControlTable32 differ diff --git a/ArcFormats/ScrPlayer/ImgDeltaTable2 b/ArcFormats/ScrPlayer/ImgDeltaTable2 new file mode 100644 index 00000000..5c74fce2 Binary files /dev/null and b/ArcFormats/ScrPlayer/ImgDeltaTable2 differ diff --git a/ArcFormats/Software House Parsley/ArcScn.cs b/ArcFormats/Software House Parsley/ArcScn.cs new file mode 100644 index 00000000..9ad9bba9 --- /dev/null +++ b/ArcFormats/Software House Parsley/ArcScn.cs @@ -0,0 +1,63 @@ +//! \file ArcScn.cs +//! \date 2023 Aug 28 +//! \brief Software House Parsley scripts 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.Parsley +{ + [Export(typeof(ArchiveFormat))] + public class ScnDatOpener : ArchiveFormat + { + public override string Tag { get => "DAT/SCN"; } + public override string Description { get => "Software House Parsley scenario archive"; } + public override uint Signature { get => 0; } + public override bool IsHierarchic { get => false; } + public override bool CanWrite { get => false; } + + public override ArcFile TryOpen (ArcView file) + { + if (!VFS.IsPathEqualsToFileName (file.Name, "scn.dat")) + return null; + uint base_offset = 0x1400; + uint index_pos = 0; + var dir = new List(); + while (index_pos < base_offset && file.View.ReadByte (index_pos) != 0) + { + var name = file.View.ReadString (index_pos, 0x20); + var entry = Create (name); + entry.Offset = file.View.ReadUInt32 (index_pos+0x20) + base_offset; + entry.Size = file.View.ReadUInt32 (index_pos+0x24); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_pos += 0x28; + } + if (dir.Count == 0) + return null; + return new ArcFile (file, this, dir); + } + } +} diff --git a/ArcFormats/Unity/ScriptDSM.cs b/ArcFormats/Unity/ScriptDSM.cs new file mode 100644 index 00000000..4254a350 --- /dev/null +++ b/ArcFormats/Unity/ScriptDSM.cs @@ -0,0 +1,87 @@ +//! \file ScriptDSM.cs +//! \date 2023 Sep 03 +//! \brief Decrypt data.dsm 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; +using System.ComponentModel.Composition; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace GameRes.Formats.NScripter +{ + [Export(typeof(ScriptFormat))] + public class DsmConverter : GenericScriptFormat + { + public override string Tag { get => "DSM/UTAGE"; } + public override string Description { get => "UTAGE Unity engine script file"; } + public override uint Signature { get => 0; } + + public override bool IsScript (IBinaryStream file) + { + return VFS.IsPathEqualsToFileName (file.Name, "data.dsm"); + } + + public override Stream ConvertFrom (IBinaryStream file) + { + using (var reader = new StreamReader (file.AsStream)) + { + var sourceString = reader.ReadToEnd(); + var text = DsmDecryptor.DecryptString (sourceString, "pass"); + return new BinMemoryStream (text, file.Name); + } + } + + public override Stream ConvertBack (IBinaryStream file) + { + throw new NotSupportedException(); + } + } + + internal static class DsmDecryptor + { + internal static void GenerateKeyFromPassword (string password, int keySize, out byte[] key, int blockSize, out byte[] iv) + { + var bytes = Encoding.UTF8.GetBytes ("saltは必ず8バイト以上"); + var derive = new Rfc2898DeriveBytes (password, bytes); + derive.IterationCount = 1000; + key = derive.GetBytes (keySize / 8); + iv = derive.GetBytes (blockSize / 8); + } + + internal static byte[] DecryptString (string sourceString, string password) + { + var rij = new RijndaelManaged(); + byte[] key, iv; + GenerateKeyFromPassword (password, rij.KeySize, out key, rij.BlockSize, out iv); + rij.Key = key; + rij.IV = iv; + var array = Convert.FromBase64String (sourceString); + using (var cryptoTransform = rij.CreateDecryptor()) + { + return cryptoTransform.TransformFinalBlock (array, 0, array.Length); + } + } + } +} diff --git a/Experimental/Experimental.csproj b/Experimental/Experimental.csproj index d3cf2e40..bec2be29 100644 --- a/Experimental/Experimental.csproj +++ b/Experimental/Experimental.csproj @@ -150,6 +150,8 @@ + + diff --git a/Experimental/Microsoft/ArcEXE.cs b/Experimental/Microsoft/ArcEXE.cs new file mode 100644 index 00000000..336b46e8 --- /dev/null +++ b/Experimental/Microsoft/ArcEXE.cs @@ -0,0 +1,184 @@ +//! \file ArcEXE.cs +//! \date 2023 Aug 24 +//! \brief Access portable executable (PE) resources. +// +// 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; + +namespace GameRes.Formats.Microsoft +{ + [Export(typeof(ArchiveFormat))] + 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 ExeOpener () + { + Extensions = new[] { "exe",/* "dll"*/ }; + } + + static readonly Dictionary RuntimeTypeMap = new Dictionary() { + { "#2", "RT_BITMAP" }, + { "#10", "RT_RCDATA" }, + }; + + static readonly Dictionary ExtensionTypeMap = new Dictionary() { + { "PNG", ".PNG" }, + { "WAVE", ".WAV" }, + { "MIDS", ".MID" }, + { "SCR", ".BIN" }, + { "#2", ".BMP" }, + { "#10", ".BIN" }, + }; + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (0, "MZ") || VFS.IsVirtual) + return null; + var res = new ExeFile.ResourceAccessor (file.Name); + try + { + var dir = new List(); + foreach (var type in res.EnumTypes()) + { + string dir_name = type; + if (type.StartsWith ("#") && !RuntimeTypeMap.TryGetValue (type, out dir_name)) + continue; + string ext; + if (!ExtensionTypeMap.TryGetValue (type, out ext)) + ext = ""; + foreach (var name in res.EnumNames (type)) + { + string full_name = name; + if (name.StartsWith ("#")) + full_name = IdToString (name); + full_name = string.Join ("/", dir_name, full_name) + ext; + var entry = Create (full_name); + entry.NativeName = name; + entry.NativeType = type; + entry.Offset = 0; + entry.Size = res.GetResourceSize (name, type); + dir.Add (entry); + } + } + if (0 == dir.Count) + { + res.Dispose(); + return null; + } + return new ResourcesArchive (file, this, dir, res); + } + catch + { + res.Dispose(); + throw; + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var rarc = (ResourcesArchive)arc; + var rent = (ResourceEntry)entry; + var data = rarc.Accessor.GetResource (rent.NativeName, rent.NativeType); + if (null == data) + return Stream.Null; + return new BinMemoryStream (data, rent.Name); + } + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var rent = (ResourceEntry)entry; + if (rent.NativeType != "#2") + return base.OpenImage (arc, entry); + var rarc = (ResourcesArchive)arc; + var bitmap = new byte[14 + entry.Size]; + int length = rarc.Accessor.ReadResource (rent.NativeName, rent.NativeType, bitmap, 14); + + length += 14; + bitmap[0] = (byte)'B'; + bitmap[1] = (byte)'M'; + LittleEndian.Pack (length, bitmap, 2); + int bits_length = bitmap.ToInt32 (0x22); + int bits_pos = length - bits_length; + if (bits_length == 0) + bits_pos = bitmap.ToInt32 (14) + 22; + LittleEndian.Pack (bits_pos, bitmap, 10); + + var bm = new BinMemoryStream (bitmap, 0, length, entry.Name); + var info = ImageFormat.Bmp.ReadMetaData (bm); + if (null == info) + { + bm.Dispose(); + throw new InvalidFormatException ("Invalid bitmap resource."); + } + bm.Position = 0; + return new ImageFormatDecoder (bm, ImageFormat.Bmp, info); + } + + internal static string IdToString (string id) + { + if (id.Length > 1 && id[0] == '#' && char.IsDigit (id[1])) + id = id.Substring (1).PadLeft (5, '0'); + return id; + } + } + + internal class ResourceEntry : Entry + { + public string NativeName; + public string NativeType; + } + + internal class ResourcesArchive : ArcFile + { + public readonly ExeFile.ResourceAccessor Accessor; + + public ResourcesArchive (ArcView arc, ArchiveFormat impl, ICollection dir, ExeFile.ResourceAccessor acc) + : base (arc, impl, dir) + { + Accessor = acc; + } + + #region IDisposable Members + bool _acc_disposed = false; + protected override void Dispose (bool disposing) + { + if (_acc_disposed) + return; + if (disposing) + Accessor.Dispose(); + _acc_disposed = true; + base.Dispose (disposing); + } + #endregion + } +} diff --git a/Experimental/Microsoft/ArcNE.cs b/Experimental/Microsoft/ArcNE.cs new file mode 100644 index 00000000..ba795e57 --- /dev/null +++ b/Experimental/Microsoft/ArcNE.cs @@ -0,0 +1,103 @@ +//! \file ArcNE.cs +//! \date 2023 Aug 29 +//! \brief Access 16-bit NE executable resources. +// +// 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.Microsoft +{ + [Export(typeof(ArchiveFormat))] + public class PakOpener : 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; } + + static readonly Dictionary TypeMap = new Dictionary { + { 1, "RT_CURSOR" }, + { 2, "RT_BITMAP" }, + { 3, "RT_ICON" }, + { 4, "RT_MENU" }, + { 5, "RT_DIALOG" }, + { 6, "RT_STRING" }, + { 10, "RT_DATA" }, + { 11, "RT_MESSAGETABLE" }, + { 16, "RT_VERSION" }, + }; + + public override ArcFile TryOpen (ArcView file) + { + if (!file.View.AsciiEqual (0, "MZ")) + return null; + uint ne_offset = file.View.ReadUInt32 (0x3C); + if (!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) + return null; + int shift = file.View.ReadUInt16 (res_table_offset); + res_table_offset += 2; + var dir = new List(); + while (res_table_offset + 1 < file.MaxOffset) + { + int type_id = file.View.ReadUInt16 (res_table_offset); + if (0 == type_id) + break; + string dir_name = null; + if ((type_id & 0x8000) != 0) + { + type_id &= 0x7FFF; + TypeMap.TryGetValue (type_id, out dir_name); + } + int count = file.View.ReadUInt16 (res_table_offset+2); + res_table_offset += 8; + if (null == dir_name) + dir_name = string.Format ("#{0}", type_id); + for (int i = 0; i < count; ++i) + { + 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); + res_table_offset += 12; + string name = res_id.ToString ("D5"); + name = string.Join ("/", dir_name, name); + var entry = new Entry { + Name = name, + Offset = offset, + Size = size, + }; + dir.Add (entry); + } + } + if (0 == dir.Count) + return null; + return new ArcFile (file, this, dir); + } + } +} diff --git a/Experimental/SakanaGL/ArcSX.cs b/Experimental/SakanaGL/ArcSX.cs index 1a0b2897..f5121c30 100644 --- a/Experimental/SakanaGL/ArcSX.cs +++ b/Experimental/SakanaGL/ArcSX.cs @@ -30,6 +30,7 @@ using System.ComponentModel.Composition; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; namespace GameRes.Formats.Sakana { @@ -37,6 +38,7 @@ namespace GameRes.Formats.Sakana { public int Storage; public ushort Flags; + public ushort ArcIndex; public bool IsEncrypted { get { return 0 == (Flags & 0x10); } } } @@ -57,11 +59,23 @@ namespace GameRes.Formats.Sakana public override bool CanWrite { get { return false; } } const uint DefaultKey = 0x2E76034B; + static readonly Regex ArchiveNameRe = new Regex (@"^(.*)-([^-]+)$"); public override ArcFile TryOpen (ArcView file) { - var sx_name = FindSxName (file.Name); - if (!VFS.FileExists (sx_name) || file.Name.Equals (sx_name, StringComparison.InvariantCultureIgnoreCase)) + var base_name = Path.GetFileNameWithoutExtension (file.Name); + var sx_name = base_name.Substring (0, 4) + "(00).sx"; + sx_name = VFS.ChangeFileName (file.Name, sx_name); + if (!VFS.FileExists (sx_name)) + { + var match = ArchiveNameRe.Match (base_name); + if (!match.Success) + return null; + sx_name = VFS.ChangeFileName (file.Name, match.Groups[1] + "(00).sx"); + if (!VFS.FileExists (sx_name)) + return null; + } + if (file.Name.Equals (sx_name, StringComparison.OrdinalIgnoreCase)) return null; byte[] index_data; using (var sx = VFS.OpenView (sx_name)) @@ -86,6 +100,8 @@ namespace GameRes.Formats.Sakana { var reader = new SxIndexDeserializer (index, file.MaxOffset); var dir = reader.Deserialize(); + if (null == dir || dir.Count == 0) + return null; return new ArcFile (file, this, dir); } } @@ -103,7 +119,11 @@ namespace GameRes.Formats.Sakana DecryptData (input, key_lo, key_hi); } if (sx_entry.IsPacked) + { input = UnpackZstd (input); + if (sx_entry.UnpackedSize == 0) + sx_entry.UnpackedSize = (uint)input.Length; + } return new BinMemoryStream (input, entry.Name); } @@ -190,8 +210,7 @@ namespace GameRes.Formats.Sakana m_dir = new List (count); for (int i = 0; i < count; ++i) { - m_index.ReadByte(); - int storage = m_index.ReadByte(); + ushort arc = Binary.BigEndian (m_index.ReadUInt16()); ushort flags = Binary.BigEndian (m_index.ReadUInt16()); uint offset = Binary.BigEndian (m_index.ReadUInt32()); uint size = Binary.BigEndian (m_index.ReadUInt32()); @@ -201,35 +220,32 @@ namespace GameRes.Formats.Sakana Offset = (long)offset << 4, Size = size, IsPacked = 0 != (flags & 0x03), + ArcIndex = arc, }; m_dir.Add (entry); } - count = Binary.BigEndian (m_index.ReadUInt16()); - var storages = new List(count); - for (int i = 0; i < count; ++i) + int arc_count = Binary.BigEndian (m_index.ReadUInt16()); + int arc_index = -1; + for (int i = 0; i < arc_count; ++i) { m_index.ReadUInt32(); m_index.ReadUInt32(); m_index.ReadUInt32(); - var storage = new SxStorage { - Size = Binary.BigEndian (m_index.ReadUInt32()) << 4, - Timestamp = Binary.BigEndian (m_index.ReadUInt64()), - }; - storages.Add (storage); - m_index.Seek (16, SeekOrigin.Current); // MD5 + long arc_size = (long)Binary.BigEndian (m_index.ReadUInt32()) << 4; // archive body length + if (m_max_offset == arc_size) + arc_index = i; + m_index.ReadUInt64(); + m_index.Seek (16, SeekOrigin.Current); // MD5 sum } count = Binary.BigEndian (m_index.ReadUInt16()); if (count > 0) m_index.Seek (count * 24, SeekOrigin.Current); DeserializeTree(); - // Remove entries in other archives - // Note using file size as archive identification can be problematic, but faster than MD5 - var current_storage = storages.FindIndex (s => m_max_offset == s.Size); - if (-1 != current_storage) + if (arc_count > 1 && arc_index != -1) { - m_dir = m_dir.Where (e => e.CheckPlacement (m_max_offset) && (e as SxEntry).Storage == current_storage).ToList(); + return m_dir.Where (e => (e as SxEntry).ArcIndex == arc_index).ToList(); } return m_dir; } diff --git a/GUI/AboutBox.xaml b/GUI/AboutBox.xaml index 2ad63b3d..1b5ff33d 100644 --- a/GUI/AboutBox.xaml +++ b/GUI/AboutBox.xaml @@ -46,6 +46,11 @@ IN THE SOFTWARE. + + + + + @@ -126,7 +131,7 @@ IN THE SOFTWARE. - + diff --git a/GUI/ImagePreview.cs b/GUI/ImagePreview.cs index 43ddf683..774aec29 100644 --- a/GUI/ImagePreview.cs +++ b/GUI/ImagePreview.cs @@ -79,9 +79,9 @@ namespace GARbro.GUI public string Name { get; set; } public Entry Entry { get; set; } - public bool IsEqual (IEnumerable path, string name) + public bool IsEqual (IEnumerable path, Entry entry) { - return Path != null && path.SequenceEqual (Path) && name.Equals (Name); + return Path != null && path.SequenceEqual (Path) && Entry == entry; } } @@ -139,7 +139,7 @@ namespace GARbro.GUI /// private void PreviewEntry (Entry entry) { - if (m_current_preview.IsEqual (ViewModel.Path, entry.Name)) + if (m_current_preview.IsEqual (ViewModel.Path, entry)) return; UpdatePreviewPane (entry); } diff --git a/GameRes/ArcView.cs b/GameRes/ArcView.cs index 66747307..59a04d08 100644 --- a/GameRes/ArcView.cs +++ b/GameRes/ArcView.cs @@ -440,9 +440,22 @@ namespace GameRes { byte* s = m_mem + (offset - m_offset); uint string_length = 0; - while (string_length < size && 0 != s[string_length]) + // enc.WindowsCodePage property might throw an exception for some encodings, while + // CodePage is just a direct field access. + if (enc.CodePage == 1200 || enc.CodePage == 1201) // for UTF-16 encodings stop marker is 2-bytes long { - ++string_length; + ushort* u = (ushort*)s; + while (string_length + 1 < size && 0 != u[string_length >> 1]) + { + string_length += 2; + } + } + else + { + while (string_length < size && 0 != s[string_length]) + { + ++string_length; + } } return new string ((sbyte*)s, 0, (int)string_length, enc); } diff --git a/GameRes/FileSystem.cs b/GameRes/FileSystem.cs index 534dc61c..0fa33e6d 100644 --- a/GameRes/FileSystem.cs +++ b/GameRes/FileSystem.cs @@ -263,7 +263,9 @@ namespace GameRes public bool FileExists (string filename) { - return m_dir.ContainsKey (CombinePath (CurrentDirectory, filename)); + return m_dir.ContainsKey (filename) + || !string.IsNullOrEmpty (CurrentDirectory) + && m_dir.ContainsKey (CombinePath (CurrentDirectory, filename)); } public Stream OpenStream (Entry entry) @@ -408,6 +410,8 @@ namespace GameRes Entry entry = null; if (m_dir.TryGetValue (filename, out entry)) return entry; + if (m_dir.TryGetValue (CombinePath (CurrentDirectory, filename), out entry)) + return entry; var dir_name = filename + PathDelimiter; if (m_dir.Keys.Any (n => n.StartsWith (dir_name))) return new SubDirEntry (filename); diff --git a/GameRes/ImageBMP.cs b/GameRes/ImageBMP.cs index 9c8af046..8ee0c054 100644 --- a/GameRes/ImageBMP.cs +++ b/GameRes/ImageBMP.cs @@ -220,6 +220,13 @@ namespace GameRes file.Read (pixels, dst, stride); for (int x = 3; !has_alpha && x < stride; x += 4) has_alpha = pixels[dst+x] != 0; + /* // sometimes alpha channel is inverted + for (int x = 3; x < stride; x += 4) + { + pixels[dst+x] ^= 0xFF; + has_alpha = has_alpha || pixels[dst+x] != 0; + } + */ } PixelFormat format = has_alpha ? PixelFormats.Bgra32 : PixelFormats.Bgr32; return ImageData.Create (info, format, null, pixels, stride); diff --git a/GameRes/ImageDecoder.cs b/GameRes/ImageDecoder.cs index 0196854b..c5217681 100644 --- a/GameRes/ImageDecoder.cs +++ b/GameRes/ImageDecoder.cs @@ -122,7 +122,7 @@ namespace GameRes protected ImageData m_image; public Stream Source { get { m_input.Position = 0; return m_input.AsStream; } } - public ImageFormat SourceFormat { get { return null; } } + public ImageFormat SourceFormat { get; protected set; } public ImageMetaData Info { get; protected set; } public ImageData Image { get { return m_image ?? (m_image = GetImageData()); } } diff --git a/Legacy/Gsx/ArcK3.cs b/Legacy/Gsx/ArcK3.cs index 00f12ea0..e9972854 100644 --- a/Legacy/Gsx/ArcK3.cs +++ b/Legacy/Gsx/ArcK3.cs @@ -26,7 +26,7 @@ using System.Collections.Generic; using System.ComponentModel.Composition; -// [000225][Light Plan] My Fairink Yousei Byakuya Monogatari +// [000225][Light Plan] My Fair Link Yousei Byakuya Monogatari namespace GameRes.Formats.Gsx { diff --git a/Legacy/Gsx/ArcK5.cs b/Legacy/Gsx/ArcK5.cs new file mode 100644 index 00000000..82787c6a --- /dev/null +++ b/Legacy/Gsx/ArcK5.cs @@ -0,0 +1,70 @@ +//! \file ArcK5.cs +//! \date 2023 Aug 27 +//! \brief GSX 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; +using System.IO; +using System.Text; + +namespace GameRes.Formats.Gsx +{ + [Export(typeof(ArchiveFormat))] + public class K5Opener : ArchiveFormat + { + public override string Tag => "K5"; + public override string Description => "GSX engine resource archive"; + public override uint Signature => 0x01354B; // 'K5' + public override bool IsHierarchic => true; + public override bool CanWrite => false; + + public K5Opener () + { + ContainedFormats = new[] { "K4", "OGG" }; + } + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (4); + if (!IsSaneCount (count)) + return null; + uint index_offset = file.View.ReadUInt32 (8); + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var dir_name = file.View.ReadString (index_offset, 0x80, Encoding.Unicode); + var name = file.View.ReadString (index_offset+0x80, 0x40, Encoding.Unicode); + name = Path.Combine (dir_name, name); + var entry = Create (name); + entry.Offset = file.View.ReadUInt32 (index_offset+0xC8); + entry.Size = file.View.ReadUInt32 (index_offset+0xCC); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_offset += 0x100; + } + return new ArcFile (file, this, dir); + } + } +} diff --git a/Legacy/Gsx/ImageK4.cs b/Legacy/Gsx/ImageK4.cs index 93c3dca8..79866dc8 100644 --- a/Legacy/Gsx/ImageK4.cs +++ b/Legacy/Gsx/ImageK4.cs @@ -1,8 +1,8 @@ //! \file ImageK4.cs -//! \date 2019 Feb 07 -//! \brief Toyo GSX image format. +//! \date 2023 Aug 27 +//! \brief GSX engine image format. // -// Copyright (C) 2019 by morkt +// 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 @@ -23,6 +23,8 @@ // IN THE SOFTWARE. // +using GameRes.Utility; +using System; using System.ComponentModel.Composition; using System.IO; using System.Windows.Media; @@ -33,16 +35,16 @@ namespace GameRes.Formats.Gsx { internal class K4MetaData : ImageMetaData { - public bool HasAlpha; + public byte AlphaMode; public int FrameCount; } [Export(typeof(ImageFormat))] public class K4Format : ImageFormat { - public override string Tag { get { return "K4"; } } - public override string Description { get { return "Toyo GSX image format"; } } - public override uint Signature { get { return 0x0201344B; } } // 'K4' + public override string Tag => "K4"; + public override string Description => "GSX engine image format"; + public override uint Signature => 0x0201344B; // 'K4' public override ImageMetaData ReadMetaData (IBinaryStream file) { @@ -51,20 +53,22 @@ namespace GameRes.Formats.Gsx return null; if (header[2] != 1 || header[3] != 2) return null; + int frame_count = header.ToInt16 (0xC); + if (frame_count <= 0) + return null; return new K4MetaData { Width = header.ToUInt16 (4), Height = header.ToUInt16 (6), BPP = header[0xF], - HasAlpha = header[0xB] != 0, - FrameCount = header.ToUInt16 (0xC), + AlphaMode = header[0xB], + FrameCount = frame_count, }; } public override ImageData Read (IBinaryStream file, ImageMetaData info) { - var meta = (K4MetaData)info; - - return ImageData.Create (info, format, palette, pixels); + var reader = new K4Reader (file, (K4MetaData)info); + return reader.Unpack(); } public override void Write (Stream file, ImageData image) @@ -72,4 +76,190 @@ namespace GameRes.Formats.Gsx throw new System.NotImplementedException ("K4Format.Write not implemented"); } } + + internal sealed class K4Reader + { + IBinaryStream m_input; + K4MetaData m_info; + + public K4Reader (IBinaryStream input, K4MetaData info) + { + m_input = input; + m_info = info; + } + + int m_stride; + int m_pixel_size; + + public ImageData Unpack () + { + uint base_offset = 0x30; + m_input.Position = base_offset; + m_info.Width = m_input.ReadUInt16(); + m_info.Height = m_input.ReadUInt16(); + m_input.Seek (8, SeekOrigin.Current); + int bpp = m_input.ReadUInt16(); + int flags = m_input.ReadUInt16(); + m_input.Seek (4, SeekOrigin.Current); + uint alpha_pos = m_input.ReadUInt32(); + m_input.Seek (12, SeekOrigin.Current); + int ctl_length = m_input.ReadInt32(); + + m_pixel_size = bpp / 8; + m_stride = (m_info.iWidth * m_pixel_size + 3) & ~3; + var pixels = new byte[m_info.iHeight * m_stride]; + int dst = 0; + bool do_delta = (flags & 1) != 0; + var control_bytes = m_input.ReadBytes (ctl_length - 0x10); + using (var mem = new MemoryStream (control_bytes)) + using (var ctl = new MsbBitStream (mem)) + using (var data = new MsbBitStream (m_input.AsStream, true)) + { + while (dst < pixels.Length) + { + int b = ctl.GetNextBit(); + if (-1 == b) + break; + if (b != 0) + { + if (!do_delta) + { + pixels[dst++] = (byte)data.GetBits (8); + } + else if (dst >= m_pixel_size) + { + pixels[dst] = (byte)(pixels[dst - m_pixel_size] + data.GetBits (9) + 1); + ++dst; + } + else + { + pixels[dst++] = (byte)data.GetBits (9); + } + } + else + { + int pos, count; + if (ctl.GetNextBit() != 0) + { + pos = data.GetBits (14); + count = data.GetBits (4) + 3; + } + else + { + pos = data.GetBits (9); + count = data.GetBits (3) + 2; + } + int src = dst - pos - 1; + count = Math.Min (count, pixels.Length - dst); + if (!do_delta || dst < m_pixel_size) + { + Binary.CopyOverlapped (pixels, src, dst, count); + dst += count; + } + else + { + while (count --> 0) + { + pixels[dst++] = (byte)(pixels[src] + pixels[src + pos - m_pixel_size + 1] - pixels[src - m_pixel_size]); + ++src; + } + } + } + } + } + if (0 == alpha_pos) + { + if (24 == bpp) + return ImageData.CreateFlipped (m_info, PixelFormats.Bgr24, null, pixels, m_stride); + else + return ImageData.CreateFlipped (m_info, PixelFormats.Bgr32, null, pixels, m_stride); + } + if (0xFF == m_info.AlphaMode) + pixels = UnpackAlphaFF (alpha_pos + base_offset, pixels); + else if (0xFE == m_info.AlphaMode) + pixels = UnpackAlphaFE (alpha_pos + base_offset, pixels); + else + throw new NotSupportedException (string.Format ("Not supported alpha channel mode 0x{0:X2}", m_info.AlphaMode)); + m_stride = m_info.iWidth * 4; + return ImageData.Create (m_info, PixelFormats.Bgra32, null, pixels, m_stride); + } + + byte[] UnpackAlphaFF (uint alpha_pos, byte[] pixels) + { + m_input.Position = alpha_pos; + var offsets = new int[m_info.iHeight]; + for (int i = 0; i < offsets.Length; ++i) + offsets[i] = m_input.ReadInt32(); + + var output = new byte[m_info.iWidth * m_info.iHeight * 4]; + int dst = 0; + for (int y = 0; y < m_info.iHeight; y++) + { + m_input.Position = alpha_pos + offsets[y]; + int src = (m_info.iHeight - y - 1) * m_stride; + int dst_a = dst + 3; + for (int x = 0; x < m_info.iWidth; ++x) + { + output[dst ] = pixels[src ]; + output[dst+1] = pixels[src+1]; + output[dst+2] = pixels[src+2]; + dst += 4; + src += m_pixel_size; + } + for (int x = 0; x < m_info.iWidth; ) + { + byte alpha = m_input.ReadUInt8(); + int count = m_input.ReadUInt8(); + count = Math.Min (count, m_info.iWidth - x); + x += count; + if (alpha > 0) + { + alpha = (byte)((alpha * 0xFF) >> 7); + while (count --> 0) + { + output[dst_a] = alpha; + dst_a += 4; + } + } + else + { + dst_a += 4 * count; + } + } + } + return output; + } + + byte[] UnpackAlphaFE (uint alpha_pos, byte[] pixels) + { + m_input.Position = alpha_pos; + var output = new byte[m_info.iWidth * m_info.iHeight * 4]; + int dst = 0; + for (int y = 0; y < m_info.iHeight; y++) + { + int src = (m_info.iHeight - y - 1) * m_stride; + int dst_a = dst + 3; + for (int x = 0; x < m_info.iWidth; ++x) + { + output[dst ] = pixels[src ]; + output[dst+1] = pixels[src+1]; + output[dst+2] = pixels[src+2]; + dst += 4; + src += m_pixel_size; + } + for (int x = 0; x < m_info.iWidth; x += 8) + { + byte alpha = m_input.ReadUInt8(); + int count = Math.Min (8, m_info.iWidth - x); + for (int i = 0; i < count; ++i) + { + output[dst_a] = (byte)-(alpha & 1); + dst_a += 4; + alpha >>= 1; + } + } + } + return output; + } + } } diff --git a/Legacy/HyperWorks/ImageG.cs b/Legacy/HyperWorks/ImageG.cs new file mode 100644 index 00000000..0e83d113 --- /dev/null +++ b/Legacy/HyperWorks/ImageG.cs @@ -0,0 +1,421 @@ +//! \file ImageG.cs +//! \date 2023 Aug 28 +//! \brief HyperWorks 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; + +// [951207][Love Gun] ACE OF SPADES +// I24 predecessor + +namespace GameRes.Formats.HyperWorks +{ + [Export(typeof(ImageFormat))] + public class GFormat : ImageFormat + { + public override string Tag => "G"; + public override string Description => "HyperWorks indexed image format"; + public override uint Signature => 0x1A477D00; + + public GFormat () + { + Signatures = new[] { 0x1A477D00u, 0u }; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (12); + if (header.ToUInt16 (2) != 0x1A47) + return null; + // not sure if 0x7D00 is a required signature, so rely on filename + if (header.ToUInt16 (0) != 0x7D00 && !file.Name.HasExtension (".G")) + return null; + return new ImageMetaData { + Width = header.ToUInt16 (8), + Height = header.ToUInt16 (10), + OffsetX = header.ToInt16 (4), + OffsetY = header.ToInt16 (6), + BPP = 8, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + var reader = new GReader (file, info); + return reader.Unpack(); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("GFormat.Write not implemented"); + } + } + + internal sealed class GReader + { + IBinaryStream m_input; + ImageMetaData m_info; + int m_stride; + byte[] m_palette_data; + byte[] m_output; + ushort[] m_buffer; + int[] m_line_ptr; + + public GReader (IBinaryStream input, ImageMetaData info) + { + m_input = input; + m_info = info; + m_stride = (m_info.iWidth * m_info.BPP / 8 + 1) & -2; + int s = Math.Max (0x142, m_stride / 2 + 2); // line buffer size + m_buffer = new ushort[s * 3 + 1]; + m_line_ptr = new int[3] { 1, 1 + s, 1 + s*2 }; + } + + public ImageData Unpack () + { + m_input.Position = 0x0C; + m_palette_data = m_input.ReadBytes (0x30); + m_input.Position = 0x40; + int width = ((m_info.iWidth + 7) & -8); + int rows = ((m_info.iHeight + 1) & -2); + m_output = new byte[m_stride * rows]; + InitColorTable(); + int blockW = width >> 1; + int blockH = rows >> 1; + SetupBitReader(); + for (int y = 0; y < blockH; ++y) + { + var dst = m_line_ptr[2]; + m_line_ptr[2] = m_line_ptr[1]; + m_line_ptr[1] = m_line_ptr[0]; + m_line_ptr[0] = dst; + int x = 0; + while (x < blockW) + { + if (GetNextBit() != 0) + { + m_buffer[dst++] = GetColorFromTable (x); + ++x; + } + else + { + int count = ExtractBits (BitTable1); + if (count >= 0x40) + count += ExtractBits (BitTable1); + int idx = ExtractBits (BitTable2) * 2; + int src = m_line_ptr[OffTable[idx + 1]]; + src += OffTable[idx] + x; + x += count; + while (count --> 0) + { + m_buffer[dst++] = m_buffer[src++]; + } + } + } + UnpackRow (y, blockW, m_line_ptr[0]); + } + var palette = UnpackPalette(); + return ImageData.Create (m_info, PixelFormats.Indexed8, palette, m_output, m_stride); + } + + void UnpackRow (int y, int width, int buf_pos) + { + int row1 = m_stride * y * 2; + int row2 = row1 + m_stride; + for (int i = 0; i < width; ++i) + { + ushort v = m_buffer[buf_pos++]; + ushort v0 = (ushort)((v & 0xF00 | ((v & 0xF000) >> 12)) + 0xA0A); + LittleEndian.Pack (v0, m_output, row2); + row2 += 2; + ushort v1 = (ushort)((((v & 0xF) << 8) | ((v & 0xF0) >> 4)) + 0xA0A); + LittleEndian.Pack (v1, m_output, row1); + row1 += 2; + } + } + + byte[] g_palIndexes = { 0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 0xC, 0xD, 0xA, 0xB, 0xE, 0xF }; + + BitmapPalette UnpackPalette () + { + var colors = new Color[42]; + for (int i = 0; i < 16; ++i) + { + int R = m_palette_data[3 * g_palIndexes[i] ]; R |= R << 4; + int G = m_palette_data[3 * g_palIndexes[i] + 1]; G |= G << 4; + int B = m_palette_data[3 * g_palIndexes[i] + 2]; B |= B << 4; + colors[i+10] = Color.FromRgb ((byte)R, (byte)G, (byte)B); + + int b = R & 1; + int c = (sbyte)R >> 1; + if (c < 0) + c += b; + colors[i+26].R = (byte)c; + + b = G & 1; + c = (sbyte)G >> 1; + if (c < 0) + c += b; + colors[i+26].G = (byte)c; + + b = B & 1; + c = (sbyte)B >> 1; + if (c < 0) + c += b; + colors[i+26].B = (byte)c; + } + return new BitmapPalette (colors); + } + + byte[] g_colorTable = new byte[256]; + + void InitColorTable () + { + int dst = 0; + for (int i = 0; i < 16; ++i) + for (int j = 0; j < 16; ++j) + g_colorTable[dst++] = (byte)((j + i + 1) & 0xF); + } + + ushort GetColorFromTable (int x) + { + ushort b0 = m_buffer[m_line_ptr[1] + x]; + int n0 = b0 & 0xF; + int n1 = (b0 >> 4) & 0xF; + int n2 = (b0 >> 8) & 0xF; + int n3 = (b0 >> 12) & 0xF; + + ushort b1 = m_buffer[m_line_ptr[1] + x - 1]; + int m0 = b1 & 0xF; + int m1 = (b1 >> 4) & 0xF; + int m2 = (b1 >> 8) & 0xF; + int m3 = (b1 >> 12) & 0xF; + + ushort b2 = m_buffer[m_line_ptr[0] + x - 1]; + int p0 = b2 & 0xF; + int p1 = (b2 >> 4) & 0xF; + int p2 = (b2 >> 8) & 0xF; + int p3 = (b2 >> 12) & 0xF; + + int r1 = n1; + if (n1 != n3 && (n1 != m1 || n1 != p1)) + { + if (p0 == p1) + r1 = p0; + else + r1 = m2; + } + if (GetNextBit() != 0) + r1 = AdjustColorTable (r1); + + int r0 = n0; + if (n0 != n2 && (n0 != m0 || n0 != p0)) + { + if (r1 == p1) + r0 = p1; + else + r0 = n3; + } + if (GetNextBit() != 0) + r0 = AdjustColorTable (r0); + + int r3 = n3; + if (r1 != n3 && (n3 != m3 || n3 != p3)) + { + if (p2 == p3) + r3 = p2; + else + r3 = p0; + } + if (GetNextBit() != 0) + r3 = AdjustColorTable (r3); + + int r2 = n2; + if (n2 != r0 && (n2 != m2 || n2 != p2)) + { + if (p3 == r3) + r2 = p3; + else + r2 = r1; + } + if (GetNextBit() != 0) + r2 = AdjustColorTable (r2); + + return (ushort)((r3 << 12) | (r2 << 8) | (r1 << 4) | r0); + } + + byte AdjustColorTable (int idx) + { + int shift_count = ExtractBits (BitTable3); + int i = 16 * idx + shift_count; + byte c = g_colorTable[i]; + if (shift_count != 0) + { + while (shift_count --> 0) + { + g_colorTable[i] = g_colorTable[i-1]; + --i; + } + g_colorTable[i] = c; + } + return c; + } + + int ExtractBits (byte[] table) + { + int idx = ((bits >> 8) & 0xFF) << 1; + int n = table[idx]; + if (n != 0) + { + if (n >= bitCount) + { + n -= bitCount; + bits <<= bitCount; + int b = m_input.ReadByte(); + if (b != -1) // XXX ignore EOF + bits |= b; + bitCount = 8; + } + bits <<= n; + bitCount -= n; + return table[idx+1]; + } + else + { + bits <<= bitCount; + int b = m_input.ReadByte(); + if (b != -1) // XXX ignore EOF + bits |= b; + bits <<= 8 - bitCount; + int t = table[idx+1]; + do + { + int i = GetNextBit(); + t = OffTable[2 * t + 54 + i]; + } + while (OffTable[2 * t + 54] != 0); + return OffTable[2 * t + 55]; + } + } + + int bits; + int bitCount; + + private void SetupBitReader () + { + bits = m_input.ReadUInt8() << 8; + bits |= m_input.ReadUInt8(); + bitCount = 8; + } + + private int GetNextBit () + { + bits <<= 1; + if (0 == --bitCount) + { + int b = m_input.ReadByte(); + if (b != -1) // XXX ignore EOF + bits |= b; + bitCount = 8; + } + return (bits >> 16) & 1; + } + + static readonly byte[] BitTable1 = new byte[] { + 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, + 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 8, + 0xD, 0, 0, 0, 1, 8, 0xE, 6, 7, 6, 7, 6, 7, 6, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 7, 0xA, 7, 0xA, 0, 2, 0, 7, 7, 0xB, 7, 0xB, 8, 0xF, 0, 3, 6, 8, 6, 8, 6, 8, 6, 8, 8, 0x10, 0, 4, + 0, 5, 8, 0x11, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 7, 0xC, 7, 0xC, 0, 8, 0, 6, 6, 9, 6, + 9, 6, 9, 6, 9, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + }; + static readonly byte[] BitTable2 = new byte[] { + 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 7, 0x17, 7, 0x17, 7, 0x16, 7, 0x16, 6, 0x0E, 6, + 0x0E, 6, 0x0E, 6, 0x0E, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, + 3, 4, 3, 4, 3, 4, 3, 6, 0x0D, 6, 0x0D, 6, 0x0D, 6, 0x0D, 6, 0x0C, 6, 0x0C, 6, 0x0C, 6, 0x0C, 5, 6, + 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 5, 8, 7, 0x14, 7, + 0x14, 7, 0x18, 7, 0x18, 6, 0x10, 6, 0x10, 6, 0x10, 6, 0x10, 6, 0x11, 6, 0x11, 6, 0x11, 6, 0x11, 6, + 0x0F, 6, 0x0F, 6, 0x0F, 6, 0x0F, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, 0x0A, 5, + 0x0A, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, + 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, + 6, 0x13, 6, 0x13, 6, 0x13, 6, 0x13, 6, 0x12, 6, 0x12, 6, 0x12, 6, 0x12, 5, 0x0B, 5, 0x0B, 5, 0x0B, + 5, 0x0B, 5, 0x0B, 5, 0x0B, 5, 0x0B, 5, 0x0B, 7, 0x19, 7, 0x19, 7, 0x1A, 7, 0x1A, 6, 0x15, 6, 0x15, + 6, 0x15, 6, 0x15, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 5, 9, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, + 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, + 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, + 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, + 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, + 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, + 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 0, + }; + static readonly byte[] BitTable3 = new byte[] { + 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, + 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 6, 6, 6, 6, 6, 6, 6, 6, 7, 8, 7, 8, 8, 0x0A, 8, 0x0B, + 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 4, 3, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 7, 6, 7, 6, 7, 6, 7, 7, 9, 7, 9, 0, 0x5F, 8, 0x0C, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + }; + static readonly short[] OffTable = new short[] { + 0, 1, 1, 1, -1, 0, -1, 1, -4, 0, -2, 0, 2, 1, -2, 1, 4, 1, 0, 2, 1, 2, -1, 2, -8, 0, -4, 1, 8, 1, + -2, 2, 2, 2, -4, 2, 4, 2, 8, 2, -8, 1, -0x10, 0, -8, 2, 0x10, 1, 0x10, 2, -0x10, 1, -0x10, 2, + 0x2C, 9, 0x2D, 0x0A, 0x2E, 0x0B, 0x2F, 0x0C, 0x30, 0x0D, 0x31, 0x32, 0x0E, 0x33, 0x0F, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x34, 0x15, 0x37, 0x35, 0x16, 0x38, 0x17, 0x39, 0x3D, 0x18, 0x19, 0x36, 0x1A, + 0x1B, 0x3A, 0x3C, 0x1C, 0x3B, 0x1D, 0x3E, 0x3F, 0x1E, 0x40, 0x1F, 0x45, 0x46, 0x43, 0x20, 0x21, + 0x48, 0x22, 0x41, 0x23, 0x42, 0x24, 0x25, 0x44, 0x47, 0x4F, 0x4A, 0x49, 0x53, 0x4B, 0x4D, 0x57, + 0x52, 0x59, 0x58, 0x4E, 0x50, 0x56, 0x26, 0x54, 0x4C, 0x51, 0x55, 0x27, 0x28, 0x5A, 0x2B, 0x29, + 0x5B, 0x2A, 0x5C, 0x5D, 0x5E, 0, 0, 0, 0x12, 0, 0x13, 0, 0x14, 0, 0x15, 0, 0x16, 0, 0x17, 0, 0x18, + 0, 0x19, 0, 0x1A, 0, 0x1B, 0, 0x1C, 0, 0x1D, 0, 0x1E, 0, 0x1F, 0, 0x20, 0, 0x21, 0, 0x22, 0, 0x23, + 0, 0x24, 0, 0x25, 0, 0x26, 0, 0x27, 0, 0x28, 0, 0x29, 0, 0x2A, 0, 0x2B, 0, 0x2C, 0, 0x2D, 0, 0x2E, + 0, 0x2F, 0, 0x30, 0, 0x31, 0, 0x32, 0, 0x33, 0, 0x34, 0, 0x35, 0, 0x36, 0, 0x37, 0, 0x38, 0, 0x39, + 0, 0x3A, 0, 0x3B, 0, 0x3C, 0, 0x3D, 0, 0x3E, 0, 0x3F, 0, 0x40, 0, 0x80, 0, 0x0C0, 0, 0x100, 0, + 0x140, 0x60, 0x61, 0, 0x0D, 0, 0x0E, + }; + } +} diff --git a/Legacy/HyperWorks/ImageI24.cs b/Legacy/HyperWorks/ImageI24.cs index ea822005..6b905437 100644 --- a/Legacy/HyperWorks/ImageI24.cs +++ b/Legacy/HyperWorks/ImageI24.cs @@ -23,22 +23,32 @@ // IN THE SOFTWARE. // -using GameRes.Utility; using System; using System.ComponentModel.Composition; using System.IO; using System.Windows.Media; +// [970905][Ucom] Winter Kiss // [980626][Love Gun] ACE OF SPADES 2 namespace GameRes.Formats.HyperWorks { + internal class I24MetaData : ImageMetaData + { + public byte Version; + } + [Export(typeof(ImageFormat))] public class I24Format : ImageFormat { - public override string Tag { get { return "I24"; } } - public override string Description { get { return "HyperWorks image format"; } } - public override uint Signature { get { return 0x41343249; } } // 'I24A' + public override string Tag => "I24"; + public override string Description => "HyperWorks RGB image format"; + public override uint Signature => 0x41343249; // 'I24A' + + public I24Format () + { + Signatures = new[] { 0x41343249u, 0x20343249u }; + } public override ImageMetaData ReadMetaData (IBinaryStream file) { @@ -46,16 +56,17 @@ namespace GameRes.Formats.HyperWorks int bpp = header.ToInt16 (0x10); if (bpp != 24) return null; - return new ImageMetaData { + return new I24MetaData { Width = header.ToUInt16 (0xC), Height = header.ToUInt16 (0xE), BPP = bpp, + Version = header[3], }; } public override ImageData Read (IBinaryStream file, ImageMetaData info) { - var reader = new I24Decoder (file, info); + var reader = new I24Decoder (file, (I24MetaData)info); return reader.Unpack(); } @@ -68,9 +79,9 @@ namespace GameRes.Formats.HyperWorks internal class I24Decoder { IBinaryStream m_input; - ImageMetaData m_info; + I24MetaData m_info; - public I24Decoder (IBinaryStream input, ImageMetaData info) + public I24Decoder (IBinaryStream input, I24MetaData info) { m_input = input; m_info = info; @@ -170,14 +181,24 @@ namespace GameRes.Formats.HyperWorks short s2 = shift_table[shift_idx + 1]; if (shift_token != 0) { - while (shift_token --> 0) + if (m_info.Version == 'A') { - shift_table[shift_idx] = shift_table[shift_idx - 2]; - shift_table[shift_idx+1] = shift_table[shift_idx - 1]; - shift_idx -= 2; + while (shift_idx > 0) + { + shift_table[shift_idx] = shift_table[shift_idx - 2]; + shift_table[shift_idx+1] = shift_table[shift_idx - 1]; + shift_idx -= 2; + } + shift_table[0] = s1; + shift_table[1] = s2; + } + else + { + shift_table[shift_idx ] = shift_table[shift_idx - 2]; + shift_table[shift_idx + 1] = shift_table[shift_idx - 1]; + shift_table[shift_idx - 2] = s1; + shift_table[shift_idx - 1] = s2; } - shift_table[0] = s1; - shift_table[1] = s2; } int src = 4 * (x + s1); if (color_token >= 216) diff --git a/Legacy/Inspire/ArcIDA.cs b/Legacy/Inspire/ArcIDA.cs index 21d92289..a5e767ee 100644 --- a/Legacy/Inspire/ArcIDA.cs +++ b/Legacy/Inspire/ArcIDA.cs @@ -30,19 +30,20 @@ using System.IO; using System.Text; using GameRes.Compression; +// [971205][Azlocks] Isle Mystique // [991001][Inspire] days innocent // [000707][inspire] ambience namespace GameRes.Formats.Inspire { - internal class IdaEntry : Entry + internal class IdaEntry : PackedEntry { public uint Flags; public uint Key; } [Export(typeof(ArchiveFormat))] - public class PakOpener : ArchiveFormat + public class IdaOpener : ArchiveFormat { public override string Tag { get { return "IDA"; } } public override string Description { get { return "Inspire resource archive"; } } @@ -50,6 +51,11 @@ namespace GameRes.Formats.Inspire public override bool IsHierarchic { get { return false; } } public override bool CanWrite { get { return false; } } + public IdaOpener () + { + Extensions = new[] { "ida", "mha" }; + } + public override ArcFile TryOpen (ArcView file) { int version = file.View.ReadInt32 (4); @@ -58,8 +64,9 @@ namespace GameRes.Formats.Inspire using (var index = file.CreateStream()) { var dir = new List(); + bool has_packed = false; long index_pos = 8; - for (;;) + do { index.Position = index_pos; uint entry_length = index.ReadUInt32(); @@ -76,15 +83,27 @@ namespace GameRes.Formats.Inspire var entry = FormatCatalog.Instance.Create (name); entry.Offset = offset; - entry.Size = size; - if (!entry.CheckPlacement (file.MaxOffset)) + entry.Size = entry.UnpackedSize = size; + if (offset > file.MaxOffset || offset < index_pos) return null; + entry.IsPacked = (flags & 0x14) != 0; entry.Flags = flags; entry.Key = key; + has_packed = has_packed || entry.IsPacked; dir.Add (entry); } + while (index_pos < dir[0].Offset); if (0 == dir.Count) return null; + if (has_packed) // set proper sizes + { + 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); } } diff --git a/Legacy/Legacy.csproj b/Legacy/Legacy.csproj index 127b6b06..576baaec 100644 --- a/Legacy/Legacy.csproj +++ b/Legacy/Legacy.csproj @@ -90,8 +90,11 @@ + + + @@ -125,6 +128,8 @@ + + @@ -132,6 +137,7 @@ + @@ -145,8 +151,10 @@ + + diff --git a/Legacy/Logg/ArcARF.cs b/Legacy/Logg/ArcARF.cs new file mode 100644 index 00000000..446ab626 --- /dev/null +++ b/Legacy/Logg/ArcARF.cs @@ -0,0 +1,141 @@ +//! \file ArcARF.cs +//! \date 2023 Sep 03 +//! \brief Logg 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.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +// [980417][Logg] Tenshi Kourin +// [980828][Logg] Kazeiro no Romance + +namespace GameRes.Formats.Logg +{ + [Export(typeof(ArchiveFormat))] + public class ArfOpener : ArchiveFormat + { + public override string Tag => "ARF"; + public override string Description => "Logg archive file"; + public override uint Signature => 0; + public override bool IsHierarchic => true; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (0); + if (!IsSaneCount (count)) + return null; + + uint index = 4; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + uint offset = file.View.ReadUInt32 (index); + if (offset <= index || offset > file.MaxOffset) + return null; + uint size = file.View.ReadUInt32 (index+4); + byte name_len = file.View.ReadByte (index+8); + var name = file.View.ReadString (index+9, name_len); + var entry = Create (name); + entry.Offset = offset; + entry.UnpackedSize = size; + dir.Add (entry); + index += name_len + 9u; + if (index > dir[0].Offset) + return null; + } + long last_offset = file.MaxOffset; + for (int i = count-1; i >= 0; --i) + { + var entry = dir[i] as PackedEntry; + entry.Size = (uint)(last_offset - entry.Offset); + last_offset = entry.Offset; + if (string.IsNullOrEmpty (entry.Name)) + dir.RemoveAt (i); + else + entry.IsPacked = entry.Size != entry.UnpackedSize; + } + 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 input = arc.File.CreateStream (pent.Offset, pent.Size); + var output = new byte[pent.UnpackedSize]; + Decompress (input, output); + return new BinMemoryStream (output, pent.Name); + } + + void Decompress (IBinaryStream input, byte[] output) + { + using (var bits = new LsbBitStream (input.AsStream, true)) + { + int dst = 0; + while (dst < output.Length) + { + if (bits.GetNextBit() == 0) + { + output[dst++] = (byte)bits.GetBits (8); + } + else + { + int count; + if (bits.GetNextBit() == 0) + count = 2; + else if (bits.GetNextBit() == 0) + count = 3; + else if (bits.GetNextBit() == 0) + count = 4; + else if (bits.GetNextBit() == 0) + count = 5; + else + { + switch (bits.GetBits (2)) + { + case 0: count = 6; break; + case 1: count = bits.GetBits (2) + 7; break; + case 2: count = bits.GetBits (4) + 11; break; + case 3: count = bits.GetBits (10) + 26; break; + default: throw new EndOfStreamException(); + } + } + int offset; + if (bits.GetNextBit() == 0) + offset = bits.GetBits (8); + else if (bits.GetNextBit() == 0) + offset = bits.GetBits (10) + 0x100; + else + offset = bits.GetBits (12) + 0x500; + Binary.CopyOverlapped (output, dst - offset - 1, dst, count); + dst += count; + } + } + } + } + } +} diff --git a/Legacy/Logg/ArcMBM.cs b/Legacy/Logg/ArcMBM.cs index dbb057e6..7d5870bf 100644 --- a/Legacy/Logg/ArcMBM.cs +++ b/Legacy/Logg/ArcMBM.cs @@ -36,11 +36,11 @@ namespace GameRes.Formats.Logg [Export(typeof(ArchiveFormat))] public class MbmOpener : ArchiveFormat { - public override string Tag { get { return "MBM"; } } - public override string Description { get { return "Logg Adv engine 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 => "MBM"; + public override string Description => "Logg Adv engine resource archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; public override ArcFile TryOpen (ArcView file) { @@ -63,19 +63,27 @@ namespace GameRes.Formats.Logg IDictionary GetArchiveIndex (ArcView file) { - uint last_offset = FileListMap.Value.Keys.Last(); + string list_name; + if (!ArcSizeToFileListMap.TryGetValue ((uint)file.MaxOffset, out list_name)) + return null; + var file_map = ReadFileList (list_name); + uint last_offset = file_map.Keys.Last(); if (last_offset != file.MaxOffset) return null; - return FileListMap.Value; + return file_map; } - Lazy> FileListMap = new Lazy> (ReadFileList); + static readonly Dictionary ArcSizeToFileListMap = new Dictionary { + { 0x0AB0F5F4, "logg_pl.lst" }, + { 0x0BFFD3DA, "logg_ak.lst" }, + { 0x09809196, "logg_th.lst" }, + }; - static IDictionary ReadFileList () + static IDictionary ReadFileList (string list_name) { var file_map = new SortedDictionary(); var comma = new char[] {','}; - FormatCatalog.Instance.ReadFileList ("logg_pl.lst", line => { + FormatCatalog.Instance.ReadFileList (list_name, line => { var parts = line.Split (comma, 2); uint offset = uint.Parse (parts[0], NumberStyles.HexNumber); if (2 == parts.Length) diff --git a/Legacy/Logg/ImageFRM.cs b/Legacy/Logg/ImageFRM.cs new file mode 100644 index 00000000..145cedfc --- /dev/null +++ b/Legacy/Logg/ImageFRM.cs @@ -0,0 +1,63 @@ +//! \file ImageFRM.cs +//! \date 2023 Sep 03 +//! \brief Logg image file. +// +// 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.Logg +{ + [Export(typeof(ImageFormat))] + public class FrmFormat : ImageFormat + { + public override string Tag { get => "FRM"; } + public override string Description { get => "Logg image format"; } + public override uint Signature { get => 0x4D5246; } // 'FRM' + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + var header = file.ReadHeader (0x10); + return new ImageMetaData { + Width = header.ToUInt32 (4), + Height = header.ToUInt32 (8), + BPP = 8, + }; + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + file.Position = 0x0C; + int stride = file.ReadInt32(); + var palette = ReadPalette (file.AsStream); + var pixels = file.ReadBytes (stride * info.iHeight); + return ImageData.Create (info, PixelFormats.Indexed8, palette, pixels, stride); + } + + public override void Write (Stream file, ImageData image) + { + throw new System.NotImplementedException ("FrmFormat.Write not implemented"); + } + } +} diff --git a/Legacy/Omi/ArcDAT.cs b/Legacy/Omi/ArcDAT.cs new file mode 100644 index 00000000..c2f5a7fb --- /dev/null +++ b/Legacy/Omi/ArcDAT.cs @@ -0,0 +1,213 @@ +//! \file ArcDAT.cs +//! \date 2023 Sep 04 +//! \brief OMI Script 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 GameRes.Utility; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Text; + +namespace GameRes.Formats.Omi +{ + [Export(typeof(ArchiveFormat))] + public class DatOpener : ArchiveFormat + { + public override string Tag => "DAT/OMI"; + public override string Description => "OMI Script Engine resource archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + internal const uint DefaultKey = 7654321u; + + public DatOpener () + { + ContainedFormats = new[] { "BMP", "TGA", "WAV", "TXT" }; + } + + public override ArcFile TryOpen (ArcView file) + { + if (!VFS.IsPathEqualsToFileName (file.Name, "scrdat")) + return null; + using (var input = file.CreateStream()) + using (var index = new DecryptedStream (input, DefaultKey, 0)) + { + var line = index.ReadLine(); + int count = int.Parse (line); + if (!IsSaneCount (count)) + return null; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = index.ReadLine(); + line = index.ReadLine(); + uint size = uint.Parse (line); + var entry = Create (name); + entry.Size = size; + entry.IsPacked = entry.Type == "image"; + dir.Add (entry); + } + long data_pos = index.Position; + for (int i = 0; i < count; ++i) + { + dir[i].Offset = data_pos; + if (!dir[i].CheckPlacement (file.MaxOffset)) + return null; + data_pos += dir[i].Size; + } + return new ArcFile (file, this, dir); + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var pent = (PackedEntry)entry; + Stream input = arc.File.CreateStream (entry.Offset, entry.Size); + input = new DecryptedStream (input, DefaultKey, (uint)entry.Offset); + if (!pent.IsPacked) + return input; + using (var packed = new BinaryStream (input, pent.Name)) + { + var unpacked = DecompressRle (packed); + if (pent.UnpackedSize == 0) + pent.UnpackedSize = (uint)unpacked.Length; + return new BinMemoryStream (unpacked, pent.Name); + } + } + + internal static byte[] DecompressRle (IBinaryStream input) + { + int size = input.ReadInt32(); + var output = new byte[size * 2]; + ushort rle_marker = input.ReadUInt16(); + int dst = 0; + while (dst < output.Length) + { + input.Read (output, dst, 2); + if (output.ToUInt16 (dst) == rle_marker) + { + input.Read (output, dst, 2); + dst += 2; + int count = input.ReadUInt16() - 1; + if (count > 0) + { + count *= 2; + Binary.CopyOverlapped (output, dst-2, dst, count); + dst += count; + } + } + else + { + dst += 2; + } + } + return output; + } + } + + internal class DecryptedStream : InputProxyStream + { + private uint m_key; + + static readonly Encoding Encoding = Encodings.cp932; + + public override bool CanSeek { get => false; } + public override long Position + { + get => BaseStream.Position; + set => throw new NotSupportedException ("Stream.Position property is not supported"); + } + + public DecryptedStream (Stream stream, uint key, uint start_offset) : base (stream) + { + if (start_offset > 0) + { + do + { + key = 5 * key - 3; + } + while (--start_offset > 0); + } + m_key = key; + } + + public override int Read (byte[] buffer, int offset, int count) + { + int read = BaseStream.Read (buffer, offset, count); + Decrypt (buffer, offset, read); + return read; + } + + byte[] m_byte_buffer = new byte[1]; + + public override int ReadByte () + { + int b = BaseStream.ReadByte(); + if (-1 != b) + { + m_byte_buffer[0] = (byte)b; + Decrypt (m_byte_buffer, 0, 1); + b = m_byte_buffer[0]; + } + return b; + } + + internal void Decrypt (byte[] data, int offset, int count) + { + for (int i = 0; i < count; ++i) + { + data[offset+i] = (byte)(Binary.RotByteR (data[offset+i], 1) - m_key); + m_key = 5 * m_key - 3; + } + } + + byte[] m_buffer; + + public string ReadLine () + { + if (null == m_buffer) + m_buffer = new byte[32]; + int size = 0; + for (;;) + { + int b = ReadByte(); + if (-1 == b || '\n' == b) + break; + if (m_buffer.Length == size) + { + Array.Resize (ref m_buffer, checked(size/2*3)); + } + m_buffer[size++] = (byte)b; + } + return Encoding.GetString (m_buffer, 0, size); + } + + public override long Seek (long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + } +} diff --git a/Legacy/Rare/ArcX.cs b/Legacy/Rare/ArcX.cs new file mode 100644 index 00000000..4cca0ad0 --- /dev/null +++ b/Legacy/Rare/ArcX.cs @@ -0,0 +1,132 @@ +//! \file ArcX.cs +//! \date 2023 Sep 02 +//! \brief Rare 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; + +// [990528][Rare] Seisen Ren'ai Yuugi + +namespace GameRes.Formats.Rare +{ + [Export(typeof(ArchiveFormat))] + public class XOpener : ArchiveFormat + { + public override string Tag => "X/RARE"; + public override string Description => "Rare resource archive"; + public override uint Signature => 0; + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + static readonly Dictionary> KnownExeMap = new Dictionary> { + { "seisen.exe", Tuple.Create (0x3A9A0u, 715) }, + }; + + public override ArcFile TryOpen (ArcView file) + { + if (!VFS.IsPathEqualsToFileName (file.Name, "PP.X")) + return null; + string full_exe_name = null; + Tuple index_pos = null; + foreach (var exe_name in KnownExeMap.Keys) + { + full_exe_name = VFS.ChangeFileName (file.Name, exe_name); + if (VFS.FileExists (full_exe_name)) + { + index_pos = KnownExeMap[exe_name]; + break; + } + } + if (null == index_pos) + return null; + uint index_offset = index_pos.Item1; + int count = index_pos.Item2; + using (var index = VFS.OpenView (full_exe_name)) + { + index.View.Reserve (index_offset, (uint)count * 12u); + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var entry = new PackedEntry { + Name = string.Format ("PP#{0:D5}.BMP", i), + Type = "image", + Offset = index.View.ReadUInt32 (index_offset), + Size = index.View.ReadUInt32 (index_offset+4), + UnpackedSize = index.View.ReadUInt32 (index_offset+8), + IsPacked = true, + }; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_offset += 12; + } + return new ArcFile (file, this, dir); + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var pent = (PackedEntry)entry; + using (var input = arc.File.CreateStream (entry.Offset, entry.Size)) + { + var output = new byte[pent.UnpackedSize]; + Decompress (input, output); + return new BinMemoryStream (output, entry.Name); + } + } + + internal static void Decompress (IBinaryStream input, byte[] output) + { + var frame = new byte[0x400]; + int frame_pos = 1; + int dst = 0; + using (var bits = new MsbBitStream (input.AsStream, true)) + { + while (dst < output.Length) + { + int ctl = bits.GetNextBit(); + if (-1 == ctl) + break; + if (ctl != 0) + { + int v = bits.GetBits (8); + output[dst++] = frame[frame_pos++ & 0x3FF] = (byte)v; + } + else + { + int offset = bits.GetBits (10); + int count = bits.GetBits (5) + 2; + while (count --> 0) + { + byte v = frame[offset++ & 0x3FF]; + output[dst++] = frame[frame_pos++ & 0x3FF] = v; + } + } + } + } + } + } +} diff --git a/Legacy/Rhss/ArcCRG.cs b/Legacy/Rhss/ArcCRG.cs new file mode 100644 index 00000000..8a04632a --- /dev/null +++ b/Legacy/Rhss/ArcCRG.cs @@ -0,0 +1,83 @@ +//! \file ArcCRG.cs +//! \date 2023 Aug 28 +//! \brief Raishū Hyōjun Script 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.Compression; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes.Formats.Rhss +{ + [Export(typeof(ArchiveFormat))] + public class PakOpener : ArchiveFormat + { + public override string Tag => "DAT/CRG"; + public override string Description => "RHSS engine resource archive"; + public override uint Signature => 0x00475243; // 'CRG' + public override bool IsHierarchic => false; + public override bool CanWrite => false; + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (4); + if (!IsSaneCount (count)) + return null; + uint index_pos = 8; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + var name = file.View.ReadString (index_pos+8, 0x30); + var entry = Create (name); + entry.Offset = file.View.ReadUInt32 (index_pos); + entry.Size = file.View.ReadUInt32 (index_pos+4); + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_pos += 60; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var pent = entry as PackedEntry; + if (null != pent) + { + if (!pent.IsPacked && arc.File.View.AsciiEqual (entry.Offset, "CMP\0")) + { + pent.IsPacked = true; + pent.UnpackedSize = arc.File.View.ReadUInt32 (entry.Offset + 0x4C); + } + if (pent.IsPacked) + { + Stream input = arc.File.CreateStream (entry.Offset+0x50, entry.Size-0x50); + input = new ZLibStream (input, CompressionMode.Decompress); + return new XoredStream (input, 0xFF); + } + } + return base.OpenEntry (arc, entry); + } + } +} diff --git a/Legacy/SplushWave/ArcDAT.cs b/Legacy/SplushWave/ArcDAT.cs index 05b39396..dfd90fe9 100644 --- a/Legacy/SplushWave/ArcDAT.cs +++ b/Legacy/SplushWave/ArcDAT.cs @@ -27,6 +27,9 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; +using System.Windows.Media; + +// [030817][Splush Wave] Knock Out -Taisengata Datsui Mahjong- namespace GameRes.Formats.SplushWave { @@ -129,5 +132,92 @@ namespace GameRes.Formats.SplushWave } return new BinMemoryStream (output, 0, dst); } + + public override IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var fent = (FlkEntry)entry; + var input = BinaryStream.FromStream (OpenEntry (arc, fent), fent.Name); + if ((fent.Flags & 0x10) == 0) + return ImageFormatDecoder.Create (input); + try + { + var info = Swg.ReadMetaData (input) as SwgMetaData; + if (null == info) + { + input.Position = 0; + return new ImageFormatDecoder(input); + } + return new Swg1ImageDecoder (input, info); + } + catch + { + input.Dispose(); + throw; + } + } + + static readonly ResourceInstance s_swg = new ResourceInstance ("SWG"); + + internal static SwgFormat Swg { get => s_swg.Value; } + } + + internal sealed class Swg1ImageDecoder : BinaryImageDecoder + { + SwgMetaData m_info; + + public Swg1ImageDecoder (IBinaryStream input, SwgMetaData info) : base (input, info) + { + SourceFormat = DatOpener.Swg; + m_info = info; + } + + static readonly byte[] PlaneMap = { 3, 2, 1, 0 }; + + protected override ImageData GetImageData () + { + m_input.Position = m_info.DataOffset; + int stride = 4 * m_info.iWidth; + int plane_size = m_info.iWidth * m_info.iHeight; + var output = new byte[stride * m_info.iHeight]; + ushort[] ctl_buf = new ushort[m_info.iHeight]; + for (int c = 0; c < 4; ++c) + { + int compress_method = ReadU16BE(); + if (0 == compress_method) + { + int dst = PlaneMap[c] + stride * (m_info.iHeight - 1); + for (int y = 0; y < m_info.iHeight; ++y) + { + for (int x = 0; x < stride; x += 4) + { + output[dst+x] = m_input.ReadUInt8(); + } + dst -= stride; + } + continue; + } + if (compress_method != 1) + throw new InvalidFormatException(); + for (int y = 0; y < m_info.iHeight; ++y) + { + ctl_buf[y] = ReadU16BE(); + } + int row = PlaneMap[c]; + for (int y = 0; y < m_info.iHeight; ++y) + { + int dst = row; + int row_size = ctl_buf[y]; + SwgFormat.DecompressRow (m_input, row_size, output, dst, 4); + row += stride; + } + } + return ImageData.Create (m_info, PixelFormats.Bgra32, null, output, stride); + } + + ushort ReadU16BE () + { + ushort le = m_input.ReadUInt16(); + return (ushort)(le >> 8 | le << 8); + } } } diff --git a/Legacy/SplushWave/ImageSWG.cs b/Legacy/SplushWave/ImageSWG.cs index 41274bb9..490fe560 100644 --- a/Legacy/SplushWave/ImageSWG.cs +++ b/Legacy/SplushWave/ImageSWG.cs @@ -68,7 +68,7 @@ namespace GameRes.Formats.SplushWave { var meta = (SwgMetaData)info; PixelFormat format = meta.BPP == 8 ? PixelFormats.Indexed8 - : meta.BPP == 32 ? PixelFormats.Bgr32 : PixelFormats.Bgr24; + : meta.BPP == 32 ? PixelFormats.Bgra32 : PixelFormats.Bgr24; BitmapPalette palette = null; if (meta.BPP == 8) { @@ -77,36 +77,40 @@ namespace GameRes.Formats.SplushWave } int stride = meta.iWidth * meta.BPP / 8; file.Position = meta.DataOffset; -// var pixels = new byte[stride * meta.iHeight]; - var pixels = new byte[4 * meta.iWidth * meta.iHeight]; + var pixels = new byte[stride * meta.iHeight]; if (!meta.IsCompressed) { file.Read (pixels, 0, pixels.Length); return ImageData.CreateFlipped (meta, format, palette, pixels, stride); } - var input = file.ReadBytes ((int)(file.Length - file.Position)); - if (!Decompress (input, pixels, meta.Depth + 2, meta.iWidth, meta.iHeight)) + if (!Decompress (file, pixels, meta.Depth + 2, meta.iWidth, meta.iHeight)) throw new InvalidFormatException ("Invalid SWG file."); return ImageData.CreateFlipped (meta, format, palette, pixels, stride); } - bool Decompress (byte[] input, byte[] output, int channels, int width, int height) + static readonly byte[] PlaneMap = { 2, 1, 0, 3 }; + + bool Decompress (IBinaryStream input, byte[] output, int channels, int width, int height) { - int src = 0; - if (input[0] != 0 || input[1] != 1) + long start_pos = input.Position; + byte hi = input.ReadUInt8(); + byte lo = input.ReadUInt8(); + if (hi != 0 || lo != 1) { + input.Position = start_pos; int n = 0; for (int i = 0; i < channels; ++i) { - if (0 == input[i]) + if (0 == input.ReadByte()) ++n; } if (n != channels) return false; - src = 4; + input.Position = start_pos + 4; + hi = input.ReadUInt8(); + lo = input.ReadUInt8(); } - int compress_method = input[src+1] + (input[src] << 8); - src += 2; + int compress_method = lo | hi << 8; if (0 == compress_method) { for (int i = 0; i < channels; ++i) @@ -115,7 +119,7 @@ namespace GameRes.Formats.SplushWave int count = height * width; while (count --> 0) { - output[pos] = input[src++]; + output[pos] = input.ReadUInt8(); pos += channels; } } @@ -123,63 +127,57 @@ namespace GameRes.Formats.SplushWave } if (compress_method != 1) return false; - int dst = 0; - int v33 = src; - int v37 = height * channels; - src += 2 * v37; - for (int row = 0; row < v37; ++row) + int stride = width * channels; + var row_sizes = input.ReadBytes (2 * height * channels); + int ctl_pos = 0; + for (int c = 0; c < channels; ++c) + for (int y = height - 1; y >= 0; --y) { - int y = row % height; - dst = channels * (width * (height - y - 1) + 1) - row / height - 1; - if (dst > output.Length) - return true; - int v24 = 0; - int v36 = input[v33+1] + (input[v33] << 8); - v33 += 2; - do - { - byte lo = input[src]; - byte hi = input[src+1]; - if (lo != 0) - { - if (lo < 0x81) - { - ++src; - int count = lo + 1; - v24 += count + 1; - while (count --> 0) - { - output[dst] = input[src++]; - dst += channels; - } - } - else - { - src += 2; - v24 += 2; - int count = Math.Min (0x101 - lo, output.Length - dst); - while (count --> 0) - { - output[dst] = hi; - dst += channels; - } - } - } - else - { - src += 2; - v24 += 2; - output[dst] = hi; - dst += channels; - } - if (dst >= output.Length) - return true; - } - while (v24 < v36); + int dst = stride * y + PlaneMap[c]; + int row_size = row_sizes[ctl_pos+1] | row_sizes[ctl_pos] << 8; + ctl_pos += 2; + DecompressRow (input, row_size, output, dst, channels); } return true; } + internal static void DecompressRow (IBinaryStream input, int row_size, byte[] output, int dst, int step) + { + int x = 0; + while (x < row_size) + { + byte ctl = input.ReadUInt8(); + if (ctl == 0) + { + byte v = input.ReadUInt8(); + x += 2; + output[dst] = v; + dst += step; + } + else if (ctl < 0x81u) + { + int count = ctl + 1; + x += count + 1; + while (count --> 0) + { + output[dst] = input.ReadUInt8(); + dst += step; + } + } + else + { + byte v = input.ReadUInt8(); + x += 2; + int count = 0x101 - ctl; + while (count --> 0) + { + output[dst] = v; + dst += step; + } + } + } + } + public override void Write (Stream file, ImageData image) { throw new System.NotImplementedException ("SwgFormat.Write not implemented");