mirror of
https://github.com/crskycode/GARbro.git
synced 2024-12-23 19:34:15 +08:00
implemented Ren'Py game engine archives support.
This commit is contained in:
parent
83e4414ffc
commit
5e15fb5091
@ -62,6 +62,7 @@
|
||||
<Compile Include="ArcNPA.cs" />
|
||||
<Compile Include="ArcNSA.cs" />
|
||||
<Compile Include="ArcPD.cs" />
|
||||
<Compile Include="ArcRPA.cs" />
|
||||
<Compile Include="ArcSteinsGate.cs" />
|
||||
<Compile Include="ArcXFL.cs" />
|
||||
<Compile Include="ArcXP3.cs" />
|
||||
|
546
ArcFormats/ArcRPA.cs
Normal file
546
ArcFormats/ArcRPA.cs
Normal file
@ -0,0 +1,546 @@
|
||||
//! \file ArcRPA.cs
|
||||
//! \date Sat Aug 16 05:26:13 2014
|
||||
//! \brief Ren'Py game engine archive implementation.
|
||||
//
|
||||
// Copyright (C) 2014 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
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using ZLibNet;
|
||||
|
||||
namespace GameRes.Formats.RenPy
|
||||
{
|
||||
internal class RpaEntry : Entry
|
||||
{
|
||||
public byte[] Header = null;
|
||||
}
|
||||
|
||||
[Export(typeof(ArchiveFormat))]
|
||||
public class RpaOpener : ArchiveFormat
|
||||
{
|
||||
public override string Tag { get { return "RPA"; } }
|
||||
public override string Description { get { return Strings.arcStrings.RPADescription; } }
|
||||
public override uint Signature { get { return 0x2d415052; } } // "RPA-"
|
||||
public override bool IsHierarchic { get { return true; } }
|
||||
|
||||
public override ArcFile TryOpen (ArcView file)
|
||||
{
|
||||
if (0x20302e33 != file.View.ReadUInt32 (4))
|
||||
return null;
|
||||
string index_offset_str = file.View.ReadString (8, 16, Encoding.ASCII);
|
||||
long index_offset;
|
||||
if (!long.TryParse (index_offset_str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out index_offset))
|
||||
return null;
|
||||
if (index_offset >= file.MaxOffset)
|
||||
return null;
|
||||
uint key;
|
||||
string key_str = file.View.ReadString (0x19, 8, Encoding.ASCII);
|
||||
if (!uint.TryParse (key_str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out key))
|
||||
return null;
|
||||
|
||||
Hashtable dict = null;
|
||||
using (var index = new ZLibStream (file.CreateStream (index_offset), CompressionMode.Decompress))
|
||||
{
|
||||
var pickle = new Pickle (index);
|
||||
dict = pickle.Load() as Hashtable;
|
||||
}
|
||||
if (null == dict)
|
||||
return null;
|
||||
var dir = new List<Entry> (dict.Count);
|
||||
foreach (DictionaryEntry item in dict)
|
||||
{
|
||||
var name_raw = item.Key as byte[];
|
||||
var value = item.Value as ArrayList;
|
||||
if (null == name_raw || null == value || value.Count < 1)
|
||||
{
|
||||
Trace.WriteLine ("invalid index entry", "RpaOpener.TryOpen");
|
||||
return null;
|
||||
}
|
||||
string name = Encoding.UTF8.GetString (name_raw);
|
||||
if (string.IsNullOrEmpty (name))
|
||||
return null;
|
||||
var tuple = value[0] as ArrayList;
|
||||
if (null == tuple || tuple.Count < 2)
|
||||
{
|
||||
Trace.WriteLine ("invalid index tuple", "RpaOpener.TryOpen");
|
||||
return null;
|
||||
}
|
||||
var entry = new RpaEntry
|
||||
{
|
||||
Name = name,
|
||||
Type = FormatCatalog.Instance.GetTypeFromName (name),
|
||||
Offset = (uint)((int)tuple[0] ^ key),
|
||||
Size = (uint)((int)tuple[1] ^ key),
|
||||
};
|
||||
if (tuple.Count > 2)
|
||||
entry.Header = tuple[2] as byte[];
|
||||
|
||||
dir.Add (entry);
|
||||
}
|
||||
if (dir.Count > 0)
|
||||
Trace.TraceInformation ("[{0}] [{1:X8}] [{2}]", dir[0].Name, dir[0].Offset, dir[0].Size);
|
||||
return new ArcFile (file, this, dir);
|
||||
}
|
||||
|
||||
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
||||
{
|
||||
var input = arc.File.CreateStream (entry.Offset, entry.Size);
|
||||
var rpa_entry = entry as RpaEntry;
|
||||
if (null == rpa_entry || null == rpa_entry.Header)
|
||||
return input;
|
||||
return new RpaStream (rpa_entry.Header, input);
|
||||
}
|
||||
}
|
||||
|
||||
public class Pickle
|
||||
{
|
||||
Stream m_stream;
|
||||
|
||||
ArrayList m_stack = new ArrayList();
|
||||
Stack<int> m_marks = new Stack<int>();
|
||||
|
||||
const int HIGHEST_PROTOCOL = 2;
|
||||
const int PROTO = 0x80; /* identify pickle protocol */
|
||||
const int TUPLE2 = 0x86; /* build 2-tuple from two topmost stack items */
|
||||
const int TUPLE3 = 0x87; /* build 3-tuple from three topmost stack items */
|
||||
const int MARK = '(';
|
||||
const int STOP = '.';
|
||||
const int BININT = 'J';
|
||||
const int BININT1 = 'K';
|
||||
const int BININT2 = 'M';
|
||||
const int SHORT_BINSTRING = 'U';
|
||||
const int EMPTY_LIST = ']';
|
||||
const int APPEND = 'a';
|
||||
const int BINPUT = 'q';
|
||||
const int LONG_BINPUT = 'r';
|
||||
const int SETITEMS = 'u';
|
||||
const int EMPTY_DICT = '}';
|
||||
|
||||
public Pickle (Stream stream)
|
||||
{
|
||||
m_stream = stream;
|
||||
}
|
||||
|
||||
public object Load ()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
int sym = m_stream.ReadByte();
|
||||
switch (sym)
|
||||
{
|
||||
case PROTO:
|
||||
if (!LoadProto())
|
||||
break;
|
||||
continue;
|
||||
|
||||
case EMPTY_DICT:
|
||||
if (!LoadEmptyDict())
|
||||
break;
|
||||
continue;
|
||||
|
||||
case BINPUT:
|
||||
if (!LoadBinPut())
|
||||
break;
|
||||
continue;
|
||||
|
||||
case LONG_BINPUT:
|
||||
if (!LoadLongBinPut())
|
||||
break;
|
||||
continue;
|
||||
|
||||
case MARK:
|
||||
if (!LoadMark())
|
||||
break;
|
||||
continue;
|
||||
|
||||
case SHORT_BINSTRING:
|
||||
if (!LoadShortBinstring())
|
||||
break;
|
||||
continue;
|
||||
|
||||
case EMPTY_LIST:
|
||||
if (!LoadEmptyList())
|
||||
break;
|
||||
continue;
|
||||
|
||||
case BININT:
|
||||
if (!LoadBinInt (4))
|
||||
break;
|
||||
continue;
|
||||
|
||||
case BININT1:
|
||||
if (!LoadBinInt (1))
|
||||
break;
|
||||
continue;
|
||||
|
||||
case BININT2:
|
||||
if (!LoadBinInt (2))
|
||||
break;
|
||||
continue;
|
||||
|
||||
case TUPLE2:
|
||||
if (!LoadCountedTuple (2))
|
||||
break;
|
||||
continue;
|
||||
|
||||
case TUPLE3:
|
||||
if (!LoadCountedTuple (3))
|
||||
break;
|
||||
continue;
|
||||
|
||||
case APPEND:
|
||||
if (!LoadAppend())
|
||||
break;
|
||||
continue;
|
||||
|
||||
case SETITEMS:
|
||||
if (!LoadSetItems())
|
||||
break;
|
||||
continue;
|
||||
|
||||
case STOP:
|
||||
break;
|
||||
|
||||
case -1: // EOF
|
||||
case 0:
|
||||
Trace.WriteLine ("Unexpected end of file", "Pickle.Load");
|
||||
return null;
|
||||
|
||||
default:
|
||||
Trace.TraceError ("Unknown Pickle serialization key {0:X2}", sym);
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (0 == m_stack.Count)
|
||||
{
|
||||
Trace.WriteLine ("Invalid pickle data", "Pickle.Load");
|
||||
return null;
|
||||
}
|
||||
return m_stack.Pop();
|
||||
}
|
||||
|
||||
bool LoadProto ()
|
||||
{
|
||||
int i = m_stream.ReadByte();
|
||||
if (-1 == i)
|
||||
return false;
|
||||
if (i > HIGHEST_PROTOCOL)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadEmptyDict ()
|
||||
{
|
||||
m_stack.Push (new Hashtable());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadBinPut ()
|
||||
{
|
||||
int key = m_stream.ReadByte();
|
||||
if (-1 == key || 0 == m_stack.Count)
|
||||
return false;
|
||||
// m_memo[key] = m_stack.Peek();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadLongBinPut ()
|
||||
{
|
||||
int key;
|
||||
if (!ReadInt (4, out key) || 0 == m_stack.Count || key < 0)
|
||||
return false;
|
||||
// m_memo[key] = m_stack.Peek();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadMark ()
|
||||
{
|
||||
m_marks.Push (m_stack.Count);
|
||||
return true;
|
||||
}
|
||||
|
||||
int GetMarker ()
|
||||
{
|
||||
if (0 == m_marks.Count)
|
||||
{
|
||||
Trace.TraceError ("MARK list is empty");
|
||||
return -1;
|
||||
}
|
||||
return m_marks.Pop();
|
||||
}
|
||||
|
||||
bool LoadShortBinstring ()
|
||||
{
|
||||
int length = m_stream.ReadByte();
|
||||
if (-1 == length)
|
||||
return false;
|
||||
var bytes = new byte[length];
|
||||
if (length != m_stream.Read (bytes, 0, length))
|
||||
return false;
|
||||
m_stack.Push (bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadEmptyList ()
|
||||
{
|
||||
m_stack.Push (new ArrayList());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadInt (int size, out int value)
|
||||
{
|
||||
value = 0;
|
||||
for (int i = 0; i < size; ++i)
|
||||
{
|
||||
int b = m_stream.ReadByte();
|
||||
if (-1 == b)
|
||||
return false;
|
||||
value |= b << (i * 8);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadBinInt (int size)
|
||||
{
|
||||
int x = 0;
|
||||
if (!ReadInt (size, out x))
|
||||
return false;
|
||||
m_stack.Push (x);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadCountedTuple (int count)
|
||||
{
|
||||
if (m_stack.Count < count)
|
||||
return false;
|
||||
var tuple = new ArrayList (count);
|
||||
while (--count >= 0)
|
||||
{
|
||||
var item = m_stack.Pop();
|
||||
tuple.Add (item);
|
||||
}
|
||||
tuple.Reverse();
|
||||
m_stack.Push (tuple);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadAppend ()
|
||||
{
|
||||
int x = m_stack.Count - 1;
|
||||
if (m_stack.Count < x || 0 == x)
|
||||
{
|
||||
Trace.WriteLine ("Stack underflow", "LoadAppend");
|
||||
return false;
|
||||
}
|
||||
var list = m_stack[x-1] as ArrayList;
|
||||
if (null == list)
|
||||
{
|
||||
Trace.WriteLine ("Object is not a list", "LoadAppend");
|
||||
return false;
|
||||
}
|
||||
var slice = PdataPopList (x);
|
||||
if (null == slice)
|
||||
return false;
|
||||
list.AddRange (slice);
|
||||
return true;
|
||||
}
|
||||
|
||||
ArrayList PdataPopList (int start)
|
||||
{
|
||||
int count = m_stack.Count - start;
|
||||
var list = new ArrayList (count);
|
||||
for (int i = start; i < m_stack.Count; ++i)
|
||||
list.Add (m_stack[i]);
|
||||
m_stack.RemoveRange (start, count);
|
||||
return list;
|
||||
}
|
||||
|
||||
bool LoadSetItems ()
|
||||
{
|
||||
int mark = GetMarker();
|
||||
if (!(m_stack.Count >= mark && mark > 0))
|
||||
{
|
||||
Trace.WriteLine ("Stack underflow", "LoadSetItems");
|
||||
return false;
|
||||
}
|
||||
var dict = m_stack[mark-1] as Hashtable;
|
||||
if (null == dict)
|
||||
{
|
||||
Trace.WriteLine ("Marked object is not a dictionary", "LoadSetItems");
|
||||
return false;
|
||||
}
|
||||
for (int i = mark+1; i < m_stack.Count; i += 2)
|
||||
{
|
||||
var key = m_stack[i-1];
|
||||
var value = m_stack[i];
|
||||
dict[key] = value;
|
||||
}
|
||||
return PdataClear (mark);
|
||||
}
|
||||
|
||||
bool PdataClear (int clearto)
|
||||
{
|
||||
if (clearto < 0)
|
||||
return false;
|
||||
if (clearto >= m_stack.Count)
|
||||
return true;
|
||||
m_stack.RemoveRange (clearto, m_stack.Count-clearto);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static public class ArrayListEx
|
||||
{
|
||||
static public object Peek (this ArrayList array)
|
||||
{
|
||||
return array[array.Count-1];
|
||||
}
|
||||
|
||||
static public void Push (this ArrayList array, object item)
|
||||
{
|
||||
array.Add (item);
|
||||
}
|
||||
|
||||
static public object Pop (this ArrayList array)
|
||||
{
|
||||
var item = array[array.Count-1];
|
||||
array.RemoveAt (array.Count-1);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
public class RpaStream : Stream
|
||||
{
|
||||
byte[] m_header;
|
||||
Stream m_stream;
|
||||
long m_position = 0;
|
||||
|
||||
public RpaStream (byte[] header, Stream main)
|
||||
{
|
||||
m_header = header;
|
||||
m_stream = main;
|
||||
}
|
||||
|
||||
public override bool CanRead { get { return m_stream.CanRead; } }
|
||||
public override bool CanSeek { get { return m_stream.CanSeek; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
public override long Length { get { return m_stream.Length + m_header.Length; } }
|
||||
public override long Position
|
||||
{
|
||||
get { return m_position; }
|
||||
set
|
||||
{
|
||||
m_position = Math.Max (value, 0);
|
||||
if (m_position > m_header.Length)
|
||||
{
|
||||
long stream_pos = m_stream.Seek (m_position - m_header.Length, SeekOrigin.Begin);
|
||||
m_position = m_header.Length + stream_pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
m_stream.Flush();
|
||||
}
|
||||
|
||||
public override long Seek (long offset, SeekOrigin origin)
|
||||
{
|
||||
if (SeekOrigin.Begin == origin)
|
||||
Position = offset;
|
||||
else if (SeekOrigin.Current == origin)
|
||||
Position = m_position + offset;
|
||||
else
|
||||
Position = Length + offset;
|
||||
|
||||
return m_position;
|
||||
}
|
||||
|
||||
public override int Read (byte[] buffer, int offset, int count)
|
||||
{
|
||||
int read = 0;
|
||||
if (m_position < m_header.Length)
|
||||
{
|
||||
int header_count = Math.Min (count, m_header.Length - (int)m_position);
|
||||
Array.Copy (m_header, (int)m_position, buffer, offset, header_count);
|
||||
m_position += header_count;
|
||||
read += header_count;
|
||||
offset += header_count;
|
||||
count -= header_count;
|
||||
if (count > 0)
|
||||
m_stream.Position = 0;
|
||||
}
|
||||
if (count > 0)
|
||||
{
|
||||
int stream_read = m_stream.Read (buffer, offset, count);
|
||||
m_position += stream_read;
|
||||
read += stream_read;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
public override int ReadByte ()
|
||||
{
|
||||
if (m_position < m_header.Length)
|
||||
return m_header[m_position++];
|
||||
if (m_position == m_header.Length)
|
||||
m_stream.Position = 0;
|
||||
int b = m_stream.ReadByte();
|
||||
if (-1 != b)
|
||||
m_position++;
|
||||
return b;
|
||||
}
|
||||
|
||||
public override void SetLength (long length)
|
||||
{
|
||||
throw new NotSupportedException ("RpaStream.SetLength method is not supported");
|
||||
}
|
||||
|
||||
public override void Write (byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException ("RpaStream.Write method is not supported");
|
||||
}
|
||||
|
||||
public override void WriteByte (byte value)
|
||||
{
|
||||
throw new NotSupportedException ("RpaStream.WriteByte method is not supported");
|
||||
}
|
||||
|
||||
bool disposed = false;
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
m_stream.Dispose();
|
||||
disposed = true;
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
ArcFormats/Strings/arcStrings.Designer.cs
generated
9
ArcFormats/Strings/arcStrings.Designer.cs
generated
@ -387,6 +387,15 @@ namespace GameRes.Formats.Strings {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Ren'Py game engine archive.
|
||||
/// </summary>
|
||||
public static string RPADescription {
|
||||
get {
|
||||
return ResourceManager.GetString("RPADescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Amaterasu Translations Muv-Luv script file.
|
||||
/// </summary>
|
||||
|
@ -228,6 +228,9 @@ predefined encryption scheme.</value>
|
||||
<data name="PDScrambleContents" xml:space="preserve">
|
||||
<value>Scramble contents</value>
|
||||
</data>
|
||||
<data name="RPADescription" xml:space="preserve">
|
||||
<value>Ren'Py game engine archive</value>
|
||||
</data>
|
||||
<data name="SCRDescription" xml:space="preserve">
|
||||
<value>Amaterasu Translations Muv-Luv script file</value>
|
||||
</data>
|
||||
|
Loading…
x
Reference in New Issue
Block a user