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">
+
+
+
-
+
-
-
-
\ No newline at end of file
+
+
+
diff --git a/ArcFormats/WidgetYPF.xaml.cs b/ArcFormats/WidgetYPF.xaml.cs
index 94b1d244..bad6d307 100644
--- a/ArcFormats/WidgetYPF.xaml.cs
+++ b/ArcFormats/WidgetYPF.xaml.cs
@@ -12,12 +12,6 @@ namespace GameRes.Formats.GUI
public WidgetYPF ()
{
InitializeComponent();
-
- uint key = Settings.Default.YPFKey;
- if (key < 0x100)
- this.Passkey.Text = key.ToString();
- else
- this.Passkey.Text = null;
}
public uint? GetKey ()
diff --git a/ArcFormats/app.config b/ArcFormats/app.config
index c03c64d8..50534fe7 100644
--- a/ArcFormats/app.config
+++ b/ArcFormats/app.config
@@ -43,6 +43,9 @@
False
+
+ 290
+