(GrpOpener): implemented TPW compression.

This commit is contained in:
morkt 2016-10-03 23:22:37 +04:00
parent e006b898e2
commit ecdb6d147f
3 changed files with 230 additions and 26 deletions

View File

@ -34,23 +34,23 @@ namespace GameRes.Formats.Ankh
[Export(typeof(ArchiveFormat))]
public class GrpOpener : ArchiveFormat
{
public override string Tag { get { return "GRP/ANKH"; } }
public override string Description { get { return "Ankh resource archive"; } }
public override string Tag { get { return "GRP/ICE"; } }
public override string Description { get { return "Ice Soft resource archive"; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return false; } }
public override bool CanCreate { get { return false; } }
public GrpOpener ()
{
Extensions = new string[] { "grp" };
Extensions = new string[] { "grp", "bin" };
}
public override ArcFile TryOpen (ArcView file)
{
uint first_offset = file.View.ReadUInt32 (0);
if (first_offset < 8 || first_offset >= file.MaxOffset)
if (first_offset < 8 || first_offset >= file.MaxOffset || 0 != (first_offset & 3))
return null;
int count = (int)(first_offset - 8) / 4;
int count = (int)(first_offset - 4) / 4;
if (!IsSaneCount (count))
return null;
@ -58,7 +58,7 @@ namespace GameRes.Formats.Ankh
uint index_offset = 0;
uint next_offset = first_offset;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
for (int i = 0; i < count && next_offset < file.MaxOffset; ++i)
{
var entry = new PackedEntry { Offset = next_offset };
index_offset += 4;
@ -77,57 +77,106 @@ namespace GameRes.Formats.Ankh
}
if (0 == dir.Count)
return null;
DetectFileTypes (file, dir);
return new ArcFile (file, this, dir);
}
void DetectFileTypes (ArcView file, List<Entry> dir)
{
foreach (PackedEntry entry in dir)
{
if (entry.Size < 4)
if (entry.Size <= 8)
continue;
uint unpacked_size = file.View.ReadUInt32 (entry.Offset);
if (entry.Size > 8 && file.View.AsciiEqual (entry.Offset+4, "HDJ\0"))
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"))
{
if (file.View.AsciiEqual (entry.Offset+12, "BM"))
{
entry.Name = Path.ChangeExtension (entry.Name, "bmp");
entry.Type = "image";
}
entry.UnpackedSize = unpacked_size;
entry.UnpackedSize = file.View.ReadUInt32 (entry.Offset);
entry.IsPacked = true;
}
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;
}
else if (entry.Size > 12 && file.View.AsciiEqual (entry.Offset+8, "RIFF"))
{
entry.Name = Path.ChangeExtension (entry.Name, "wav");
entry.Type = "audio";
entry.UnpackedSize = unpacked_size;
entry.UnpackedSize = file.View.ReadUInt32 (entry.Offset);
entry.IsPacked = true;
}
else if (0x4D42 == (unpacked_size & 0xFFFF))
else if (file.View.AsciiEqual (entry.Offset, "BM"))
{
entry.Name = Path.ChangeExtension (entry.Name, "bmp");
entry.Type = "image";
}
else if (entry.Size > 0x16 && IsAudioEntry (file, entry))
{
entry.Type = "audio";
}
}
return new ArcFile (file, this, dir);
}
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;
}
public override Stream OpenEntry (ArcFile arc, Entry entry)
{
if (entry.Size > 8 && arc.File.View.AsciiEqual (entry.Offset+4, "HDJ\0"))
return OpenImage (arc, entry);
else if (entry.Size > 12 && 'W' == arc.File.View.ReadByte (entry.Offset+4)
&& arc.File.View.AsciiEqual (entry.Offset+8, "RIFF"))
return OpenAudio (arc, entry);
else
return base.OpenEntry (arc, entry);
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);
}
Stream OpenImage (ArcFile arc, Entry entry)
Stream OpenImage (ArcFile arc, PackedEntry entry)
{
int unpacked_size = arc.File.View.ReadInt32 (entry.Offset);
if (unpacked_size <= 0)
return base.OpenEntry (arc, entry);
using (var packed = arc.File.CreateStream (entry.Offset+8, entry.Size-8))
using (var reader = new GrpUnpacker (packed))
{
var unpacked = new byte[unpacked_size];
var unpacked = new byte[entry.UnpackedSize];
reader.UnpackHDJ (unpacked, 0);
return new MemoryStream (unpacked);
}
@ -155,6 +204,81 @@ namespace GameRes.Formats.Ankh
return new MemoryStream (unpacked);
}
}
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);
}
}
}
internal sealed class GrpUnpacker : IDisposable

View File

@ -0,0 +1,74 @@
//! \file AudioPCM.cs
//! \date Mon Oct 03 22:21:15 2016
//! \brief Ice Soft PCM audio file.
//
// 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.ComponentModel.Composition;
using System.IO;
using GameRes.Utility;
namespace GameRes.Formats.Ice
{
[Export(typeof(AudioFormat))]
public class IceAudio : AudioFormat
{
public override string Tag { get { return "PCM/ICE"; } }
public override string Description { get { return "Ice Soft PCM audio"; } }
public override uint Signature { get { return 0; } }
public IceAudio ()
{
Extensions = new string[] { "" };
Signatures = new uint[] { 0x010001, 0x020001 };
}
public override SoundInput TryOpen (Stream file)
{
var header = new byte[0x16];
if (header.Length != file.Read (header, 0, header.Length))
return null;
ushort tag = LittleEndian.ToUInt16 (header, 0);
if (tag != 1)
return null;
int extra_size = LittleEndian.ToUInt16 (header, 0x10);
if (0 != extra_size)
return null;
uint pcm_size = LittleEndian.ToUInt32 (header, 0x12);
if (pcm_size + 0x16 != file.Length)
return null;
var format = new WaveFormat { FormatTag = tag };
format.Channels = LittleEndian.ToUInt16 (header, 2);
if (format.Channels != 1 && format.Channels != 2)
return null;
format.SamplesPerSecond = LittleEndian.ToUInt32 (header, 4);
format.AverageBytesPerSecond = LittleEndian.ToUInt32 (header, 8);
format.BlockAlign = LittleEndian.ToUInt16 (header, 0xC);
format.BitsPerSample = LittleEndian.ToUInt16 (header, 0xE);
if (0 == format.AverageBytesPerSecond
|| format.SamplesPerSecond * format.BlockAlign != format.AverageBytesPerSecond)
return null;
var pcm = new StreamRegion (file, 0x16, pcm_size);
return new RawPcmInput (pcm, format);
}
}
}

View File

@ -179,6 +179,7 @@ Binary Pot<br/>
Chou Gedou Yuusha<br/>
Shinshoku ~Inma no Ikenie~<br/>
Shinshoku 2 ~Inma no Ikenie~<br/>
Shokusou Tenshi Serika<br/>
Tsukihime<br/>
Umineko<br/>
</td></tr>
@ -265,6 +266,7 @@ Kurenai no Tsuki<br/>
LOVELY x CATION<br/>
Mayoeru Futari to Sekai no Subete<br/>
Mahoutsukai no Yoru<br/>
Mizukoi<br/>
Nakadashi Hara Maid series<br/>
Natsupochi<br/>
Natsuzora Kanata<br/>
@ -411,6 +413,7 @@ Chikan Circle 2 <span class="footnote">ShiinaRio v2.47</span><br/>
Chuuchuu Nurse <span class="footnote">ShiinaRio v2.45</span><br/>
Classmate no Okaa-san <span class="footnote">ShiinaRio v2.37</span><br/>
Cleavage <span class="footnote">ShiinaRio v2.33</span><br/>
Danchi Wife ~Hitozuma Nude Model no Kenshin~ <span class="footnote">ShiinaRio v2.47</span><br/>
Doushite Daite Kurenai no!?<span class="footnote">ShiinaRio v2.49</span><br/>
Draculius <span class="footnote">ShiinaRio v2.38</span><br/>
Enkaku Sousa <span class="footnote">2.36 or 2.37</span><br/>
@ -832,6 +835,7 @@ Trouble Trap Laboratory<br/>
<tr class="odd last"><td>*.ngp</td><td><tt>NGP</tt></td><td>No</td></tr>
<tr><td>*.hxp</td><td><tt>Him4</tt><br/><tt>Him5</tt><br/><tt>SHS6</tt><br/><tt>SHS7</tt></td><td>No</td><td>SH System</td><td>
Natsumero<br/>
Sumeragi Ryouko no Bitch na 1 nichi<br/>
</td></tr>
<tr class="odd"><td>*.det<br/>+*.nme<br/>+*.atm</td><td>-</td><td>No</td><td rowspan="2">μ-GameOperationSystem</td><td rowspan="2">
Ippai Shimasho<br/>
@ -981,7 +985,8 @@ Hitomi no Rakuin ~Inbaku no Mesu Dorei~<br/>
Jokujima<br/>
Saiin Haramase Keyword<br/>
</td></tr>
<tr class="odd"><td>*.grp</td><td>-</td><td>No</td><td>Ankh</td><td>
<tr class="odd"><td>*.grp<br/>*.bin</td><td>-<br/><tt>TPW</tt></td><td>No</td><td>Tirol<br/>Ankh</td><td>
Aigan Shoujo<br/>
Mozu<br/>
</td></tr>
<tr><td>*.gpc</td><td><tt>Gpc7</tt></td><td>No</td><td>Super NekoX</td><td>
@ -1105,6 +1110,7 @@ Soushinjutsu 2<br/>
Rakuin Hime Runed Princess<br/>
Shinshoku -Zero- ~Inma no Ikenie~<br/>
Shinshoku 3 ~Inma no Ikenie~<br/>
Shokusou Tenshi Serika 2<br/>
</td></tr>
<tr class="odd"><td>*.dat</td><td>-</td><td>No</td><td>NekoSDK</td><td>
Elevator Panic ~Misshitsu no Inkou~<br/>