using System;
using System.Buffers.Binary;
/// Godot-specific WAV input adapter. Native AGE decodes only the first declared RIFF/WAVE
/// extent, while Godot walks the entire supplied buffer. AGE's Japanese assets also commonly store
/// CP932 strings in INFO metadata that Godot assumes is UTF-8. Prepare a transient decoder copy that
/// follows AGE's first-RIFF boundary and removes only INFO metadata within it.
internal static class RiffWaveSanitizer
{
public static byte[] PrepareForGodot(byte[] wavBytes)
{
ReadOnlySpan input = wavBytes;
if (input.Length < 12 || !HasId(input, 0, "RIFF") || !HasId(input, 8, "WAVE"))
return wavBytes;
ulong declaredEnd64 = 8UL + BinaryPrimitives.ReadUInt32LittleEndian(input.Slice(4, 4));
if (declaredEnd64 < 12 || declaredEnd64 > (ulong)input.Length || declaredEnd64 > int.MaxValue)
return wavBytes;
int declaredEnd = (int)declaredEnd64;
int cursor = 12;
int removedBytes = 0;
while (cursor < declaredEnd)
{
if (!TryGetChunk(input, cursor, declaredEnd, out int chunkBytes, out bool isInfoList))
return wavBytes;
if (isInfoList) removedBytes = checked(removedBytes + chunkBytes);
cursor += chunkBytes;
}
if (removedBytes == 0 && declaredEnd == wavBytes.Length) return wavBytes;
int newDeclaredEnd = checked(declaredEnd - removedBytes);
var output = new byte[newDeclaredEnd];
input[..12].CopyTo(output);
cursor = 12;
int destination = 12;
while (cursor < declaredEnd)
{
_ = TryGetChunk(input, cursor, declaredEnd, out int chunkBytes, out bool isInfoList);
if (!isInfoList)
{
input.Slice(cursor, chunkBytes).CopyTo(output.AsSpan(destination));
destination += chunkBytes;
}
cursor += chunkBytes;
}
BinaryPrimitives.WriteUInt32LittleEndian(output.AsSpan(4, 4),
checked((uint)(newDeclaredEnd - 8)));
return output;
}
private static bool TryGetChunk(ReadOnlySpan input, int offset, int declaredEnd,
out int chunkBytes, out bool isInfoList)
{
chunkBytes = 0;
isInfoList = false;
if (offset > declaredEnd - 8) return false;
uint payloadBytes = BinaryPrimitives.ReadUInt32LittleEndian(input.Slice(offset + 4, 4));
ulong total64 = 8UL + payloadBytes + (payloadBytes & 1U);
if (total64 > int.MaxValue || total64 > (ulong)(declaredEnd - offset)) return false;
chunkBytes = (int)total64;
isInfoList = payloadBytes >= 4 && HasId(input, offset, "LIST")
&& HasId(input, offset + 8, "INFO");
return true;
}
private static bool HasId(ReadOnlySpan bytes, int offset, string id)
=> offset >= 0 && offset <= bytes.Length - 4
&& bytes[offset] == id[0] && bytes[offset + 1] == id[1]
&& bytes[offset + 2] == id[2] && bytes[offset + 3] == id[3];
}