diff --git a/ArcFormats/KiriKiri/ArcXP3.cs b/ArcFormats/KiriKiri/ArcXP3.cs
index 290d929b..fcf08e06 100644
--- a/ArcFormats/KiriKiri/ArcXP3.cs
+++ b/ArcFormats/KiriKiri/ArcXP3.cs
@@ -184,17 +184,16 @@ namespace GameRes.Formats.KiriKiri
entry.Size = (uint)packed_size;
entry.UnpackedSize = (uint)file_size;
- int name_size = header.ReadInt16();
- if (name_size > 0x100 || name_size <= 0)
- {
- goto NextEntry;
- }
if (entry.IsEncrypted || ForceEncryptionQuery)
entry.Cipher = crypt_algorithm.Value;
else
entry.Cipher = NoCryptAlgorithm;
- var name = new string (header.ReadChars (name_size));
+ var name = entry.Cipher.ReadName (header);
+ if (null == name)
+ {
+ goto NextEntry;
+ }
if (entry.Cipher.ObfuscatedIndex && ObfuscatedPathRe.IsMatch (name))
{
goto NextEntry;
diff --git a/ArcFormats/KiriKiri/CryptAlgorithms.cs b/ArcFormats/KiriKiri/CryptAlgorithms.cs
index e44b33e5..1cfb71cb 100644
--- a/ArcFormats/KiriKiri/CryptAlgorithms.cs
+++ b/ArcFormats/KiriKiri/CryptAlgorithms.cs
@@ -71,6 +71,18 @@ namespace GameRes.Formats.KiriKiri
public virtual void Init (ArcFile arc)
{
}
+
+ ///
+ /// Read entry name from archive index.
+ ///
+ public virtual string ReadName (BinaryReader header)
+ {
+ int name_size = header.ReadInt16();
+ if (name_size > 0 && name_size <= 0x100)
+ return new string (header.ReadChars (name_size));
+ else
+ return null;
+ }
}
[Serializable]
@@ -1021,4 +1033,96 @@ namespace GameRes.Formats.KiriKiri
Decrypt (entry, offset, buffer, pos, count);
}
}
+
+ [Serializable]
+ public class RhapsodyCrypt : ICrypt
+ {
+ public string FileListName { get; set; }
+
+ public override void Decrypt (Xp3Entry entry, long offset, byte[] buffer, int pos, int count)
+ {
+ var key = new byte[12];
+ LittleEndian.Pack (entry.Hash, key, 0);
+ LittleEndian.Pack (0x6E1DA9B2u, key, 4);
+ LittleEndian.Pack (0x0040C800u, key, 8);
+ int k = (int)(offset % 12);
+ for (int i = 0; i < count; ++i)
+ {
+ buffer[pos+i] ^= key[k++];
+ if (12 == k)
+ k = 0;
+ }
+ }
+
+ public override void Encrypt (Xp3Entry entry, long offset, byte[] buffer, int pos, int count)
+ {
+ Decrypt (entry, offset, buffer, pos, count);
+ }
+
+ public override string ReadName (BinaryReader header)
+ {
+ if (null == KnownNames)
+ ReadNames();
+ uint key = header.ReadUInt32();
+ uint name_hash = header.ReadUInt32() ^ key;
+ string name;
+ if (KnownNames.TryGetValue (name_hash, out name))
+ return name;
+ uint ext_hash = header.ReadUInt32() ^ key;
+ name = name_hash.ToString ("X8");
+ switch (ext_hash)
+ {
+ case 0x01854675: name += ".png"; break; // GetNameHash (".png")
+ case 0x03D435DE: name += ".map"; break; // GetNameHash (".map")
+ case 0x2D1F13E0: name += ".asd"; break; // GetNameHash (".asd")
+ case 0x482F4319: name += ".tjs"; break; // GetNameHash (".tjs")
+ case 0xB01C48CA: name += ".ks"; break; // GetNameHash (".ks")
+ case 0xC0F7DFB2: name += ".wav"; break; // GetNameHash (".wav")
+ case 0xE3A31D19: name += ".jpg"; break; // GetNameHash (".jpg")
+ case 0xE7F3FEEB: name += ".ogg"; break; // GetNameHash (".ogg")
+ default: name += ext_hash.ToString ("X8"); break;
+ }
+ return name;
+ }
+
+ static uint GetNameHash (string name)
+ {
+ uint hash = 0;
+ for (int i = 0; i < name.Length; ++i)
+ {
+ int c = char.ToLowerInvariant (name[i]);
+ hash = 0x1000193u * hash ^ (byte)c;
+ hash = 0x1000193u * hash ^ (byte)(c >> 8);
+ }
+ return hash;
+ }
+
+ void ReadNames ()
+ {
+ var dir = FormatCatalog.Instance.DataDirectory;
+ var names_file = Path.Combine (dir, FileListName);
+ var names = new Dictionary();
+ try
+ {
+ using (var reader = new StreamReader (names_file))
+ {
+ for (;;)
+ {
+ var name = reader.ReadLine();
+ if (null == name)
+ break;
+ names[GetNameHash (name)] = name;
+ }
+ }
+ }
+ catch (Exception X)
+ {
+ System.Diagnostics.Trace.WriteLine (X.Message, "[RhapsodyCrypt]");
+ }
+ KnownNames = names;
+ }
+
+ [NonSerialized]
+ Dictionary KnownNames = null;
+ }
}