diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 388b38a2..bde0d065 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -139,6 +139,8 @@ + + @@ -157,6 +159,7 @@ + @@ -178,6 +181,7 @@ + @@ -1094,6 +1098,7 @@ + diff --git a/ArcFormats/CatSystem/ArcZT.cs b/ArcFormats/CatSystem/ArcZT.cs new file mode 100644 index 00000000..d0a9e4b5 --- /dev/null +++ b/ArcFormats/CatSystem/ArcZT.cs @@ -0,0 +1,120 @@ +//! \file ArcZT.cs +//! \date 2021 May 25 +//! \brief CatSystem2 pack file. +// +// Copyright (C) 2021 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 GameRes.Compression; + +namespace GameRes.Formats.CatSystem +{ + [Export(typeof(ArchiveFormat))] + public class ZtOpener : ArchiveFormat + { + public override string Tag { get { return "ZT/PACK"; } } + public override string Description { get { return "CatSystem2 pack file"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return true; } } + public override bool CanWrite { get { return false; } } + + public ZtOpener () + { + Extensions = new string[] { "zt" }; + } + + struct ZtSubdirectory + { + public string Name; + public long Offset; + } + + public override ArcFile TryOpen (ArcView file) + { + if (file.MaxOffset < 0x11C) + return null; + if (0x110 > file.View.ReadUInt32 (8) || 1 < file.View.ReadUInt32 (0xC)) + return null; // First entry is too small, or not a file or folder + var dir = new List (); + var subdirs = new Queue (); + const string sep = "\\"; + string parent_name = ""; + long offset = 0; + uint offset_next; + do + { + offset_next = file.View.ReadUInt32 (offset); + uint entry_size = file.View.ReadUInt32 (offset+8); + if (0 != offset_next && 0xC + entry_size > offset_next) + return null; + uint attributes = file.View.ReadUInt32 (offset+0xC); + string name = file.View.ReadString (offset+0x10, 0x104); + if (1 < attributes || 0 == name.Length) + return null; + + if (0 == attributes) + { + var entry = FormatCatalog.Instance.Create (parent_name + name); + uint packed_size = file.View.ReadUInt32 (offset+0x114); + if (0x110 + packed_size != entry_size) + return null; + entry.Offset = offset + 0x11C; + entry.Size = packed_size; + entry.UnpackedSize = file.View.ReadUInt32 (offset+0x118); + entry.IsPacked = true; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + } + else if (0 != file.View.ReadUInt32 (offset+4)) + { + subdirs.Enqueue (new ZtSubdirectory { Name = parent_name + name + sep, Offset = offset }); + } + + if (0 == offset_next && 0 != subdirs.Count) + { // No more entries in current directory, go to next subdirectory + var subdir = subdirs.Dequeue (); + parent_name = subdir.Name; + offset = subdir.Offset; + offset_next = file.View.ReadUInt32 (offset+4); // offset_child + if (0xC + file.View.ReadUInt32 (offset+8) > offset_next) + return null; + } + offset += offset_next; + } + while (0 != offset_next); + + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var pentry = (PackedEntry)entry; + if (0 == pentry.UnpackedSize) + return Stream.Null; + var input = arc.File.CreateStream (entry.Offset, entry.Size); + return new ZLibStream (input, CompressionMode.Decompress); + } + } +} diff --git a/ArcFormats/Enigma/ArcEVB.cs b/ArcFormats/Enigma/ArcEVB.cs index e117e293..a4adfc95 100644 --- a/ArcFormats/Enigma/ArcEVB.cs +++ b/ArcFormats/Enigma/ArcEVB.cs @@ -29,6 +29,7 @@ using System.IO; using System.Linq; using System.Text; using GameRes.Utility; +using GameRes.Compression; namespace GameRes.Formats.Enigma { public enum NodeTypes { @@ -45,14 +46,34 @@ namespace GameRes.Formats.Enigma { public override bool IsHierarchic { get { return true; } } public override bool CanWrite { get { return false; } } + public EvbPackOpener() { + Signatures = new uint[] { 0x425645, 0x905a4d, 0 }; + Extensions = new[] { "exe" }; + } + public override ArcFile TryOpen(ArcView file) { - uint index_size = file.View.ReadUInt32(0x40) + 68; - uint index_offset = 0x4F; + uint base_offset = 0; + if (file.View.AsciiEqual(0, "MZ")) { + var exe = new ExeFile(file); + var sig = new byte[] { 0x45, 0x56, 0x42, 0x00 }; + if (exe.ContainsSection(".enigma1")) { + var ofs = exe.FindString(exe.Sections[".enigma1"], sig); + if (ofs != -1) + base_offset = (uint)ofs; + } + if (base_offset == 0) + return null; + } + else if (!file.View.AsciiEqual(0, "EVB")) + return null; + + uint index_size = file.View.ReadUInt32(base_offset + 0x40) + base_offset + 68; + uint index_offset = base_offset + 0x4F; uint file_offset = index_size; var dir = new List(); var name_buffer = new StringBuilder(); - var counts = new List { file.View.ReadUInt32(0x4C) }; + var counts = new List { file.View.ReadUInt32(base_offset + 0x4C) }; var names = new List { "" }; while (index_offset < index_size - 4) { @@ -73,12 +94,12 @@ namespace GameRes.Formats.Enigma { index_offset++; counts[counts.Count - 1]--; if (type == NodeTypes.File) { - var entry = Create(Path.Combine(names.Concat(new[] { name }).ToArray())); + var entry = Create(Path.Combine(names.Concat(new[] { name }).ToArray())); uint unpacked_size = file.View.ReadUInt32(index_offset + 2); uint size = file.View.ReadUInt32(index_offset + 49); - if (unpacked_size != size) - return null; // packed entry not implemented + entry.IsPacked = unpacked_size != size; entry.Offset = file_offset; + entry.UnpackedSize = unpacked_size; entry.Size = size; file_offset += size; if (!entry.CheckPlacement(file.MaxOffset)) @@ -106,5 +127,30 @@ namespace GameRes.Formats.Enigma { return new ArcFile(file, this, dir); } + + public override Stream OpenEntry(ArcFile arc, Entry entry) { + var pent = entry as PackedEntry; + if (pent.IsPacked) { + uint header_size = arc.File.View.ReadUInt32(pent.Offset); + uint offset = header_size; + Stream input = null; + + for (uint i = 8; i < header_size; i += 12) { + uint chunk_size = arc.File.View.ReadUInt32(pent.Offset + i); + var chunk = new aPLibStream( + arc.File.CreateStream(pent.Offset + offset, chunk_size) + ); + if (input != null) + input = new ConcatStream(input, chunk); + else + input = chunk; + offset += chunk_size; + } + + return input; + } + else + return arc.File.CreateStream(pent.Offset, pent.Size); + } } } diff --git a/ArcFormats/Fog/ArcDAT.cs b/ArcFormats/Fog/ArcDAT.cs new file mode 100644 index 00000000..421de416 --- /dev/null +++ b/ArcFormats/Fog/ArcDAT.cs @@ -0,0 +1,93 @@ +//! \file ArcDAT.cs +//! \date 2026-01-24 +//! \brief FOG resource archive format. +// +// Copyright (C) 2026 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 GameRes.Utility; + +namespace GameRes.Formats.Fog { + internal class DatEntry : Entry { + public string FileName; + } + + [Export(typeof(ArchiveFormat))] + public class DatOpener : ArchiveFormat { + public override string Tag { get { return "DAT/FOG"; } } + public override string Description { get { return "FOG resource archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return true; } } + public override bool CanWrite { get { return false; } } + + public override ArcFile TryOpen(ArcView file) { + var base_name = Path.GetFileNameWithoutExtension(file.Name); + bool multipart = base_name.Contains("_"); + if (multipart) + base_name = base_name.Split('_')[0]; + var index_file_name = base_name + "File.dat"; + if (!File.Exists(index_file_name)) + return null; + + var index = File.ReadAllBytes(index_file_name); + var transformer = new NotTransform(); + transformer.TransformBlock(index, 0, index.Length, index, 0); + + using (var mem = new MemoryStream(index)) + using (var reader = new BinaryReader(mem)) { + var dir = new List(); + + while (mem.Position < mem.Length) { + uint name_length = Binary.BigEndian(reader.ReadUInt32()); + string name = Binary.GetCString(reader.ReadBytes((int)name_length), 0); + var entry = Create(name); + if (multipart) { + uint part = Binary.BigEndian(reader.ReadUInt32()); + entry.FileName = string.Format("{0}_{1:00}.dat", base_name, part); + if (!File.Exists(entry.FileName)) + return null; + } + else { + entry.FileName = file.Name; + } + reader.ReadUInt32(); + entry.Offset = Binary.BigEndian(reader.ReadUInt32()); + reader.ReadUInt32(); + entry.Size = Binary.BigEndian(reader.ReadUInt32()); + dir.Add(entry); + } + + return new ArcFile(file, this, dir); + } + } + + public override Stream OpenEntry(ArcFile arc, Entry entry) { + var dent = entry as DatEntry; + using (var data_file = new ArcView(dent.FileName)) { + var input = data_file.CreateStream(dent.Offset, dent.Size); + return new XoredStream(input, 0xFF); + } + } + } +} diff --git a/ArcFormats/Resources/Formats.dat b/ArcFormats/Resources/Formats.dat index c56c9e26..3a4af139 100644 Binary files a/ArcFormats/Resources/Formats.dat and b/ArcFormats/Resources/Formats.dat differ diff --git a/ArcFormats/aNCHOR/ArcFPD.cs b/ArcFormats/aNCHOR/ArcFPD.cs new file mode 100644 index 00000000..f9c1a3cc --- /dev/null +++ b/ArcFormats/aNCHOR/ArcFPD.cs @@ -0,0 +1,134 @@ +//! \file ArcFPD.cs +//! \date 2026-01-19 +//! \brief AGES Mk2 resource archive. +// +// Copyright (C) 2026 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Text; +using GameRes.Compression; +using GameRes.Utility; + +namespace GameRes.Formats.Anchor { + internal class FpdArchive : ArcFile { + public readonly byte[] Key; + + public FpdArchive(ArcView arc, ArchiveFormat impl, ICollection dir, byte[] key) : base (arc, impl, dir) { + Key = key; + } + } + + [Export(typeof(ArchiveFormat))] + public class FpdOpener : ArchiveFormat { + public override string Tag { get { return "FPD"; } } + public override string Description { get { return "AGES Mk2 resource archive"; } } + public override uint Signature { get { return 0x00445046; } } // 'FPD\x00' + public override bool IsHierarchic { get { return true; } } + public override bool CanWrite { get { return false; } } + + public override ArcFile TryOpen(ArcView file) { + int version = Binary.BigEndian(file.View.ReadInt32(4)); + if (version != 2) + return null; + + int count = (int)Binary.BigEndian(file.View.ReadInt64(8)); + if (!IsSaneCount(count)) + return null; + + long data_start = Binary.BigEndian(file.View.ReadInt64(0x10)); + long offset = 0x38; + if (data_start < count * 32 + offset) + return null; + uint index_size = (uint)(data_start - offset); + + var key = QueryKey(); + var dir = new List(count); + + using (var input = file.CreateStream(offset, (uint)(data_start - offset))) + using (var decrypted = new ByteStringEncryptedStream(input, key)) + using (var reader = new BinaryReader(decrypted)) { + var name_offsets = new List(count); + for (int i = 0; i < count; i++) { + long name_offset = Binary.BigEndian(reader.ReadInt64()); + long data_offset = Binary.BigEndian(reader.ReadInt64()); + long size = Binary.BigEndian(reader.ReadInt64()); + long unpacked_size = Binary.BigEndian(reader.ReadInt64()); + + var entry = new PackedEntry { + Offset = data_start + data_offset, + Size = (uint)size, + UnpackedSize = (uint)unpacked_size, + IsPacked = unpacked_size != 0 + }; + if (!entry.CheckPlacement(file.MaxOffset)) + return null; + dir.Add(entry); + name_offsets.Add(name_offset); + } + var name_block = reader.ReadBytes((int)(data_start - offset - count * 32)); + using (var mem = new MemoryStream(name_block)) + using (var stream = new ZLibStream(mem, CompressionMode.Decompress)) + using (var output = new MemoryStream()) { + stream.CopyTo(output); + var names = output.ToArray(); + for (int i = 0; i < count; i++) { + dir[i].Name = Binary.GetCString(names, (int)name_offsets[i], Encoding.UTF8); + dir[i].Type = FormatCatalog.Instance.GetTypeFromName(dir[i].Name); + } + } + return new FpdArchive(file, this, dir, key); + } + } + + public override Stream OpenEntry(ArcFile arc, Entry entry) { + var farc = arc as FpdArchive; + var pent = entry as PackedEntry; + var input = farc.File.CreateStream(entry.Offset, entry.Size); + var decrypted = new ByteStringEncryptedStream(input, farc.Key); + if (pent.IsPacked) + return new ZLibStream(decrypted, CompressionMode.Decompress); + else + return decrypted; + // TODO: epk decryption + } + + byte[] QueryKey() { + return DefaultScheme.ArchiveKey; + } + + FpdScheme DefaultScheme = new FpdScheme(); + + public override ResourceScheme Scheme { + get { return DefaultScheme; } + set { DefaultScheme = (FpdScheme)value; } + } + } + + [Serializable] + public class FpdScheme : ResourceScheme { + public byte[] ArchiveKey; + public byte[] EpkKey; + } +} diff --git a/ArcFormats/aNCHOR/AudioFCD.cs b/ArcFormats/aNCHOR/AudioFCD.cs new file mode 100644 index 00000000..56f79fa1 --- /dev/null +++ b/ArcFormats/aNCHOR/AudioFCD.cs @@ -0,0 +1,55 @@ +//! \file AudioFCD.cs +//! \date 2026-01-19 +//! \brief AGES Mk2 audio format. +// +// Copyright (C) 2026 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.Linq; + +namespace GameRes.Formats.Anchor +{ + [Export(typeof(AudioFormat))] + public class FcdAudio : AudioFormat + { + public override string Tag { get { return "FCD"; } } + public override string Description { get { return "AGES Mk2 audio format"; } } + public override uint Signature { get { return 0x00444346; } } // 'FCD\x00' + + public FcdAudio () + { + Extensions = new string[] { "fcd" }; + } + + public override SoundInput TryOpen (IBinaryStream file) + { + file.Position = 4; + // guess: big endian, version=2, type=0 (ogg), offset=0xC + byte[] data = { 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x4F, 0x67, 0x67, 0x53 }; + if (!file.ReadBytes (0x0C).SequenceEqual (data)) + throw new NotSupportedException(); + return new OggInput (new StreamRegion (file.AsStream, 0x0C)); + } + } +} diff --git a/ArcFormats/aPLibStream.cs b/ArcFormats/aPLibStream.cs new file mode 100644 index 00000000..a454750f --- /dev/null +++ b/ArcFormats/aPLibStream.cs @@ -0,0 +1,188 @@ +//! \file aPLibStream.cs +//! \date 2026-01-09 + +/* + * aPLib compression library - the smaller the better :) + * + * C depacker + * + * Copyright (c) 1998-2014 Joergen Ibsen + * All Rights Reserved + * + * http://www.ibsensoftware.com/ + */ + +// C# port by scientificworld + +using System; +using System.Collections.Generic; +using System.IO; + +namespace GameRes.Compression { + public sealed class aPLibCoroutine : Decompressor { + Stream m_input; + uint m_tag; + uint m_bitcount; + + public override void Initialize (Stream input) { + m_input = input; + } + + protected override IEnumerator Unpack () { + uint offs, len, R0, LWM; + + var hist = new List(); + + m_bitcount = 0; + R0 = uint.MaxValue; // (uint) -1 + LWM = 0; + + m_buffer[m_pos] = ReadByte(); + hist.Add(m_buffer[m_pos++]); + if (--m_length == 0) + yield return m_pos; + + while (true) { + if (GetBit() != 0) { + if (GetBit() != 0) { + if (GetBit() != 0) { + offs = 0; + + for (int i = 0; i < 4; i++) { + offs = (offs << 1) + GetBit(); + } + + if (offs != 0) { + m_buffer[m_pos] = hist[(int)(hist.Count - offs)]; + } + else { + m_buffer[m_pos] = 0x00; + } + hist.Add(m_buffer[m_pos++]); + + LWM = 0; + if (--m_length == 0) + yield return m_pos; + } + else { + offs = (uint)ReadByte(); + + len = 2 + (offs & 0x0001); + + offs >>= 1; + + if (offs != 0) { + for (; len != 0; len--) { + m_buffer[m_pos] = hist[(int)(hist.Count - offs)]; + hist.Add(m_buffer[m_pos++]); + if (--m_length == 0) + yield return m_pos; + } + } + else { + yield break; + } + + R0 = offs; + LWM = 1; + } + } + else { + offs = GetGamma(); + + if ((LWM == 0) && (offs == 2)) { + offs = R0; + + len = GetGamma(); + + for (; len != 0; len--) { + m_buffer[m_pos] = hist[(int)(hist.Count - offs)]; + hist.Add(m_buffer[m_pos++]); + if (--m_length == 0) + yield return m_pos; + } + } + else { + if (LWM == 0) { + offs -= 3; + } + else { + offs -= 2; + } + + offs <<= 8; + offs += (uint)ReadByte(); + + len = GetGamma(); + + if (offs >= 32000) { + len++; + } + if (offs >= 1280) { + len++; + } + if (offs < 128) { + len += 2; + } + + for (; len != 0; len--) { + m_buffer[m_pos] = hist[(int)(hist.Count - offs)]; + hist.Add(m_buffer[m_pos++]); + if (--m_length == 0) + yield return m_pos; + } + + R0 = offs; + } + + LWM = 1; + } + } + else { + m_buffer[m_pos] = ReadByte(); + hist.Add(m_buffer[m_pos++]); + LWM = 0; + if (--m_length == 0) + yield return m_pos; + } + } + } + + uint GetBit () { + uint bit; + + if (m_bitcount-- == 0) { + m_tag = (uint)ReadByte(); + m_bitcount = 7; + } + bit = (m_tag >> 7) & 0x01; + m_tag <<= 1; + + return bit; + } + + uint GetGamma () { + uint result = 1; + + do { + result = (result << 1) + GetBit(); + } while (GetBit() != 0); + + return result; + } + + byte ReadByte () { + int b = m_input.ReadByte(); + if (b == -1) + throw new EndOfStreamException(); + return (byte)b; + } + } + + public class aPLibStream : PackedStream { + public aPLibStream (Stream input, CompressionMode mode = CompressionMode.Decompress, bool leave_open = false) : base (input, leave_open) { + if (mode != CompressionMode.Decompress) + throw new NotImplementedException ("aPLibStream compression not implemented"); + } + } +} diff --git a/Experimental/CatSystem/ArcIRIS.cs b/Experimental/CatSystem/ArcIRIS.cs new file mode 100644 index 00000000..9adeb297 --- /dev/null +++ b/Experimental/CatSystem/ArcIRIS.cs @@ -0,0 +1,102 @@ +//! \file ArcIRIS.cs +//! \date 2026-01-25 +//! \brief CatSystem for Android resource archive. +// +// Copyright (C) 2026 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; +using GameRes.Utility; + +namespace GameRes.Formats.CatSystem { + [Export(typeof(ArchiveFormat))] + public class IrisPckOpener : ArchiveFormat { + public override string Tag { get { return "DAT/IRIS"; } } + public override string Description { get { return "CatSystem for Android resource archive"; } } + public override uint Signature { get { return 0x53495249; } } // 'IRISPCK' + public override bool IsHierarchic { get { return true; } } + public override bool CanWrite { get { return false; } } + + public override ArcFile TryOpen(ArcView file) { + if (!file.View.AsciiEqual(4, "PCK")) + return null; + + uint offset = 0x18; + var dir = new List(); + var name_buffer = new StringBuilder(); + + while (offset < file.MaxOffset) { + offset += 8; + uint name_length = file.View.ReadUInt32(offset); + + offset += 8; + name_buffer.Clear(); + for (int i = 0; i < name_length; i += 2) { + char c = (char)file.View.ReadUInt16(offset + i); + if (c == 0) + break; + name_buffer.Append(c); + } + var dirname = name_buffer.ToString().Replace("/", "\\"); + offset += name_length; + + offset += 4; + int count = (int)file.View.ReadUInt32(offset); + var dir_inner = new List(count); + + offset += 0xC; + uint prev = 0; + for (int i = 0; i < count; i++) { + offset += 8; + uint size = file.View.ReadUInt32(offset); + uint padded_size = file.View.ReadUInt32(offset + 4); + name_length = file.View.ReadUInt32(offset + 8); + offset += 0x18; + name_buffer.Clear(); + for (int j = 0; j < name_length; j += 2) { + char c = (char)file.View.ReadUInt16(offset + j); + if (c == 0) + break; + name_buffer.Append(c); + } + var basename = name_buffer.ToString(); + var entry = Create(Path.Combine(dirname, basename)); + entry.Offset = prev; + entry.Size = size; + prev += padded_size; + offset += name_length; + dir_inner.Add(entry); + } + + for (int i = 0; i < count; i++) { + dir_inner[i].Offset += offset; + } + offset += prev; + dir.AddRange(dir_inner); + } + + return new ArcFile(file, this, dir); + } + } +} diff --git a/Experimental/Experimental.csproj b/Experimental/Experimental.csproj index 26d81de4..b6643c32 100644 --- a/Experimental/Experimental.csproj +++ b/Experimental/Experimental.csproj @@ -149,6 +149,7 @@ + diff --git a/GameRes/Utility.cs b/GameRes/Utility.cs index 315b9120..2310f934 100644 --- a/GameRes/Utility.cs +++ b/GameRes/Utility.cs @@ -113,6 +113,11 @@ namespace GameRes.Utility return GetCString (data, index, length_limit, Encodings.cp932); } + public static string GetCString (byte[] data, int index, Encoding enc) + { + return GetCString (data, index, data.Length - index, enc); + } + public static string GetCString (byte[] data, int index) { return GetCString (data, index, data.Length - index, Encodings.cp932);