diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 92b64b63..2dbdcbda 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -107,6 +107,9 @@ + + WidgetEAGLS.xaml + WidgetGYU.xaml @@ -667,6 +670,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/ArcFormats/Eagls/ArcEAGLS.cs b/ArcFormats/Eagls/ArcEAGLS.cs index 67a9d3ff..010c8518 100644 --- a/ArcFormats/Eagls/ArcEAGLS.cs +++ b/ArcFormats/Eagls/ArcEAGLS.cs @@ -27,7 +27,9 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; -using System.Linq; +using System.Text; +using GameRes.Formats.Properties; +using GameRes.Formats.Strings; using GameRes.Utility; namespace GameRes.Formats.Eagls @@ -47,10 +49,8 @@ namespace GameRes.Formats.Eagls } internal static readonly string IndexKey = "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/-@:^[]"; - internal static readonly string Key = "EAGLS_SYSTEM"; - - // FIXME not thread-safe - CRuntimeRandomGenerator m_rng = new CRuntimeRandomGenerator(); + internal static readonly byte[] EaglsKey = Encoding.ASCII.GetBytes ("EAGLS_SYSTEM"); + internal static readonly byte[] AdvSysKey = Encoding.ASCII.GetBytes ("ADVSYS"); public override ArcFile TryOpen (ArcView file) { @@ -70,6 +70,7 @@ namespace GameRes.Formats.Eagls bool long_offsets = 40 == entry_size; int name_size = long_offsets ? 0x18 : 0x14; long first_offset = LittleEndian.ToUInt32 (index, name_size); + bool has_scripts = false; var dir = new List(); while (index_offset < index.Length) { @@ -79,7 +80,10 @@ namespace GameRes.Formats.Eagls index_offset += name_size; var entry = FormatCatalog.Instance.Create (name); if (name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase)) + { entry.Type = "script"; + has_scripts = true; + } if (long_offsets) { entry.Offset = LittleEndian.ToInt64 (index, index_offset) - first_offset; @@ -99,32 +103,28 @@ namespace GameRes.Formats.Eagls if (0 == dir.Count) return null; if (dir[0].Name.EndsWith (".gr", StringComparison.InvariantCultureIgnoreCase)) // CG archive - return new CgArchive (file, this, dir); - else - return new ArcFile (file, this, dir); + { + var rng = DetectEncryptionScheme (file, dir[0]); + return new EaglsArchive (file, this, dir, new CgEncryption (rng)); + } + else if (has_scripts) + { + var enc = QueryEncryption(); + if (enc != null) + return new EaglsArchive (file, this, dir, enc); + } + return new ArcFile (file, this, dir); } public override Stream OpenEntry (ArcFile arc, Entry 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); - } + var earc = arc as EaglsArchive; + if (null == earc + || !(entry.Name.EndsWith (".dat", StringComparison.InvariantCultureIgnoreCase) || + entry.Name.EndsWith (".gr", StringComparison.InvariantCultureIgnoreCase))) + return arc.File.CreateStream (entry.Offset, entry.Size); - Stream DecryptDat (ArcFile arc, Entry entry) - { - byte[] input = arc.File.View.ReadBytes (entry.Offset, entry.Size); - int text_offset = 3600; - int text_length = (int)(entry.Size - text_offset - 2); - m_rng.SRand ((sbyte)input[input.Length-1]); - for (int i = 0; i < text_length; i += 2) - { - input[text_offset + i] ^= (byte)Key[m_rng.Rand() % Key.Length]; - } - return new MemoryStream (input); + return earc.DecryptEntry (entry); } byte[] DecryptIndex (ArcView idx) @@ -134,13 +134,14 @@ namespace GameRes.Formats.Eagls using (var view = idx.CreateViewAccessor (0, (uint)idx.MaxOffset)) unsafe { - m_rng.SRand (view.ReadInt32 (idx_size)); + var rng = new CRuntimeRandomGenerator(); + rng.SRand (view.ReadInt32 (idx_size)); byte* ptr = view.GetPointer (0); try { for (int i = 0; i < idx_size; ++i) { - output[i] = (byte)(ptr[i] ^ IndexKey[m_rng.Rand() % IndexKey.Length]); + output[i] = (byte)(ptr[i] ^ IndexKey[rng.Rand() % IndexKey.Length]); } return output; } @@ -150,15 +151,69 @@ namespace GameRes.Formats.Eagls } } } + + IRandomGenerator DetectEncryptionScheme (ArcView file, Entry first_entry) + { + 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 ^= EaglsKey[rng.Rand() % EaglsKey.Length]; + test ^= EaglsKey[rng.Rand() % EaglsKey.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 override ResourceOptions GetDefaultOptions () + { + IEntryEncryption enc = null; + if (!string.IsNullOrEmpty (Settings.Default.EAGLSEncryption)) + KnownSchemes.TryGetValue (Settings.Default.EAGLSEncryption, out enc); + return new EaglsOptions { Encryption = enc }; + } + + public override object GetAccessWidget () + { + return new GUI.WidgetEAGLS(); + } + + IEntryEncryption QueryEncryption () + { + var options = Query (arcStrings.ArcEncryptedNotice); + return options.Encryption; + } + + internal static Dictionary KnownSchemes = new Dictionary + { + { "EAGLS", new EaglsEncryption() }, + { "AdvSys", new AdvSysEncryption() }, + }; } - internal interface IRandomGenerator + public class EaglsOptions : ResourceOptions + { + public IEntryEncryption Encryption; + } + + public interface IRandomGenerator { void SRand (int seed); int Rand (); } - internal class CRuntimeRandomGenerator : IRandomGenerator + [Serializable] + public class CRuntimeRandomGenerator : IRandomGenerator { uint m_seed; @@ -174,7 +229,8 @@ namespace GameRes.Formats.Eagls } } - internal class LehmerRandomGenerator : IRandomGenerator + [Serializable] + public class LehmerRandomGenerator : IRandomGenerator { int m_seed; @@ -197,57 +253,86 @@ namespace GameRes.Formats.Eagls } } - internal class CgArchive : ArcFile + public interface IEntryEncryption { - IRandomGenerator m_rng; + void Decrypt (byte[] data); + } - public CgArchive (ArcView arc, ArchiveFormat impl, ICollection dir) - : base (arc, impl, dir) + [Serializable] + public class CgEncryption : IEntryEncryption + { + readonly byte[] Key = PakOpener.EaglsKey; + readonly IRandomGenerator m_rng; + + public CgEncryption (IRandomGenerator rng) { - try - { - m_rng = DetectEncryptionScheme(); - } - catch - { - this.Dispose(); - throw; - } + m_rng = rng; } - IRandomGenerator DetectEncryptionScheme () + public void Decrypt (byte[] data) { - 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) + m_rng.SRand (data[data.Length-1]); + int limit = Math.Min (data.Length-1, 0x174b); + for (int i = 0; i < limit; ++i) { - 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; + data[i] ^= (byte)Key[m_rng.Rand() % Key.Length]; } - throw new UnknownEncryptionScheme(); + } + } + + [Serializable] + public class EaglsEncryption : IEntryEncryption + { + readonly byte[] Key = PakOpener.EaglsKey; + readonly IRandomGenerator m_rng; + + public EaglsEncryption () + { + m_rng = new CRuntimeRandomGenerator(); + } + + public void Decrypt (byte[] data) + { + int text_offset = 3600; + int text_length = data.Length - text_offset - 2; + m_rng.SRand ((sbyte)data[data.Length-1]); + for (int i = 0; i < text_length; i += 2) + { + data[text_offset + i] ^= Key[m_rng.Rand() % Key.Length]; + } + } + } + + [Serializable] + public class AdvSysEncryption : IEntryEncryption + { + readonly byte[] Key = PakOpener.AdvSysKey; + + public void Decrypt (byte[] data) + { + int text_offset = 136000; + int text_length = data.Length - text_offset; + for (int i = 0; i < text_length; ++i) + { + data[text_offset + i] ^= Key[i % Key.Length]; + } + } + } + + internal class EaglsArchive : ArcFile + { + readonly IEntryEncryption Encryption; + + public EaglsArchive (ArcView arc, ArchiveFormat impl, ICollection dir, IEntryEncryption enc) + : base (arc, impl, dir) + { + Encryption = enc; } 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]; - } + byte[] input = File.View.ReadBytes (entry.Offset, entry.Size); + Encryption.Decrypt (input); return new MemoryStream (input); } } diff --git a/ArcFormats/Eagls/WidgetEAGLS.xaml b/ArcFormats/Eagls/WidgetEAGLS.xaml new file mode 100644 index 00000000..8e8f2961 --- /dev/null +++ b/ArcFormats/Eagls/WidgetEAGLS.xaml @@ -0,0 +1,9 @@ + + + diff --git a/ArcFormats/Eagls/WidgetEAGLS.xaml.cs b/ArcFormats/Eagls/WidgetEAGLS.xaml.cs new file mode 100644 index 00000000..e9fc6729 --- /dev/null +++ b/ArcFormats/Eagls/WidgetEAGLS.xaml.cs @@ -0,0 +1,22 @@ +using System.Windows.Controls; +using System.Linq; +using GameRes.Formats.Eagls; +using GameRes.Formats.Strings; + +namespace GameRes.Formats.GUI +{ + /// + /// Interaction logic for WidgetEAGLS.xaml + /// + public partial class WidgetEAGLS : StackPanel + { + public WidgetEAGLS () + { + InitializeComponent (); + var schemes = new string[] { arcStrings.ArcIgnoreEncryption }; + Scheme.ItemsSource = schemes.Concat (PakOpener.KnownSchemes.Keys); + if (-1 == Scheme.SelectedIndex) + Scheme.SelectedValue = PakOpener.KnownSchemes.First().Key; + } + } +} diff --git a/ArcFormats/Properties/Settings.Designer.cs b/ArcFormats/Properties/Settings.Designer.cs index a744a3aa..b8c7815e 100644 --- a/ArcFormats/Properties/Settings.Designer.cs +++ b/ArcFormats/Properties/Settings.Designer.cs @@ -645,5 +645,17 @@ namespace GameRes.Formats.Properties { this["PAZTitle"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string EAGLSEncryption { + get { + return ((string)(this["EAGLSEncryption"])); + } + set { + this["EAGLSEncryption"] = value; + } + } } } diff --git a/ArcFormats/Properties/Settings.settings b/ArcFormats/Properties/Settings.settings index 95bb5245..73836f72 100644 --- a/ArcFormats/Properties/Settings.settings +++ b/ArcFormats/Properties/Settings.settings @@ -158,5 +158,8 @@ + + + \ No newline at end of file diff --git a/ArcFormats/app.config b/ArcFormats/app.config index 0ac84150..5a8f517e 100644 --- a/ArcFormats/app.config +++ b/ArcFormats/app.config @@ -160,6 +160,9 @@ + + + diff --git a/supported.html b/supported.html index 24dc174a..df8f4c20 100644 --- a/supported.html +++ b/supported.html @@ -420,6 +420,7 @@ Draculius ShiinaRio v2.38 Enkaku Sousa 2.36 or 2.37 Gensou no Idea ~Oratorio Phantasm Historia~ShiinaRio v2.49 Gohoushi Nurse ~Mayonaka no Kyousei Call~ ShiinaRio v2.50 +Hana to Otome ni Shukufuku o ShiinaRio v2.46 Helter Skelter ShiinaRio v2.40 Hin wa Bokura no Fuku no Kami ShiinaRio v2.49 Hitozuma Onna Kyoushi Reika ShiinaRio v2.39 @@ -524,6 +525,8 @@ Futago Hinyuu x 3 Oppai Baka Oshiete! Yuiko Sensei Mainichi Shabutte Ii Desu ka? +Ryoujoku Chikan Jigoku +Saimin Class WONDERFUL Senpai - Oppai - Kako ni Modori Pai Tsuribaka ~Gakuen Taikou! Joshikousei Tsuriage Adventure~ @@ -567,6 +570,7 @@ Medorei ~Okasareta Houkago~ Onna Kyoushi Ore to Kanojo wa Shuju na Kankei Saishuu Chikan Densha +Saishuu Chikan Densha 3 Serina Ura Nyuugaku ~Ineki ni Nureta Kyoukasho~ @@ -878,6 +882,7 @@ Gakuen Saimin Reido *.akbAKBNo *.cpzCPZ5CPZ6NoCMVS Amatsutsumi +Ashita no Kimi to Au Tame ni Chrono Clock Hapymaher Haruiro Ouse