support different schemes for YPF archives.

This commit is contained in:
morkt 2015-12-18 13:39:26 +04:00
parent 0a118f6911
commit 923f520814
12 changed files with 213 additions and 109 deletions

View File

@ -234,6 +234,7 @@
<Compile Include="Majiro\WidgetRCT.xaml.cs">
<DependentUpon>WidgetRCT.xaml</DependentUpon>
</Compile>
<Compile Include="YuRis\ImageYCG.cs" />
<Compile Include="Zyx\ArcBDF.cs" />
<Compile Include="Zyx\ImageSPL.cs" />
<None Include="ArcSeraph.cs" />

View File

@ -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;
}
}
}
}

View File

@ -113,5 +113,8 @@
<Setting Name="QLIEScheme" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="YPFScheme" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
</Settings>
</SettingsFile>

View File

@ -779,12 +779,21 @@ namespace GameRes.Formats.Strings {
/// <summary>
/// Looks up a localized string similar to Archive directory is encrypted.
///Enter archive encryption key..
///Choose appropriate encryption scheme..
/// </summary>
public static string YPFNotice {
get {
return ResourceManager.GetString("YPFNotice", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Try to guess.
/// </summary>
public static string YPFTryGuess {
get {
return ResourceManager.GetString("YPFTryGuess", resourceCulture);
}
}
}
}

View File

@ -296,7 +296,7 @@
</data>
<data name="YPFNotice" xml:space="preserve">
<value>아카이브 폴더가 암호화됨.
아카이브 암호값을 입력하세요.</value>
적절한 암호체계를 선택하세요.</value>
</data>
<data name="LabelEncScheme" xml:space="preserve">
<value>암호체계</value>
@ -368,4 +368,8 @@
<data name="MCAEncryptedNotice" xml:space="preserve">
<value>아카이브 내용이 암호화됨.</value>
</data>
<data name="YPFTryGuess" xml:space="preserve">
<value>Try to guess</value>
<comment>translation pending</comment>
</data>
</root>

View File

@ -296,7 +296,7 @@ Choose appropriate encryption scheme.</value>
</data>
<data name="YPFNotice" xml:space="preserve">
<value>Archive directory is encrypted.
Enter archive encryption key.</value>
Choose appropriate encryption scheme.</value>
</data>
<data name="LabelEncScheme" xml:space="preserve">
<value>Encryption scheme</value>
@ -364,4 +364,7 @@ Choose appropriate encryption scheme.</value>
<data name="MCAEncryptedNotice" xml:space="preserve">
<value>Archive content is encrypted.</value>
</data>
<data name="YPFTryGuess" xml:space="preserve">
<value>Try to guess</value>
</data>
</root>

View File

@ -317,6 +317,9 @@
</data>
<data name="YPFNotice" xml:space="preserve">
<value>Заголовок архива зашифрован.
Введите ключ шифрования.</value>
Выберите способ дешифровки.</value>
</data>
<data name="YPFTryGuess" xml:space="preserve">
<value>Попытаться подобрать</value>
</data>
</root>

View File

@ -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<string, YpfScheme> 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<string, YpfScheme> KnownSchemes = new Dictionary<string, YpfScheme>();
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<YpfOptions> (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<Entry> ScanDir (uint key)
public List<Entry> ScanDir (YpfScheme scheme)
{
uint dir_offset = 0x20;
uint dir_remaining = m_dir_size;
var dir = new List<Entry> ((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<PackedEntry> (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,
};
}
}

View File

@ -5,14 +5,6 @@
xmlns:p="clr-namespace:GameRes.Formats.Properties"
xmlns:gui="clr-namespace:GameRes.Formats.GUI"
MaxWidth="250">
<Grid.Resources>
<gui:KeyToStringConverter x:Key="guiKeyConverter" />
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition MinWidth="50" Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{x:Static s:arcStrings.YPFLabelKey}" Target="{Binding ElementName=Passkey}" ToolTip="{x:Static s:arcStrings.TooltipHex}" Grid.Column="0" Grid.Row="0" Padding="0,1" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox Name="Passkey" Text="{Binding Source={x:Static p:Settings.Default}, Path=YPFKey, Mode=TwoWay, Converter={StaticResource guiKeyConverter}}"
ToolTip="{x:Static s:arcStrings.TooltipHex}" Margin="5" Width="50" Grid.Column="1" Grid.Row="0"/>
<ComboBox Name="Scheme" ItemsSource="{Binding}" DisplayMemberPath="Key" SelectedValuePath="Key"
SelectedValue="{Binding Source={x:Static p:Settings.Default}, Path=YPFScheme, Mode=TwoWay}" Width="180"/>
</Grid>

View File

@ -1,6 +1,10 @@
using System.Windows;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using GameRes.Formats.Properties;
using GameRes.Formats.Strings;
using GameRes.Formats.YuRis;
namespace GameRes.Formats.GUI
{
@ -12,15 +16,10 @@ namespace GameRes.Formats.GUI
public WidgetYPF ()
{
InitializeComponent();
}
public uint? GetKey ()
{
uint key;
if (uint.TryParse (this.Passkey.Text, out key) && key < 0x100)
return key;
else
return null;
var guess = new Dictionary<string, YpfScheme> { { arcStrings.YPFTryGuess, null } };
Scheme.ItemsSource = guess.Concat (YpfOpener.KnownSchemes.OrderBy (x => x.Key));
if (-1 == Scheme.SelectedIndex)
Scheme.SelectedIndex = 0;
}
}
}

View File

@ -115,6 +115,9 @@
<setting name="QLIEScheme" serializeAs="String">
<value />
</setting>
<setting name="YPFScheme" serializeAs="String">
<value />
</setting>
</GameRes.Formats.Properties.Settings>
</userSettings>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup></configuration>

View File

@ -26,6 +26,7 @@ Chou Dengeki Stryker<br/>
Chou Jikuu Bakuren Monogatari ~door pi chu~<br/>
H2O -Footprints in the Sand-<br/>
Melty Moment<br/>
Traveling Stars<br/>
</td></tr>
<tr><td>-</td><td><tt>CompressedBG___</tt></td><td>No</td></tr>
<tr class="odd"><td>*</td><td>-<br/><tt>SM2MPX10</tt></td><td>No</td><td rowspan="4">DRS</td><td rowspan="4">
@ -185,7 +186,11 @@ Swan Song<br/>
Zecchou Spiral!!<br/>
</td></tr>
<tr><td>*.tlg</td><td><tt>TLG0.0</tt><br/><tt>TLG5.0</tt><br/><tt>TLG6.0</tt></td><td>No</td></tr>
<tr class="odd"><td>*.ypf</td><td><tt>YPF</tt></td><td>Yes</td><td>Clock Up</td><td>Eroge! ~H mo Game mo Kaihatsu Zanmai~</td></tr>
<tr class="odd"><td>*.ypf</td><td><tt>YPF</tt></td><td>Yes</td><td rowspan="2">YU-RIS</td><td rowspan="2">
Eroge! ~H mo Game mo Kaihatsu Zanmai~<br/>
Unionism Quartet<br/>
</td></tr>
<tr class="odd"><td>*.ycg</td><td><tt>YCG</tt></td><td>No</td></tr>
<tr><td>*.isa</td><td><tt>ISM ARCHIVED</tt></td><td>No</td><td rowspan="2">ISM</td><td rowspan="2">Green ~Akizora no Screen~</td></tr>
<tr><td>*.isg</td><td><tt>ISM IMAGEFILE</tt></td><td>No</td></tr>
<tr class="odd"><td>*.dat<br/>*.pak</td><td>-</td><td>No</td><td rowspan="2">Black Rainbow</td><td rowspan="2">
@ -521,6 +526,7 @@ Natsumero<br/>
</td></tr>
<tr class="odd"><td>*.det<br/>+*.nme<br/>+*.atm</td><td>-</td><td>No</td><td rowspan="2">μ-GameOperationSystem</td><td rowspan="2">
Ippai Shimasho<br/>
Minami no Shima ni Furu Yuki<br/>
</td></tr>
<tr class="odd"><td>*.bmp</td><td><tt>Fe</tt></td><td>No</td></tr>
<tr><td>*.gpc+*.gph<br/>*.snd+*.snh<br/>*.snr+*.snh</td><td>-</td><td>No</td><td>Eushully</td><td>