mirror of
https://github.com/crskycode/GARbro.git
synced 2024-12-24 03:44:13 +08:00
implemented Ren'Py archives creation.
This commit is contained in:
parent
7faf641f6f
commit
3bbf2d6a90
@ -80,6 +80,9 @@
|
|||||||
<Compile Include="CreatePDWidget.xaml.cs">
|
<Compile Include="CreatePDWidget.xaml.cs">
|
||||||
<DependentUpon>CreatePDWidget.xaml</DependentUpon>
|
<DependentUpon>CreatePDWidget.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="CreateRPAWidget.xaml.cs">
|
||||||
|
<DependentUpon>CreateRPAWidget.xaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="CreateSGWidget.xaml.cs">
|
<Compile Include="CreateSGWidget.xaml.cs">
|
||||||
<DependentUpon>CreateSGWidget.xaml</DependentUpon>
|
<DependentUpon>CreateSGWidget.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
@ -158,6 +161,10 @@
|
|||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
|
<Page Include="CreateRPAWidget.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
<Page Include="CreateSGWidget.xaml">
|
<Page Include="CreateSGWidget.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
@ -31,15 +31,22 @@ using System.Diagnostics;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using GameRes.Formats.Properties;
|
||||||
|
using GameRes.Formats.Strings;
|
||||||
using ZLibNet;
|
using ZLibNet;
|
||||||
|
|
||||||
namespace GameRes.Formats.RenPy
|
namespace GameRes.Formats.RenPy
|
||||||
{
|
{
|
||||||
internal class RpaEntry : Entry
|
internal class RpaEntry : PackedEntry
|
||||||
{
|
{
|
||||||
public byte[] Header = null;
|
public byte[] Header = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class RpaOptions : ResourceOptions
|
||||||
|
{
|
||||||
|
public uint Key;
|
||||||
|
}
|
||||||
|
|
||||||
[Export(typeof(ArchiveFormat))]
|
[Export(typeof(ArchiveFormat))]
|
||||||
public class RpaOpener : ArchiveFormat
|
public class RpaOpener : ArchiveFormat
|
||||||
{
|
{
|
||||||
@ -47,6 +54,7 @@ namespace GameRes.Formats.RenPy
|
|||||||
public override string Description { get { return Strings.arcStrings.RPADescription; } }
|
public override string Description { get { return Strings.arcStrings.RPADescription; } }
|
||||||
public override uint Signature { get { return 0x2d415052; } } // "RPA-"
|
public override uint Signature { get { return 0x2d415052; } } // "RPA-"
|
||||||
public override bool IsHierarchic { get { return true; } }
|
public override bool IsHierarchic { get { return true; } }
|
||||||
|
public override bool CanCreate { get { return true; } }
|
||||||
|
|
||||||
public override ArcFile TryOpen (ArcView file)
|
public override ArcFile TryOpen (ArcView file)
|
||||||
{
|
{
|
||||||
@ -63,11 +71,11 @@ namespace GameRes.Formats.RenPy
|
|||||||
if (!uint.TryParse (key_str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out key))
|
if (!uint.TryParse (key_str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out key))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
Hashtable dict = null;
|
IDictionary dict = null;
|
||||||
using (var index = new ZLibStream (file.CreateStream (index_offset), CompressionMode.Decompress))
|
using (var index = new ZLibStream (file.CreateStream (index_offset), CompressionMode.Decompress))
|
||||||
{
|
{
|
||||||
var pickle = new Pickle (index);
|
var pickle = new Pickle (index);
|
||||||
dict = pickle.Load() as Hashtable;
|
dict = pickle.Load() as IDictionary;
|
||||||
}
|
}
|
||||||
if (null == dict)
|
if (null == dict)
|
||||||
return null;
|
return null;
|
||||||
@ -75,8 +83,8 @@ namespace GameRes.Formats.RenPy
|
|||||||
foreach (DictionaryEntry item in dict)
|
foreach (DictionaryEntry item in dict)
|
||||||
{
|
{
|
||||||
var name_raw = item.Key as byte[];
|
var name_raw = item.Key as byte[];
|
||||||
var value = item.Value as ArrayList;
|
var values = item.Value as IList;
|
||||||
if (null == name_raw || null == value || value.Count < 1)
|
if (null == name_raw || null == values || values.Count < 1)
|
||||||
{
|
{
|
||||||
Trace.WriteLine ("invalid index entry", "RpaOpener.TryOpen");
|
Trace.WriteLine ("invalid index entry", "RpaOpener.TryOpen");
|
||||||
return null;
|
return null;
|
||||||
@ -84,7 +92,7 @@ namespace GameRes.Formats.RenPy
|
|||||||
string name = Encoding.UTF8.GetString (name_raw);
|
string name = Encoding.UTF8.GetString (name_raw);
|
||||||
if (string.IsNullOrEmpty (name))
|
if (string.IsNullOrEmpty (name))
|
||||||
return null;
|
return null;
|
||||||
var tuple = value[0] as ArrayList;
|
var tuple = values[0] as IList;
|
||||||
if (null == tuple || tuple.Count < 2)
|
if (null == tuple || tuple.Count < 2)
|
||||||
{
|
{
|
||||||
Trace.WriteLine ("invalid index tuple", "RpaOpener.TryOpen");
|
Trace.WriteLine ("invalid index tuple", "RpaOpener.TryOpen");
|
||||||
@ -95,11 +103,15 @@ namespace GameRes.Formats.RenPy
|
|||||||
Name = name,
|
Name = name,
|
||||||
Type = FormatCatalog.Instance.GetTypeFromName (name),
|
Type = FormatCatalog.Instance.GetTypeFromName (name),
|
||||||
Offset = (uint)((int)tuple[0] ^ key),
|
Offset = (uint)((int)tuple[0] ^ key),
|
||||||
Size = (uint)((int)tuple[1] ^ key),
|
UnpackedSize = (uint)((int)tuple[1] ^ key),
|
||||||
};
|
};
|
||||||
|
entry.Size = entry.UnpackedSize;
|
||||||
if (tuple.Count > 2)
|
if (tuple.Count > 2)
|
||||||
|
{
|
||||||
entry.Header = tuple[2] as byte[];
|
entry.Header = tuple[2] as byte[];
|
||||||
|
if (null != entry.Header)
|
||||||
|
entry.Size -= (uint)entry.Header.Length;
|
||||||
|
}
|
||||||
dir.Add (entry);
|
dir.Add (entry);
|
||||||
}
|
}
|
||||||
if (dir.Count > 0)
|
if (dir.Count > 0)
|
||||||
@ -109,12 +121,80 @@ namespace GameRes.Formats.RenPy
|
|||||||
|
|
||||||
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
||||||
{
|
{
|
||||||
var input = arc.File.CreateStream (entry.Offset, entry.Size);
|
Stream input;
|
||||||
|
if (0 != entry.Size)
|
||||||
|
input = arc.File.CreateStream (entry.Offset, entry.Size);
|
||||||
|
else
|
||||||
|
input = Stream.Null;
|
||||||
var rpa_entry = entry as RpaEntry;
|
var rpa_entry = entry as RpaEntry;
|
||||||
if (null == rpa_entry || null == rpa_entry.Header)
|
if (null == rpa_entry || null == rpa_entry.Header)
|
||||||
return input;
|
return input;
|
||||||
return new RpaStream (rpa_entry.Header, input);
|
return new RpaStream (rpa_entry.Header, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override ResourceOptions GetDefaultOptions ()
|
||||||
|
{
|
||||||
|
return new RpaOptions { Key = Settings.Default.RPAKey };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object GetCreationWidget ()
|
||||||
|
{
|
||||||
|
return new GUI.CreateRPAWidget();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Create (Stream output, IEnumerable<Entry> list, ResourceOptions options,
|
||||||
|
EntryCallback callback)
|
||||||
|
{
|
||||||
|
var rpa_options = GetOptions<RpaOptions> (options);
|
||||||
|
int callback_count = 0;
|
||||||
|
var file_table = new Dictionary<PyString, ArrayList>();
|
||||||
|
long data_offset = 0x22;
|
||||||
|
output.Position = data_offset;
|
||||||
|
foreach (var entry in list)
|
||||||
|
{
|
||||||
|
if (null != callback)
|
||||||
|
callback (callback_count++, entry, arcStrings.MsgAddingFile);
|
||||||
|
|
||||||
|
string name = entry.Name.Replace (@"\", "/");
|
||||||
|
var rpa_entry = new RpaEntry { Name = name };
|
||||||
|
using (var file = File.OpenRead (entry.Name))
|
||||||
|
{
|
||||||
|
var size = file.Length;
|
||||||
|
if (size > uint.MaxValue)
|
||||||
|
throw new FileSizeException();
|
||||||
|
int header_size = (int)Math.Min (size, 0x10);
|
||||||
|
rpa_entry.Offset = output.Position ^ rpa_options.Key;
|
||||||
|
rpa_entry.Header = new byte[header_size];
|
||||||
|
rpa_entry.UnpackedSize = (uint)size ^ rpa_options.Key;
|
||||||
|
rpa_entry.Size = (uint)(size - header_size);
|
||||||
|
file.Read (rpa_entry.Header, 0, header_size);
|
||||||
|
file.CopyTo (output);
|
||||||
|
}
|
||||||
|
var py_name = new PyString (name);
|
||||||
|
if (file_table.ContainsKey (py_name))
|
||||||
|
file_table[py_name].Add (rpa_entry);
|
||||||
|
else
|
||||||
|
file_table[py_name] = new ArrayList { rpa_entry };
|
||||||
|
}
|
||||||
|
long index_pos = output.Position;
|
||||||
|
string signature = string.Format (CultureInfo.InvariantCulture, "RPA-3.0 {0:x16} {1:x8}\n",
|
||||||
|
index_pos, rpa_options.Key);
|
||||||
|
var header = Encoding.ASCII.GetBytes (signature);
|
||||||
|
if (header.Length > data_offset)
|
||||||
|
throw new ApplicationException ("Signature serialization failed.");
|
||||||
|
|
||||||
|
if (null != callback)
|
||||||
|
callback (callback_count++, null, arcStrings.MsgWritingIndex);
|
||||||
|
|
||||||
|
using (var index = new ZLibStream (output, CompressionMode.Compress, CompressionLevel.Level9, true))
|
||||||
|
{
|
||||||
|
var pickle = new Pickle (index);
|
||||||
|
if (!pickle.Dump (file_table))
|
||||||
|
throw new ApplicationException ("Archive index serialization failed.");
|
||||||
|
}
|
||||||
|
output.Position = 0;
|
||||||
|
output.Write (header, 0, header.Length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Pickle
|
public class Pickle
|
||||||
@ -125,27 +205,254 @@ namespace GameRes.Formats.RenPy
|
|||||||
Stack<int> m_marks = new Stack<int>();
|
Stack<int> m_marks = new Stack<int>();
|
||||||
|
|
||||||
const int HIGHEST_PROTOCOL = 2;
|
const int HIGHEST_PROTOCOL = 2;
|
||||||
const int PROTO = 0x80; /* identify pickle protocol */
|
const int BATCHSIZE = 1000;
|
||||||
const int TUPLE2 = 0x86; /* build 2-tuple from two topmost stack items */
|
const byte PROTO = 0x80; /* identify pickle protocol */
|
||||||
const int TUPLE3 = 0x87; /* build 3-tuple from three topmost stack items */
|
const byte TUPLE2 = 0x86; /* build 2-tuple from two topmost stack items */
|
||||||
const int MARK = '(';
|
const byte TUPLE3 = 0x87; /* build 3-tuple from three topmost stack items */
|
||||||
const int STOP = '.';
|
const byte MARK = (byte)'(';
|
||||||
const int BININT = 'J';
|
const byte STOP = (byte)'.';
|
||||||
const int BININT1 = 'K';
|
const byte INT = (byte)'I';
|
||||||
const int BININT2 = 'M';
|
const byte BININT = (byte)'J';
|
||||||
const int SHORT_BINSTRING = 'U';
|
const byte BININT1 = (byte)'K';
|
||||||
const int EMPTY_LIST = ']';
|
const byte BININT2 = (byte)'M';
|
||||||
const int APPEND = 'a';
|
const byte BINSTRING = (byte)'T';
|
||||||
const int BINPUT = 'q';
|
const byte SHORT_BINSTRING = (byte)'U';
|
||||||
const int LONG_BINPUT = 'r';
|
const byte BINUNICODE = (byte)'X';
|
||||||
const int SETITEMS = 'u';
|
const byte EMPTY_LIST = (byte)']';
|
||||||
const int EMPTY_DICT = '}';
|
const byte APPEND = (byte)'a';
|
||||||
|
const byte APPENDS = (byte)'e';
|
||||||
|
const byte BINPUT = (byte)'q';
|
||||||
|
const byte LONG_BINPUT = (byte)'r';
|
||||||
|
const byte SETITEM = (byte)'s';
|
||||||
|
const byte TUPLE = (byte)'t';
|
||||||
|
const byte SETITEMS = (byte)'u';
|
||||||
|
const byte EMPTY_DICT = (byte)'}';
|
||||||
|
|
||||||
public Pickle (Stream stream)
|
public Pickle (Stream stream)
|
||||||
{
|
{
|
||||||
m_stream = stream;
|
m_stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Dump (object obj)
|
||||||
|
{
|
||||||
|
m_stream.WriteByte (PROTO);
|
||||||
|
m_stream.WriteByte ((byte)HIGHEST_PROTOCOL);
|
||||||
|
if (!Save (obj))
|
||||||
|
return false;
|
||||||
|
m_stream.WriteByte (STOP);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Save (object obj)
|
||||||
|
{
|
||||||
|
if (null == obj)
|
||||||
|
{
|
||||||
|
Trace.WriteLine ("Null reference not serialized", "Pickle.Save");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
switch (Type.GetTypeCode (obj.GetType()))
|
||||||
|
{
|
||||||
|
case TypeCode.Byte: return SaveInt ((uint)(byte)obj);
|
||||||
|
case TypeCode.SByte: return SaveInt ((uint)(sbyte)obj);
|
||||||
|
case TypeCode.UInt16: return SaveInt ((uint)(ushort)obj);
|
||||||
|
case TypeCode.Int16: return SaveInt ((uint)(short)obj);
|
||||||
|
case TypeCode.Int32: return SaveInt ((uint)(int)obj);
|
||||||
|
case TypeCode.UInt32: return SaveInt ((uint)obj);
|
||||||
|
case TypeCode.Int64: return SaveLong ((long)obj);
|
||||||
|
case TypeCode.UInt64: return SaveLong ((long)(ulong)obj);
|
||||||
|
case TypeCode.Object: break;
|
||||||
|
default:
|
||||||
|
Trace.WriteLine (obj, "Object could not be serialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (obj is RpaEntry)
|
||||||
|
return SaveEntry (obj as RpaEntry);
|
||||||
|
if (obj is PyString)
|
||||||
|
return SaveString (obj as PyString);
|
||||||
|
if (obj is byte[])
|
||||||
|
return SaveString (obj as byte[]);
|
||||||
|
if (obj is IDictionary)
|
||||||
|
return SaveDict (obj as IDictionary);
|
||||||
|
if (obj is IList)
|
||||||
|
return SaveList (obj as IList);
|
||||||
|
|
||||||
|
Trace.WriteLine (obj, "Object could not be serialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveString (byte[] str)
|
||||||
|
{
|
||||||
|
int size = str.Length;
|
||||||
|
if (size < 256)
|
||||||
|
{
|
||||||
|
m_stream.WriteByte (SHORT_BINSTRING);
|
||||||
|
m_stream.WriteByte ((byte)size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_stream.WriteByte (BINSTRING);
|
||||||
|
PutInt (size);
|
||||||
|
}
|
||||||
|
m_stream.Write (str, 0, size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveString (PyString str)
|
||||||
|
{
|
||||||
|
if (str.IsAscii)
|
||||||
|
return SaveString (str.Bytes);
|
||||||
|
m_stream.WriteByte (BINUNICODE);
|
||||||
|
PutInt (str.Length);
|
||||||
|
m_stream.Write (str.Bytes, 0, str.Length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveEntry (RpaEntry entry)
|
||||||
|
{
|
||||||
|
byte opcode = null == entry.Header ? TUPLE2 : TUPLE3;
|
||||||
|
SaveLong (entry.Offset);
|
||||||
|
SaveInt (entry.UnpackedSize);
|
||||||
|
if (null != entry.Header)
|
||||||
|
SaveString (entry.Header);
|
||||||
|
m_stream.WriteByte (opcode);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveList (IList list)
|
||||||
|
{
|
||||||
|
m_stream.WriteByte (EMPTY_LIST);
|
||||||
|
if (0 == list.Count)
|
||||||
|
return true;
|
||||||
|
return BatchList (list.GetEnumerator());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BatchList (IEnumerator iterator)
|
||||||
|
{
|
||||||
|
int n = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (!iterator.MoveNext())
|
||||||
|
return false;
|
||||||
|
var first_item = iterator.Current;
|
||||||
|
if (!iterator.MoveNext())
|
||||||
|
{
|
||||||
|
if (!Save (first_item))
|
||||||
|
return false;
|
||||||
|
m_stream.WriteByte (APPEND);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_stream.WriteByte (MARK);
|
||||||
|
if (!Save (first_item))
|
||||||
|
return false;
|
||||||
|
n = 1;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (!Save (iterator.Current))
|
||||||
|
return false;
|
||||||
|
if (++n == BATCHSIZE)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
while (iterator.MoveNext());
|
||||||
|
m_stream.WriteByte (APPENDS);
|
||||||
|
}
|
||||||
|
while (n == BATCHSIZE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveInt (uint i)
|
||||||
|
{
|
||||||
|
byte[] buf = new byte[5];
|
||||||
|
buf[1] = (byte)( i & 0xff);
|
||||||
|
buf[2] = (byte)((i >> 8) & 0xff);
|
||||||
|
buf[3] = (byte)((i >> 16) & 0xff);
|
||||||
|
buf[4] = (byte)((i >> 24) & 0xff);
|
||||||
|
int length;
|
||||||
|
if (0 == buf[4] && 0 == buf[3])
|
||||||
|
{
|
||||||
|
if (0 == buf[2])
|
||||||
|
{
|
||||||
|
buf[0] = BININT1;
|
||||||
|
length = 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buf[0] = BININT2;
|
||||||
|
length = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buf[0] = BININT;
|
||||||
|
length = 5;
|
||||||
|
}
|
||||||
|
m_stream.Write (buf, 0, length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveLong (long l)
|
||||||
|
{
|
||||||
|
if (0 == ((l >> 32) & 0xffffffff))
|
||||||
|
return SaveInt ((uint)l);
|
||||||
|
m_stream.WriteByte (INT);
|
||||||
|
string num = l.ToString (CultureInfo.InvariantCulture);
|
||||||
|
var num_data = Encoding.ASCII.GetBytes (num);
|
||||||
|
m_stream.Write (num_data, 0, num_data.Length);
|
||||||
|
m_stream.WriteByte (0x0a);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveDict (IDictionary dict)
|
||||||
|
{
|
||||||
|
m_stream.WriteByte (EMPTY_DICT);
|
||||||
|
if (0 == dict.Count)
|
||||||
|
return true;
|
||||||
|
return BatchDict (dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BatchDict (IDictionary dict)
|
||||||
|
{
|
||||||
|
int dict_size = dict.Count;
|
||||||
|
var iterator = dict.GetEnumerator();
|
||||||
|
if (1 == dict_size)
|
||||||
|
{
|
||||||
|
if (!iterator.MoveNext())
|
||||||
|
return false;
|
||||||
|
if (!Save (iterator.Key))
|
||||||
|
return false;
|
||||||
|
if (!Save (iterator.Value))
|
||||||
|
return false;
|
||||||
|
m_stream.WriteByte (SETITEM);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
int i;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
i = 0;
|
||||||
|
m_stream.WriteByte (MARK);
|
||||||
|
while (iterator.MoveNext())
|
||||||
|
{
|
||||||
|
if (!Save (iterator.Key))
|
||||||
|
return false;
|
||||||
|
if (!Save (iterator.Value))
|
||||||
|
return false;
|
||||||
|
if (++i == BATCHSIZE)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_stream.WriteByte (SETITEMS);
|
||||||
|
}
|
||||||
|
while (i == BATCHSIZE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PutInt (int i)
|
||||||
|
{
|
||||||
|
m_stream.WriteByte ((byte)(i & 0xff));
|
||||||
|
m_stream.WriteByte ((byte)((i >> 8) & 0xff));
|
||||||
|
m_stream.WriteByte ((byte)((i >> 16) & 0xff));
|
||||||
|
m_stream.WriteByte ((byte)((i >> 24) & 0xff));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public object Load ()
|
public object Load ()
|
||||||
{
|
{
|
||||||
for (;;)
|
for (;;)
|
||||||
@ -183,6 +490,12 @@ namespace GameRes.Formats.RenPy
|
|||||||
break;
|
break;
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
case BINSTRING:
|
||||||
|
case BINUNICODE:
|
||||||
|
if (!LoadBinUnicode())
|
||||||
|
break;
|
||||||
|
continue;
|
||||||
|
|
||||||
case EMPTY_LIST:
|
case EMPTY_LIST:
|
||||||
if (!LoadEmptyList())
|
if (!LoadEmptyList())
|
||||||
break;
|
break;
|
||||||
@ -203,6 +516,11 @@ namespace GameRes.Formats.RenPy
|
|||||||
break;
|
break;
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
case INT:
|
||||||
|
if (!LoadInt())
|
||||||
|
break;
|
||||||
|
continue;
|
||||||
|
|
||||||
case TUPLE2:
|
case TUPLE2:
|
||||||
if (!LoadCountedTuple (2))
|
if (!LoadCountedTuple (2))
|
||||||
break;
|
break;
|
||||||
@ -218,6 +536,11 @@ namespace GameRes.Formats.RenPy
|
|||||||
break;
|
break;
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
case SETITEM:
|
||||||
|
if (!LoadSetItem())
|
||||||
|
break;
|
||||||
|
continue;
|
||||||
|
|
||||||
case SETITEMS:
|
case SETITEMS:
|
||||||
if (!LoadSetItems())
|
if (!LoadSetItems())
|
||||||
break;
|
break;
|
||||||
@ -232,7 +555,7 @@ namespace GameRes.Formats.RenPy
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Trace.TraceError ("Unknown Pickle serialization key {0:X2}", sym);
|
Trace.TraceError ("Unknown Pickle serialization opcode 0x{0:X2}", sym);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -266,7 +589,6 @@ namespace GameRes.Formats.RenPy
|
|||||||
int key = m_stream.ReadByte();
|
int key = m_stream.ReadByte();
|
||||||
if (-1 == key || 0 == m_stack.Count)
|
if (-1 == key || 0 == m_stack.Count)
|
||||||
return false;
|
return false;
|
||||||
// m_memo[key] = m_stack.Peek();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,7 +597,6 @@ namespace GameRes.Formats.RenPy
|
|||||||
int key;
|
int key;
|
||||||
if (!ReadInt (4, out key) || 0 == m_stack.Count || key < 0)
|
if (!ReadInt (4, out key) || 0 == m_stack.Count || key < 0)
|
||||||
return false;
|
return false;
|
||||||
// m_memo[key] = m_stack.Peek();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,6 +621,19 @@ namespace GameRes.Formats.RenPy
|
|||||||
int length = m_stream.ReadByte();
|
int length = m_stream.ReadByte();
|
||||||
if (-1 == length)
|
if (-1 == length)
|
||||||
return false;
|
return false;
|
||||||
|
return LoadBinString (length);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoadBinUnicode ()
|
||||||
|
{
|
||||||
|
int length;
|
||||||
|
if (!ReadInt (4, out length))
|
||||||
|
return false;
|
||||||
|
return LoadBinString (length);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoadBinString (int length)
|
||||||
|
{
|
||||||
var bytes = new byte[length];
|
var bytes = new byte[length];
|
||||||
if (length != m_stream.Read (bytes, 0, length))
|
if (length != m_stream.Read (bytes, 0, length))
|
||||||
return false;
|
return false;
|
||||||
@ -335,6 +669,16 @@ namespace GameRes.Formats.RenPy
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LoadInt ()
|
||||||
|
{
|
||||||
|
var num = m_stream.ReadStringUntil (0x0a, Encoding.ASCII);
|
||||||
|
long n;
|
||||||
|
if (!long.TryParse (num, NumberStyles.Integer, CultureInfo.InvariantCulture, out n))
|
||||||
|
return false;
|
||||||
|
m_stack.Push (n);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool LoadCountedTuple (int count)
|
bool LoadCountedTuple (int count)
|
||||||
{
|
{
|
||||||
if (m_stack.Count < count)
|
if (m_stack.Count < count)
|
||||||
@ -353,7 +697,7 @@ namespace GameRes.Formats.RenPy
|
|||||||
bool LoadAppend ()
|
bool LoadAppend ()
|
||||||
{
|
{
|
||||||
int x = m_stack.Count - 1;
|
int x = m_stack.Count - 1;
|
||||||
if (m_stack.Count < x || 0 == x)
|
if (x <= 0)
|
||||||
{
|
{
|
||||||
Trace.WriteLine ("Stack underflow", "LoadAppend");
|
Trace.WriteLine ("Stack underflow", "LoadAppend");
|
||||||
return false;
|
return false;
|
||||||
@ -381,9 +725,18 @@ namespace GameRes.Formats.RenPy
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LoadSetItem ()
|
||||||
|
{
|
||||||
|
return DoSetItems (m_stack.Count-2);
|
||||||
|
}
|
||||||
|
|
||||||
bool LoadSetItems ()
|
bool LoadSetItems ()
|
||||||
{
|
{
|
||||||
int mark = GetMarker();
|
return DoSetItems (GetMarker());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DoSetItems (int mark)
|
||||||
|
{
|
||||||
if (!(m_stack.Count >= mark && mark > 0))
|
if (!(m_stack.Count >= mark && mark > 0))
|
||||||
{
|
{
|
||||||
Trace.WriteLine ("Stack underflow", "LoadSetItems");
|
Trace.WriteLine ("Stack underflow", "LoadSetItems");
|
||||||
@ -408,8 +761,7 @@ namespace GameRes.Formats.RenPy
|
|||||||
{
|
{
|
||||||
if (clearto < 0)
|
if (clearto < 0)
|
||||||
return false;
|
return false;
|
||||||
if (clearto >= m_stack.Count)
|
if (clearto < m_stack.Count)
|
||||||
return true;
|
|
||||||
m_stack.RemoveRange (clearto, m_stack.Count-clearto);
|
m_stack.RemoveRange (clearto, m_stack.Count-clearto);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -435,6 +787,59 @@ namespace GameRes.Formats.RenPy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class PyString : IEquatable<PyString>
|
||||||
|
{
|
||||||
|
int m_hash;
|
||||||
|
byte[] m_bytes;
|
||||||
|
Lazy<bool> m_is_ascii;
|
||||||
|
|
||||||
|
public PyString (string s)
|
||||||
|
{
|
||||||
|
m_hash = s.GetHashCode();
|
||||||
|
m_bytes = Encoding.UTF8.GetBytes (s);
|
||||||
|
m_is_ascii = new Lazy<bool> (() => -1 == Array.FindIndex (m_bytes, x => x > 0x7f));
|
||||||
|
}
|
||||||
|
|
||||||
|
public PyString () : this ("")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsAscii { get { return m_is_ascii.Value; } }
|
||||||
|
|
||||||
|
public byte[] Bytes { get { return m_bytes; } }
|
||||||
|
|
||||||
|
public int Length { get { return m_bytes.Length; } }
|
||||||
|
|
||||||
|
public bool Equals (PyString other)
|
||||||
|
{
|
||||||
|
if (null == other)
|
||||||
|
return false;
|
||||||
|
if (this.m_hash != other.m_hash)
|
||||||
|
return false;
|
||||||
|
if (this.Length != other.Length)
|
||||||
|
return false;
|
||||||
|
for (var i = 0; i < m_bytes.Length; ++i)
|
||||||
|
if (m_bytes[i] != other.m_bytes[i])
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals (object other)
|
||||||
|
{
|
||||||
|
return this.Equals (other as PyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode ()
|
||||||
|
{
|
||||||
|
return m_hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString ()
|
||||||
|
{
|
||||||
|
return Encoding.UTF8.GetString (m_bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class RpaStream : Stream
|
public class RpaStream : Stream
|
||||||
{
|
{
|
||||||
byte[] m_header;
|
byte[] m_header;
|
||||||
|
35
ArcFormats/CreateRPAWidget.xaml
Normal file
35
ArcFormats/CreateRPAWidget.xaml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<Grid x:Class="GameRes.Formats.GUI.CreateRPAWidget"
|
||||||
|
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:local="clr-namespace:GameRes.Formats.GUI">
|
||||||
|
<Grid.Resources>
|
||||||
|
<local:UInt32Converter x:Key="keyConverter"/>
|
||||||
|
</Grid.Resources>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition MinWidth="75"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Label Content="{x:Static s:arcStrings.RPALabelKey}" Target="{Binding ElementName=Key}"
|
||||||
|
Grid.Column="0" Grid.Row="0" ToolTip="{x:Static s:arcStrings.TooltipHex}" HorizontalAlignment="Right"/>
|
||||||
|
<TextBox Name="Key" Grid.Column="1" Grid.Row="0" ToolTip="{x:Static s:arcStrings.TooltipHex}" Margin="3" Width="75">
|
||||||
|
<TextBox.Text>
|
||||||
|
<Binding Path="RPAKey" Source="{x:Static p:Settings.Default}" Converter="{StaticResource keyConverter}" UpdateSourceTrigger="PropertyChanged">
|
||||||
|
<Binding.ValidationRules>
|
||||||
|
<local:PasskeyRule/>
|
||||||
|
</Binding.ValidationRules>
|
||||||
|
</Binding>
|
||||||
|
</TextBox.Text>
|
||||||
|
<Validation.ErrorTemplate>
|
||||||
|
<ControlTemplate>
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Foreground="Red" FontWeight="Bold" Text="!" VerticalAlignment="Center"/>
|
||||||
|
<Border BorderBrush="Red" BorderThickness="1">
|
||||||
|
<AdornedElementPlaceholder Name="ValidationAdorner" />
|
||||||
|
</Border>
|
||||||
|
</DockPanel>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Validation.ErrorTemplate>
|
||||||
|
</TextBox>
|
||||||
|
</Grid>
|
39
ArcFormats/CreateRPAWidget.xaml.cs
Normal file
39
ArcFormats/CreateRPAWidget.xaml.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace GameRes.Formats.GUI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for CreateRPAWidget.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class CreateRPAWidget : Grid
|
||||||
|
{
|
||||||
|
public CreateRPAWidget ()
|
||||||
|
{
|
||||||
|
InitializeComponent ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ValueConversion(typeof(uint), typeof(string))]
|
||||||
|
public class UInt32Converter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert (object value, System.Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (null == value)
|
||||||
|
return "";
|
||||||
|
uint key = (uint)value;
|
||||||
|
return key.ToString ("x");
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack (object value, System.Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
string strValue = value as string;
|
||||||
|
uint result_key;
|
||||||
|
if (uint.TryParse(strValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out result_key))
|
||||||
|
return result_key;
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
ArcFormats/Properties/Settings.Designer.cs
generated
12
ArcFormats/Properties/Settings.Designer.cs
generated
@ -189,5 +189,17 @@ namespace GameRes.Formats.Properties {
|
|||||||
this["YPFVersion"] = value;
|
this["YPFVersion"] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Configuration.DefaultSettingValueAttribute("1111638594")]
|
||||||
|
public uint RPAKey {
|
||||||
|
get {
|
||||||
|
return ((uint)(this["RPAKey"]));
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
this["RPAKey"] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,5 +44,8 @@
|
|||||||
<Setting Name="YPFVersion" Type="System.UInt32" Scope="User">
|
<Setting Name="YPFVersion" Type="System.UInt32" Scope="User">
|
||||||
<Value Profile="(Default)">290</Value>
|
<Value Profile="(Default)">290</Value>
|
||||||
</Setting>
|
</Setting>
|
||||||
|
<Setting Name="RPAKey" Type="System.UInt32" Scope="User">
|
||||||
|
<Value Profile="(Default)">1111638594</Value>
|
||||||
|
</Setting>
|
||||||
</Settings>
|
</Settings>
|
||||||
</SettingsFile>
|
</SettingsFile>
|
9
ArcFormats/Strings/arcStrings.Designer.cs
generated
9
ArcFormats/Strings/arcStrings.Designer.cs
generated
@ -396,6 +396,15 @@ namespace GameRes.Formats.Strings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to 32-bit key.
|
||||||
|
/// </summary>
|
||||||
|
public static string RPALabelKey {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("RPALabelKey", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Amaterasu Translations Muv-Luv script file.
|
/// Looks up a localized string similar to Amaterasu Translations Muv-Luv script file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -231,6 +231,9 @@ predefined encryption scheme.</value>
|
|||||||
<data name="RPADescription" xml:space="preserve">
|
<data name="RPADescription" xml:space="preserve">
|
||||||
<value>Ren'Py game engine archive</value>
|
<value>Ren'Py game engine archive</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="RPALabelKey" xml:space="preserve">
|
||||||
|
<value>32-bit key</value>
|
||||||
|
</data>
|
||||||
<data name="SCRDescription" xml:space="preserve">
|
<data name="SCRDescription" xml:space="preserve">
|
||||||
<value>Amaterasu Translations Muv-Luv script file</value>
|
<value>Amaterasu Translations Muv-Luv script file</value>
|
||||||
</data>
|
</data>
|
||||||
|
@ -201,6 +201,9 @@
|
|||||||
<data name="PDScrambleContents" xml:space="preserve">
|
<data name="PDScrambleContents" xml:space="preserve">
|
||||||
<value>Шифровать содержимое</value>
|
<value>Шифровать содержимое</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="RPALabelKey" xml:space="preserve">
|
||||||
|
<value>32-битный ключ</value>
|
||||||
|
</data>
|
||||||
<data name="SGLabelEncoding" xml:space="preserve">
|
<data name="SGLabelEncoding" xml:space="preserve">
|
||||||
<value>Кодировка имён файлов</value>
|
<value>Кодировка имён файлов</value>
|
||||||
</data>
|
</data>
|
||||||
|
@ -46,6 +46,9 @@
|
|||||||
<setting name="YPFVersion" serializeAs="String">
|
<setting name="YPFVersion" serializeAs="String">
|
||||||
<value>290</value>
|
<value>290</value>
|
||||||
</setting>
|
</setting>
|
||||||
|
<setting name="RPAKey" serializeAs="String">
|
||||||
|
<value>1111638594</value>
|
||||||
|
</setting>
|
||||||
</GameRes.Formats.Properties.Settings>
|
</GameRes.Formats.Properties.Settings>
|
||||||
</userSettings>
|
</userSettings>
|
||||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup></configuration>
|
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup></configuration>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user