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
+
+
+
+
+
+