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 @@
+