2016-03-20 13:42:36 +08:00
|
|
|
//! \file ArcGRP.cs
|
|
|
|
//! \date Sun Mar 20 02:07:17 2016
|
|
|
|
//! \brief Ankh 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;
|
|
|
|
using System.IO;
|
|
|
|
using GameRes.Utility;
|
|
|
|
|
|
|
|
namespace GameRes.Formats.Ankh
|
|
|
|
{
|
|
|
|
[Export(typeof(ArchiveFormat))]
|
|
|
|
public class GrpOpener : ArchiveFormat
|
|
|
|
{
|
2016-10-04 03:22:37 +08:00
|
|
|
public override string Tag { get { return "GRP/ICE"; } }
|
|
|
|
public override string Description { get { return "Ice Soft resource archive"; } }
|
2016-03-20 13:42:36 +08:00
|
|
|
public override uint Signature { get { return 0; } }
|
|
|
|
public override bool IsHierarchic { get { return false; } }
|
|
|
|
public override bool CanCreate { get { return false; } }
|
|
|
|
|
|
|
|
public GrpOpener ()
|
|
|
|
{
|
2016-10-04 03:22:37 +08:00
|
|
|
Extensions = new string[] { "grp", "bin" };
|
2016-03-20 13:42:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
public override ArcFile TryOpen (ArcView file)
|
|
|
|
{
|
|
|
|
uint first_offset = file.View.ReadUInt32 (0);
|
2016-10-04 03:22:37 +08:00
|
|
|
if (first_offset < 8 || first_offset >= file.MaxOffset || 0 != (first_offset & 3))
|
2016-03-20 13:42:36 +08:00
|
|
|
return null;
|
2016-10-04 03:22:37 +08:00
|
|
|
int count = (int)(first_offset - 4) / 4;
|
2016-03-20 13:42:36 +08:00
|
|
|
if (!IsSaneCount (count))
|
|
|
|
return null;
|
|
|
|
|
|
|
|
var base_name = Path.GetFileNameWithoutExtension (file.Name);
|
|
|
|
uint index_offset = 0;
|
|
|
|
uint next_offset = first_offset;
|
|
|
|
var dir = new List<Entry> (count);
|
2016-10-04 03:22:37 +08:00
|
|
|
for (int i = 0; i < count && next_offset < file.MaxOffset; ++i)
|
2016-03-20 13:42:36 +08:00
|
|
|
{
|
|
|
|
var entry = new PackedEntry { Offset = next_offset };
|
|
|
|
index_offset += 4;
|
|
|
|
next_offset = file.View.ReadUInt32 (index_offset);
|
|
|
|
if (next_offset < entry.Offset)
|
|
|
|
return null;
|
|
|
|
entry.Size = (uint)(next_offset - entry.Offset);
|
|
|
|
entry.UnpackedSize = entry.Size;
|
|
|
|
if (entry.Size != 0)
|
|
|
|
{
|
|
|
|
if (!entry.CheckPlacement (file.MaxOffset))
|
|
|
|
return null;
|
|
|
|
entry.Name = string.Format ("{0}#{1:D4}", base_name, i);
|
|
|
|
dir.Add (entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (0 == dir.Count)
|
|
|
|
return null;
|
2016-10-04 03:22:37 +08:00
|
|
|
DetectFileTypes (file, dir);
|
|
|
|
return new ArcFile (file, this, dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DetectFileTypes (ArcView file, List<Entry> dir)
|
|
|
|
{
|
2016-03-22 10:58:28 +08:00
|
|
|
foreach (PackedEntry entry in dir)
|
2016-03-20 13:42:36 +08:00
|
|
|
{
|
2016-10-04 03:22:37 +08:00
|
|
|
if (entry.Size <= 8)
|
2016-03-20 13:42:36 +08:00
|
|
|
continue;
|
2016-10-04 03:22:37 +08:00
|
|
|
if (file.View.AsciiEqual (entry.Offset, "TPW"))
|
|
|
|
{
|
|
|
|
entry.IsPacked = file.View.ReadByte (entry.Offset+3) != 0;
|
|
|
|
long start_offset = entry.Offset+4;
|
|
|
|
if (entry.IsPacked)
|
|
|
|
{
|
|
|
|
entry.UnpackedSize = file.View.ReadUInt32 (start_offset);
|
|
|
|
start_offset = entry.Offset+11;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
entry.Offset = start_offset;
|
|
|
|
entry.Size -= 4;
|
|
|
|
}
|
|
|
|
if (file.View.AsciiEqual (start_offset, "BM"))
|
|
|
|
{
|
|
|
|
entry.Name = Path.ChangeExtension (entry.Name, "bmp");
|
|
|
|
entry.Type = "image";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (file.View.AsciiEqual (entry.Offset+4, "HDJ\0"))
|
2016-03-20 13:42:36 +08:00
|
|
|
{
|
|
|
|
if (file.View.AsciiEqual (entry.Offset+12, "BM"))
|
|
|
|
{
|
|
|
|
entry.Name = Path.ChangeExtension (entry.Name, "bmp");
|
|
|
|
entry.Type = "image";
|
|
|
|
}
|
2016-10-04 03:22:37 +08:00
|
|
|
entry.UnpackedSize = file.View.ReadUInt32 (entry.Offset);
|
2016-03-20 13:42:36 +08:00
|
|
|
entry.IsPacked = true;
|
|
|
|
}
|
2016-10-04 03:22:37 +08:00
|
|
|
else if (file.View.AsciiEqual (entry.Offset+4, "OggS"))
|
|
|
|
{
|
|
|
|
entry.Name = Path.ChangeExtension (entry.Name, "ogg");
|
|
|
|
entry.Type = "audio";
|
|
|
|
entry.Offset += 4;
|
|
|
|
entry.Size -= 4;
|
|
|
|
}
|
2016-03-20 13:42:36 +08:00
|
|
|
else if (entry.Size > 12 && file.View.AsciiEqual (entry.Offset+8, "RIFF"))
|
|
|
|
{
|
|
|
|
entry.Name = Path.ChangeExtension (entry.Name, "wav");
|
|
|
|
entry.Type = "audio";
|
2016-10-04 03:22:37 +08:00
|
|
|
entry.UnpackedSize = file.View.ReadUInt32 (entry.Offset);
|
2016-03-20 13:42:36 +08:00
|
|
|
entry.IsPacked = true;
|
|
|
|
}
|
2016-10-04 03:22:37 +08:00
|
|
|
else if (file.View.AsciiEqual (entry.Offset, "BM"))
|
2016-03-20 13:42:36 +08:00
|
|
|
{
|
|
|
|
entry.Name = Path.ChangeExtension (entry.Name, "bmp");
|
|
|
|
entry.Type = "image";
|
|
|
|
}
|
2016-10-04 03:22:37 +08:00
|
|
|
else if (entry.Size > 0x16 && IsAudioEntry (file, entry))
|
|
|
|
{
|
|
|
|
entry.Type = "audio";
|
|
|
|
}
|
2016-03-20 13:42:36 +08:00
|
|
|
}
|
2016-10-04 03:22:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool IsAudioEntry (ArcView file, Entry entry)
|
|
|
|
{
|
|
|
|
uint signature = file.View.ReadUInt32 (entry.Offset);
|
|
|
|
if (signature != 0x010001 && signature != 0x020001)
|
|
|
|
return false;
|
|
|
|
int extra = file.View.ReadUInt16 (entry.Offset+0x10);
|
|
|
|
if (extra != 0)
|
|
|
|
return false;
|
|
|
|
uint size = file.View.ReadUInt32 (entry.Offset+0x12);
|
|
|
|
return 0x16 + size == entry.Size;
|
2016-03-20 13:42:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
public override Stream OpenEntry (ArcFile arc, Entry entry)
|
|
|
|
{
|
2016-10-04 03:22:37 +08:00
|
|
|
var pent = entry as PackedEntry;
|
|
|
|
if (pent != null && pent.IsPacked && pent.Size > 8)
|
|
|
|
{
|
|
|
|
if (arc.File.View.AsciiEqual (entry.Offset, "TPW"))
|
|
|
|
return OpenTpw (arc, pent);
|
|
|
|
if (arc.File.View.AsciiEqual (entry.Offset+4, "HDJ\0"))
|
|
|
|
return OpenImage (arc, pent);
|
|
|
|
if (entry.Size > 12 && 'W' == arc.File.View.ReadByte (entry.Offset+4)
|
|
|
|
&& arc.File.View.AsciiEqual (entry.Offset+8, "RIFF"))
|
|
|
|
return OpenAudio (arc, entry);
|
|
|
|
}
|
|
|
|
return base.OpenEntry (arc, entry);
|
2016-03-20 13:42:36 +08:00
|
|
|
}
|
|
|
|
|
2016-10-04 03:22:37 +08:00
|
|
|
Stream OpenImage (ArcFile arc, PackedEntry entry)
|
2016-03-20 13:42:36 +08:00
|
|
|
{
|
|
|
|
using (var packed = arc.File.CreateStream (entry.Offset+8, entry.Size-8))
|
|
|
|
using (var reader = new GrpUnpacker (packed))
|
|
|
|
{
|
2016-10-04 03:22:37 +08:00
|
|
|
var unpacked = new byte[entry.UnpackedSize];
|
2016-03-20 13:42:36 +08:00
|
|
|
reader.UnpackHDJ (unpacked, 0);
|
|
|
|
return new MemoryStream (unpacked);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Stream OpenAudio (ArcFile arc, Entry entry)
|
|
|
|
{
|
|
|
|
int unpacked_size = arc.File.View.ReadInt32 (entry.Offset);
|
|
|
|
byte pack_type = arc.File.View.ReadByte (entry.Offset+5);
|
|
|
|
byte channels = arc.File.View.ReadByte (entry.Offset+6);
|
|
|
|
byte header_size = arc.File.View.ReadByte (entry.Offset+7);
|
|
|
|
if (unpacked_size <= 0 || header_size > unpacked_size
|
|
|
|
|| !('A' == pack_type || 'S' == pack_type))
|
|
|
|
return base.OpenEntry (arc, entry);
|
|
|
|
var unpacked = new byte[unpacked_size];
|
|
|
|
arc.File.View.Read (entry.Offset+8, unpacked, 0, header_size);
|
|
|
|
uint packed_size = entry.Size - 8 - header_size;
|
|
|
|
using (var packed = arc.File.CreateStream (entry.Offset+8+header_size, packed_size))
|
|
|
|
using (var reader = new GrpUnpacker (packed))
|
|
|
|
{
|
|
|
|
if ('A' == pack_type)
|
|
|
|
reader.UnpackA (unpacked, header_size, channels);
|
|
|
|
else
|
|
|
|
reader.UnpackS (unpacked, header_size, channels);
|
|
|
|
return new MemoryStream (unpacked);
|
|
|
|
}
|
|
|
|
}
|
2016-10-04 03:22:37 +08:00
|
|
|
|
|
|
|
Stream OpenTpw (ArcFile arc, PackedEntry entry)
|
|
|
|
{
|
|
|
|
var output = new byte[entry.UnpackedSize];
|
|
|
|
using (var file = arc.File.CreateStream (entry.Offset, entry.Size))
|
|
|
|
using (var input = new BinaryReader (file))
|
|
|
|
{
|
|
|
|
file.Position = 8;
|
|
|
|
var offsets = new int[4];
|
|
|
|
offsets[0] = input.ReadUInt16();
|
|
|
|
offsets[1] = offsets[0] * 2;
|
|
|
|
offsets[2] = offsets[0] * 3;
|
|
|
|
offsets[3] = offsets[0] * 4;
|
|
|
|
int dst = 0;
|
|
|
|
while (dst < output.Length)
|
|
|
|
{
|
|
|
|
byte ctl = input.ReadByte();
|
|
|
|
if (0 == ctl)
|
|
|
|
break;
|
|
|
|
int count;
|
|
|
|
if (ctl < 0x40)
|
|
|
|
{
|
|
|
|
input.Read (output, dst, ctl);
|
|
|
|
dst += ctl;
|
|
|
|
}
|
|
|
|
else if (ctl <= 0x6F)
|
|
|
|
{
|
|
|
|
if (0x6F == ctl)
|
|
|
|
count = input.ReadUInt16();
|
|
|
|
else
|
|
|
|
count = (ctl + 0xC3) & 0xFF;
|
|
|
|
byte v = input.ReadByte();
|
|
|
|
while (count --> 0)
|
|
|
|
output[dst++] = v;
|
|
|
|
}
|
|
|
|
else if (ctl <= 0x9F)
|
|
|
|
{
|
|
|
|
if (ctl == 0x9F)
|
|
|
|
count = input.ReadUInt16();
|
|
|
|
else
|
|
|
|
count = (ctl + 0x92) & 0xFF;
|
|
|
|
byte v1 = input.ReadByte();
|
|
|
|
byte v2 = input.ReadByte();
|
|
|
|
while (count --> 0)
|
|
|
|
{
|
|
|
|
output[dst++] = v1;
|
|
|
|
output[dst++] = v2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (ctl <= 0xBF)
|
|
|
|
{
|
|
|
|
if (ctl == 0xBF)
|
|
|
|
count = input.ReadUInt16();
|
|
|
|
else
|
|
|
|
count = ((ctl + 0x62) & 0xFF);
|
|
|
|
input.Read (output, dst, 3);
|
|
|
|
if (count > 0)
|
|
|
|
{
|
|
|
|
count *= 3;
|
|
|
|
Binary.CopyOverlapped (output, dst, dst+3, count-3);
|
|
|
|
dst += count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
count = (ctl & 0x3F) + 3;
|
|
|
|
int offset = input.ReadByte();
|
|
|
|
offset = (offset & 0x3F) - offsets[offset >> 6];
|
|
|
|
Binary.CopyOverlapped (output, dst+offset, dst, count);
|
|
|
|
dst += count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return new MemoryStream (output);
|
|
|
|
}
|
|
|
|
}
|
2016-03-20 13:42:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
internal sealed class GrpUnpacker : IDisposable
|
|
|
|
{
|
|
|
|
BinaryReader m_input;
|
|
|
|
uint m_bits;
|
|
|
|
int m_cached_bits;
|
|
|
|
|
|
|
|
public GrpUnpacker (Stream input)
|
|
|
|
{
|
|
|
|
m_input = new ArcView.Reader (input);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void UnpackHDJ (byte[] output, int dst)
|
|
|
|
{
|
|
|
|
ResetBits();
|
|
|
|
int word_count = 0;
|
|
|
|
int byte_count = 0;
|
|
|
|
uint next_byte = 0;
|
|
|
|
uint next_word = 0;
|
|
|
|
while (dst < output.Length)
|
|
|
|
{
|
|
|
|
if (GetNextBit() != 0)
|
|
|
|
{
|
|
|
|
int count = 0;
|
|
|
|
bool long_count = false;
|
|
|
|
int offset;
|
|
|
|
if (GetNextBit() != 0)
|
|
|
|
{
|
|
|
|
if (0 == word_count)
|
|
|
|
{
|
|
|
|
next_word = m_input.ReadUInt32();
|
|
|
|
word_count = 2;
|
|
|
|
}
|
|
|
|
count = (int)((next_word >> 13) & 7) + 3;
|
|
|
|
offset = (int)(next_word | 0xFFFFE000);
|
|
|
|
next_word >>= 16;
|
|
|
|
--word_count;
|
|
|
|
long_count = 10 == count;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
count = GetBits (2) + 2;
|
|
|
|
long_count = 5 == count;
|
|
|
|
if (0 == byte_count)
|
|
|
|
{
|
|
|
|
next_byte = m_input.ReadUInt32();
|
|
|
|
byte_count = 4;
|
|
|
|
}
|
|
|
|
offset = (int)(next_byte | 0xFFFFFF00);
|
|
|
|
next_byte >>= 8;
|
|
|
|
--byte_count;
|
|
|
|
}
|
|
|
|
if (long_count)
|
|
|
|
{
|
|
|
|
int n = 0;
|
|
|
|
while (GetNextBit() != 0)
|
|
|
|
++n;
|
|
|
|
|
|
|
|
if (n != 0)
|
|
|
|
count += GetBits (n) + 1;
|
|
|
|
}
|
|
|
|
Binary.CopyOverlapped (output, dst+offset, dst, count);
|
|
|
|
dst += count;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (0 == byte_count)
|
|
|
|
{
|
|
|
|
next_byte = m_input.ReadUInt32();
|
|
|
|
byte_count = 4;
|
|
|
|
}
|
|
|
|
output[dst++] = (byte)next_byte;
|
|
|
|
next_byte >>= 8;
|
|
|
|
--byte_count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void UnpackS (byte[] output, int dst, int channels)
|
|
|
|
{
|
|
|
|
if (channels != 1)
|
|
|
|
m_input.BaseStream.Seek ((channels-1) * 4, SeekOrigin.Current);
|
|
|
|
int step = channels * 2;
|
|
|
|
for (int i = 0; i < channels; ++i)
|
|
|
|
{
|
|
|
|
ResetBits();
|
|
|
|
int pos = dst;
|
|
|
|
short last_word = 0;
|
|
|
|
while (pos < output.Length)
|
|
|
|
{
|
|
|
|
int word;
|
|
|
|
if (GetNextBit() != 0)
|
|
|
|
{
|
|
|
|
if (GetNextBit() != 0)
|
|
|
|
{
|
|
|
|
word = GetBits (10) << 6;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int repeat;
|
|
|
|
if (GetNextBit() != 0)
|
|
|
|
{
|
|
|
|
int bit_length = 0;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
++bit_length;
|
|
|
|
}
|
|
|
|
while (GetNextBit() != 0);
|
|
|
|
repeat = GetBits (bit_length) + 4;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
repeat = GetBits (2);
|
|
|
|
}
|
|
|
|
word = 0;
|
|
|
|
while (repeat --> 0)
|
|
|
|
{
|
|
|
|
output[pos] = 0;
|
|
|
|
output[pos+1] = 0;
|
|
|
|
pos += step;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int adjust = (short)(GetBits (5) << 11) >> 5;
|
|
|
|
word = last_word + adjust;
|
|
|
|
}
|
|
|
|
LittleEndian.Pack ((short)word, output, pos);
|
|
|
|
last_word = (short)word;
|
|
|
|
pos += step;
|
|
|
|
}
|
|
|
|
dst += 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void UnpackA (byte[] output, int dst, int channels)
|
|
|
|
{
|
|
|
|
if (channels != 1)
|
|
|
|
m_input.BaseStream.Seek ((channels-1) * 4, SeekOrigin.Current);
|
|
|
|
int step = 2 * channels;
|
|
|
|
for (int i = 0; i < channels; ++i)
|
|
|
|
{
|
|
|
|
int pos = dst;
|
|
|
|
ResetBits();
|
|
|
|
while (pos < output.Length)
|
|
|
|
{
|
|
|
|
int word = GetBits (10) << 6;;
|
|
|
|
LittleEndian.Pack ((short)word, output, pos);
|
|
|
|
pos += step;
|
|
|
|
}
|
|
|
|
dst += 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ResetBits ()
|
|
|
|
{
|
|
|
|
m_cached_bits = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int GetNextBit ()
|
|
|
|
{
|
|
|
|
return GetBits (1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int GetBits (int count)
|
|
|
|
{
|
|
|
|
if (0 == m_cached_bits)
|
|
|
|
{
|
|
|
|
m_bits = m_input.ReadUInt32();
|
|
|
|
m_cached_bits = 32;
|
|
|
|
}
|
|
|
|
uint val;
|
|
|
|
if (m_cached_bits < count)
|
|
|
|
{
|
|
|
|
uint next_bits = m_input.ReadUInt32();
|
|
|
|
val = (m_bits | (next_bits >> m_cached_bits)) >> (32 - count);
|
|
|
|
m_bits = next_bits << (count - m_cached_bits);
|
|
|
|
m_cached_bits = 32 - (count - m_cached_bits);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
val = m_bits >> (32 - count);
|
|
|
|
m_bits <<= count;
|
|
|
|
m_cached_bits -= count;
|
|
|
|
}
|
|
|
|
return (int)val;
|
|
|
|
}
|
|
|
|
|
|
|
|
#region IDisposable Members
|
|
|
|
bool _disposed = false;
|
|
|
|
public void Dispose ()
|
|
|
|
{
|
|
|
|
if (!_disposed)
|
|
|
|
{
|
|
|
|
m_input.Dispose();
|
|
|
|
_disposed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|