feat: support RepiPack v2

This commit is contained in:
scientificworld
2026-02-04 02:25:00 +08:00
parent 2837415e20
commit 7ca2e1b16d

View File

@@ -44,6 +44,8 @@ namespace GameRes.Formats.Littlewitch
internal class RepiEntry : PackedEntry
{
public bool HasEncryptionKey;
public int Mode;
public uint[] LegacyKey;
public byte[] CreateKey ()
{
@@ -81,7 +83,9 @@ namespace GameRes.Formats.Littlewitch
if (!file.View.AsciiEqual (4, "Pack"))
return null;
int version = file.View.ReadInt32 (8);
if (version != 5)
if (version == 2 || version == 3)
return OpenV2 (file);
else if (version != 5)
return null;
uint name_length = file.View.ReadUInt32 (0xC);
if (name_length < 4)
@@ -106,6 +110,7 @@ namespace GameRes.Formats.Littlewitch
entry.UnpackedSize = index.ToUInt32 (pos+0x18);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
entry.Mode = -1;
entry.IsPacked = entry.Size != entry.UnpackedSize;
dir.Add (entry);
pos += 0x20;
@@ -113,15 +118,62 @@ namespace GameRes.Formats.Littlewitch
return new ArcFile (file, this, dir);
}
ArcFile OpenV2 (ArcView file)
{
uint name_length = file.View.ReadUInt32 (0xC);
if (name_length < 4)
return null;
uint name_key = file.View.ReadUInt32 (0x10);
var key = FindKey (file.Name, name_key);
if (null == key)
return null;
int count = file.View.ReadInt32 (0x10 + name_length);
if (!IsSaneCount (count))
return null;
var index = file.View.ReadBytes (0x14 + name_length, (uint)count * 0x50);
int pos = 0;
var dir = new List<Entry> (count);
var name_builder = new StringBuilder();
for (int i = 0; i < count; ++i)
{
DecryptData (index, pos, 0x50, key[2], key[1]);
var name = Binary.GetCString (index, pos, 0x40);
var entry = FormatCatalog.Instance.Create<RepiEntry> (name);
entry.Offset = index.ToUInt32 (pos+0x40);
entry.UnpackedSize = index.ToUInt32 (pos+0x44);
entry.Size = index.ToUInt32 (pos+0x48);
if (!entry.CheckPlacement (file.MaxOffset))
return null;
entry.Mode = index.ToInt32 (pos+0x4C);
entry.IsPacked = entry.Size != entry.UnpackedSize;
entry.HasEncryptionKey = true;
entry.LegacyKey = key;
dir.Add (entry);
pos += 0x50;
}
return new ArcFile (file, this, dir);
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var rent = entry as RepiEntry;
if (null == rent || !rent.HasEncryptionKey)
return arc.File.CreateStream (entry.Offset, entry.Size);
var key = rent.CreateKey();
uint enc_length = Math.Min ((uint)key.Length, entry.Size);
uint enc_length = rent.Mode == -1 ? Math.Min ((uint)key.Length, rent.Size) : rent.Size;
byte[] encrypted = arc.File.View.ReadBytes (entry.Offset, enc_length);
DecryptEntry (encrypted, key);
switch (rent.Mode)
{
case -1:
DecryptEntry (encrypted, key);
break;
case 1:
DecryptData (encrypted, 0, (int)enc_length, rent.LegacyKey[2], rent.LegacyKey[1]);
break;
case 2:
DecryptData2 (encrypted, 0, (int)enc_length, (byte)rent.LegacyKey[2]);
break;
}
Stream input;
if (enc_length == entry.Size)
{
@@ -163,6 +215,28 @@ namespace GameRes.Formats.Littlewitch
}
}
static unsafe void DecryptData2 (byte[] data, int pos, int length, byte key)
{
if (pos < 0 || pos + length > data.Length)
throw new ArgumentOutOfRangeException ("pos", "Invalid byte array index.");
uint seed = (uint)(key | key << 8 | key << 16 | key << 24);
fixed (byte* data8 = &data[pos])
{
uint* data32 = (uint*)data8;
for (int count = length / 4; count > 0; --count)
{
*data32 = seed ^ (*data32 << 6) ^ (((*data32 >> 2) ^ (*data32 << 6)) & 0x3F3F3F3F);
data32++;
}
byte* data_end = (byte*)data32;
for (int count = length % 4; count > 0; --count)
{
*data_end = (byte)(key ^ Binary.RotByteR (*data_end, 2));
data_end++;
}
}
}
RepiEntry EntryFromMd5 (byte[] data, int pos, StringBuilder builder)
{
var key = new CowArray<byte> (data, pos, 16).ToArray();