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; + } }