From 75de4663b8f81aa8eb3fa1e919b35633f8b95c3e Mon Sep 17 00:00:00 2001 From: morkt Date: Fri, 27 Jan 2017 13:22:34 +0400 Subject: [PATCH] (GRP): introduced HDJ compression variation. --- ArcFormats/Ankh/ArcGRP.cs | 111 +++++++++++++++++++++++++++++++------- 1 file changed, 93 insertions(+), 18 deletions(-) diff --git a/ArcFormats/Ankh/ArcGRP.cs b/ArcFormats/Ankh/ArcGRP.cs index 70136ac8..26937d46 100644 --- a/ArcFormats/Ankh/ArcGRP.cs +++ b/ArcFormats/Ankh/ArcGRP.cs @@ -102,39 +102,33 @@ namespace GameRes.Formats.Ankh entry.Size -= 4; } if (file.View.AsciiEqual (start_offset, "BM")) - { - entry.Name = Path.ChangeExtension (entry.Name, "bmp"); - entry.Type = "image"; - } + entry.ChangeType (ImageFormat.Bmp); } 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.ChangeType (ImageFormat.Bmp); + else if (file.View.AsciiEqual (entry.Offset+12, "MThd")) + entry.Name = Path.ChangeExtension (entry.Name, "mid"); + 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.ChangeType (OggAudio.Instance); 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.ChangeType (AudioFormat.Wav); entry.UnpackedSize = file.View.ReadUInt32 (entry.Offset); entry.IsPacked = true; } else if (file.View.AsciiEqual (entry.Offset, "BM")) { - entry.Name = Path.ChangeExtension (entry.Name, "bmp"); - entry.Type = "image"; + entry.ChangeType (ImageFormat.Bmp); } else if (entry.Size > 0x16 && IsAudioEntry (file, entry)) { @@ -298,7 +292,33 @@ namespace GameRes.Formats.Ankh m_input = input; } + // Different games have slightly different formats using exact same headers, + // so have to invent some flawed format recognition here. + enum GrpVariant { Default, BoD }; + + static GrpVariant LastUsedMethod = GrpVariant.Default; + + static GrpVariant GetOppositeVariant () + { + return GrpVariant.Default == LastUsedMethod ? GrpVariant.BoD : GrpVariant.Default; + } + public void UnpackHDJ (byte[] output, int dst) + { + try + { + if (UnpackHDJVariant (output, dst, LastUsedMethod)) + return; + } + catch { /* ignore unpack errors */ } + var method = GetOppositeVariant(); + m_input.Position = 0; + if (!UnpackHDJVariant (output, dst, method)) + throw new InvalidFormatException(); + LastUsedMethod = method; + } + + private bool UnpackHDJVariant (byte[] output, int dst, GrpVariant method) { ResetBits(); int word_count = 0; @@ -327,13 +347,17 @@ namespace GameRes.Formats.Ankh } else { - count = GetBits (2) + 2; - long_count = 5 == count; + if (method == GrpVariant.Default) + count = GetBits (2); if (0 == byte_count) { next_byte = m_input.ReadUInt32(); byte_count = 4; } + if (method != GrpVariant.Default) + count = GetBits (2); + count += 2; + long_count = 5 == count; offset = (int)(next_byte | 0xFFFFFF00); next_byte >>= 8; --byte_count; @@ -348,8 +372,8 @@ namespace GameRes.Formats.Ankh count += GetBits (n) + 1; } int src = dst + offset; - if (src < 0) - throw new InvalidDataException(); + if (src < 0 || src >= dst || dst + count > output.Length) + return false; Binary.CopyOverlapped (output, src, dst, count); dst += count; } @@ -365,9 +389,60 @@ namespace GameRes.Formats.Ankh --byte_count; } } + return true; } public void UnpackS (byte[] output, int dst, int channels) + { + try + { + if (UnpackSVariant (output, dst, channels, LastUsedMethod)) + return; + } + catch { /* ignore parse errors */ } + var method = GetOppositeVariant(); + m_input.Position = 0; + if (UnpackSVariant (output, dst, channels, method)) + LastUsedMethod = method; + } + + bool UnpackSVariant (byte[] output, int dst, int channels, GrpVariant method) + { + if (GrpVariant.Default == method) + UnpackSv2 (output, dst, channels); + else + UnpackSv1 (output, dst, channels); + return m_input.PeekByte() == -1; // rather loose test, but whatever + } + + void UnpackSv1 (byte[] output, int dst, int channels) + { + ResetBits(); + short last_word = 0; + while (dst < output.Length) + { + int word; + if (GetNextBit() != 0) + { + if (GetNextBit() != 0) + word = GetBits (10) << 6; + else + word = 0; + } + else + { + int adjust = GetBits (5) << 6; + if (0 != (adjust & 0x400)) + adjust = -(adjust & 0x3FF); + word = last_word + adjust; + } + last_word = (short)word; + LittleEndian.Pack (last_word, output, dst); + dst += 2; + } + } + + void UnpackSv2 (byte[] output, int dst, int channels) { if (channels != 1) m_input.Seek ((channels-1) * 4, SeekOrigin.Current);