Last active
December 13, 2024 20:20
-
-
Save barncastle/5748b602751c0de2649f47f4e079dd59 to your computer and use it in GitHub Desktop.
Code for reading Shin'en GAX Sound Engine GHX2 files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Diagnostics; | |
using System.IO; | |
using System.Runtime.CompilerServices; | |
namespace ConsoleApp2 | |
{ | |
internal class GHX2 | |
{ | |
struct GHX2_Header | |
{ | |
public uint HeaderMagic; // "GHXC" | |
public int DecompressedDataLen; | |
public uint CompressedDataChecksum; // Fletcher32(CompressedData) | |
public uint LZODMagic; // "LZOD" | |
public int CompressedDataLen; | |
// byte CompressedData[CompressedDataLen] | |
} | |
public static unsafe void DecompressAndExport(string filepath) | |
{ | |
const int CompressedDataOffset = 20; | |
Span<byte> buffer = File.ReadAllBytes(filepath); | |
// read header and compressed data | |
GHX2_Header header = Unsafe.As<byte, GHX2_Header>(ref buffer[0]); | |
Span<byte> compressedData = buffer[CompressedDataOffset..]; | |
Debug.Assert(header.HeaderMagic == 0x43584847); // "GHXC" | |
Debug.Assert(header.LZODMagic == 0x444F5A4C); // "LZOD" | |
Debug.Assert(header.CompressedDataLen == buffer.Length - CompressedDataOffset); // to EOF | |
// validate checksum | |
uint checksum = Fletcher32(compressedData, header.CompressedDataLen); | |
Debug.Assert(checksum == header.CompressedDataChecksum); | |
// decompress the data | |
byte[] output = Decompress(compressedData, header.DecompressedDataLen); | |
// write to a new file | |
File.WriteAllBytes(Path.ChangeExtension(filepath, ".bin"), output); | |
} | |
public static uint Fletcher32(ReadOnlySpan<byte> data, int size) | |
{ | |
uint sum1 = 0, sum2 = 0; | |
for (int i = 0; i < size;) | |
{ | |
int blockSize = Math.Min(size - i, 0x15B0); | |
for (int j = 0; j < blockSize; j++) | |
{ | |
sum1 += data[i++]; | |
sum2 += sum1; | |
} | |
sum1 %= 0xFFF1; | |
sum2 %= 0xFFF1; | |
} | |
return (sum2 << 0x10) | (sum1 & 0xFFFF); | |
} | |
public static byte[] Decompress(ReadOnlySpan<byte> input, int outLen) | |
{ | |
int copyLen; | |
int delta; | |
int backRefIndex; | |
byte[] output = new byte[outLen]; | |
// i = input index, o = output index | |
for (int i = 0, o = 0; o < outLen;) | |
{ | |
byte val = input[i++]; | |
if (val >= 0x40) // short distance back ref | |
{ | |
copyLen = (val >> 5) + 1; | |
delta = input[i++]; | |
backRefIndex = o - (((val >> 2) & 7) + 1 + (delta << 3)); | |
CopyBlockUnaligned(output, ref o, output, ref backRefIndex, copyLen); | |
} | |
else if (val >= 0x20) // med distance back ref with dword copy | |
{ | |
copyLen = ReadVarInt(input, ref i, val, 0x1F) + 2; | |
delta = input[i++] | (input[i++] << 8); // read uint16 | |
backRefIndex = o - ((delta >> 2) + 1); | |
// note: dword copy not implemented | |
CopyBlockUnaligned(output, ref o, output, ref backRefIndex, copyLen); | |
} | |
else if (val >= 0x10) // long distance back ref | |
{ | |
copyLen = ReadVarInt(input, ref i, val, 0x7) + 2; | |
delta = input[i++] | (input[i++] << 8); // read uint16 | |
backRefIndex = o - (((val & 8) << 11) + (delta >> 2) + 0x4000); | |
if (backRefIndex == o) | |
continue; | |
CopyBlockUnaligned(output, ref o, output, ref backRefIndex, copyLen); | |
} | |
else // literal copy | |
{ | |
copyLen = ReadVarInt(input, ref i, val, 0xF) + 3; | |
CopyBlockUnaligned(output, ref o, input, ref i, copyLen); | |
} | |
// back refs can incur an extra literal copy | |
if (val >= 0x10) | |
{ | |
copyLen = input[i - 2] & 3; | |
if (copyLen != 0) | |
CopyBlockUnaligned(output, ref o, input, ref i, copyLen); | |
} | |
} | |
return output; | |
} | |
private static int ReadVarInt(ReadOnlySpan<byte> buffer, ref int index, int val, int mask) | |
{ | |
int value = val & mask; | |
if (value == 0) | |
{ | |
while (buffer[index] == 0) | |
{ | |
value += 0xFF; | |
index++; | |
} | |
value += buffer[index++] + mask; | |
} | |
return value; | |
} | |
private static void CopyBlockUnaligned(Span<byte> dest, ref int destIndex, ReadOnlySpan<byte> src, ref int srcIndex, int size) | |
{ | |
for (int i = 0; i < size; i++) | |
dest[destIndex++] = src[srcIndex++]; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment