From dc30eb00833dbf7aa10e7e753b20150c02bb5d26 Mon Sep 17 00:00:00 2001 From: morkt Date: Sat, 16 Aug 2014 04:42:20 +0400 Subject: [PATCH] implemented Yu-Ris archives creation. --- ArcFormats/ArcFormats.csproj | 7 + ArcFormats/ArcYPF.cs | 178 ++++++++++++++++++--- ArcFormats/CreateYPFWidget.xaml | 27 ++++ ArcFormats/CreateYPFWidget.xaml.cs | 62 +++++++ ArcFormats/Properties/Settings.Designer.cs | 12 ++ ArcFormats/Properties/Settings.settings | 3 + ArcFormats/Strings/arcStrings.Designer.cs | 36 +++++ ArcFormats/Strings/arcStrings.resx | 12 ++ ArcFormats/Strings/arcStrings.ru-RU.resx | 12 ++ ArcFormats/WidgetYPF.xaml | 14 +- ArcFormats/WidgetYPF.xaml.cs | 6 - ArcFormats/app.config | 3 + 12 files changed, 344 insertions(+), 28 deletions(-) create mode 100644 ArcFormats/CreateYPFWidget.xaml create mode 100644 ArcFormats/CreateYPFWidget.xaml.cs diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 1920ebc3..fa2b1948 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -85,6 +85,9 @@ CreateXP3Widget.xaml + + CreateYPFWidget.xaml + @@ -162,6 +165,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + MSBuild:Compile Designer diff --git a/ArcFormats/ArcYPF.cs b/ArcFormats/ArcYPF.cs index b9c1e885..9b366ae6 100644 --- a/ArcFormats/ArcYPF.cs +++ b/ArcFormats/ArcYPF.cs @@ -33,12 +33,14 @@ using System.Windows.Media.Imaging; using ZLibNet; using GameRes.Formats.Strings; using GameRes.Formats.Properties; +using GameRes.Utility; -namespace GameRes.Formats +namespace GameRes.Formats.YuRis { public class YpfOptions : ResourceOptions { - public uint Key { get; set; } + public uint Key { get; set; } + public uint Version { get; set; } } [Export(typeof(ArchiveFormat))] @@ -48,6 +50,7 @@ namespace GameRes.Formats public override string Description { get { return arcStrings.YPFDescription; } } public override uint Signature { get { return 0x00465059; } } public override bool IsHierarchic { get { return true; } } + public override bool CanCreate { get { return true; } } private const uint DefaultKey = 0xffffffff; @@ -82,19 +85,11 @@ namespace GameRes.Formats public override ResourceOptions GetDefaultOptions () { - return new YpfOptions { Key = Settings.Default.YPFKey }; - } - - public override ResourceOptions GetOptions (object w) - { - var widget = w as GUI.WidgetYPF; - if (null != widget) + return new YpfOptions { - uint last_key = widget.GetKey() ?? DefaultKey; - Settings.Default.YPFKey = last_key; - return new YpfOptions { Key = last_key }; - } - return this.GetDefaultOptions(); + Key = Settings.Default.YPFKey, + Version = Settings.Default.YPFVersion, + }; } public override object GetAccessWidget () @@ -102,12 +97,159 @@ namespace GameRes.Formats return new GUI.WidgetYPF(); } + public override object GetCreationWidget () + { + return new GUI.CreateYPFWidget(); + } + uint QueryEncryptionKey () { var options = Query (arcStrings.YPFNotice); return options.Key; } + internal class YpfEntry : PackedEntry + { + public byte[] IndexName; + public uint NameHash; + public byte FileType; + public uint CheckSum; + } + + delegate uint ChecksumFunc (byte[] data); + + public override void Create (Stream output, IEnumerable list, ResourceOptions options, + EntryCallback callback) + { + var ypf_options = GetOptions (options); + if (null == ypf_options) + throw new ArgumentException ("Invalid archive creation options", "options"); + if (ypf_options.Key > 0xff) + throw new InvalidEncryptionScheme (arcStrings.MsgCreationKeyRequired); + if (0 == ypf_options.Version) + throw new InvalidFormatException (arcStrings.MsgInvalidVersion); + + int callback_count = 0; + var encoding = Encodings.cp932.WithFatalFallback(); + + ChecksumFunc Checksum = data => Crc32.Compute (data, 0, data.Length); + + uint data_offset = 0x20; + var file_table = new List(); + foreach (var entry in list) + { + try + { + string file_name = entry.Name; + byte[] name_buf = encoding.GetBytes (file_name); + if (name_buf.Length > 0xff) + throw new InvalidFileName (entry.Name, arcStrings.MsgFileNameTooLong); + uint hash = Checksum (name_buf); + byte file_type = GetFileType (ypf_options.Version, file_name); + + for (int i = 0; i < name_buf.Length; ++i) + name_buf[i] = (byte)(name_buf[i] ^ ypf_options.Key); + + file_table.Add (new YpfEntry { + Name = file_name, + IndexName = name_buf, + NameHash = hash, + FileType = file_type, + IsPacked = 0 == file_type, + }); + data_offset += (uint)(0x17 + name_buf.Length); + } + catch (EncoderFallbackException X) + { + throw new InvalidFileName (entry.Name, arcStrings.MsgIllegalCharacters, X); + } + } + file_table.Sort ((a, b) => a.NameHash.CompareTo (b.NameHash)); + + output.Position = data_offset; + uint current_offset = data_offset; + foreach (var entry in file_table) + { + if (null != callback) + callback (callback_count++, entry, arcStrings.MsgAddingFile); + + entry.Offset = current_offset; + using (var input = File.OpenRead (entry.Name)) + { + var file_size = input.Length; + if (file_size > uint.MaxValue || current_offset + file_size > uint.MaxValue) + throw new FileSizeException(); + entry.UnpackedSize = (uint)file_size; + using (var checked_stream = new CheckedStream (output, new Adler32())) + { + if (entry.IsPacked) + { + using (var zstream = new ZLibStream (checked_stream, CompressionMode.Compress, true)) + { + input.CopyTo (zstream); + zstream.Flush(); + entry.Size = (uint)zstream.TotalOut; + } + } + else + { + input.CopyTo (checked_stream); + entry.Size = entry.UnpackedSize; + } + checked_stream.Flush(); + entry.CheckSum = checked_stream.CheckSumValue; + current_offset += entry.Size; + } + } + } + + if (null != callback) + callback (callback_count++, null, arcStrings.MsgWritingIndex); + + output.Position = 0; + using (var writer = new BinaryWriter (output, encoding, true)) + { + writer.Write (Signature); + writer.Write (ypf_options.Version); + writer.Write (file_table.Count); + writer.Write (data_offset); + writer.BaseStream.Seek (0x20, SeekOrigin.Begin); + foreach (var entry in file_table) + { + writer.Write (entry.NameHash); + byte name_len = (byte)~Parser.Decrypt (ypf_options.Version, (byte)entry.IndexName.Length); + writer.Write (name_len); + writer.Write (entry.IndexName); + writer.Write (entry.FileType); + writer.Write ((byte)(entry.IsPacked ? 1 : 0)); + writer.Write (entry.UnpackedSize); + writer.Write (entry.Size); + writer.Write ((uint)entry.Offset); + writer.Write (entry.CheckSum); + } + } + } + + static byte GetFileType (uint version, string name) + { + // 0x0F7: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-avi, 6-wav, 7-ogg, 8-psd + // 0x122, 0x12C, 0x196: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-wav, 6-ogg, 7-psd + string ext = Path.GetExtension (name).TrimStart ('.').ToLower(); + if ("ybn" == ext) return 0; + if ("bmp" == ext) return 1; + if ("png" == ext) return 2; + if ("jpg" == ext || "jpeg" == ext) return 3; + if ("gif" == ext) return 4; + if ("avi" == ext && 0xf7 == version) return 5; + byte type = 0; + if ("wav" == ext) type = 5; + else if ("ogg" == ext) type = 6; + else if ("psd" == ext) type = 7; + if (0xf7 == version && 0 != type) + ++type; + return type; + } + private class Parser { ArcView m_file; @@ -136,7 +278,7 @@ namespace GameRes.Formats break; dir_remaining -= 0x17; - uint name_size = Decrypt ((byte)(m_file.View.ReadByte (dir_offset+4) ^ 0xff)); + uint name_size = Decrypt (m_version, (byte)(m_file.View.ReadByte (dir_offset+4) ^ 0xff)); if (name_size > dir_remaining) break; dir_remaining -= name_size; @@ -201,12 +343,12 @@ namespace GameRes.Formats // 0x28 0x12C "Suzukaze no Melt" (no recovery - 00 00 00 00) // 0xFF 0x196 "Mamono Musume-tachi to no Rakuen ~Slime & Scylla~" - public uint Decrypt (byte value) + static public byte Decrypt (uint version, byte value) { int pos = 4; - if (m_version >= 0x100) + if (version >= 0x100) { - if (m_version >= 0x12c && m_version < 0x196) + if (version >= 0x12c && version < 0x196) pos = 10; else pos = 0; diff --git a/ArcFormats/CreateYPFWidget.xaml b/ArcFormats/CreateYPFWidget.xaml new file mode 100644 index 00000000..127c9c7d --- /dev/null +++ b/ArcFormats/CreateYPFWidget.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/ArcFormats/CreateYPFWidget.xaml.cs b/ArcFormats/CreateYPFWidget.xaml.cs new file mode 100644 index 00000000..1e73d508 --- /dev/null +++ b/ArcFormats/CreateYPFWidget.xaml.cs @@ -0,0 +1,62 @@ +using System; +using System.Globalization; +using System.Windows.Controls; +using System.Windows.Data; + +namespace GameRes.Formats.GUI +{ + /// + /// Interaction logic for CreateYPFWidget.xaml + /// + public partial class CreateYPFWidget : Grid + { + public CreateYPFWidget () + { + InitializeComponent (); + } + } + + [ValueConversion (typeof (uint), typeof (string))] + class VersionToStringConverter : IValueConverter + { + public object Convert (object value, Type targetType, object parameter, CultureInfo culture) + { + uint version = (uint)value; + return version.ToString ("X"); + } + + public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture) + { + string s = value as string; + if (string.IsNullOrEmpty (s)) + return null; + uint version; + if (!uint.TryParse (s, NumberStyles.HexNumber, culture, out version)) + return null; + return version; + } + } + + [ValueConversion (typeof (uint), typeof (string))] + class KeyToStringConverter : IValueConverter + { + public object Convert (object value, Type targetType, object parameter, CultureInfo culture) + { + uint key = (uint)value; + if (key > 0xff) + return ""; + return key.ToString ("X2"); + } + + public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture) + { + string s = value as string; + if (string.IsNullOrEmpty (s)) + return uint.MaxValue; + uint key; + if (!uint.TryParse (s, NumberStyles.HexNumber, culture, out key) || key > 0xff) + return uint.MaxValue; + return key; + } + } +} diff --git a/ArcFormats/Properties/Settings.Designer.cs b/ArcFormats/Properties/Settings.Designer.cs index 4b59afd3..681b4a15 100644 --- a/ArcFormats/Properties/Settings.Designer.cs +++ b/ArcFormats/Properties/Settings.Designer.cs @@ -177,5 +177,17 @@ namespace GameRes.Formats.Properties { this["PDScrambleContents"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("290")] + public uint YPFVersion { + get { + return ((uint)(this["YPFVersion"])); + } + set { + this["YPFVersion"] = value; + } + } } } diff --git a/ArcFormats/Properties/Settings.settings b/ArcFormats/Properties/Settings.settings index be5bc78a..e94deacf 100644 --- a/ArcFormats/Properties/Settings.settings +++ b/ArcFormats/Properties/Settings.settings @@ -41,5 +41,8 @@ False + + 290 + \ No newline at end of file diff --git a/ArcFormats/Strings/arcStrings.Designer.cs b/ArcFormats/Strings/arcStrings.Designer.cs index f4e68dca..8124072f 100644 --- a/ArcFormats/Strings/arcStrings.Designer.cs +++ b/ArcFormats/Strings/arcStrings.Designer.cs @@ -234,6 +234,15 @@ namespace GameRes.Formats.Strings { } } + /// + /// Looks up a localized string similar to Encryption key required for archive creation.. + /// + public static string MsgCreationKeyRequired { + get { + return ResourceManager.GetString("MsgCreationKeyRequired", resourceCulture); + } + } + /// /// Looks up a localized string similar to Encryption method not implemented. /// @@ -270,6 +279,15 @@ namespace GameRes.Formats.Strings { } } + /// + /// Looks up a localized string similar to Invalid archive version specified.. + /// + public static string MsgInvalidVersion { + get { + return ResourceManager.GetString("MsgInvalidVersion", resourceCulture); + } + } + /// /// Looks up a localized string similar to Number of files exceedes archive limit.. /// @@ -387,6 +405,15 @@ namespace GameRes.Formats.Strings { } } + /// + /// Looks up a localized string similar to Hex number. + /// + public static string TooltipHex { + get { + return ResourceManager.GetString("TooltipHex", resourceCulture); + } + } + /// /// Looks up a localized string similar to Liar-soft game resource archive. /// @@ -468,6 +495,15 @@ namespace GameRes.Formats.Strings { } } + /// + /// Looks up a localized string similar to Archive version. + /// + public static string YPFLabelVersion { + get { + return ResourceManager.GetString("YPFLabelVersion", resourceCulture); + } + } + /// /// Looks up a localized string similar to Archive directory is encrypted. ///Enter archive encryption key.. diff --git a/ArcFormats/Strings/arcStrings.resx b/ArcFormats/Strings/arcStrings.resx index 5e1be3c9..86c8d770 100644 --- a/ArcFormats/Strings/arcStrings.resx +++ b/ArcFormats/Strings/arcStrings.resx @@ -177,6 +177,9 @@ predefined encryption scheme. Compressing index... + + Encryption key required for archive creation. + Encryption method not implemented @@ -189,6 +192,9 @@ predefined encryption scheme. {0}: image format not recognized. + + Invalid archive version specified. + Number of files exceedes archive limit. @@ -228,6 +234,9 @@ predefined encryption scheme. Filename encoding + + Hex number + Liar-soft game resource archive @@ -255,6 +264,9 @@ predefined encryption scheme. 8-bit encryption key + + Archive version + Archive directory is encrypted. Enter archive encryption key. diff --git a/ArcFormats/Strings/arcStrings.ru-RU.resx b/ArcFormats/Strings/arcStrings.ru-RU.resx index 60868c06..a3116a53 100644 --- a/ArcFormats/Strings/arcStrings.ru-RU.resx +++ b/ArcFormats/Strings/arcStrings.ru-RU.resx @@ -162,6 +162,9 @@ Сжимается оглавление... + + Для создания архива требуется ключ шифрования. + Метод шифрования не реализован @@ -174,6 +177,9 @@ {0}: не удалось распознать формат изображения. + + Указана некорректная версия архива. + Количество файлов превышает ограничения архива. @@ -198,6 +204,9 @@ Кодировка имён файлов + + Шестнадцатеричное число + Сжать содержимое @@ -216,6 +225,9 @@ 8-битный ключ шифрования + + Версия архива + Заголовок архива зашифрован. Введите ключ шифрования. diff --git a/ArcFormats/WidgetYPF.xaml b/ArcFormats/WidgetYPF.xaml index 848e3bb4..aef53417 100644 --- a/ArcFormats/WidgetYPF.xaml +++ b/ArcFormats/WidgetYPF.xaml @@ -2,11 +2,17 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:GameRes.Formats.Strings" + xmlns:p="clr-namespace:GameRes.Formats.Properties" + xmlns:gui="clr-namespace:GameRes.Formats.GUI" MaxWidth="250"> + + + - + -