2015-08-18 18:18:22 +04:00
|
|
|
//! \file ArcGCEX.cs
|
|
|
|
//! \date Tue Aug 18 04:25:02 2015
|
|
|
|
//! \brief G2 engine resources archive.
|
|
|
|
//
|
|
|
|
// Copyright (C) 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
|
|
|
|
// 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.Generic;
|
|
|
|
using System.ComponentModel.Composition;
|
|
|
|
using System.Diagnostics;
|
|
|
|
using System.IO;
|
|
|
|
using GameRes.Utility;
|
|
|
|
|
|
|
|
namespace GameRes.Formats.G2
|
|
|
|
{
|
|
|
|
[Export(typeof(ArchiveFormat))]
|
|
|
|
public class PakOpener : ArchiveFormat
|
|
|
|
{
|
|
|
|
public override string Tag { get { return "PAK/G2"; } }
|
|
|
|
public override string Description { get { return "G2 engine resource archive"; } }
|
|
|
|
public override uint Signature { get { return 0x58454347; } } // 'GCEX'
|
|
|
|
public override bool IsHierarchic { get { return true; } }
|
2016-10-11 00:05:22 +04:00
|
|
|
public override bool CanWrite { get { return false; } }
|
2015-08-18 18:18:22 +04:00
|
|
|
|
2015-08-29 19:40:14 +04:00
|
|
|
public PakOpener ()
|
|
|
|
{
|
|
|
|
Extensions = new string[] { "pak" };
|
|
|
|
}
|
|
|
|
|
2015-08-18 18:18:22 +04:00
|
|
|
public override ArcFile TryOpen (ArcView file)
|
|
|
|
{
|
|
|
|
if (0 != file.View.ReadInt32 (4))
|
|
|
|
return null;
|
|
|
|
long index_offset = file.View.ReadInt64 (8);
|
|
|
|
if (index_offset >= file.MaxOffset)
|
|
|
|
return null;
|
|
|
|
if (!file.View.AsciiEqual (index_offset, "GCE3"))
|
|
|
|
return null;
|
|
|
|
int count = file.View.ReadInt32 (index_offset+0x18);
|
|
|
|
if (!IsSaneCount (count))
|
|
|
|
return null;
|
|
|
|
bool index_packed = 0x11 == file.View.ReadInt32 (index_offset+4);
|
|
|
|
uint index_size = file.View.ReadUInt32 (index_offset+8);
|
|
|
|
byte[] index = null;
|
|
|
|
if (index_packed)
|
|
|
|
{
|
|
|
|
index_size -= 0x28;
|
|
|
|
int unpacked_size = file.View.ReadInt32 (index_offset+0x20);
|
|
|
|
using (var input = file.CreateStream (index_offset+0x28, index_size))
|
|
|
|
using (var reader = new GceReader (input, unpacked_size))
|
|
|
|
index = reader.Data;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
index_size -= 0x20;
|
|
|
|
index = new byte[index_size];
|
|
|
|
if (index.Length != file.View.Read (index_offset+0x20, index, 0, index_size))
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
int current_index = 0;
|
|
|
|
int current_filename = 0x20*count;
|
|
|
|
long current_offset = 0x10;
|
|
|
|
var dir = new List<Entry> (count);
|
|
|
|
for (int i = 0; i < count; ++i)
|
|
|
|
{
|
|
|
|
int name_length = LittleEndian.ToUInt16 (index, current_filename);
|
|
|
|
if (current_filename+2+name_length > index.Length)
|
|
|
|
return null;
|
|
|
|
uint size = LittleEndian.ToUInt32 (index, current_index+0x18);
|
|
|
|
if (size != 0)
|
|
|
|
{
|
|
|
|
string name = Encodings.cp932.GetString (index, current_filename+2, name_length);
|
|
|
|
var entry = new PackedEntry
|
|
|
|
{
|
|
|
|
Name = name,
|
|
|
|
Type = FormatCatalog.Instance.GetTypeFromName (name),
|
|
|
|
Offset = current_offset,
|
|
|
|
Size = size,
|
|
|
|
UnpackedSize = LittleEndian.ToUInt32 (index, current_index+0x10),
|
|
|
|
};
|
|
|
|
if (!entry.CheckPlacement (file.MaxOffset))
|
|
|
|
return null;
|
|
|
|
entry.IsPacked = entry.Size != entry.UnpackedSize;
|
|
|
|
current_offset += entry.Size;
|
|
|
|
dir.Add (entry);
|
|
|
|
}
|
|
|
|
current_index += 0x20;
|
|
|
|
current_filename += 2 + name_length;
|
|
|
|
}
|
|
|
|
return new ArcFile (file, this, dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
|
|
|
{
|
|
|
|
if (0 == entry.Size)
|
|
|
|
return Stream.Null;
|
|
|
|
var input = arc.File.CreateStream (entry.Offset, entry.Size);
|
|
|
|
var pentry = entry as PackedEntry;
|
|
|
|
if (null == pentry || !pentry.IsPacked)
|
|
|
|
return input;
|
|
|
|
if (!arc.File.View.AsciiEqual (entry.Offset, "GCE"))
|
|
|
|
{
|
|
|
|
Trace.WriteLine ("Packed entry is not GCE", entry.Name);
|
|
|
|
return input;
|
|
|
|
}
|
|
|
|
using (input)
|
|
|
|
using (var reader = new GceReader (input, (int)pentry.UnpackedSize))
|
|
|
|
{
|
2016-10-16 09:22:53 +04:00
|
|
|
return new BinMemoryStream (reader.Data, entry.Name);
|
2015-08-18 18:18:22 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal class GceReader : IDisposable
|
|
|
|
{
|
2016-10-16 18:29:54 +04:00
|
|
|
IBinaryStream m_input;
|
2015-08-18 18:18:22 +04:00
|
|
|
int m_unpacked_size;
|
|
|
|
byte[] m_output = null;
|
|
|
|
int m_dst;
|
|
|
|
|
|
|
|
public byte[] Data
|
|
|
|
{
|
|
|
|
get
|
|
|
|
{
|
|
|
|
if (null == m_output)
|
|
|
|
{
|
|
|
|
m_output = new byte[m_unpacked_size];
|
|
|
|
Unpack();
|
|
|
|
}
|
|
|
|
return m_output;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-16 18:29:54 +04:00
|
|
|
public GceReader (IBinaryStream input, int unpacked_size)
|
2015-08-18 18:18:22 +04:00
|
|
|
{
|
2016-10-16 18:29:54 +04:00
|
|
|
m_input = input;
|
2015-08-18 18:18:22 +04:00
|
|
|
m_unpacked_size = unpacked_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void Unpack ()
|
|
|
|
{
|
|
|
|
m_dst = 0;
|
|
|
|
byte[] id = new byte[4];
|
2015-08-18 23:26:30 +04:00
|
|
|
while (4 == m_input.Read (id, 0, 4))
|
2015-08-18 18:18:22 +04:00
|
|
|
{
|
|
|
|
int segment_length = m_input.ReadInt32();
|
|
|
|
if (Binary.AsciiEqual (id, "GCE1"))
|
|
|
|
{
|
|
|
|
m_input.ReadInt32();
|
|
|
|
int data_length = m_input.ReadInt32();
|
|
|
|
|
|
|
|
m_input.ReadInt32();
|
|
|
|
int cmd_len = m_input.ReadInt32();
|
2016-10-16 18:29:54 +04:00
|
|
|
long cmd_pos = m_input.Position + data_length;
|
2015-08-18 18:18:22 +04:00
|
|
|
ReadControlStream (cmd_pos, cmd_len);
|
|
|
|
|
|
|
|
int next = m_dst + segment_length;
|
2015-08-18 23:26:30 +04:00
|
|
|
UnpackGce1Segment (segment_length);
|
2015-08-18 18:18:22 +04:00
|
|
|
m_dst = next;
|
2016-10-16 18:29:54 +04:00
|
|
|
m_input.Position = cmd_pos + cmd_len;
|
2015-08-18 18:18:22 +04:00
|
|
|
}
|
|
|
|
else if (Binary.AsciiEqual (id, "GCE0"))
|
|
|
|
{
|
|
|
|
if (segment_length != m_input.Read (m_output, m_dst, segment_length))
|
|
|
|
throw new EndOfStreamException();
|
|
|
|
m_dst += segment_length;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
throw new InvalidFormatException ("Unknown compression type in GCE stream");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int[] m_frame = new int[0x10000];
|
|
|
|
|
2015-08-18 23:26:30 +04:00
|
|
|
void UnpackGce1Segment (int segment_length)
|
2015-08-18 18:18:22 +04:00
|
|
|
{
|
|
|
|
int frame_pos = 0;
|
|
|
|
int dst_end = m_dst + segment_length;
|
2015-08-18 23:26:30 +04:00
|
|
|
while (m_dst < dst_end)
|
2015-08-18 18:18:22 +04:00
|
|
|
{
|
|
|
|
int n = GetLength();
|
|
|
|
while (n --> 0)
|
|
|
|
{
|
|
|
|
m_frame[frame_pos] = m_dst;
|
2016-10-16 18:29:54 +04:00
|
|
|
byte b = m_input.ReadUInt8();
|
2015-08-18 18:18:22 +04:00
|
|
|
frame_pos = ((frame_pos << 8) | b) & 0xFFFF;
|
|
|
|
m_output[m_dst++] = b;
|
|
|
|
}
|
|
|
|
if (m_dst >= dst_end)
|
|
|
|
break;
|
|
|
|
n = GetLength() + 1;
|
|
|
|
int src = m_frame[frame_pos];
|
|
|
|
while (n --> 0)
|
|
|
|
{
|
|
|
|
m_frame[frame_pos] = m_dst;
|
|
|
|
frame_pos = ((frame_pos << 8) | m_output[src]) & 0xFFFF;
|
|
|
|
m_output[m_dst++] = m_output[src++];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int GetLength ()
|
|
|
|
{
|
|
|
|
int v = 0;
|
|
|
|
if (0 == GetBit())
|
|
|
|
{
|
|
|
|
int digits = 0;
|
|
|
|
while (0 == GetBit())
|
|
|
|
++digits;
|
|
|
|
v = 1 << digits;
|
|
|
|
while (digits --> 0)
|
|
|
|
v |= GetBit() << digits;
|
|
|
|
}
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
|
2015-08-18 23:26:30 +04:00
|
|
|
byte[] m_control;
|
2015-08-18 18:18:22 +04:00
|
|
|
int m_control_pos;
|
|
|
|
int m_control_len;
|
|
|
|
int m_bit_pos;
|
|
|
|
|
|
|
|
void ReadControlStream (long pos, int length)
|
|
|
|
{
|
2016-10-16 18:29:54 +04:00
|
|
|
var data_pos = m_input.Position;
|
2015-08-18 18:18:22 +04:00
|
|
|
if (null == m_control || m_control.Length < length)
|
|
|
|
m_control = new byte[length];
|
2016-10-16 18:29:54 +04:00
|
|
|
m_input.Position = pos;
|
2015-08-18 18:18:22 +04:00
|
|
|
if (length != m_input.Read (m_control, 0, length))
|
|
|
|
throw new EndOfStreamException();
|
|
|
|
m_control_pos = 0;
|
|
|
|
m_control_len = length;
|
2016-10-16 18:29:54 +04:00
|
|
|
m_input.Position = data_pos;
|
2015-08-18 18:18:22 +04:00
|
|
|
m_bit_pos = 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
int GetBit ()
|
|
|
|
{
|
|
|
|
if (0 == m_bit_pos--)
|
|
|
|
{
|
|
|
|
++m_control_pos;
|
|
|
|
m_bit_pos = 7;
|
|
|
|
--m_control_len;
|
|
|
|
if (0 == m_control_len)
|
|
|
|
throw new EndOfStreamException();
|
|
|
|
}
|
|
|
|
return 1 & (m_control[m_control_pos] >> m_bit_pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
#region IDisposable Members
|
|
|
|
public void Dispose ()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|