diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 0b37488c..97e1b49a 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -607,6 +607,7 @@ + diff --git a/ArcFormats/Cyberworks/ArcDAT.cs b/ArcFormats/Cyberworks/ArcDAT.cs index 481e16f7..bd0ab453 100644 --- a/ArcFormats/Cyberworks/ArcDAT.cs +++ b/ArcFormats/Cyberworks/ArcDAT.cs @@ -220,7 +220,7 @@ namespace GameRes.Formats.Cyberworks var toc = ReadToc (toc_name, 8); if (null == toc) return null; - using (var index = new ArcIndexReader (toc, file, arc_idx, game_name)) + using (var index = GetIndexReader (toc, file, arc_idx, game_name)) { if (!index.Read()) return null; @@ -228,6 +228,16 @@ namespace GameRes.Formats.Cyberworks } } + internal virtual IndexReader GetIndexReader (byte[] toc, ArcView file, int arc_idx, string game_name) + { + return new ArcIndexReader (toc, file, arc_idx, game_name); + } + + internal virtual TocUnpacker GetTocUnpacker (string toc_name) + { + return new TocUnpacker (toc_name); + } + internal ArcFile ArchiveFromDir (ArcView file, List dir, bool has_images) { if (0 == dir.Count) @@ -245,7 +255,7 @@ namespace GameRes.Formats.Cyberworks { if (!VFS.FileExists (meta_arc_name)) return null; - using (var unpacker = new TocUnpacker (meta_arc_name)) + using (var unpacker = GetTocUnpacker (meta_arc_name)) { if (unpacker.Length > 0x1000) return null; @@ -269,7 +279,7 @@ namespace GameRes.Formats.Cyberworks { if (!VFS.FileExists (toc_name)) return null; - using (var toc_unpacker = new TocUnpacker (toc_name)) + using (var toc_unpacker = GetTocUnpacker (toc_name)) return toc_unpacker.Unpack (num_length); } @@ -403,6 +413,26 @@ namespace GameRes.Formats.Cyberworks public AImageScheme Scheme; } + [Export(typeof(ArchiveFormat))] + public class DatOpener2024 : DatOpener + { + public override string Tag { get { return "ARC/Cyberworks/2"; } } + public override string Description { get { return "Cyberworks/TinkerBell resource archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + internal override IndexReader GetIndexReader (byte[] toc, ArcView file, int arc_idx, string game_name) + { + return new ArcIndexReader2 (toc, file, arc_idx, game_name); + } + + internal override TocUnpacker GetTocUnpacker (string toc_name) + { + return new TocUnpacker (toc_name, true); + } + } + [Export(typeof(ArchiveFormat))] public class OldDatOpener : DatOpener { @@ -514,19 +544,22 @@ namespace GameRes.Formats.Cyberworks { ArcView m_file; bool m_should_dispose; + bool m_reversed_decimal; public long Length { get { return m_file.MaxOffset; } } public uint PackedSize { get; private set; } public uint UnpackedSize { get; private set; } - public TocUnpacker (string toc_name) : this (VFS.OpenView (toc_name), true) + public TocUnpacker (string toc_name, bool reversed_decimal = false) + : this (VFS.OpenView (toc_name), true, reversed_decimal) { } - public TocUnpacker (ArcView file, bool should_dispose = false) + public TocUnpacker (ArcView file, bool should_dispose = false, bool reversed_decimal = false) { m_file = file; m_should_dispose = should_dispose; + m_reversed_decimal = reversed_decimal; } public byte[] Unpack (int num_length) @@ -564,7 +597,22 @@ namespace GameRes.Formats.Cyberworks { uint v = 0; uint rank = 1; - for (int i = num_length-1; i >= 0; --i, rank *= 10) + + int start, end, step; + if (m_reversed_decimal) + { + start = 0; + end = num_length; + step = 1; + } + else + { + start = num_length-1; + end = -1; + step = -1; + } + + for (int i = start; i != end; i += step, rank *= 10) { uint b = m_file.View.ReadByte (offset+i); if (b != 0xFF) @@ -627,9 +675,9 @@ namespace GameRes.Formats.Cyberworks return true; } - uint m_fault_id = 100000; + protected uint m_fault_id = 100000; - internal PackedEntry ReadEntryInfo () + internal virtual PackedEntry ReadEntryInfo () { uint id = m_index.ReadUInt32(); if (id > m_fault_id) @@ -707,6 +755,27 @@ namespace GameRes.Formats.Cyberworks } } + internal class ArcIndexReader2 : ArcIndexReader + { + public ArcIndexReader2 (byte[] toc, ArcView file, int arc_number, string game_name = null) + : base (toc, file, arc_number, game_name) + { + } + + internal override PackedEntry ReadEntryInfo () + { + uint id = m_index.ReadUInt32(); + if (id > m_fault_id) + id = m_fault_id++; + var entry = new PackedEntry { Name = id.ToString ("D6") }; + entry.UnpackedSize = m_index.ReadUInt32(); + entry.Offset = m_index.ReadUInt32(); + entry.Size = m_index.ReadUInt32(); + entry.IsPacked = entry.UnpackedSize != entry.Size && entry.UnpackedSize != 0; + return entry; + } + } + internal class DatIndexReader : IndexReader { public DatIndexReader (byte[] toc, ArcView file) : base (toc, file) diff --git a/ArcFormats/Cyberworks/ArcDATA.cs b/ArcFormats/Cyberworks/ArcDATA.cs index f45d0d53..4820325d 100644 --- a/ArcFormats/Cyberworks/ArcDATA.cs +++ b/ArcFormats/Cyberworks/ArcDATA.cs @@ -74,12 +74,15 @@ namespace GameRes.Formats.Cyberworks { var arc_name = Path.GetFileName(file.Name); var dir_name = VFS.GetDirectoryName(file.Name); + var toc_arc_name = VFS.CombinePath(dir_name, "Data00.dat"); + if (!VFS.FileExists(toc_arc_name)) + return null; if ("Data00.dat".Equals(arc_name, StringComparison.OrdinalIgnoreCase)) return null; if (!int.TryParse(arc_name.Substring(4, arc_name.IndexOf('.') - 4), out int arc_index)) return null; var scheme = QueryScheme(arc_name); - var dir = ScanDir(VFS.CombinePath(dir_name, "Data00.dat"), arc_index, scheme); + var dir = ScanDir(toc_arc_name, arc_index, scheme); if (null == dir || 0 == dir.Count) return null; @@ -151,9 +154,6 @@ namespace GameRes.Formats.Cyberworks List ScanDir(string toc_arc_name, int arc_index, DataScheme scheme) { - if(!VFS.FileExists(toc_arc_name)) - return null; - var dir = new List(); var toc_offset = 0; diff --git a/ArcFormats/FrontWing/ArcDAT.cs b/ArcFormats/FrontWing/ArcDAT.cs index 9abbb8bb..3a1ce1a1 100644 --- a/ArcFormats/FrontWing/ArcDAT.cs +++ b/ArcFormats/FrontWing/ArcDAT.cs @@ -31,6 +31,19 @@ using GameRes.Utility; namespace GameRes.Formats.FrontWing { + internal class TimeLeapArchive : ArcFile + { + private bool m_encrypted; + + public bool IsEncrypted { get { return m_encrypted; } } + + public TimeLeapArchive (ArcView arc, ArchiveFormat impl, ICollection dir, bool encrypted) + : base (arc, impl, dir) + { + m_encrypted = encrypted; + } + } + [Export(typeof(ArchiveFormat))] public class DatOpener : ArchiveFormat { @@ -52,7 +65,17 @@ namespace GameRes.Formats.FrontWing if (file.MaxOffset < index_size + 4) return null; var index = file.View.ReadBytes (file.MaxOffset - 4 - index_size, (uint)index_size); - NibbleSwap (index); + bool encrypted = false; + for (int i = 0; i < 40 && index[i] != 0; i++) + { + if (index[i] < 0x20 || index[i] >= 0x7f) + { + encrypted = true; + break; + } + } + if (encrypted) + NibbleSwap (index); int index_offset = 0; var dir = new List (count); @@ -67,7 +90,7 @@ namespace GameRes.Formats.FrontWing index_offset += 0x50; dir.Add (entry); } - return new ArcFile (file, this, dir); + return new TimeLeapArchive (file, this, dir, encrypted); } static readonly byte[] keyTable = { @@ -80,16 +103,20 @@ namespace GameRes.Formats.FrontWing public override Stream OpenEntry (ArcFile arc, Entry entry) { - var data = arc.File.View.ReadBytes (entry.Offset, entry.Size); - for (int i = 1; i < data.Length; i += 4) + var tarc = arc as TimeLeapArchive; + var data = tarc.File.View.ReadBytes (entry.Offset, entry.Size); + if (tarc.IsEncrypted) { - data[i] = (byte)(-data[i] & 0xff); + for (int i = 1; i < data.Length; i += 4) + { + data[i] = (byte)(-data[i] & 0xff); + } + for (int i = 0; i < data.Length; i += 3) + { + data[i] ^= keyTable[i / 5 % 5 + i % 6]; + } + NibbleSwap (data, 2, 6); } - for (int i = 0; i < data.Length; i += 3) - { - data[i] ^= keyTable[i / 5 % 5 + i % 6]; - } - NibbleSwap(data, 2, 6); return new BinMemoryStream (data); } diff --git a/ArcFormats/Lucifen/ArcLPK.cs b/ArcFormats/Lucifen/ArcLPK.cs index 63f71292..45ab12a2 100644 --- a/ArcFormats/Lucifen/ArcLPK.cs +++ b/ArcFormats/Lucifen/ArcLPK.cs @@ -122,6 +122,7 @@ namespace GameRes.Formats.Lucifen public bool IsEncrypted; public bool PackedEntries; public bool WholeCrypt; + public bool IsPatchFile; public uint Key; public byte[] Prefix; } @@ -206,7 +207,7 @@ namespace GameRes.Formats.Lucifen { input.Read (data, 0, data.Length); } - if (larc.Info.WholeCrypt) + if (larc.Info.WholeCrypt && !(larc.Info.IsPatchFile && lent.Name.HasExtension ("elg"))) { larc.Scheme.DecryptContent (data); } @@ -257,15 +258,13 @@ namespace GameRes.Formats.Lucifen IsEncrypted = 0 != (flags & 4), PackedEntries = 0 != (flags & 8), WholeCrypt = 0 != (flags & 0x10), + IsPatchFile = 0 != (flags & 0x20), Key = key1 }; var reader = new IndexReader (lpk_info); var dir = reader.Read (index); if (null == dir) return null; - // this condition is fishy, probably patch files have additional bitflag set - if (lpk_info.WholeCrypt && Binary.AsciiEqual (basename, "PATCH")) - lpk_info.WholeCrypt = false; return new LuciArchive (file, this, dir, scheme, reader.Info); } diff --git a/ArcFormats/Musica/ArcMIP.cs b/ArcFormats/Musica/ArcMIP.cs new file mode 100644 index 00000000..18c54597 --- /dev/null +++ b/ArcFormats/Musica/ArcMIP.cs @@ -0,0 +1,655 @@ +//! \file ArcMIP.cs +//! \date 2026-01-16 +//! \brief Minori 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 GameRes.Utility; + +namespace GameRes.Formats.Musica { + [Export(typeof(ArchiveFormat))] + public class MipOpener : ArchiveFormat { + public override string Tag { get { return "MIP"; } } + public override string Description { get { return "Minori resource archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanWrite { get { return false; } } + + // 0x42e100 + static readonly uint[] resEntrySizeList = { + 0xDDE, + 0x1622, + 0x44F, + 0xF06, + 0x1EFEA, + 0x64B3, + 0x10684E, + 0x9288, + 0x144, + 0x49993, + 0x5065C, + 0x52424, + 0x4EB4B, + 0x4EA1F, + 0x4A334, + 0x4B8E4, + 0x55DA6, + 0x56D7C, + 0x3A3ED, + 0x307B3, + 0x55FB8, + 0x96D68, + 0x4B821, + 0x4C33B, + 0x54EB3, + 0x4DBDD, + 0x52D69, + 0x50A3F, + 0x50C0C, + 0x4975A, + 0x47E5F, + 0x37177, + 0x3E0D4, + 0x46029, + 0xC854A, + 0x4463F, + 0x44E40, + 0x40AA5, + 0x432BF, + 0x3C0A5, + 0x29496, + 0xABC1B, + 0x88427, + 0x9F549, + 0x104EA, + 0x7C524, + 0x75CE, + 0xA419, + 0x6419, + 0x9C62, + 0x6A9C, + 0xC5862, + 0x134F2D, + 0x1271, + 0x11D6, + 0x4D1F, + 0x6134, + 0x63C1, + 0x8730, + 0x6BF9, + 0x6CDA, + 0x898A, + 0x706B, + 0x696A, + 0x60AA, + 0x9007, + 0x662F, + 0x72B3, + 0x55E8, + 0x6E47, + 0x9EDA, + 0x622D, + 0x5BE4, + 0x73A9, + 0x9269, + 0x5C11, + 0x553A, + 0x66EA, + 0x7174, + 0x6A8C, + 0x6AC0, + 0x6389, + 0x7167, + 0x845A, + 0x900A, + 0x726E, + 0x68F3, + 0x88E4, + 0x8B70, + 0x83BB, + 0x7D1E, + 0x84CC, + 0x583C, + 0x6AD1, + 0x7981, + 0x48F0, + 0x765A, + 0x763F, + 0x5ACB, + 0x5B4E, + 0x6490, + 0x7441, + 0x5D9F, + 0x5FD9, + 0x5E9A, + 0x7330, + 0x69D1, + 0x86BE, + 0x92AC, + 0x9787, + 0x972D, + 0x9963, + 0x96E3, + 0x8101, + 0x707C, + 0x70DC, + 0x9105, + 0x7A06, + 0x7A8F, + 0x595F, + 0x58FA, + 0x7F95, + 0x86CB, + 0x86F7, + 0x7B3B, + 0x8971, + 0x8BFC, + 0x7FE6, + 0x77EE, + 0x83FF, + 0x606D, + 0x6125, + 0x66FB, + 0x66F9, + 0x6E76, + 0x6826, + 0x67B1, + 0x76E3, + 0x7487, + 0x4E7B, + 0x4CD34, + 0x750EF, + 0x761B6, + 0xB29F9, + 0x9CE38, + 0x95BEF, + 0x79F4F, + 0x9BEB6, + 0xA1C95, + 0x72147, + 0x68007, + 0x59070, + 0xCADF5, + 0x71FB4, + 0x67BF2, + 0x5C639, + 0x57FAF, + 0x63334, + 0x703D2, + 0x5DCDB, + 0xA796B, + 0x71728, + 0x7F52D, + 0x71B7A, + 0xA024D, + 0x73670, + 0x47E4B, + 0x63AF1, + 0x83F4F, + 0x6FFB2, + 0x82C74, + 0x708D6, + 0x79208, + 0x99011, + 0x8CFD4, + 0x7B119, + 0x65D69, + 0x982BA, + 0x86387, + 0x7F047, + 0x91A4A, + 0x9B149, + 0x65C0F, + 0x8050B, + 0x75798, + 0x369A9, + 0x39FD1, + 0x6E791, + 0x6E31B, + 0x558B7, + 0x66149, + 0x5C5FE, + 0x6A9CC, + 0x670E8, + 0x687DA, + 0x5F998, + 0x69569, + 0x5EF73, + 0x789BD, + 0xAE0EC, + 0xB7733, + 0x9422C, + 0xB1BBE, + 0xAC68D, + 0x91074, + 0x65FC9, + 0x6664C, + 0xB0867, + 0x6992C, + 0x6A4A9, + 0x5011C, + 0x5ECBB, + 0xA4332, + 0x7411F, + 0x73E87, + 0x7322C, + 0x82260, + 0x8E294, + 0x82594, + 0x6F54E, + 0x80E86, + 0x535AF, + 0x545B0, + 0x50437, + 0x503C8, + 0x56674, + 0x4E048, + 0x4D5C8, + 0x654DD, + 0x67968, + 0x5AAAA, + 0x2F45, + 0x3098, + 0x3DF5F, + 0xE6D49, + 0xD866D, + 0x9632C, + 0xE20BD, + 0x33061, + 0x375DF, + 0x417C2, + 0x37764, + 0x37934, + 0x40807, + 0xCDA33, + 0x9FA9C, + 0x44103, + 0xDA3E9, + 0x44E92, + 0x3C9DB, + 0x46F57, + 0x3881A, + 0x3A9B9, + 0x3B0A8, + 0x430A8, + 0xE2156, + 0x415F1, + 0x318EC, + 0x3AFCC, + 0x39769, + 0xDCEA7, + 0x4E43C, + 0x3CC83, + 0xBDF5E, + 0x33CF8, + 0x6D250, + 0x8570A, + 0x60C21, + 0x9782C, + 0x783D0, + 0x783D0, + 0x67935, + 0xAA5A7, + 0xDCA2E, + 0x606E9, + 0xA9047, + 0x10E5F, + 0x10E92, + 0x10E61, + 0x136E5, + 0x136EB, + 0x1371A, + 0x1368E, + 0x13818, + 0x139D4, + 0x11350, + 0x11372, + 0x11372, + 0x115CD, + 0x161F1, + 0x16022, + 0x16176, + 0x1503F, + 0x140CC, + 0x144D3, + 0x1433F, + 0x1365E, + 0x1388C, + 0x1365C, + 0x138FE, + 0x13616, + 0x1389D, + 0x13312, + 0x134A5, + 0x18BC8, + 0x18CD9, + 0x1785D, + 0x17A8A, + 0x176B5, + 0x17924, + 0x177F4, + 0x17A2D, + 0x1786E, + 0x17A9D, + 0x101CA, + 0x101AE, + 0x1058B, + 0x10184, + 0xD8D3, + 0xD936, + 0xD8AB, + 0xDA4E, + 0x11968, + 0x11935, + 0x11988, + 0x119D4, + 0x11938, + 0x1282A, + 0x127E9, + 0x12836, + 0x12813, + 0x12803, + 0xFC09, + 0xFBF2, + 0xEFE0, + 0xF149, + 0xEECF, + 0xEFF5, + 0xF02E, + 0xE872, + 0xEA4C, + 0xE871, + 0xE778, + 0xF835, + 0xFB43, + 0xF91B, + 0xFC05, + 0xE75F, + 0xE7DC, + 0x10295, + 0x102A9, + 0x102B6, + 0xD72E, + 0xD713, + 0xF21D, + 0xF51B, + 0xF26C, + 0xF55D, + 0x117B0, + 0x117B7, + 0x11783, + 0x1171E, + 0x118CD, + 0x118F0, + 0x119ED, + 0x18318, + 0x18300, + 0x182EF, + 0x18710, + 0x13F5F, + 0x13E96, + 0x13E4D, + 0x13FA9, + 0x13B17, + 0x13A41, + 0x13962, + 0x13C14, + 0x1396E, + 0x13569, + 0x13830, + 0xF939, + 0xF901, + 0xF925, + 0xEB9A, + 0x10C46, + 0x10B7D, + 0xEB8E, + 0x10B3A, + 0xEB68, + 0x10B1C, + 0xEB5F, + 0x10B5A, + 0xEC53, + 0xE494, + 0xE4B0, + 0x11A7C, + 0x1152B, + 0x11ABA, + 0x11620, + 0x11A1A, + 0x114E0, + 0x144EE, + 0x149CF, + 0x14519, + 0x10B71, + 0x10AFA, + 0x10B04, + 0xF64E, + 0xF8B2, + 0xF65F, + 0xF90E, + 0xF5C0, + 0xF87D, + 0xF5CC, + 0xF89A, + 0xF5A2, + 0xF883, + 0xF4CE, + 0xF7C6, + 0xF6E2, + 0xF326, + 0xF31D, + 0xFE72, + 0xFE64, + 0xFE4B, + 0x101B2, + 0xF1FF, + 0x10D8C, + 0x101F6, + 0xF2DD, + 0x10244, + 0xF1B6, + 0x101D9, + 0xFDC3, + 0x10363, + 0xFEB9, + 0x13450, + 0x133C1, + 0x126D1, + 0x115B8, + 0x126AD, + 0x115BC, + 0x26EA8, + 0x26E49, + 0x10A6F, + 0x10AE4, + 0x10A3F, + 0xFEC0, + 0xFF2F, + 0xFE55, + 0x13D6A, + 0x13DB7, + 0x13D94, + 0x13C03, + 0x123AD, + 0x121E2, + 0x12342, + 0x1252A, + 0x126A0, + 0x1285E, + 0xDD8C, + 0xDB7C, + 0xDAA5, + 0xD2A8, + 0xD564, + 0xD2F6, + 0xD6D1, + 0xD3F3, + 0xD775, + 0xD2F5, + 0xD8AE, + 0xD824, + 0xD8A2, + 0x1532E, + 0x151B5, + 0x12F49, + 0x141C8, + 0x12EA7, + 0x12E57, + 0x12F0B, + 0x12EF5, + 0x13131, + 0x15066, + 0x15003, + 0x150CD, + 0x10C4B, + 0x112CE, + 0x112AD, + 0x10C21, + 0x112B3, + 0x10CDF, + 0x113AF, + 0x10CA1, + 0x1133C, + 0x10C4B, + 0x11915, + 0x11900, + 0x118CA, + 0x118AC, + 0x11985, + 0x107CC, + 0x10759, + 0x1071B, + 0x4D14, + 0x4EF7, + 0x5804, + 0x5BDA, + 0x3BB7, + 0x30BD, + 0x39E0, + 0x3A3C, + 0x3AF2, + 0x3A3C, + 0x3969, + 0x393C, + 0x3B8C, + 0x4040, + 0x37FC, + 0x3973, + 0x3856, + 0x397B, + 0x3865, + 0x38DD, + 0x44C5, + 0x37C0, + 0x3BD9, + 0x3887, + 0x3C29, + 0x3BFA, + 0x3199, + 0x4848, + 0x151FD, + 0xD890, + 0x24E0A, + 0xBFAD, + 0x1531A, + 0x1B25F + }; + + // 0x42f1d0 + static readonly uint[] scEntrySizeList = { + 0x1D1E7, + 0x130B4, + 0xE7AB, + 0xEC2E, + 0x1408A, + 0x96C8, + 0x1006F, + 0x109D8, + 0x1389F, + 0xEA2E, + 0xE57C, + 0xD331, + 0xD0F1, + 0x11730, + 0x12FC4, + 0xCC50, + 0xE043, + 0xD7AB, + 0x18978, + 0x2F87, + 0x4, + 0x22F3, + 0x6C2A, + 0x28BE, + 0x4080, + 0x3897 + }; + + public MipOpener() { + Extensions = new string[] { "mip" }; + } + + public override ArcFile TryOpen(ArcView file) { + string extension; + uint[] sizes; + + string basename = Path.GetFileName(file.Name).ToLower(); + if (basename == "res.mip") { + extension = ".png"; + sizes = resEntrySizeList; + } + else if (basename == "sc.mip") { + extension = ""; + sizes = scEntrySizeList; + } + else + return null; + + int count = sizes.Length; + var dir = new List(count); + uint offset = 0; + for (int i = 0; i < count; i++) { + var entry = new Entry { + Name = i.ToString("D4") + extension, + Type = extension == "" ? "script" : "image", + Offset = offset, + Size = sizes[i] + }; + dir.Add(entry); + offset += entry.Size; + } + + return new ArcFile(file, this, dir); + } + + public override Stream OpenEntry(ArcFile arc, Entry entry) { + var s = new XoredStream(arc.File.CreateStream(entry.Offset, entry.Size), 0xFF); + if (entry.Name.EndsWith(".png")) + return new PrefixStream(PngFormat.HeaderBytes, s); + return s; + } + } +} diff --git a/ArcFormats/Musica/ArcPAZ.cs b/ArcFormats/Musica/ArcPAZ.cs index 178509c7..480297a7 100644 --- a/ArcFormats/Musica/ArcPAZ.cs +++ b/ArcFormats/Musica/ArcPAZ.cs @@ -140,7 +140,7 @@ namespace GameRes.Formats.Musica Extensions = new string[] { "paz", "dat" }; Signatures = new uint[] { 0x858F8493, 0x8F889395, 0x6E656465, 0x848F8486, 0x61657453, 0x6873616D, 0x92808483, - 0x6E697274, 0 + 0x6E697274, 0x5F465342, 0 }; ContainedFormats = new string[] { "PNG", "ANI/PAZ", "SQZ", "OGG", "WAV", "TXT" }; } diff --git a/ArcFormats/NitroPlus/ArcNPK.cs b/ArcFormats/NitroPlus/ArcNPK.cs index adf753d9..19e038d8 100644 --- a/ArcFormats/NitroPlus/ArcNPK.cs +++ b/ArcFormats/NitroPlus/ArcNPK.cs @@ -57,11 +57,13 @@ namespace GameRes.Formats.NitroPlus internal class NpkArchive : ArcFile { public readonly Aes Encryption; + public readonly int Version; - public NpkArchive (ArcView arc, ArchiveFormat impl, ICollection dir, Aes enc) + public NpkArchive (ArcView arc, ArchiveFormat impl, ICollection dir, Aes enc, int version) : base (arc, impl, dir) { Encryption = enc; + Version = version; } #region IDisposable Members @@ -100,8 +102,14 @@ namespace GameRes.Formats.NitroPlus set { DefaultScheme = (Npk2Scheme)value; } } + public NpkOpener () + { + Signatures = new uint[] { 0x324B504E, 0x334B504E }; + } + public override ArcFile TryOpen (ArcView file) { + int version = file.View.ReadByte (3) - '0'; int count = file.View.ReadInt32 (0x18); if (!IsSaneCount (count)) return null; @@ -124,7 +132,7 @@ namespace GameRes.Formats.NitroPlus var dir = ReadIndex (index, count, file.MaxOffset); if (null == dir) return null; - var arc = new NpkArchive (file, this, dir, aes); + var arc = new NpkArchive (file, this, dir, aes, version); aes = null; // object ownership passed to NpkArchive, don't dispose return arc; } @@ -481,6 +489,7 @@ namespace GameRes.Formats.NitroPlus { ArcView m_file; Aes m_encryption; + int m_version; IEnumerator m_segment; Stream m_stream; bool m_eof = false; @@ -493,6 +502,7 @@ namespace GameRes.Formats.NitroPlus { m_file = arc.File; m_encryption = arc.Encryption; + m_version = arc.Version; m_segment = entry.Segments.GetEnumerator(); NextSegment(); } @@ -511,7 +521,15 @@ namespace GameRes.Formats.NitroPlus var decryptor = m_encryption.CreateDecryptor(); m_stream = new InputCryptoStream (m_stream, decryptor); if (segment.IsCompressed) - m_stream = new DeflateStream (m_stream, CompressionMode.Decompress); + switch (m_version) + { + case 2: + m_stream = new DeflateStream (m_stream, CompressionMode.Decompress); + break; + case 3: + m_stream = new ZstdNet.DecompressionStream (m_stream); + break; + } } public override int Read (byte[] buffer, int offset, int count) diff --git a/ArcFormats/Resources/Formats.dat b/ArcFormats/Resources/Formats.dat index 665e6f5c..9b7bcf24 100644 Binary files a/ArcFormats/Resources/Formats.dat and b/ArcFormats/Resources/Formats.dat differ diff --git a/Legacy/Broccoli/ArcP00.cs b/Legacy/Broccoli/ArcP00.cs new file mode 100644 index 00000000..00a471a3 --- /dev/null +++ b/Legacy/Broccoli/ArcP00.cs @@ -0,0 +1,96 @@ +//! \file ArcP00.cs +//! \date 2026-01-08 +//! \brief Broccoli 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 System.IO.Compression; +using GameRes.Utility; + +namespace GameRes.Formats.Broccoli { + internal class P00Entry : Entry { + public string FileName; + } + + [Export(typeof(ArchiveFormat))] + public class P00Opener : ArchiveFormat { + public override string Tag { get { return "P00"; } } + public override string Description { get { return "Broccoli multipart archive format"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return true; } } + public override bool CanWrite { get { return false; } } + + public P00Opener() { + Extensions = new string[] { "p00" }; + } + + public override ArcFile TryOpen(ArcView file) { + var index_file_name = Path.ChangeExtension(file.Name, "pak"); + if (!VFS.FileExists(index_file_name)) + return null; + using (var index_file = VFS.OpenView(index_file_name)) { + if (!index_file.View.AsciiEqual(0, "IPF ")) + return null; + + int count = index_file.View.ReadInt32(4); + if (!IsSaneCount(count)) + return null; + + long index_offset = 8; + var dir = new List(count); + for (int i = 0; i < count; i++) { + uint hash = index_file.View.ReadUInt32(index_offset); + string name = hash.ToString("X8"); + var entry = Create(name); + entry.Offset = index_file.View.ReadUInt32(index_offset + 4); + entry.Size = index_file.View.ReadUInt32(index_offset + 8); + var data_file_name = Path.ChangeExtension(file.Name, string.Format("p{0:00}", entry.Offset >> 28)); + if (!VFS.FileExists(data_file_name)) + return null; + entry.FileName = data_file_name; + dir.Add(entry); + index_offset += 12; + } + + return new ArcFile(file, this, dir); + } + } + + public override Stream OpenEntry(ArcFile arc, Entry entry) { + var pent = entry as P00Entry; + using (var data_file = new ArcView(pent.FileName)) { + var input = data_file.CreateStream(pent.Offset & 0xFFFFFFF, pent.Size); + if (input.ReadUInt16() == 0x305A) { // 'Z0' + input.Seek(10, SeekOrigin.Begin); + return new DeflateStream(input, CompressionMode.Decompress); + } + else { + input.Seek(0, SeekOrigin.Begin); + return input; + } + } + } + } +} diff --git a/Legacy/Legacy.csproj b/Legacy/Legacy.csproj index fadcf734..78d24d21 100644 --- a/Legacy/Legacy.csproj +++ b/Legacy/Legacy.csproj @@ -99,6 +99,7 @@ +