1415 lines
45 KiB
C#

//! \file ArcRIO.cs
//! \date Thu Nov 03 13:21:56 2016
//! \brief rUGP engine resource archive.
//
// Copyright (C) 2016 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.IO;
using System.Linq;
using System.Text;
namespace GameRes.Formats.Rugp
{
[Export(typeof(ArchiveFormat))]
public class RioOpener : ArchiveFormat
{
public override string Tag { get { return "RIO"; } }
public override string Description { get { return "rUGP engine resource archive"; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return true; } }
public override bool CanWrite { get { return false; } }
public RioOpener ()
{
Signatures = new uint[] { CRioArchive.RioSignature, 0 };
}
static readonly Dictionary<string, string> SupportedClasses = new Dictionary<string, string> {
{ "CRip007", "image" },
{ "CRip", "image" },
{ "CS5i", "image" },
{ "CIcon", "image" },
{ "CRsa", "script" },
{ "CVmFunc", "script" },
{ "CWaveAudio", "audio" },
{ "CrelicHicompAudio", "audio" },
};
public override ArcFile TryOpen (ArcView file)
{
using (var reader = RioReader.Create (file))
{
if (null == reader)
return null;
reader.DeserializeRelic();
var nodes = reader.Arc.LoadArray.OfType<COceanNode>();
var types = nodes.Select (n => n.ClassName).Distinct();
var dir = from node in nodes
where SupportedClasses.ContainsKey (node.ClassName)
select new Entry {
Name = node.GetPathName(),
Type = SupportedClasses[node.ClassName],
Offset = node.Offset,
Size = node.Size
};
if (!dir.Any())
return null;
return new ArcFile (file, this, dir.ToList());
}
}
}
internal sealed class RioReader : IDisposable
{
IBinaryStream m_input;
IBinaryStream m_toc;
CRioArchive m_arc;
bool m_read_toc;
CrelicUnitedGameProject m_relic;
const uint IciKey = 0xB29D5A0C;
public CRioArchive Arc { get { return m_arc; } }
public CrelicUnitedGameProject DeserializeRelic ()
{
if (!m_read_toc)
{
m_read_toc = true;
m_relic = m_arc.DeserializeRoot() as CrelicUnitedGameProject;
if (m_toc != m_input)
{
m_toc.Dispose();
m_toc = m_input;
m_arc.SetSource (m_input);
}
}
return m_relic;
}
static public RioReader Create (ArcView file)
{
if (CRioArchive.RioSignature == file.View.ReadUInt32 (0))
return new RioReader (file);
if (file.Name.HasExtension (".ici"))
return null;
var ici_name = file.Name + ".ici";
if (!VFS.FileExists (ici_name))
{
ici_name = Path.ChangeExtension (file.Name, ".ici");
if (!VFS.FileExists (ici_name))
return null;
}
byte[] ici_data;
using (var ici = VFS.OpenBinaryStream (ici_name))
ici_data = ReadIci (ici, IciKey);
CObjectArcMan arc_man;
using (var ici = new BinMemoryStream (ici_data))
{
var rio = new CRioArchive (ici);
arc_man = rio.DeserializeRoot() as CObjectArcMan;
if (null == arc_man)
return null;
}
var arc_object = arc_man.ArcList.FirstOrDefault();
if (null == arc_object || !VFS.IsPathEqualsToFileName (file.Name, arc_object.RioName))
return null;
return new RioReader (arc_man, file);
}
private RioReader (ArcView file)
{
m_input = file.CreateStream();
m_toc = m_input;
m_arc = new CRioArchive (m_input);
}
private RioReader (CObjectArcMan arc_man, ArcView file)
{
long toc_offset = arc_man.TocOffset;
uint signature = file.View.ReadUInt32 (toc_offset);
int shift = 0;
if (signature != CRioArchive.EncryptedSignature)
{
toc_offset *= 2;
signature = file.View.ReadUInt32 (toc_offset);
if (signature != CRioArchive.EncryptedSignature)
throw new InvalidFormatException ("CPmArchive signature not found");
shift = 1;
}
m_input = file.CreateStream();
m_toc = file.CreateStream (toc_offset, (uint)arc_man.TocSize);
m_arc = new CRioArchive (m_toc, shift, true);
}
public CObject ReadObject (COceanNode node)
{
return m_arc.ReadObject (node);
}
static byte[] ReadIci (IBinaryStream ici, uint key)
{
var rio = new CRioArchive (ici);
var ici_data = rio.ReadEncrypted (key);
return DecryptIci (ici_data);
}
static byte[] DecryptIci (byte[] input)
{
var output = new byte[input.Length];
int src = 0;
int dst = 0;
int tail_size;
int chunk_count = Math.DivRem (input.Length, 6, out tail_size);
for (int n = chunk_count; n > 0; --n)
{
output[dst++] = input[src];
output[dst++] = input[src + chunk_count];
output[dst++] = input[src + chunk_count * 2];
output[dst++] = input[src + chunk_count * 3];
output[dst++] = input[src + chunk_count * 4];
output[dst++] = input[src + chunk_count * 5];
++src;
}
if (tail_size > 0)
Buffer.BlockCopy (input, input.Length - tail_size, output, dst, tail_size);
byte acc = 0;
for (int i = 0; i < output.Length; ++i)
{
output[i] -= acc;
acc += output[i];
output[i] ^= 0xA5;
}
src = 0;
dst = 0;
chunk_count = Math.DivRem (input.Length, 5, out tail_size);
for (int n = chunk_count; n > 0; --n)
{
input[dst++] = output[src];
input[dst++] = output[src + chunk_count];
input[dst++] = output[src + chunk_count * 2];
input[dst++] = output[src + chunk_count * 3];
input[dst++] = output[src + chunk_count * 4];
++src;
}
if (tail_size > 0)
Buffer.BlockCopy (output, output.Length - tail_size, input, dst, tail_size);
acc = 0;
for (int i = input.Length-1; i >= 0; --i)
{
input[i] -= acc;
acc += input[i];
}
src = 0;
dst = 0;
chunk_count = Math.DivRem (input.Length, 3, out tail_size);
for (int n = chunk_count; n > 0; --n)
{
output[dst++] = (byte)(input[src] ^ 0x18);
output[dst++] = (byte)(input[src + chunk_count] ^ 0x3F);
output[dst++] = (byte)(input[src + chunk_count * 2] ^ 0xE2);
++src;
}
if (tail_size > 0)
Buffer.BlockCopy (input, input.Length - tail_size, output, dst, tail_size);
return output;
}
#region IDisposable Members
bool _disposed = false;
public void Dispose ()
{
if (!_disposed)
{
m_input.Dispose();
if (m_toc != m_input)
m_toc.Dispose();
_disposed = true;
}
}
#endregion
}
internal class CRioArchive
{
IBinaryStream m_input;
int m_field_4C;
string m_field_50;
int m_field_54;
bool m_field_60;
int m_shift;
int m_objectSchema = -1;
Dictionary<int, COceanNode> m_OceanMap = new Dictionary<int, COceanNode>();
ArrayList m_LoadArray = new ArrayList();
public IBinaryStream Input { get { return m_input; } }
public ArrayList LoadArray { get { return m_LoadArray; } }
public bool IsEncrypted { get { return 0 != (m_field_4C & 4); } }
private bool m_field_5C { get { return IsEncrypted; } }
public int Schema { get; private set; }
public const uint EncryptedSignature = 0x1EDB927C;
public const uint ObjectSignature = 0x29F6CBA4;
public const uint RioSignature = 0x596E32CD;
public const uint IciSignature = 0x673CE92A;
public CRioArchive (IBinaryStream input)
{
m_input = input;
}
public CRioArchive (IBinaryStream input, int shift, bool encrypted) : this (input)
{
m_shift = shift;
if (encrypted)
m_field_4C |= 4;
}
public IBinaryStream SetSource (IBinaryStream source)
{
var prev = m_input;
m_input = source;
return prev;
}
public int GetObjectSchema ()
{
int schema = m_objectSchema;
m_objectSchema = -1;
return schema;
}
public CObject DeserializeRoot ()
{
PopulateLoadArray();
uint signature;
var arc_class = LoadRioTypeCore (out signature);
var obj = CreateObject (arc_class);
MapObjectEntry (obj);
if (RioSignature == signature)
obj.Flags |= 0x80;
else if (EncryptedSignature == signature)
obj.Flags |= 0x180;
DeserializeClassList (obj);
obj.Deserialize (this);
return obj;
}
public CObject ReadObject (COceanNode node)
{
var obj = CreateObject (node.Name);
PopulateLoadArray();
m_input.Position = ((long)node.Offset << m_shift);
int f1 = ReadByte() & 3;
int f2 = ReadByte();
int f3 = ReadByte();
int f4 = 0;
switch (f2 >> 6)
{
case 0: f4 = f3 >> 6; break;
case 1: f4 = f3 >> 2; break;
case 2: f4 = (f3 & 0xFE) << 1 | (ReadByte() & 0xC0) << 1; break;
case 3: f4 = (f3 & 0xFE) << 1 | (ReadByte() & 0xFE) << 6; break;
}
obj.Flags = node.Flags;
MapObjectEntry (obj);
if (2 == f1)
{
int flags = ReadUInt16();
m_field_4C = (m_field_4C & 0xFFFF) | flags << 16;
}
else if (3 == f1)
{
int schema = ReadUInt16();
int flags = ReadUInt16();
m_field_4C = (m_field_4C & 0xFFFF) | flags << 16;
}
obj.Deserialize (this);
return obj;
}
CObject CreateObject (string class_name)
{
if (!s_classTable.ContainsKey (class_name))
throw new InvalidFormatException (string.Format ("[RIO] Unknown class '{0}'", class_name));
return s_classTable[class_name].CreateObject();
}
void PopulateLoadArray ()
{
m_LoadArray.Clear();
m_LoadArray.Add (null);
m_LoadArray.Add (this);
}
static readonly ISet<uint> CoreSignatures = new HashSet<uint> {
IciSignature, EncryptedSignature, RioSignature, ObjectSignature
};
public string LoadRioTypeCore (out uint signature)
{
signature = m_input.ReadUInt32();
if (!CoreSignatures.Contains (signature))
throw new InvalidFormatException ("[RIO] invalid signature");
int version = ReadUInt16();
if (version >= 0x10 && version <= 0x3FFF)
{
Schema = version;
if (version >= 0x11)
{
m_field_4C &= 0xFFFF;
m_field_4C |= ReadUInt16() << 16;
}
}
else
m_input.Seek (-2, SeekOrigin.Current);
if (EncryptedSignature == signature)
m_field_4C |= 0xC;
return ReadClass();
}
int m_depth = 0;
const int MaxRecursionDepth = 40; // arbitrary
void DeserializeClassList (CObject root)
{
if (IsEncrypted && 0 != (root.Flags & 0x200))
return;
try
{
if (++m_depth > MaxRecursionDepth)
throw new InvalidFormatException ("[RIO] deserialization recursion limit exceeded");
int count = ReadCount();
for (int i = 0; i < count; ++i)
{
if (IsEncrypted)
{
var node = CreateOceanEntry1 ("unrefix");
DeserializeNode (node, node != null);
if (node != null)
node.Parent = root;
}
else
{
m_field_50 = ReadString();
m_field_54 = 0;
var node = CreateOceanEntry2 (m_field_50);
if (node != null)
{
MapObjectEntry (node);
DeserializeNode (node);
node.Parent = root;
}
else
{
node = FindObject (m_field_50) as COceanNode;
MapObjectEntry (node);
DeserializeNode (node, false);
}
}
}
}
finally
{
--m_depth;
}
}
void DeserializeNode (COceanNode node, bool store_to_map = true)
{
int flags = ReadUInt16(); // this.field_1E
string class_ref;
switch (flags & 7)
{
case 0:
if (0 != (flags & 0x8000))
ReadByte();
else
ReadUInt16();
class_ref = ReadClass();
break;
case 1:
ReadInt32();
class_ref = ReadCType();
break;
default:
throw new InvalidFormatException();
}
node.ClassName = class_ref;
if (node != null)
{
if (store_to_map)
node.Flags = flags;
if (0 != (flags & 8))
{
if (!store_to_map)
node.Flags |= 8;
int id1 = ReadInt32(); // this.field_20
int id2 = ReadInt32(); // this.field_24
if (IsEncrypted && store_to_map)
{
node.Flags |= 0x100;
if (!m_OceanMap.ContainsKey (id1))
m_OceanMap[id1] = node;
}
if (node != null)
{
node.Offset = (uint)id1;
node.Size = (uint)id2;
}
}
}
else
{
if (0 == (flags & 8))
throw new InvalidFormatException();
int id = ReadInt32();
ReadInt32();
if (!m_OceanMap.TryGetValue (id, out node))
throw new InvalidFormatException();
}
DeserializeClassList (node);
}
string ReadCType ()
{
int c = ReadUInt16();
switch (c)
{
case 0x1E57:
return ReadClass();
case 0x2D6B:
return ReadBasicType();
case 0x2F1A:
return ReadBasicType();
case 0x369E:
if (m_field_54 > 0x13)
return ReadMsgClass();
else
return ReadMessage();
default:
throw new InvalidFormatException();
}
}
string ReadMessage ()
{
int id = ReadUInt16();
int cLen = ReadUInt16();
if (id != -1 || cLen >= 0x400)
throw new InvalidFormatException();
var buffer = new byte[cLen];
if (cLen != Read (buffer, 0, cLen))
return null;
var name = Encodings.cp932.GetString (buffer, 2, cLen-2);
return GetRtcFromMessageName (name);
}
string ReadBasicType ()
{
ReadUInt16();
int cLen = ReadUInt16();
if (cLen >= 0x40 || cLen != Read (m_name_buf, 0, cLen))
throw new InvalidFormatException();
var name = Encodings.cp932.GetString (m_name_buf, 0, cLen);
if (!s_basicTypeList.ContainsKey (name))
throw new InvalidFormatException (string.Format ("[RIO] Unknown basic type '{0}'", name));
return s_basicTypeList[name];
}
string ReadMsgClass ()
{
int wTag = ReadUInt16();
int obTag;
if (0x7FFF == wTag)
obTag = ReadInt32();
else
obTag = ((wTag & wClassTag) << 16) | (wTag & ~wClassTag);
if (0 == (obTag & dwBigClassTag))
throw new InvalidFormatException ("[RIO] invalid message class");
if (0xFFFF == wTag)
{
throw new NotImplementedException();
}
else
{
throw new NotImplementedException();
}
}
string GetRtcFromMessageName (string name)
{
throw new NotImplementedException();
}
void MapObjectEntry (CObject node) // CArchive::MapObject
{
m_LoadArray.Add (node);
}
CObject FindObject (string name)
{
throw new NotImplementedException();
}
COceanNode CreateOceanEntry1 (string name)
{
return new COceanNode (name);
}
COceanNode CreateOceanEntry2 (string name)
{
return new COceanNode (name);
}
COceanNode CreateAnonymousRio (string name)
{
throw new NotImplementedException();
}
const int wClassTag = 0x8000; // 0x8000 indicates class tag (OR'd)
const int dwBigClassTag = (int)-0x80000000; // 0x8000000 indicates big class tag (OR'd)
public CObject ReadRioReference (string base_ref)
{
if (!m_field_60)
{
m_field_60 = true;
if (m_field_5C)
{
int count = ReadShortCount();
for (int i = 0; i < count; ++i)
MapObjectEntry (null); // new CObject()
}
}
int tag;
var class_ref = ReadClass (out tag);
if (null == class_ref)
{
if (tag >= m_LoadArray.Count)
throw new InvalidFormatException ("Bad class");
return m_LoadArray[tag] as CObject;
}
COceanNode obj;
int flags = ReadUInt16();
if (0 != (flags & 0x40))
{
obj = CreateAnonymousRio (class_ref);
MapObjectEntry (obj);
return obj;
}
int id1 = 0, id2 = 0;
string name = null;
if (IsEncrypted)
{
id1 = ReadInt32();
id2 = ReadInt32();
}
else
{
name = ReadString();
}
var rio = ReadRioReference ("CRio");
if (null == rio)
throw new InvalidFormatException();
if (0 != (flags & 7))
ReadInt32();
else if (0 != (flags & 0x8000))
ReadByte();
else
ReadUInt16();
if (IsEncrypted)
{
obj = ReadEncryptedObject (rio, class_ref, flags, id1, id2);
obj.Name = base_ref;
}
else
{
throw new NotImplementedException();
}
MapObjectEntry (obj);
return obj;
}
string ReadClass ()
{
int tag;
return ReadClass (out tag);
}
string ReadClass (out int obTag)
{
int wTag = ReadUInt16();
if (0x7FFF == wTag)
{
obTag = ReadInt32();
}
else
{
obTag = ((wTag & wClassTag) << 16) | (wTag & ~wClassTag);
}
if (0 == (obTag & dwBigClassTag))
{
return null;
}
string class_ref;
if (0xFFFF == wTag)
{
uint schema = 0;
if ((m_field_4C & 8) != 0)
class_ref = LoadScrambledClass (out schema);
else
class_ref = LoadRuntimeClass (out schema);
if (null == class_ref)
throw new InvalidFormatException();
// m_objectSchema = (int)schema;
m_LoadArray.Add (class_ref);
}
else
{
obTag &= 0x7FFFFFFF;
if (0 == obTag || obTag >= m_LoadArray.Count)
throw new InvalidFormatException ("Bad class");
class_ref = (string)m_LoadArray[obTag];
}
return class_ref;
}
byte[] m_name_buf = new byte[0x100];
// loads a runtime class description
string LoadRuntimeClass (out uint schema)
{
schema = ReadUInt16();
int nLen = ReadUInt16();
// load the class name
if (nLen >= 0x40 || Read (m_name_buf, 0, nLen) != nLen)
return null;
return Encoding.ASCII.GetString (m_name_buf, 0, nLen);
}
string LoadScrambledClass (out uint schema)
{
schema = ReadUInt16();
int length = ReadByte();
if (0xFF == length)
{
length = ReadUInt16();
}
if (length >= 0x100 || Read (m_name_buf, 0, length) != length)
return null;
return DecodeClassName (m_name_buf, length);
}
COceanNode ReadEncryptedObject (CObject rio, string class_ref, int flags, int id1, int id2)
{
COceanNode node;
if (!m_OceanMap.TryGetValue (id1, out node))
throw new InvalidFormatException();
if (node != null)
{
node.Offset = DecodeOffset (id1);
node.Size = DecodeSize (id2);
}
return node;
}
public int ReadInt32 ()
{
return m_input.ReadInt32();
}
public long ReadInt64 ()
{
return m_input.ReadInt64();
}
public ushort ReadUInt16 ()
{
return m_input.ReadUInt16();
}
public byte ReadByte ()
{
return m_input.ReadUInt8();
}
public int Read (byte[] buffer, int offset, int count)
{
return m_input.Read (buffer, offset, count);
}
public byte[] ReadBytes (int count)
{
return m_input.ReadBytes (count);
}
public bool ReadBool ()
{
return m_input.ReadUInt8() != 0;
}
public string ReadString ()
{
int nLength = ReadStringLength();
if (0 == nLength)
return "";
if (nLength < 0)
throw new InvalidFormatException();
var buffer = m_input.ReadBytes (nLength);
if (buffer.Length != nLength)
throw new EndOfStreamException();
return Encodings.cp932.GetString (buffer);
}
public int ReadCount ()
{
int count = ReadUInt16();
if (count != 0xFFFF)
return count;
return ReadInt32();
}
public int ReadShortCount ()
{
int count = ReadByte();
if (count != 0xFF)
return count;
return ReadUInt16();
}
int ReadStringLength ()
{
// First, try to read a one-byte length
int length = ReadByte();
if (length < 0xFF)
return length;
// Try a two-byte length
length = ReadUInt16();
if (0xFFFE == length)
throw new NotSupportedException ("[RIO] Unicode strings not supported");
if (length < 0xFFFF)
return length;
// 4-byte length
return ReadInt32();
}
public byte[] ReadEncrypted (uint key)
{
uint size1 = m_input.ReadUInt32() ^ 0xC92E568B;
uint size2 = m_input.ReadUInt32() ^ 0xC92E568F;
size2 >>= 3;
size1 = ~size1;
if (size1 != size2)
throw new InvalidFormatException ("Invalid encrypted chunk");
var ici_data = new byte[size1];
int dst = 0;
while (dst < ici_data.Length)
{
ushort checksum = 0;
int portion = Math.Min (0x20, ici_data.Length - dst);
portion = m_input.Read (ici_data, dst, portion);
for (int i = portion; i > 0; --i)
{
byte b = (byte)(ici_data[dst] ^ key);
ici_data[dst++] = b;
checksum += (ushort)(b * i);
uint bit = key;
bit = (bit >> 15) & 1;
key = ~(bit + key*2 + 0xA3B376C9u);
}
if (portion < 0x20)
break;
ushort chunk_sum = m_input.ReadUInt16();
if (chunk_sum != checksum)
throw new InvalidFormatException ("Encrypted chunk checksum mismatch");
}
return ici_data;
}
static uint DecodeOffset (int offset)
{
return (uint)offset - 0xA2FB6AD1;
}
static uint DecodeSize (int size)
{
uint a = (uint)size - 0xE7B5D9F8;
uint b = a >> 13;
return (a - (b & 0xFFF)) << 19 | b;
}
static string DecodeClassName (byte[] enc, int length)
{
using (var output = new MemoryStream())
using (var input = new MemoryStream (enc, 0, length))
using (var bits = new LsbBitStream (input))
{
if (0 == bits.GetNextBit())
output.WriteByte ((byte)'C');
for (;;)
{
int b = bits.GetNextBit();
if (-1 == b)
break;
int c;
if (0 == b)
{
int idx = bits.GetBits (4);
if (-1 == idx)
break;
c = CharMap1[idx];
}
else if (bits.GetNextBit() != 0)
{
int idx = bits.GetBits (5);
if (-1 == idx)
break;
c = CharMap3[idx];
}
else
{
int idx = bits.GetBits (4);
if (-1 == idx)
break;
if (idx != 0)
{
c = CharMap2[idx];
}
else
{
c = bits.GetBits (8);
if (-1 == c)
break;
}
}
output.WriteByte ((byte)c);
}
var buf = output.GetBuffer();
return Encodings.cp932.GetString (buf, 0, (int)output.Length);
}
}
static readonly char[] CharMap1 = {
'e', 'a', 'i', 't', 'r', 'o', 's', 'd', 'u', 'c', 'm', 'n', 'S', 'g', 'l', 'R' };
static readonly char[] CharMap2 = {
'\x1', 'C', 'O', 'F', 'L', 'f', 'B', 'M', 'x', 'p', 'h', 'y', 'A', 'V', 'b', 'I' };
static readonly char[] CharMap3 = {
'E', 'H', 'T', 'D', 'P', 'W', 'X', 'k', 'q', 'v', 'N', 'j', 'w', 'G', 'z', '0',
'2', 'U', '_', 'K', '1', '5', 'J', 'Q', 'Z', '4', '6', '7', '8', '3', '9', '\x0' };
static readonly Dictionary<string, string> s_basicTypeList = new Dictionary<string, string>
{
{ "バイト", "byte" },
{ "短正整数", "short" },
{ "短整数", "ushort" },
{ "正整数", "int" },
{ "整数", "uint" },
{ "色", "Color" },
};
static readonly Dictionary<string, IObjectFactory> s_classTable = new Dictionary<string, IObjectFactory>
{
{ "CObjectArcMan", new CObjectFactory<CObjectArcMan>() },
{ "CrelicUnitedGameProject", new CObjectFactory<CrelicUnitedGameProject>() },
{ "CStdb", new CObjectFactory<CStdb>() },
{ "CObjectOcean", new CObjectFactory<CObjectOcean>() },
};
}
internal abstract class CObject
{
public int Flags;
public string ClassName;
public abstract void Deserialize (CRioArchive arc);
}
internal class CStringArray : CObject, IReadOnlyList<string>
{
string[] m_data = new string[0];
public int Count { get { return m_data.Length; } }
public string this[int index]
{
get { return m_data[index]; }
set { m_data[index] = value; }
}
public void SetSize (int count)
{
Array.Resize (ref m_data, count);
}
public override void Deserialize (CRioArchive arc)
{
int count = arc.ReadCount();
SetSize (count);
for (int i = 0; i < count; ++i)
m_data[i] = arc.ReadString();
}
public IEnumerator<string> GetEnumerator ()
{
foreach (var s in m_data)
yield return s;
}
IEnumerator IEnumerable.GetEnumerator ()
{
return m_data.GetEnumerator();
}
}
internal class CPtrArray<CType> : CObject, IReadOnlyList<CType> where CType : CObject, new()
{
public CType[] m_data = new CType[0];
public int Count { get { return m_data.Length; } }
public CType this[int index]
{
get { return m_data[index]; }
set { m_data[index] = value; }
}
public void SetSize (int count)
{
Array.Resize (ref m_data, count);
}
public override void Deserialize (CRioArchive arc)
{
int count = arc.ReadCount();
SetSize (count);
for (int i = 0; i < count; ++i)
{
if (arc.ReadBool())
{
var obj = new CType();
m_data[i] = obj;
obj.Deserialize (arc);
}
}
}
public IEnumerator<CType> GetEnumerator ()
{
foreach (var item in m_data)
yield return item;
}
IEnumerator IEnumerable.GetEnumerator ()
{
return m_data.GetEnumerator ();
}
}
internal class CInstallSource : CObject
{
public ushort Version;
public int field_14;
public int field_18;
public string RioName; // rio filename [dst]
public long RioOffset; // rio offset
public long RioSize; // rio size
public int field_A8;
public int field_B0;
public byte[] field_D4;
public override void Deserialize (CRioArchive arc)
{
Version = arc.ReadUInt16();
if (Version >= 7)
{
field_14 = arc.ReadInt32();
field_18 = arc.ReadInt32();
arc.ReadByte();
arc.ReadString();
}
arc.ReadString(); // registry branch
arc.ReadString(); // disk name
arc.ReadString(); // rio filename [src]
arc.ReadString();
arc.ReadString();
arc.ReadInt64(); // rio offset [=0]
arc.ReadInt64(); // rio size
if (Version < 6)
{
arc.ReadInt32();
arc.ReadInt32();
}
else
{
arc.ReadInt32();
}
RioName = arc.ReadString();
RioOffset = arc.ReadInt64();
RioSize = arc.ReadInt64();
if (Version < 6)
{
arc.ReadInt64();
}
arc.ReadInt32();
arc.ReadString();
arc.ReadInt32();
arc.ReadInt32();
arc.ReadInt32();
arc.ReadInt32();
arc.ReadInt32();
arc.ReadString();
int count = arc.ReadCount();
arc.ReadBytes (count*4);
PrepareBuffer(); // sub_10011700 (this);
arc.Read (field_D4, 0, field_D4.Length);
}
void PrepareBuffer ()
{
field_B0 = (int)((RioSize + 0xFFFF) >> 16);
field_A8 = 16;
int length = (field_B0 + 7) >> 3;
field_D4 = new byte[length];
}
}
internal class CObjectArcMan : CObject
{
public int Version;
public string Title;
public CPtrArray<CInstallSource> ArcList = new CPtrArray<CInstallSource>();
public int field_14 = 0x1873BE26;
public int field_1C;
public int field_20;
public int TocOffset; // RioTocOffset
public int TocSize; // RioTocSize
public int field_38 = 0x14;
public string RioFileName;
public CStringArray field_80 = new CStringArray();
public int field_98 = 0x30;
public override void Deserialize (CRioArchive arc)
{
Version = arc.ReadInt32();
field_14 = arc.ReadInt32();
arc.ReadByte();
arc.ReadByte();
if (Version < 10)
{
field_1C = 0;
field_20 = 0;
}
else
{
field_1C = arc.ReadInt32();
field_20 = arc.ReadInt32();
}
arc.ReadInt32();
arc.ReadInt32();
arc.ReadInt32();
if (Version >= 6)
{
TocOffset = arc.ReadInt32();
TocSize = arc.ReadInt32();
arc.ReadInt32();
}
if (Version >= 8)
field_38 = arc.ReadInt32();
Title = arc.ReadString();
arc.ReadInt32();
arc.ReadString();
arc.ReadInt32();
arc.ReadString();
arc.ReadString(); // registry branch
arc.ReadString();
arc.ReadInt32();
arc.ReadString();
field_80.Deserialize (arc);
arc.ReadInt32();
if (Version >= 9)
RioFileName = arc.ReadString();
if (Version >= 7)
arc.ReadString(); // InstallManual
if (Version >= 5)
field_98 = arc.ReadInt32();
ArcList.Deserialize (arc); // CPtrArray::Serialize
for (int i = 0; i < ArcList.Count; ++i)
{
var entry = ArcList[i];
if (entry != null)
{
entry.field_14 = field_1C;
entry.field_18 = field_20;
}
}
}
}
internal class CrelicUnitedGameProject : CObject
{
public int Version;
public CObject field_08;
public CObject field_0C;
public CObject field_10;
public CObject field_14;
public CObject field_18;
public CObject field_1C;
public CObject field_24;
public CObject field_28;
public CObject field_2C;
public CObject field_30;
public CUnknown1 field_34 = new CUnknown1();
public CObject field_38;
internal const uint RioKey = 0x7E6B8CE2;
public override void Deserialize (CRioArchive arc)
{
if (arc.IsEncrypted)
{
var data = arc.ReadEncrypted (RioKey);
using (var input = new BinMemoryStream (data))
{
var prev_source = arc.SetSource (input);
try
{
ReadRelic (arc);
}
finally
{
arc.SetSource (prev_source);
}
}
}
else
ReadRelic (arc);
}
void ReadRelic (CRioArchive arc)
{
Version = arc.ReadInt32();
if (Version >= 0x24)
{
field_24 = arc.ReadRioReference ("CDatabaseBase"); // UnivUI
field_28 = arc.ReadRioReference ("CDatabaseBase");
field_10 = arc.ReadRioReference ("CBoxOcean"); // rvmm
field_14 = arc.ReadRioReference ("CObjectOcean"); // UnivUI
field_18 = arc.ReadRioReference ("CObjectOcean"); // UnivUI
field_0C = arc.ReadRioReference ("CProcessOcean"); // Vm60
if (Version >= 0x25)
field_30 = arc.ReadRioReference ("CStdb"); // UnivUI
if (Version >= 0x26)
field_2C = arc.ReadRioReference ("CRio"); // UnivUI
if (Version >= 0x27)
field_1C = arc.ReadRioReference ("CRio");
if (Version >= 0x29)
field_38 = arc.ReadRioReference ("CRio");
field_34.Deserialize (arc);
if (Version >= 0x28)
field_08 = arc.ReadRioReference ("CRio");
}
else if (Version >= 0x20)
{
field_0C = arc.ReadRioReference ("CProcessOcean");
field_10 = arc.ReadRioReference ("CBoxOcean");
field_14 = arc.ReadRioReference ("CObjectOcean");
field_18 = arc.ReadRioReference ("CObjectOcean");
field_1C = arc.ReadRioReference ("CSoundManEx");
if (Version >= 0x23)
field_24 = arc.ReadRioReference ("CDatabaseBase");
if (Version >= 0x22)
field_28 = arc.ReadRioReference ("CDatabaseBase");
if (Version >= 0x21)
field_34.Deserialize (arc);
}
else
throw new NotSupportedException (string.Format ("rUGP schema {0} not supported", Version));
}
}
internal class CUnknown1 : CObject
{
public int Version = 0;
public string field_04; // registry branch
public string field_08; // version
public string field_0C;
public string field_14;
public int field_18 = 1000;
public string field_1C; // copyright
public string field_10;
public int field_20 = 1;
public int field_24 = 1;
public byte[] field_28;
public int field_38;
public string field_3C;
public string field_40;
public string field_44;
public string field_48;
public string field_4C;
public string field_50;
public override void Deserialize (CRioArchive arc)
{
Version = arc.ReadInt32();
if (Version >= 2)
field_04 = arc.ReadString();
field_08 = arc.ReadString();
field_0C = arc.ReadString();
field_14 = arc.ReadString();
field_1C = arc.ReadString();
field_18 = arc.ReadInt32();
if (0 == Version)
return;
if (Version >= 2)
{
field_28 = arc.ReadBytes (16);
field_38 = arc.ReadInt32();
}
field_3C = arc.ReadString();
field_40 = arc.ReadString();
if (Version >= 3)
field_44 = arc.ReadString();
if (Version >= 4)
{
field_10 = arc.ReadString();
field_20 = arc.ReadInt32();
}
if (Version >= 5)
{
field_24 = arc.ReadInt32();
}
else if (Version >= 2)
{
if (field_28.ToUInt16 (0) < 0x7D3)
field_24 = 2;
else
field_24 = 1;
}
if (Version >= 6)
{
field_48 = arc.ReadString();
field_4C = arc.ReadString();
field_50 = arc.ReadString();
}
}
}
internal class CStdb : CObject
{
public string field_0C;
public override void Deserialize (CRioArchive arc)
{
field_0C = arc.ReadString();
}
}
internal class CBoxOcean : CObject
{
public CObject field_10;
public override void Deserialize (CRioArchive arc)
{
field_10 = arc.ReadRioReference ("CFrameBuffer");
}
}
internal class CObjectOcean : CObject
{
public override void Deserialize (CRioArchive arc)
{
}
}
internal class COceanNode : CObject
{
public string Name;
public CObject Parent;
public uint Offset;
public uint Size;
public COceanNode (string name)
{
Name = name;
}
public override void Deserialize (CRioArchive arc)
{
throw new NotImplementedException ("COceanNode.Deserialize not impelemented");
}
public string GetPathName ()
{
return string.Join ("/", TraversePath().Reverse());
}
private IEnumerable<string> TraversePath ()
{
COceanNode node = this;
while (node != null && node.ClassName != null && !string.IsNullOrEmpty (node.Name))
{
yield return node.Name;
node = node.Parent as COceanNode;
}
}
}
internal interface IObjectFactory
{
CObject CreateObject ();
}
internal class CObjectFactory<CType> : IObjectFactory where CType : CObject, new()
{
public CObject CreateObject ()
{
return new CType();
}
}
}