diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 321ea11d..eb2173d3 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -234,6 +234,7 @@ WidgetRCT.xaml + diff --git a/ArcFormats/Properties/Settings.Designer.cs b/ArcFormats/Properties/Settings.Designer.cs index bbbbbf6d..29d4403a 100644 --- a/ArcFormats/Properties/Settings.Designer.cs +++ b/ArcFormats/Properties/Settings.Designer.cs @@ -465,5 +465,17 @@ namespace GameRes.Formats.Properties { this["QLIEScheme"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string YPFScheme { + get { + return ((string)(this["YPFScheme"])); + } + set { + this["YPFScheme"] = value; + } + } } } diff --git a/ArcFormats/Properties/Settings.settings b/ArcFormats/Properties/Settings.settings index 81ea13a8..278f5829 100644 --- a/ArcFormats/Properties/Settings.settings +++ b/ArcFormats/Properties/Settings.settings @@ -113,5 +113,8 @@ + + + \ No newline at end of file diff --git a/ArcFormats/Strings/arcStrings.Designer.cs b/ArcFormats/Strings/arcStrings.Designer.cs index 915e6f43..7fa42da5 100644 --- a/ArcFormats/Strings/arcStrings.Designer.cs +++ b/ArcFormats/Strings/arcStrings.Designer.cs @@ -779,12 +779,21 @@ namespace GameRes.Formats.Strings { /// /// Looks up a localized string similar to Archive directory is encrypted. - ///Enter archive encryption key.. + ///Choose appropriate encryption scheme.. /// public static string YPFNotice { get { return ResourceManager.GetString("YPFNotice", resourceCulture); } } + + /// + /// Looks up a localized string similar to Try to guess. + /// + public static string YPFTryGuess { + get { + return ResourceManager.GetString("YPFTryGuess", resourceCulture); + } + } } } diff --git a/ArcFormats/Strings/arcStrings.ko-KR.resx b/ArcFormats/Strings/arcStrings.ko-KR.resx index 3bb88488..f045b63c 100644 --- a/ArcFormats/Strings/arcStrings.ko-KR.resx +++ b/ArcFormats/Strings/arcStrings.ko-KR.resx @@ -296,7 +296,7 @@ 아카이브 폴더가 암호화됨. -아카이브 암호값을 입력하세요. +적절한 암호체계를 선택하세요. 암호체계 @@ -368,4 +368,8 @@ 아카이브 내용이 암호화됨. + + Try to guess + translation pending + \ No newline at end of file diff --git a/ArcFormats/Strings/arcStrings.resx b/ArcFormats/Strings/arcStrings.resx index 80cd656c..be6da685 100644 --- a/ArcFormats/Strings/arcStrings.resx +++ b/ArcFormats/Strings/arcStrings.resx @@ -296,7 +296,7 @@ Choose appropriate encryption scheme. Archive directory is encrypted. -Enter archive encryption key. +Choose appropriate encryption scheme. Encryption scheme @@ -364,4 +364,7 @@ Choose appropriate encryption scheme. Archive content is encrypted. + + Try to guess + \ No newline at end of file diff --git a/ArcFormats/Strings/arcStrings.ru-RU.resx b/ArcFormats/Strings/arcStrings.ru-RU.resx index dac6abf3..e6d367b0 100644 --- a/ArcFormats/Strings/arcStrings.ru-RU.resx +++ b/ArcFormats/Strings/arcStrings.ru-RU.resx @@ -317,6 +317,9 @@ Заголовок архива зашифрован. -Введите ключ шифрования. +Выберите способ дешифровки. + + + Попытаться подобрать \ No newline at end of file diff --git a/ArcFormats/YuRis/ArcYPF.cs b/ArcFormats/YuRis/ArcYPF.cs index 02c4fdf5..764464ba 100644 --- a/ArcFormats/YuRis/ArcYPF.cs +++ b/ArcFormats/YuRis/ArcYPF.cs @@ -2,7 +2,7 @@ //! \date Mon Jul 14 14:40:06 2014 //! \brief YPF resource 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 @@ -28,8 +28,6 @@ using System.IO; using System.Text; using System.Collections.Generic; using System.ComponentModel.Composition; -using System.Windows.Media; -using System.Windows.Media.Imaging; using GameRes.Compression; using GameRes.Formats.Strings; using GameRes.Formats.Properties; @@ -39,20 +37,67 @@ namespace GameRes.Formats.YuRis { public class YpfOptions : ResourceOptions { - public uint Key { get; set; } - public uint Version { get; set; } + public uint Key { get; set; } + public uint Version { get; set; } + public string Scheme { get; set; } + } + + internal class YpfEntry : PackedEntry + { + public byte[] IndexName; + public uint NameHash; + public byte FileType; + public uint CheckSum; + } + + [Serializable] + public class YpfScheme + { + public byte[] SwapTable; + public byte Key; + public bool GuessKey; + public uint ExtraHeaderSize; + + public YpfScheme () { } + + public YpfScheme (byte[] swap_table, byte key, uint extra_size = 0) + { + SwapTable = swap_table; + Key = key; + GuessKey = false; + ExtraHeaderSize = extra_size; + } + + public YpfScheme (byte[] swap_table) + { + SwapTable = swap_table; + GuessKey = true; + ExtraHeaderSize = 0; + } + } + + [Serializable] + public class YuRisScheme : ResourceScheme + { + public Dictionary KnownSchemes; } [Export(typeof(ArchiveFormat))] public class YpfOpener : ArchiveFormat { - public override string Tag { get { return "YPF"; } } + public override string Tag { get { return "YPF"; } } 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; } } + 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; + static public Dictionary KnownSchemes = new Dictionary(); + + public override ResourceScheme Scheme + { + get { return new YuRisScheme { KnownSchemes = KnownSchemes }; } + set { KnownSchemes = ((YuRisScheme)value).KnownSchemes; } + } public override ArcFile TryOpen (ArcView file) { @@ -65,9 +110,9 @@ namespace GameRes.Formats.YuRis return null; var parser = new Parser (file, version, count, dir_size); - uint key = QueryEncryptionKey(); - var dir = parser.ScanDir (key); - if (0 == dir.Count) + var scheme = QueryEncryptionScheme (version); + var dir = parser.ScanDir (scheme); + if (null == dir || 0 == dir.Count) return null; return new ArcFile (file, this, dir); @@ -89,6 +134,7 @@ namespace GameRes.Formats.YuRis { Key = Settings.Default.YPFKey, Version = Settings.Default.YPFVersion, + Scheme = Settings.Default.YPFScheme, }; } @@ -102,18 +148,17 @@ namespace GameRes.Formats.YuRis return new GUI.CreateYPFWidget(); } - uint QueryEncryptionKey () + YpfScheme QueryEncryptionScheme (uint version) { var options = Query (arcStrings.YPFNotice); - return options.Key; - } - - internal class YpfEntry : PackedEntry - { - public byte[] IndexName; - public uint NameHash; - public byte FileType; - public uint CheckSum; + YpfScheme scheme; + if (!KnownSchemes.TryGetValue (options.Scheme, out scheme) || null == scheme) + scheme = new YpfScheme { + SwapTable = GuessSwapTable (version), + GuessKey = true, + ExtraHeaderSize = 0x1F4 == version ? 4u : 0u, + }; + return scheme; } delegate uint ChecksumFunc (byte[] data); @@ -128,7 +173,10 @@ namespace GameRes.Formats.YuRis throw new InvalidEncryptionScheme (arcStrings.MsgCreationKeyRequired); if (0 == ypf_options.Version) throw new InvalidFormatException (arcStrings.MsgInvalidVersion); - + var scheme = new YpfScheme { + SwapTable = GuessSwapTable (ypf_options.Version), + Key = (byte)ypf_options.Key + }; int callback_count = 0; var encoding = Encodings.cp932.WithFatalFallback(); @@ -218,7 +266,7 @@ namespace GameRes.Formats.YuRis foreach (var entry in file_table) { writer.Write (entry.NameHash); - byte name_len = (byte)~Parser.Decrypt (ypf_options.Version, (byte)entry.IndexName.Length); + byte name_len = (byte)~Parser.DecryptLength (scheme.SwapTable, (byte)entry.IndexName.Length); writer.Write (name_len); writer.Write (entry.IndexName); writer.Write (entry.FileType); @@ -235,6 +283,7 @@ namespace GameRes.Formats.YuRis { // 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 + // 0x1F4: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-wav, 6-ogg, 7-psd, 8-ycg 9-psb string ext = Path.GetExtension (name).TrimStart ('.').ToLower(); if ("ybn" == ext) return 0; if ("bmp" == ext) return 1; @@ -242,6 +291,8 @@ namespace GameRes.Formats.YuRis if ("jpg" == ext || "jpeg" == ext) return 3; if ("gif" == ext) return 4; if ("avi" == ext && 0xf7 == version) return 5; + if ("ycg" == ext) return 8; + if ("psb" == ext) return 9; byte type = 0; if ("wav" == ext) type = 5; else if ("ogg" == ext) type = 6; @@ -251,6 +302,22 @@ namespace GameRes.Formats.YuRis return type; } + byte[] GuessSwapTable (uint version) + { + if (0x1F4 == version) + { + YpfScheme scheme; + if (KnownSchemes.TryGetValue ("Unionism Quartet", out scheme)) + return scheme.SwapTable; + } + if (version < 0x100) + return SwapTable04; + else if (version >= 0x12c && version < 0x196) + return SwapTable10; + else + return SwapTable00; + } + private class Parser { ArcView m_file; @@ -265,103 +332,105 @@ namespace GameRes.Formats.YuRis m_dir_size = dir_size; m_version = version; } - // 4-name_checksum, 1-name_count, *-name, 1-file_type - // 1-pack_flag, 4-size, 4-packed_size, 4-offset, 4-packed_adler32 + // int32-name_checksum, byte-name_count, *-name, byte-file_type + // byte-pack_flag, int32-size, int32-packed_size, int32-offset, int32-file_checksum - public List ScanDir (uint key) + public List ScanDir (YpfScheme scheme) { uint dir_offset = 0x20; uint dir_remaining = m_dir_size; var dir = new List ((int)m_count); + byte key = scheme.Key; + bool guess_key = scheme.GuessKey; + uint extra_size = 0x12 + scheme.ExtraHeaderSize; for (uint num = 0; num < m_count; ++num) { - if (dir_remaining < 0x17) - break; - dir_remaining -= 0x17; + if (dir_remaining < 5+extra_size) + return null; + dir_remaining -= 5+extra_size; - uint name_size = Decrypt (m_version, (byte)(m_file.View.ReadByte (dir_offset+4) ^ 0xff)); + uint name_size = DecryptLength (scheme.SwapTable, (byte)(m_file.View.ReadByte (dir_offset+4) ^ 0xff)); if (name_size > dir_remaining) - break; + return null; dir_remaining -= name_size; dir_offset += 5; if (0 == name_size) - break; - if (0xffffffff == key) + return null; + byte[] raw_name = m_file.View.ReadBytes (dir_offset, name_size); + dir_offset += name_size; + if (guess_key) { if (name_size < 4) - break; + return null; // assume filename contains '.' and 3-characters extension. - key = (uint)(m_file.View.ReadByte (dir_offset+name_size-4) ^ 0x2e); + key = (byte)(raw_name[name_size-4] ^ '.'); + guess_key = false; } - byte[] raw_name = new byte[name_size]; - for (int i = 0; i < name_size; ++i) + for (uint i = 0; i < name_size; ++i) { - raw_name[i] = (byte)(m_file.View.ReadByte (dir_offset) ^ key); - ++dir_offset; + raw_name[i] ^= key; } string name = Encodings.cp932.GetString (raw_name); - // 0x0F7: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-avi, 6-wav, 7-ogg, 8-psd + // 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 + // 0x1F4: 0-ybn, 1-bmp, 2-png, 3-jpg, 4-gif, 5-wav, 6-ogg, 7-psd, 8-ycg 9-psb int type_id = m_file.View.ReadByte (dir_offset); - string type = ""; - switch (type_id) + var entry = FormatCatalog.Instance.Create (name); + if (string.IsNullOrEmpty (entry.Type)) { - case 0: - type = "script"; - break; - case 1: case 2: case 3: case 4: - type = "image"; - break; - case 5: - type = 0xf7 == m_version ? "video" : "audio"; - break; - case 6: - case 7: - type = "audio"; - break; + switch (type_id) + { + case 0: + entry.Type = "script"; + break; + case 1: case 2: case 3: case 4: case 8: + entry.Type = "image"; + break; + case 5: + entry.Type = 0xf7 == m_version ? "video" : "audio"; + break; + case 6: + entry.Type = "audio"; + break; + case 7: + entry.Type = 0xf7 == m_version ? "audio" : "image"; + break; + } } - var entry = new PackedEntry { Name = name, Type = type }; - entry.IsPacked = 1 == m_file.View.ReadByte (dir_offset+1); + entry.IsPacked = 0 != m_file.View.ReadByte (dir_offset+1); entry.UnpackedSize = m_file.View.ReadUInt32 (dir_offset+2); entry.Size = m_file.View.ReadUInt32 (dir_offset+6); entry.Offset = m_file.View.ReadUInt32 (dir_offset+10); if (entry.CheckPlacement (m_file.MaxOffset)) dir.Add (entry); - dir_offset += 0x12; + dir_offset += extra_size; } return dir; } - static readonly byte[] s_crypt_table = { - 0x03,0x48,0x06,0x35, // 0x122, 0x196 - 0x0C,0x10,0x11,0x19,0x1C,0x1E, // 0x0F7 - 0x09,0x0B,0x0D,0x13,0x15,0x1B, // 0x12C - 0x20,0x23,0x26,0x29, - 0x2C,0x2F,0x2E,0x32, - }; - // 0xFF 0x0F7 "Four-Leaf" adler32 - // 0x34 0x122 "Neko Koi!" crc32 - // 0x28 0x12C "Suzukaze no Melt" (no recovery - 00 00 00 00) - // 0xFF 0x196 "Mamono Musume-tachi to no Rakuen ~Slime & Scylla~" - - static public byte Decrypt (uint version, byte value) + static public byte DecryptLength (byte[] swap_table, byte value) { - int pos = 4; - if (version >= 0x100) - { - if (version >= 0x12c && version < 0x196) - pos = 10; - else - pos = 0; - } - pos = Array.IndexOf (s_crypt_table, value, pos); + int pos = Array.IndexOf (swap_table, value); if (-1 == pos) return value; if (0 != (pos & 1)) - return s_crypt_table[pos-1]; + return swap_table[pos-1]; else - return s_crypt_table[pos+1]; + return swap_table[pos+1]; } } + + static public byte[] SwapTable00 = { + 0x03, 0x48, 0x06, 0x35, + 0x0C, 0x10, 0x11, 0x19, 0x1C, 0x1E, 0x09, 0x0B, 0x0D, 0x13, 0x15, 0x1B, + 0x20, 0x23, 0x26, 0x29, 0x2C, 0x2F, 0x2E, 0x32, + }; + static public byte[] SwapTable04 = { + 0x0C, 0x10, 0x11, 0x19, 0x1C, 0x1E, 0x09, 0x0B, 0x0D, 0x13, 0x15, 0x1B, + 0x20, 0x23, 0x26, 0x29, 0x2C, 0x2F, 0x2E, 0x32, + }; + static public byte[] SwapTable10 = { + 0x20, 0x23, 0x26, 0x29, 0x2C, 0x2F, 0x2E, 0x32, + }; } } diff --git a/ArcFormats/YuRis/WidgetYPF.xaml b/ArcFormats/YuRis/WidgetYPF.xaml index 5df5bedb..3e8cfb04 100644 --- a/ArcFormats/YuRis/WidgetYPF.xaml +++ b/ArcFormats/YuRis/WidgetYPF.xaml @@ -5,14 +5,6 @@ xmlns:p="clr-namespace:GameRes.Formats.Properties" xmlns:gui="clr-namespace:GameRes.Formats.GUI" MaxWidth="250"> - - - - - - - -