diff --git a/ArcFormats/Eagls/ArcEAGLS.cs b/ArcFormats/Eagls/ArcEAGLS.cs index 91799590..23248418 100644 --- a/ArcFormats/Eagls/ArcEAGLS.cs +++ b/ArcFormats/Eagls/ArcEAGLS.cs @@ -2,7 +2,7 @@ //! \date Fri May 15 02:52:04 2015 //! \brief EAGLS system resource archives. // -// Copyright (C) 2015 by morkt +// Copyright (C) 2015-2016 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 @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; +using System.Linq; using GameRes.Utility; namespace GameRes.Formats.Eagls @@ -45,8 +46,11 @@ namespace GameRes.Formats.Eagls Extensions = new string[] { "pak" }; } - static readonly string IndexKey = "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/-@:^[]"; - static readonly string Key = "EAGLS_SYSTEM"; + internal static readonly string IndexKey = "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/-@:^[]"; + internal static readonly string Key = "EAGLS_SYSTEM"; + + // FIXME not thread-safe + CRuntimeRandomGenerator m_rng = new CRuntimeRandomGenerator(); public override ArcFile TryOpen (ArcView file) { @@ -94,45 +98,31 @@ namespace GameRes.Formats.Eagls } if (0 == dir.Count) return null; - return new ArcFile (file, this, dir); + if (dir[0].Name.EndsWith (".gr", StringComparison.InvariantCultureIgnoreCase)) // CG archive + return new CgArchive (file, this, dir); + else + return new ArcFile (file, this, dir); } public override Stream OpenEntry (ArcFile arc, Entry entry) { - if (entry.Name.EndsWith (".gr", StringComparison.InvariantCultureIgnoreCase)) - return DecryptGr (arc, entry); + var cg_arc = arc as CgArchive; + if (null != cg_arc) + return cg_arc.DecryptEntry (entry); if (entry.Name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase)) return DecryptDat (arc, entry); return arc.File.CreateStream (entry.Offset, entry.Size); } - Stream DecryptGr (ArcFile arc, Entry entry) - { - var input = new byte[entry.Size]; - arc.File.View.Read (entry.Offset, input, 0, entry.Size); - int seed = 0x75bd924 ^ input[input.Length-1]; - int limit = Math.Min (input.Length-1, 0x174b); - for (int i = 0; i < limit; ++i) - { - seed = LRand (seed); - int index = (int)(seed * 4.656612875245797e-10 * 256); - input[i] ^= (byte)Key[index % Key.Length]; - } - return new MemoryStream (input); - } - Stream DecryptDat (ArcFile arc, Entry entry) { - byte[] input = new byte[entry.Size]; - arc.File.View.Read (entry.Offset, input, 0, entry.Size); + byte[] input = arc.File.View.ReadBytes (entry.Offset, entry.Size); int text_offset = 3600; int text_length = (int)(entry.Size - text_offset - 2); - int seed = (sbyte)input[input.Length-1]; + m_rng.SRand ((sbyte)input[input.Length-1]); for (int i = 0; i < text_length; i += 2) { - seed = seed * 0x343FD + 0x269EC3; - int index = (int)(((uint)seed >> 16) & 0x7fff); - input[text_offset + i] ^= (byte)Key[index % Key.Length]; + input[text_offset + i] ^= (byte)Key[m_rng.Rand() % Key.Length]; } return new MemoryStream (input); } @@ -144,15 +134,13 @@ namespace GameRes.Formats.Eagls using (var view = idx.CreateViewAccessor (0, (uint)idx.MaxOffset)) unsafe { - uint seed = view.ReadUInt32 (idx_size); + m_rng.SRand (view.ReadInt32 (idx_size)); byte* ptr = view.GetPointer (0); try { for (int i = 0; i < idx_size; ++i) { - seed = seed * 0x343FD + 0x269EC3; - int index = (int)(seed >> 16) & 0x7FFF; - output[i] = (byte)(ptr[i] ^ IndexKey[index % IndexKey.Length]); + output[i] = (byte)(ptr[i] ^ IndexKey[m_rng.Rand() % IndexKey.Length]); } return output; } @@ -162,17 +150,97 @@ namespace GameRes.Formats.Eagls } } } + } - int LRand (int seed) + internal interface IRandomGenerator + { + void SRand (int seed); + int Rand (); + } + + internal class CRuntimeRandomGenerator : IRandomGenerator + { + uint m_seed; + + public void SRand (int seed) { - const int A = 48271; - const int Q = 44488; - const int R = 3399; - const int M = 2147483647; - seed = A * (seed % Q) - R * (seed / Q); - if (seed < 0) - seed += M; - return seed; + m_seed = (uint)seed; + } + + public int Rand () + { + m_seed = m_seed * 214013u + 2531011u; + return (int)(m_seed >> 16) & 0x7FFF; + } + } + + internal class LehmerRandomGenerator : IRandomGenerator + { + int m_seed; + + const int A = 48271; + const int Q = 44488; + const int R = 3399; + const int M = 2147483647; + + public void SRand (int seed) + { + m_seed = seed ^ 123459876; + } + + public int Rand () + { + m_seed = A * (m_seed % Q) - R * (m_seed / Q); + if (m_seed < 0) + m_seed += M; + return (int)(m_seed * 4.656612875245797e-10 * 256); + } + } + + internal class CgArchive : ArcFile + { + IRandomGenerator m_rng; + + public CgArchive (ArcView arc, ArchiveFormat impl, ICollection dir) + : base (arc, impl, dir) + { + m_rng = DetectEncryptionScheme(); + } + + IRandomGenerator DetectEncryptionScheme () + { + var first_entry = Dir.First(); + int signature = (File.View.ReadInt32 (first_entry.Offset) >> 8) & 0xFFFF; + byte seed = File.View.ReadByte (first_entry.Offset+first_entry.Size-1); + IRandomGenerator[] rng_list = { + new LehmerRandomGenerator(), + new CRuntimeRandomGenerator(), + }; + foreach (var rng in rng_list) + { + rng.SRand (seed); + rng.Rand(); // skip LZSS control byte + int test = signature; + test ^= PakOpener.Key[rng.Rand() % PakOpener.Key.Length]; + test ^= PakOpener.Key[rng.Rand() % PakOpener.Key.Length] << 8; + // FIXME + // as key is rather short, this detection could produce false results sometimes + if (0x4D42 == test) // 'BM' + return rng; + } + throw new UnknownEncryptionScheme(); + } + + public Stream DecryptEntry (Entry entry) + { + var input = File.View.ReadBytes (entry.Offset, entry.Size); + m_rng.SRand (input[input.Length-1]); + int limit = Math.Min (input.Length-1, 0x174b); + for (int i = 0; i < limit; ++i) + { + input[i] ^= (byte)PakOpener.Key[m_rng.Rand() % PakOpener.Key.Length]; + } + return new MemoryStream (input); } } } diff --git a/supported.html b/supported.html index 143d5704..f4ef2e04 100644 --- a/supported.html +++ b/supported.html @@ -328,6 +328,7 @@ Warusa
*.mgd
*.mgsMGD
MGSNoMEGUSeduce *.agcAGdNo *.pak+*.idx-NoEAGLS +Futago Hinyuu x 3
Oppai Baka
Mainichi Shabutte Ii Desu ka?
Senpai - Oppai - Kako ni Modori Pai