350 lines
13 KiB
C#
Raw Normal View History

2016-08-31 11:01:31 +04:00
//! \file ArcARC3.cs
//! \date Tue Aug 30 10:57:30 2016
//! \brief Caramel BOX 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.Generic;
using System.ComponentModel.Composition;
2018-03-27 13:38:36 +04:00
using System.Diagnostics;
2016-08-31 11:01:31 +04:00
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using GameRes.Utility;
namespace GameRes.Formats.CaramelBox
{
internal class Arc3Entry : PackedEntry
{
public uint Flags;
public bool IsEncrypted;
}
[Export(typeof(ArchiveFormat))]
public class Arc3Opener : ArchiveFormat
{
public override string Tag { get { return "ARC3"; } }
public override string Description { get { return "Caramel BOX resource archive"; } }
public override uint Signature { get { return 0x33637261; } } // 'arc3'
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
2016-08-31 11:01:31 +04:00
public Arc3Opener ()
{
2018-03-27 13:38:36 +04:00
Extensions = new string[] { "bin", "ar3", "ac3" };
2016-08-31 11:01:31 +04:00
}
public override ArcFile TryOpen (ArcView file)
{
int version = Binary.BigEndian (file.View.ReadInt32 (4));
uint cluster_size = Binary.BigEndian (file.View.ReadUInt32 (0x08));
uint base_offset = Binary.BigEndian (file.View.ReadUInt32 (0x0C));
uint index_offset = Binary.BigEndian (file.View.ReadUInt32 (0x18));
uint index_size = Binary.BigEndian (file.View.ReadUInt32 (0x1C));
if (0 == index_size)
return null;
bool new_name = false;
var dir = new List<Entry>();
var name_buffer = new byte[0x10];
long current_offset = (long)index_offset * cluster_size;
long index_end = current_offset + index_size;
if (index_end > file.MaxOffset)
return null;
// --- read index ---
uint last_entry_offset = 0x7FFFFFFF;
int current_name_length = 0;
Arc3Entry prev_entry = null;
Arc3Entry long_info = null;
while (current_offset < index_end)
{
byte name_length = file.View.ReadByte (current_offset++);
int name_offset = name_length >> 4;
name_length &= 0xF;
if (name_offset != 0xF)
{
file.View.Read (current_offset, name_buffer, name_offset, name_length);
current_offset += name_length;
current_name_length = name_offset+name_length;
}
else if (0xF == name_length)
{
name_buffer[current_name_length-1]++;
}
else if (name_length != 0)
{
file.View.Read (current_offset, name_buffer, 0, name_length);
current_offset += name_length;
current_name_length = name_length;
new_name = true;
}
else
{
uint offset = BigEndian24 (file.View.ReadUInt32 (current_offset));
offset = (uint)Math.Abs ((int)(offset - index_offset));
if (offset < last_entry_offset)
last_entry_offset = offset;
if (prev_entry != null)
prev_entry.Offset = (long)(last_entry_offset + base_offset) * cluster_size;
}
last_entry_offset = BigEndian24 (file.View.ReadUInt32 (current_offset));
current_offset += 3;
if (new_name)
{
current_offset += 3;
new_name = false;
}
string name;
if (current_name_length > 3)
{
name = Encodings.cp932.GetString (name_buffer, 3, current_name_length-3);
string ext = Encodings.cp932.GetString (name_buffer, 0, 3);
name = name + '.' + ext;
}
else
name = Encodings.cp932.GetString (name_buffer, 0, current_name_length);
var entry = new Arc3Entry { Name = name };
entry.Offset = (long)(last_entry_offset + base_offset) * cluster_size;
if (entry.Offset >= file.MaxOffset)
return null;
dir.Add (entry);
prev_entry = entry;
if (null == long_info && "longinfo.$$$" == name)
long_info = entry;
2018-03-27 13:38:36 +04:00
if (name.HasExtension ("lze"))
entry.Type = "image";
2016-08-31 11:01:31 +04:00
}
// --- read attributes ---
foreach (Arc3Entry entry in dir)
{
entry.Size = Binary.BigEndian (file.View.ReadUInt32 (entry.Offset+8));
entry.Flags = Binary.BigEndian (file.View.ReadUInt32 (entry.Offset+0x14));
entry.UnpackedSize = entry.Size;
entry.IsEncrypted = 2 == entry.Flags;
entry.Offset += 0x20;
uint signature = file.View.ReadUInt32 (entry.Offset);
if (entry.IsEncrypted)
signature = ~signature;
entry.IsPacked = (signature & 0xFFFF) == 0x7A6C; // 'lz'
if (entry.IsPacked)
{
entry.UnpackedSize = Binary.BigEndian (file.View.ReadUInt32 (entry.Offset+2));
if (entry.IsEncrypted)
entry.UnpackedSize ^= 0xFFFFFFFF;
entry.Offset += 6;
entry.Size -= 6;
}
else
{
var res = AutoEntry.DetectFileType (signature);
if (res != null)
entry.Type = res.Type;
}
}
var arc = new ArcFile (file, this, dir);
try // read long filenames stored within 'longinfo.$$$', if available
{
if (version > 1 && long_info != null)
{
var name_map = ReadNameMap (arc, long_info);
foreach (var entry in dir)
{
string orig_name;
if (name_map.TryGetValue (entry.Name, out orig_name))
entry.Name = orig_name;
}
}
}
catch { /* ignore 'longinfo.$$$' read errors */ }
foreach (var entry in dir.Where (e => e.Name.Contains ('*')))
entry.Name = entry.Name.Replace ('*', '');
return arc;
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
var a3ent = entry as Arc3Entry;
Stream input = arc.File.CreateStream (entry.Offset, entry.Size);
if (null == entry)
return input;
if (a3ent.IsEncrypted)
{
input = new InputCryptoStream (input, new NotTransform());
2016-08-31 11:01:31 +04:00
}
if (!a3ent.IsPacked)
return input;
using (input)
{
var data = UnpackLze (input, a3ent.UnpackedSize);
2016-10-16 09:22:53 +04:00
return new BinMemoryStream (data, entry.Name);
2016-08-31 11:01:31 +04:00
}
}
static uint BigEndian24 (uint x)
{
return (x & 0xFFu) << 16 | x & 0xFF00u | (x >> 16) & 0xFFu;
}
byte[] UnpackLze (Stream input, uint unpacked_size)
{
var data = new byte[unpacked_size];
var header = new byte[4];
int dst = 0;
2018-03-27 13:38:36 +04:00
using (var bits = new LzBitStream (input, true))
2016-08-31 11:01:31 +04:00
{
while (dst < data.Length)
{
if (4 != input.Read (header, 0, 4))
break;
if (!Binary.AsciiEqual (header, "ze"))
throw new InvalidFormatException ("Malformed compressed stream");
int chunk_length = BigEndian.ToUInt16 (header, 2);
bits.Reset();
UnpackZeChunk (bits, data, dst, chunk_length);
dst += chunk_length;
}
return data;
}
}
void UnpackZeChunk (IBitStream bits, byte[] output, int dst, int unpacked_size)
{
int output_end = dst + unpacked_size;
while (dst < output_end)
{
int count = LzeGetInteger (bits);
if (-1 == count)
break;
while (--count > 0)
{
int data = bits.GetBits (8);
if (-1 == data)
break;
if (dst < output_end)
output[dst++] = (byte)data;
}
if (count > 0 || dst >= output_end)
break;
int offset = LzeGetInteger (bits);
if (-1 == offset)
break;
count = LzeGetInteger (bits);
if (-1 == count)
break;
Binary.CopyOverlapped (output, dst-offset, dst, count);
dst += count;
}
if (dst < output_end)
throw new EndOfStreamException ("Premature end of compressed stream");
}
int LzeGetInteger (IBitStream bits)
{
int length = 0;
for (int i = 0; i < 16; ++i)
{
if (0 != bits.GetNextBit())
break;
++length;
}
int v = 1 << length;
if (length > 0)
v |= bits.GetBits (length);
return v;
}
IDictionary<string, string> ReadNameMap (ArcFile file, Arc3Entry entry)
{
byte[] table = new byte[entry.UnpackedSize];
using (var input = OpenEntry (file, entry))
{
input.Read (table, 0, table.Length);
}
int count = LittleEndian.ToInt32 (table, 0);
if (!IsSaneCount (count))
throw new InvalidFormatException ("Invalid longinfo map format");
var map = new Dictionary<string, string> (count);
int index_pos = 4;
int names_pos = index_pos + count * 8;
for (int i = 0; i < count; ++i)
{
int key_pos = names_pos + LittleEndian.ToInt32 (table, index_pos);
int value_pos = names_pos + LittleEndian.ToInt32 (table, index_pos+4);
index_pos += 8;
string key = Binary.GetCString (table, key_pos, table.Length - key_pos);
map[key] = Binary.GetCString (table, value_pos, table.Length - value_pos);
}
return map;
}
}
2018-03-27 13:38:36 +04:00
/// <summary>
/// like MsbBitStream, but input stream is consumed by 2 bytes at a time.
/// </summary>
internal class LzBitStream : BitStream, IBitStream
{
public LzBitStream (Stream file, bool leave_open = false)
: base (file, leave_open)
{
}
public int GetBits (int count)
{
Debug.Assert (count <= 24, "LzBitStream does not support sequences longer than 24 bits");
while (m_cached_bits < count)
{
int b = m_input.ReadByte();
if (-1 == b)
return -1;
m_bits = (m_bits << 8) | b;
m_cached_bits += 8;
b = m_input.ReadByte();
if (b != -1)
{
m_bits = (m_bits << 8) | b;
m_cached_bits += 8;
}
}
int mask = (1 << count) - 1;
m_cached_bits -= count;
return (m_bits >> m_cached_bits) & mask;
}
public int GetNextBit ()
{
return GetBits (1);
}
}
2016-08-31 11:01:31 +04:00
}