From dbd95d62e3a29a41656a5f5dcc8a3b7f3ea4f2f9 Mon Sep 17 00:00:00 2001 From: morkt Date: Thu, 21 Jan 2016 15:30:06 +0400 Subject: [PATCH 1/4] (EAGLS): auto-detect CG archive encryption scheme. --- ArcFormats/Eagls/ArcEAGLS.cs | 148 +++++++++++++++++++++++++---------- supported.html | 1 + 2 files changed, 109 insertions(+), 40 deletions(-) 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
From c8a656e1728b1569ee666a004f2c36d857966561 Mon Sep 17 00:00:00 2001 From: morkt Date: Thu, 21 Jan 2016 15:30:32 +0400 Subject: [PATCH 2/4] (DatOpener): additional sanity check. --- ArcFormats/MnoViolet/ArcMnoViolet.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ArcFormats/MnoViolet/ArcMnoViolet.cs b/ArcFormats/MnoViolet/ArcMnoViolet.cs index b0c8531b..4672a28e 100644 --- a/ArcFormats/MnoViolet/ArcMnoViolet.cs +++ b/ArcFormats/MnoViolet/ArcMnoViolet.cs @@ -52,7 +52,8 @@ namespace GameRes.Formats.MnoViolet return null; uint name_size = 100; uint index_size = (uint)((name_size+8) * count); - if (index_size > file.View.Reserve (4, index_size)) + uint first_offset = file.View.ReadUInt32 (4+name_size+4); + if (first_offset != (4 + index_size) || index_size > file.View.Reserve (4, index_size)) return null; var dir = new List (count); long index_offset = 4; From 8469deef1756369d50c47904c4d69103279bb997 Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 22 Jan 2016 02:34:07 +0400 Subject: [PATCH 3/4] implemented DAF2 archives. --- ArcFormats/ArcFormats.csproj | 1 + ArcFormats/DenSDK/ArcDAF.cs | 104 +++++++++++++++++++++++++++++++++++ supported.html | 4 ++ 3 files changed, 109 insertions(+) create mode 100644 ArcFormats/DenSDK/ArcDAF.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 712603c7..e4562467 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -82,6 +82,7 @@ + diff --git a/ArcFormats/DenSDK/ArcDAF.cs b/ArcFormats/DenSDK/ArcDAF.cs new file mode 100644 index 00000000..83d839c7 --- /dev/null +++ b/ArcFormats/DenSDK/ArcDAF.cs @@ -0,0 +1,104 @@ +//! \file ArcDAF.cs +//! \date Thu Jan 21 22:33:29 2016 +//! \brief DenSDK engine resource archive. +// +// Copyright (C) 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 +// 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; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using GameRes.Compression; +using GameRes.Utility; + +namespace GameRes.Formats.DenSdk +{ + [Export(typeof(ArchiveFormat))] + public class DatOpener : ArchiveFormat + { + public override string Tag { get { return "DAT/DenSDK"; } } + public override string Description { get { return "DenSDK resource archive"; } } + public override uint Signature { get { return 0x32464144; } } // 'DAF2' + public override bool IsHierarchic { get { return true; } } + public override bool CanCreate { get { return false; } } + + public DatOpener () + { + Extensions = new string[] { "dat" }; + } + + public override ArcFile TryOpen (ArcView file) + { + uint key = (uint)(file.View.ReadByte (0x20) << 24 + | file.View.ReadByte (0x25) << 16 + | file.View.ReadByte (0x2A) << 8 + | file.View.ReadByte (0x2F)); + int count = file.View.ReadInt32 (8) ^ (int)key; + if (!IsSaneCount (count)) + return null; + uint packed_size = file.View.ReadUInt32 (0x10) ^ key; + uint unpacked_size = file.View.ReadUInt32 (0x14) ^ key; + uint base_offset = file.View.ReadUInt32 (0x1C) ^ key; + byte[] index = new byte[unpacked_size]; + bool is_packed = file.View.ReadInt32 (0x18) == 1; + if (is_packed) + { + using (var input = file.CreateStream (0x30, packed_size)) + using (var zindex = new ZLibStream (input, CompressionMode.Decompress)) + zindex.Read (index, 0, index.Length); + base_offset = 0x30 + packed_size; + } + else + { + file.View.Read (0x30, index, 0, unpacked_size); + } + int index_offset = 0; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + int entry_size = LittleEndian.ToInt32 (index, index_offset) ^ (int)key; + if (entry_size < 0x30 || entry_size > index.Length-index_offset) + return null; + var name = Binary.GetCString (index, index_offset+0x34, entry_size-0x34); + var entry = FormatCatalog.Instance.Create (name); + entry.Offset = base_offset + (LittleEndian.ToUInt32 (index, index_offset+4) ^ (uint)key); + entry.Size = LittleEndian.ToUInt32 (index, index_offset+8) ^ (uint)key; + entry.UnpackedSize = LittleEndian.ToUInt32 (index, index_offset+0xC) ^ (uint)key; + entry.IsPacked = LittleEndian.ToInt32 (index, index_offset+0x30) != 0; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + dir.Add (entry); + index_offset += entry_size; + } + return new ArcFile (file, this, dir); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var input = arc.File.CreateStream (entry.Offset, entry.Size); + var pent = entry as PackedEntry; + if (null == pent || !pent.IsPacked) + return input; + return new ZLibStream (input, CompressionMode.Decompress); + } + } +} diff --git a/supported.html b/supported.html index f4ef2e04..7f6cd349 100644 --- a/supported.html +++ b/supported.html @@ -330,6 +330,7 @@ Warusa
*.pak+*.idx-NoEAGLS Futago Hinyuu x 3
Oppai Baka
+Oshiete! Yuiko Sensei
Mainichi Shabutte Ii Desu ka?
Senpai - Oppai - Kako ni Modori Pai
@@ -627,6 +628,9 @@ Dancing Crazies
*.gd+*.dll-NoXuse Eien no Aselia -The Spirit of Eternity Sword- +*.datDAF2NoDenSDK +Ayakashi H
+

1 Non-encrypted only

From 519e824b1f7e811bef32a32f830cee1f2749234d Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 22 Jan 2016 14:02:42 +0400 Subject: [PATCH 4/4] removed unused files. --- adler32.cs | 146 ----------------------------------------------------- deflate.cs | 32 ------------ inflate.cs | 30 ----------- 3 files changed, 208 deletions(-) delete mode 100644 adler32.cs delete mode 100644 deflate.cs delete mode 100644 inflate.cs diff --git a/adler32.cs b/adler32.cs deleted file mode 100644 index b677c0c0..00000000 --- a/adler32.cs +++ /dev/null @@ -1,146 +0,0 @@ -//! \file adler32.cs -//! \date Mon Jul 21 11:19:54 2014 -//! \brief compute adler32 checksum -// - -using System.IO; -using System; - -class Adler -{ - public static void Main (string[] args) - { - if (args.Length < 1) - return; - try - { - uint adler = 1; - using (var input = File.Open (args[0], FileMode.Open, FileAccess.Read)) - { - var buf = new byte[65536]; - for (;;) - { - int read = input.Read (buf, 0, buf.Length); - if (0 == read) - break; - adler = Adler32.Update (adler, buf, 0, read); - } - } - Console.WriteLine ("{0} => {1:X8}", args[0], adler); - } - catch (Exception X) - { - Console.Error.WriteLine (X.Message); - } - } - - public sealed class Adler32 - { - const uint BASE = 65521; /* largest prime smaller than 65536 */ - const int NMAX = 5552; - - public static uint Compute (byte[] buf, int pos, int len) - { - return Update (1, buf, pos, len); - } - - public static uint Update (uint adler, byte[] buf, int pos, int len) - { - /* split Adler-32 into component sums */ - uint sum2 = (adler >> 16) & 0xffff; - adler &= 0xffff; - - /* in case user likes doing a byte at a time, keep it fast */ - if (1 == len) { - adler += buf[pos]; - if (adler >= BASE) - adler -= BASE; - sum2 += adler; - if (sum2 >= BASE) - sum2 -= BASE; - return adler | (sum2 << 16); - } - - /* in case short lengths are provided, keep it somewhat fast */ - if (len < 16) { - while (0 != len--) { - adler += buf[pos++]; - sum2 += adler; - } - if (adler >= BASE) - adler -= BASE; - sum2 %= BASE; /* only added so many BASE's */ - return adler | (sum2 << 16); - } - - /* do length NMAX blocks -- requires just one modulo operation */ - while (len >= NMAX) { - len -= NMAX; - int n = NMAX / 16; /* NMAX is divisible by 16 */ - do { - /* 16 sums unrolled */ - adler += buf[pos]; sum2 += adler; - adler += buf[pos+1]; sum2 += adler; - adler += buf[pos+2]; sum2 += adler; - adler += buf[pos+3]; sum2 += adler; - adler += buf[pos+4]; sum2 += adler; - adler += buf[pos+5]; sum2 += adler; - adler += buf[pos+6]; sum2 += adler; - adler += buf[pos+7]; sum2 += adler; - adler += buf[pos+8]; sum2 += adler; - adler += buf[pos+9]; sum2 += adler; - adler += buf[pos+10]; sum2 += adler; - adler += buf[pos+11]; sum2 += adler; - adler += buf[pos+12]; sum2 += adler; - adler += buf[pos+13]; sum2 += adler; - adler += buf[pos+14]; sum2 += adler; - adler += buf[pos+15]; sum2 += adler; - pos += 16; - } while (0 != --n); - adler %= BASE; - sum2 %= BASE; - } - - /* do remaining bytes (less than NMAX, still just one modulo) */ - if (0 != len) { /* avoid modulos if none remaining */ - while (len >= 16) { - len -= 16; - adler += buf[pos]; sum2 += adler; - adler += buf[pos+1]; sum2 += adler; - adler += buf[pos+2]; sum2 += adler; - adler += buf[pos+3]; sum2 += adler; - adler += buf[pos+4]; sum2 += adler; - adler += buf[pos+5]; sum2 += adler; - adler += buf[pos+6]; sum2 += adler; - adler += buf[pos+7]; sum2 += adler; - adler += buf[pos+8]; sum2 += adler; - adler += buf[pos+9]; sum2 += adler; - adler += buf[pos+10]; sum2 += adler; - adler += buf[pos+11]; sum2 += adler; - adler += buf[pos+12]; sum2 += adler; - adler += buf[pos+13]; sum2 += adler; - adler += buf[pos+14]; sum2 += adler; - adler += buf[pos+15]; sum2 += adler; - pos += 16; - } - while (0 != len--) { - adler += buf[pos++]; - sum2 += adler; - } - adler %= BASE; - sum2 %= BASE; - } - - /* return recombined sums */ - return adler | (sum2 << 16); - } - - private uint m_adler = 1; - public uint Value { get { return m_adler; } } - - public void Update (byte[] buf, int pos, int len) - { - m_adler = Update (m_adler, buf, pos, len); - } - } -} diff --git a/deflate.cs b/deflate.cs deleted file mode 100644 index 366c398b..00000000 --- a/deflate.cs +++ /dev/null @@ -1,32 +0,0 @@ -//! \file deflate.cs -//! \date Tue Jul 08 15:01:34 2014 -//! \brief deflate file into zlib stream. -// - -using System; -using System.IO; -using ZLibNet; - -class Inflate -{ - public static void Main (string[] args) - { - if (args.Length != 2) - { - Console.WriteLine ("Usage: deflate INPUT-FILE OUTPUT-FILE"); - return; - } - try - { - using (var input = File.Open (args[0], FileMode.Open, FileAccess.Read)) - using (var output = File.Create (args[1])) - using (var stream = new ZLibStream (output, CompressionMode.Compress, CompressionLevel.Level9)) - input.CopyTo (stream); - Console.WriteLine ("{0} => {1}", args[0], args[1]); - } - catch (Exception X) - { - Console.Error.WriteLine (X.Message); - } - } -} diff --git a/inflate.cs b/inflate.cs deleted file mode 100644 index 3e847158..00000000 --- a/inflate.cs +++ /dev/null @@ -1,30 +0,0 @@ -//! \file inflate.cs -//! \date Tue Jul 08 14:20:38 2014 -//! \brief inflate zlib stream. -// - -using System; -using System.IO; -using System.IO.Compression; - -class Inflate -{ - public static void Main (string[] args) - { - if (args.Length != 2) - return; - try - { - var input = File.Open (args[0], FileMode.Open, FileAccess.Read); - input.Position = 2; - using (var stream = new DeflateStream (input, CompressionMode.Decompress)) - using (var output = File.Create (args[1])) - stream.CopyTo (output); - Console.WriteLine ("{0} => {1}", args[0], args[1]); - } - catch (Exception X) - { - Console.Error.WriteLine (X.Message); - } - } -}