From 7580d36414ccbea58747d2d8bf9fd0fff7e70e16 Mon Sep 17 00:00:00 2001 From: morkt Date: Wed, 11 Mar 2015 16:37:51 +0400 Subject: [PATCH] implemented Selene 'KCAP' archive format. --- ArcFormats/ArcFormats.csproj | 8 + ArcFormats/ArcKCAP.cs | 265 +++++++++++++++++++++ ArcFormats/Properties/AssemblyInfo.cs | 4 +- ArcFormats/Properties/Settings.Designer.cs | 24 ++ ArcFormats/Properties/Settings.settings | 6 + ArcFormats/Strings/arcStrings.Designer.cs | 27 +++ ArcFormats/Strings/arcStrings.resx | 9 + ArcFormats/Strings/arcStrings.ru-RU.resx | 9 + ArcFormats/WidgetKCAP.xaml | 27 +++ ArcFormats/WidgetKCAP.xaml.cs | 30 +++ ArcFormats/app.config | 6 + 11 files changed, 413 insertions(+), 2 deletions(-) create mode 100644 ArcFormats/ArcKCAP.cs create mode 100644 ArcFormats/WidgetKCAP.xaml create mode 100644 ArcFormats/WidgetKCAP.xaml.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 9b855c71..a1d326a3 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -70,6 +70,7 @@ + @@ -145,6 +146,9 @@ WidgetINT.xaml + + WidgetKCAP.xaml + WidgetNPA.xaml @@ -224,6 +228,10 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/ArcFormats/ArcKCAP.cs b/ArcFormats/ArcKCAP.cs new file mode 100644 index 00000000..21e83375 --- /dev/null +++ b/ArcFormats/ArcKCAP.cs @@ -0,0 +1,265 @@ +//! \file ArcKCAP.cs +//! \date Tue Mar 10 04:47:25 2015 +//! \brief Selene resource archive implementation. +// +// Copyright (C) 2015 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.Security.Cryptography; +using System.IO; +using GameRes.Utility; +using GameRes.Formats.Strings; +using GameRes.Formats.Properties; + +namespace GameRes.Formats.Selene +{ + public class SeleneArchive : ArcFile + { + public readonly byte[] KeyTable; + + public SeleneArchive (ArcView arc, ArchiveFormat impl, ICollection dir, byte[] key) + : base (arc, impl, dir) + { + KeyTable = key; + } + } + + internal class KcapEntry : Entry + { + public bool Encrypted; + } + + public class KcapOptions : ResourceOptions + { + public string PassPhrase { get; set; } + } + + [Export(typeof(ArchiveFormat))] + public class PackOpener : ArchiveFormat + { + public override string Tag { get { return "KCAP"; } } + public override string Description { get { return "Selene engine resource archive"; } } + public override uint Signature { get { return 0x5041434b; } } // 'KCAP' + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + static private string DefaultPassPhrase = "Selene.Default.Password"; + + public static readonly string[] KnownSchemes = new string[] { + arcStrings.KCAPDefault, + "Okaa-san ga Ippai!", + }; + private static readonly string[] Keys = new string[] { + "", // default + "hahadata256pasyamada2zikan", // "Okaa-san ga Ippai!" + }; + + public PackOpener () + { + Extensions = new string[] { "pack" }; + } + + public override ArcFile TryOpen (ArcView file) + { + int count = file.View.ReadInt32 (4); + if (count <= 0 || count > 0xfffff) + return null; + uint index_size = (uint)count * 0x54u; + if (index_size > file.View.Reserve (8, index_size)) + return null; + long index_offset = 8; + var dir = new List (count); + bool encrypted = false; + for (int i = 0; i < count; ++i) + { + string name = file.View.ReadString (index_offset, 0x40); + var entry = new KcapEntry + { + Name = name, + Type = FormatCatalog.Instance.GetTypeFromName (name), + Offset = file.View.ReadUInt32 (index_offset+0x48), + Size = file.View.ReadUInt32 (index_offset+0x4c), + Encrypted = 0 != file.View.ReadUInt32 (index_offset+0x50), + }; + if (!entry.CheckPlacement (file.MaxOffset)) + return null; + encrypted = encrypted || entry.Encrypted; + dir.Add (entry); + index_offset += 0x54; + } + if (!encrypted) + return new ArcFile (file, this, dir); + var options = Query (arcStrings.ArcEncryptedNotice); + var key = CreateKeyTable (options.PassPhrase); + return new SeleneArchive (file, this, dir, key); + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var input = arc.File.CreateStream (entry.Offset, entry.Size); + var kpa = arc as SeleneArchive; + var kpe = entry as KcapEntry; + if (null == kpa || null == kpe || !kpe.Encrypted) + return input; + return new CryptoStream (input, new KcapTransform(kpa.KeyTable), CryptoStreamMode.Read); + } + + public static string GetPassPhrase (string title) + { + System.Diagnostics.Debug.Assert (KnownSchemes.Length == Keys.Length, + "Number of known encryptions schemes does not match available pass phrases."); + var index = Array.IndexOf (KnownSchemes, title); + if (index != -1) + return Keys[index]; + else + return ""; + } + + public override object GetAccessWidget () + { + return new GUI.WidgetKCAP(); + } + + public override ResourceOptions GetDefaultOptions () + { + return new KcapOptions { + PassPhrase = Settings.Default.KCAPPassPhrase, + }; + } + + static private byte[] CreateKeyTable (string pass) // (void *this, byte *a2) + { + if (pass.Length < 8) + pass = DefaultPassPhrase; + int pass_len = pass.Length; + int hash = PasskeyHash (pass); // sub_100E0390 + var hash_table = new KeyTableGenerator (hash); +// hash_table.Init (hash); // sub_100E0680 + byte[] table = new byte[0x10000]; + for (int i = 0; i < table.Length; ++i) + { + int key = hash_table.Permutate(); // sub_100E06D0 + table[i] = (byte)((byte)pass[i % pass_len] ^ (key >> 16)); + } + return table; + } + + static int PasskeyHash (string pass) // sub_100E0390(int a1, unsigned int a2) + { + int hash = -1; // eax@1 + if (0 == pass.Length) + return ~hash; + for (int i = 0; i < pass.Length; ++i) // ecx@1 + { + hash = (byte)pass[i] ^ hash; // eax@2 + for (int j = 0; j < 8; ++j) + hash = (int)(((uint)hash >> 1) ^ (0 != (hash & 1) ? 0xEDB88320u : 0u)); + } + return ~hash; + } + + internal class KeyTableGenerator + { + private int[] m_table = new int[0x270]; + private int m_pos; + + public KeyTableGenerator (int first = 0) + { + Init (first); + } + + public void Init (int first) + { + m_table[0] = first; + for (int i = 1; i < 0x270; ++i) + m_table[i] = i + 0x6C078965 * (m_table[i-1] ^ (m_table[i-1] >> 30)); + m_pos = 0x270; + } + + private static int[] v14 = new int[2] { 0, -1727483681 }; // [sp+4h] [bp-4h]@1 + + public int Permutate () // sub_100E06D0 + { + if (m_pos >= 0x270) + { + for (int i = 0; i < 0xe3; ++i) + { + int v5 = m_table[i] ^ m_table[i+1]; + m_table[i] = m_table[i+0x18d] ^ v14[(m_table[i] ^ (byte)v5) & 1] ^ ((m_table[i] ^ v5 & 0x7FFFFFFF) >> 1); + } + for (int i = 0; i < 0x18c; ++i) + { + int v6 = i + 0xe3; + int v8 = m_table[v6] ^ m_table[v6+1]; + m_table[v6] = m_table[i] ^ v14[(m_table[v6] ^ (byte)v8) & 1] ^ ((m_table[v6] ^ v8 & 0x7FFFFFFF) >> 1); + } + int v9 = m_table[0x26f] ^ (m_table[0] ^ m_table[0x26f]) & 0x7FFFFFFF; + m_table[0x26f] = (v9 >> 1) ^ m_table[0x18c] ^ v14[v9 & 1]; + m_pos = 0; + } + int v11 = m_table[m_pos++]; // edx@7 + int v12 = (int)((((((((v11 >> 11) ^ v11) & 0xFF3A58AD) << 7) ^ (v11 >> 11) ^ v11) & 0xFFFFDF8C) << 15) ^ ((((v11 >> 11) ^ v11) & 0xFF3A58AD) << 7) ^ (v11 >> 11) ^ v11); // edx@7 + + return v12 ^ (v12 >> 18); + } + } + + internal class KcapTransform : ICryptoTransform + { + private readonly byte[] KeyTable; + + public bool CanReuseTransform { get { return true; } } + public bool CanTransformMultipleBlocks { get { return true; } } + public int InputBlockSize { get { return KeyTable.Length; } } + public int OutputBlockSize { get { return KeyTable.Length; } } + + public KcapTransform (byte[] key_table) + { + KeyTable = key_table; + } + + public int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, + byte[] outputBuffer, int outputOffset) + { + for (int i = 0; i < inputCount; ++i) + { + outputBuffer[outputOffset++] = (byte)(inputBuffer[inputOffset+i]^KeyTable[i]); + } + return inputCount; + } + + public byte[] TransformFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount) + { + byte[] outputBuffer = new byte[inputCount]; + TransformBlock (inputBuffer, inputOffset, inputCount, outputBuffer, 0); + return outputBuffer; + } + + public void Dispose () + { + System.GC.SuppressFinalize (this); + } + } + } +} diff --git a/ArcFormats/Properties/AssemblyInfo.cs b/ArcFormats/Properties/AssemblyInfo.cs index a0ceda9b..93f131ba 100644 --- a/ArcFormats/Properties/AssemblyInfo.cs +++ b/ArcFormats/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion ("1.0.3.26")] -[assembly: AssemblyFileVersion ("1.0.3.26")] +[assembly: AssemblyVersion ("1.0.3.28")] +[assembly: AssemblyFileVersion ("1.0.3.28")] diff --git a/ArcFormats/Properties/Settings.Designer.cs b/ArcFormats/Properties/Settings.Designer.cs index 523f8e8e..32919615 100644 --- a/ArcFormats/Properties/Settings.Designer.cs +++ b/ArcFormats/Properties/Settings.Designer.cs @@ -249,5 +249,29 @@ namespace GameRes.Formats.Properties { this["WARCNameLength"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string KCAPPassPhrase { + get { + return ((string)(this["KCAPPassPhrase"])); + } + set { + this["KCAPPassPhrase"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string KCAPScheme { + get { + return ((string)(this["KCAPScheme"])); + } + set { + this["KCAPScheme"] = value; + } + } } } diff --git a/ArcFormats/Properties/Settings.settings b/ArcFormats/Properties/Settings.settings index 1085099c..8de3d844 100644 --- a/ArcFormats/Properties/Settings.settings +++ b/ArcFormats/Properties/Settings.settings @@ -59,5 +59,11 @@ 8 + + + + + + \ No newline at end of file diff --git a/ArcFormats/Strings/arcStrings.Designer.cs b/ArcFormats/Strings/arcStrings.Designer.cs index 6f0e8b49..2c548ede 100644 --- a/ArcFormats/Strings/arcStrings.Designer.cs +++ b/ArcFormats/Strings/arcStrings.Designer.cs @@ -216,6 +216,15 @@ namespace GameRes.Formats.Strings { } } + /// + /// Looks up a localized string similar to Default. + /// + public static string KCAPDefault { + get { + return ResourceManager.GetString("KCAPDefault", resourceCulture); + } + } + /// /// Looks up a localized string similar to Kogado game engine resource archive. /// @@ -225,6 +234,24 @@ namespace GameRes.Formats.Strings { } } + /// + /// Looks up a localized string similar to Passphrase. + /// + public static string LabelPassphrase { + get { + return ResourceManager.GetString("LabelPassphrase", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Scheme. + /// + public static string LabelScheme { + get { + return ResourceManager.GetString("LabelScheme", resourceCulture); + } + } + /// /// Looks up a localized string similar to Liar-soft image archive. /// diff --git a/ArcFormats/Strings/arcStrings.resx b/ArcFormats/Strings/arcStrings.resx index a0b602ea..e81b8166 100644 --- a/ArcFormats/Strings/arcStrings.resx +++ b/ArcFormats/Strings/arcStrings.resx @@ -171,9 +171,18 @@ Choose appropriate encryption scheme. Enter archive encryption key or choose predefined encryption scheme. + + Default + Kogado game engine resource archive + + Passphrase + + + Scheme + Liar-soft image archive diff --git a/ArcFormats/Strings/arcStrings.ru-RU.resx b/ArcFormats/Strings/arcStrings.ru-RU.resx index aeb8bbb9..b666781f 100644 --- a/ArcFormats/Strings/arcStrings.ru-RU.resx +++ b/ArcFormats/Strings/arcStrings.ru-RU.resx @@ -159,6 +159,15 @@ Введите ключ шифрования или выберите один из предопределённых вариантов. + + Неизвестно + + + Пароль + + + Вариант + Добавляется файл diff --git a/ArcFormats/WidgetKCAP.xaml b/ArcFormats/WidgetKCAP.xaml new file mode 100644 index 00000000..5a9550f5 --- /dev/null +++ b/ArcFormats/WidgetKCAP.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/ArcFormats/WidgetKCAP.xaml.cs b/ArcFormats/WidgetKCAP.xaml.cs new file mode 100644 index 00000000..b9c789e3 --- /dev/null +++ b/ArcFormats/WidgetKCAP.xaml.cs @@ -0,0 +1,30 @@ +using System.Windows; +using System.Windows.Controls; +using GameRes.Formats.Properties; +using GameRes.Formats.Selene; + +namespace GameRes.Formats.GUI +{ + /// + /// Interaction logic for WidgetKCAP.xaml + /// + public partial class WidgetKCAP : Grid + { + public WidgetKCAP () + { + InitializeComponent (); + EncScheme.SelectionChanged += OnSchemeChanged; + + if (null == EncScheme.SelectedItem) + EncScheme.SelectedIndex = 0; + } + + void OnSchemeChanged (object sender, SelectionChangedEventArgs e) + { + var widget = sender as ComboBox; + var pass = PackOpener.GetPassPhrase (widget.SelectedItem as string); + Passphrase.Text = pass; + Settings.Default.KCAPPassPhrase = pass; + } + } +} diff --git a/ArcFormats/app.config b/ArcFormats/app.config index 476a6b4d..8de64b13 100644 --- a/ArcFormats/app.config +++ b/ArcFormats/app.config @@ -61,6 +61,12 @@ 8 + + + + + +