diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 96244cc6..4a31aca5 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -127,12 +127,18 @@ + + + + + WidgetRCT.xaml + @@ -388,6 +394,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + MSBuild:Compile Designer diff --git a/ArcFormats/ImageRCT.cs b/ArcFormats/ImageRCT.cs index 63745c81..05dba2c6 100644 --- a/ArcFormats/ImageRCT.cs +++ b/ArcFormats/ImageRCT.cs @@ -2,7 +2,7 @@ //! \date Fri Aug 01 11:36:31 2014 //! \brief RCT/RC8 image format implementation. // -// Copyright (C) 2014 by morkt +// Copyright (C) 2014-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 @@ -29,9 +29,11 @@ using System.Text; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Diagnostics; +using System.Linq; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; +using GameRes.Formats.Properties; using GameRes.Formats.Strings; using GameRes.Utility; @@ -46,6 +48,11 @@ namespace GameRes.Formats.Majiro public uint AddSize; } + internal class RctOptions : ResourceOptions + { + public string Password; + } + [Export(typeof(ImageFormat))] public class RctFormat : ImageFormat { @@ -53,6 +60,12 @@ namespace GameRes.Formats.Majiro public override string Description { get { return "Majiro game engine RGB image format"; } } public override uint Signature { get { return 0x9a925a98; } } + public static Dictionary KnownKeys = new Dictionary { + { "Akatsuki no Goei", "おぬぐり食べる?" }, + { "Nagisa no", "青い空に向かって、溜息を一つこぼす。" }, + { "White ~blanche comme la lune~", "たった3枚の紙" }, + }; + public override ImageMetaData ReadMetaData (Stream stream) { stream.Position = 4; @@ -99,6 +112,8 @@ namespace GameRes.Formats.Majiro } } + byte[] Key = null; + public override ImageData Read (Stream file, ImageMetaData info) { var meta = info as RctMetaData; @@ -110,22 +125,83 @@ namespace GameRes.Formats.Majiro file.Position = meta.DataOffset; byte[] data = new byte[meta.AddSize]; if (data.Length != file.Read (data, 0, data.Length)) - return null; + throw new EndOfStreamException(); } file.Position = meta.DataOffset + meta.AddSize; if (meta.IsEncrypted) { - throw new NotImplementedException ("RCT image decryption is not implemented"); + if (null == Key) + { + var password = QueryPassword(); + if (string.IsNullOrEmpty (password)) + throw new UnknownEncryptionScheme(); + Key = InitDecryptionKey (password); + } + byte[] data = new byte[meta.DataSize]; + if (data.Length != file.Read (data, 0, data.Length)) + throw new EndOfStreamException(); + + for (int i = 0; i < data.Length; ++i) + { + data[i] ^= Key[i & 0x3FF]; + } + file = new MemoryStream (data); } - using (var reader = new Reader (file, meta)) + try { - reader.Unpack(); - byte[] pixels = reader.Data; - var bitmap = BitmapSource.Create ((int)meta.Width, (int)meta.Height, 96, 96, - PixelFormats.Bgr24, null, pixels, (int)meta.Width*3); - bitmap.Freeze(); - return new ImageData (bitmap, meta); + using (var reader = new Reader (file, meta)) + { + reader.Unpack(); + return ImageData.Create (meta, PixelFormats.Bgr24, null, reader.Data, (int)meta.Width*3); + } } + catch + { + if (meta.IsEncrypted) + Key = null; // probably incorrect encryption scheme caused exception, reset key + throw; + } + } + + private byte[] InitDecryptionKey (string password) + { + byte[] bin_pass = Encodings.cp932.GetBytes (password); + uint crc32 = Crc32.Compute (bin_pass, 0, bin_pass.Length); + byte[] key_table = new byte[0x400]; + unsafe + { + fixed (byte* key_ptr = key_table) + { + uint* key32 = (uint*)key_ptr; + for (int i = 0; i < 0x100; ++i) + *key32++ = crc32 ^ Crc32.Table[(i + crc32) & 0xFF]; + } + } + return key_table; + } + + private string QueryPassword () + { + var options = Query (arcStrings.RCTNotice); + return options.Password; + } + + public override ResourceOptions GetDefaultOptions () + { + return new RctOptions { Password = Settings.Default.RCTPassword }; + } + + public override ResourceOptions GetOptions (object widget) + { + var w = widget as GUI.WidgetRCT; + if (null != w) + Settings.Default.RCTPassword = w.Password.Text; + return GetDefaultOptions(); + } + + public override object GetAccessWidget () + { + return new GUI.WidgetRCT(); } public override void Write (Stream file, ImageData image) diff --git a/ArcFormats/Properties/Settings.Designer.cs b/ArcFormats/Properties/Settings.Designer.cs index 0de953b7..9969900b 100644 --- a/ArcFormats/Properties/Settings.Designer.cs +++ b/ArcFormats/Properties/Settings.Designer.cs @@ -393,5 +393,29 @@ namespace GameRes.Formats.Properties { this["MCGLastKey"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string RCTPassword { + get { + return ((string)(this["RCTPassword"])); + } + set { + this["RCTPassword"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string RCTTitle { + get { + return ((string)(this["RCTTitle"])); + } + set { + this["RCTTitle"] = value; + } + } } } diff --git a/ArcFormats/Properties/Settings.settings b/ArcFormats/Properties/Settings.settings index ae03c51c..72014501 100644 --- a/ArcFormats/Properties/Settings.settings +++ b/ArcFormats/Properties/Settings.settings @@ -95,5 +95,11 @@ 0 + + + + + + \ No newline at end of file diff --git a/ArcFormats/Strings/arcStrings.Designer.cs b/ArcFormats/Strings/arcStrings.Designer.cs index 9eb86aaf..7dbadb90 100644 --- a/ArcFormats/Strings/arcStrings.Designer.cs +++ b/ArcFormats/Strings/arcStrings.Designer.cs @@ -524,6 +524,24 @@ namespace GameRes.Formats.Strings { } } + /// + /// Looks up a localized string similar to Choose title or enter a password. + /// + public static string RCTChoose { + get { + return ResourceManager.GetString("RCTChoose", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Image is encrypted.. + /// + public static string RCTNotice { + get { + return ResourceManager.GetString("RCTNotice", resourceCulture); + } + } + /// /// Looks up a localized string similar to Ren'Py game engine archive. /// diff --git a/ArcFormats/Strings/arcStrings.resx b/ArcFormats/Strings/arcStrings.resx index 37bd7fe5..8d057a21 100644 --- a/ArcFormats/Strings/arcStrings.resx +++ b/ArcFormats/Strings/arcStrings.resx @@ -325,4 +325,10 @@ Choose encryption scheme or enter a passphrase. Ignore encryption + + Image is encrypted. + + + Choose title or enter a password + \ No newline at end of file diff --git a/ArcFormats/Strings/arcStrings.ru-RU.resx b/ArcFormats/Strings/arcStrings.ru-RU.resx index a2e1cf0d..885aa6d2 100644 --- a/ArcFormats/Strings/arcStrings.ru-RU.resx +++ b/ArcFormats/Strings/arcStrings.ru-RU.resx @@ -245,6 +245,12 @@ Шифровать содержимое + + Выберите наименование или введите пароль + + + Изображение зашифровано. + 32-битный ключ diff --git a/ArcFormats/WidgetRCT.xaml b/ArcFormats/WidgetRCT.xaml new file mode 100644 index 00000000..b2423911 --- /dev/null +++ b/ArcFormats/WidgetRCT.xaml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/ArcFormats/WidgetRCT.xaml.cs b/ArcFormats/WidgetRCT.xaml.cs new file mode 100644 index 00000000..164b3d81 --- /dev/null +++ b/ArcFormats/WidgetRCT.xaml.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Windows.Controls; +using GameRes.Formats.Properties; + +namespace GameRes.Formats.GUI +{ + /// + /// Interaction logic for WidgetRCT.xaml + /// + public partial class WidgetRCT : Grid + { + public WidgetRCT () + { + InitializeComponent (); + this.Password.Text = Settings.Default.RCTPassword; + if (null != this.Title.SelectedItem) + { + var selected = (KeyValuePair)this.Title.SelectedItem; + if (Settings.Default.RCTPassword != selected.Value) + this.Title.SelectedIndex = -1; + } + } + + private void Title_SelectionChanged (object sender, SelectionChangedEventArgs e) + { + if (null != this.Title.SelectedItem && null != this.Password) + { + var selected = (KeyValuePair)this.Title.SelectedItem; + this.Password.Text = selected.Value; + } + } + } +} diff --git a/ArcFormats/app.config b/ArcFormats/app.config index 67fefa59..3182ffc0 100644 --- a/ArcFormats/app.config +++ b/ArcFormats/app.config @@ -97,6 +97,12 @@ 0 + + + + + +