Emulator/GbaBios.Decompress.cs
namespace sGBA;
public partial class GbaBios
{
private void LZ77UnCompWram() { LZ77Decompress( 1 ); }
private void LZ77UnCompVram() { LZ77Decompress( 2 ); }
private void LZ77Decompress( int width )
{
uint src = Gba.Cpu.Registers[0];
uint dst = Gba.Cpu.Registers[1];
int cycles = 20;
uint header = Gba.Memory.Load32( src );
int remaining = (int)((header & 0xFFFFFF00) >> 8);
src += 4;
int blocksRemaining = 0;
int blockHeader = 0;
int halfword = 0;
while ( remaining > 0 )
{
cycles += 14;
if ( blocksRemaining > 0 )
{
cycles += 18;
if ( (blockHeader & 0x80) != 0 )
{
int block = Gba.Memory.Load8( src + 1 ) | (Gba.Memory.Load8( src ) << 8);
src += 2;
uint disp = dst - (uint)(block & 0x0FFF) - 1;
int bytes = (block >> 12) + 3;
while ( bytes-- > 0 )
{
cycles += 10;
if ( remaining > 0 )
{
--remaining;
}
if ( width == 2 )
{
int val = (short)Gba.Memory.Load16( disp & ~1u );
if ( (dst & 1) != 0 )
{
val >>= (int)(disp & 1) * 8;
halfword |= val << 8;
Gba.Memory.Store16( dst ^ 1, (ushort)halfword );
}
else
{
val >>= (int)(disp & 1) * 8;
halfword = val & 0xFF;
}
cycles += 4;
}
else
{
int val = Gba.Memory.Load8( disp );
Gba.Memory.Store8( dst, (byte)val );
}
++disp;
++dst;
}
}
else
{
int val = Gba.Memory.Load8( src );
++src;
if ( width == 2 )
{
if ( (dst & 1) != 0 )
{
halfword |= val << 8;
Gba.Memory.Store16( dst ^ 1, (ushort)halfword );
}
else
{
halfword = val;
}
}
else
{
Gba.Memory.Store8( dst, (byte)val );
}
++dst;
--remaining;
}
blockHeader <<= 1;
--blocksRemaining;
}
else
{
blockHeader = Gba.Memory.Load8( src );
++src;
blocksRemaining = 8;
}
}
Gba.Cpu.Registers[0] = src;
Gba.Cpu.Registers[1] = dst;
Gba.Cpu.Registers[3] = 0;
BiosStall = cycles;
}
private void HuffmanUnComp()
{
uint src = Gba.Cpu.Registers[0] & 0xFFFFFFFC;
uint dst = Gba.Cpu.Registers[1];
uint header = Gba.Memory.Load32( src );
int bitSize = (int)(header & 0xF);
int decompSize = (int)(header >> 8);
if ( bitSize == 0 )
bitSize = 8;
if ( 32 % bitSize != 0 || bitSize == 1 )
return;
src += 4;
uint treeSize = Gba.Memory.Load8( src ) * 2u + 1;
uint treeBase = src + 1;
src += treeSize + 1;
int written = 0;
uint outBuffer = 0;
int outBits = 0;
uint bits = 0;
int bitsLeft = 0;
uint treeNode = treeBase;
while ( written < decompSize )
{
if ( bitsLeft == 0 )
{
bits = Gba.Memory.Load32( src );
src += 4;
bitsLeft = 32;
}
bool goRight = (bits & 0x80000000) != 0;
bits <<= 1;
bitsLeft--;
byte nodeVal = Gba.Memory.Load8( treeNode );
uint childOffset = (treeNode & ~1u) + (uint)(nodeVal & 0x3F) * 2 + 2;
if ( goRight )
childOffset++;
int endFlag = goRight ? ((nodeVal >> 6) & 1) : (nodeVal >> 7);
if ( endFlag != 0 )
{
byte data = Gba.Memory.Load8( childOffset );
outBuffer |= (uint)(data & ((1 << bitSize) - 1)) << outBits;
outBits += bitSize;
if ( outBits >= 32 )
{
Gba.Memory.Store32( dst, outBuffer );
dst += 4;
written += 4;
outBuffer = 0;
outBits = 0;
}
treeNode = treeBase;
}
else
{
treeNode = childOffset;
}
}
Gba.Cpu.Registers[0] = src;
Gba.Cpu.Registers[1] = dst;
}
private void RLUnCompWram() { RLDecompress( false ); }
private void RLUnCompVram() { RLDecompress( true ); }
private void RLDecompress( bool vram )
{
uint src = Gba.Cpu.Registers[0];
uint dst = Gba.Cpu.Registers[1];
uint header = Gba.Memory.Load32( src & 0xFFFFFFFC );
src += 4;
int remaining = (int)(header >> 8);
int padding = (4 - remaining) & 0x3;
int halfword = 0;
while ( remaining > 0 )
{
byte flag = Gba.Memory.Load8( src++ );
if ( (flag & 0x80) != 0 )
{
int length = (flag & 0x7F) + 3;
byte data = Gba.Memory.Load8( src++ );
for ( int i = 0; i < length && remaining > 0; i++ )
{
remaining--;
if ( vram )
{
if ( (dst & 1) != 0 )
{
halfword |= data << 8;
Gba.Memory.Store16( dst ^ 1, (ushort)halfword );
}
else
{
halfword = data;
}
}
else
{
Gba.Memory.Store8( dst, data );
}
dst++;
}
}
else
{
int length = (flag & 0x7F) + 1;
for ( int i = 0; i < length && remaining > 0; i++ )
{
byte data = Gba.Memory.Load8( src++ );
remaining--;
if ( vram )
{
if ( (dst & 1) != 0 )
{
halfword |= data << 8;
Gba.Memory.Store16( dst ^ 1, (ushort)halfword );
}
else
{
halfword = data;
}
}
else
{
Gba.Memory.Store8( dst, data );
}
dst++;
}
}
}
if ( vram )
{
if ( (dst & 1) != 0 )
{
padding--;
dst++;
}
for ( ; padding > 0; padding -= 2, dst += 2 )
Gba.Memory.Store16( dst, 0 );
}
else
{
for ( ; padding > 0; padding-- )
Gba.Memory.Store8( dst++, 0 );
}
Gba.Cpu.Registers[0] = src;
Gba.Cpu.Registers[1] = dst;
}
private void Diff8BitUnFilterWram() { DiffUnFilter( 1, 1 ); }
private void Diff8BitUnFilterVram() { DiffUnFilter( 1, 2 ); }
private void Diff16BitUnFilter() { DiffUnFilter( 2, 2 ); }
private void DiffUnFilter( int inWidth, int outWidth )
{
uint src = Gba.Cpu.Registers[0] & 0xFFFFFFFC;
uint dst = Gba.Cpu.Registers[1];
uint header = Gba.Memory.Load32( src );
int remaining = (int)(header >> 8);
ushort halfword = 0;
ushort old = 0;
src += 4;
while ( remaining > 0 )
{
ushort next;
if ( inWidth == 1 )
next = Gba.Memory.Load8( src );
else
next = Gba.Memory.Load16( src );
next = (ushort)(next + old);
if ( outWidth > inWidth )
{
halfword >>= 8;
halfword |= (ushort)(next << 8);
if ( (src & 1) != 0 )
{
Gba.Memory.Store16( dst, halfword );
dst += (uint)outWidth;
remaining -= outWidth;
}
}
else if ( outWidth == 1 )
{
Gba.Memory.Store8( dst, (byte)next );
dst += (uint)outWidth;
remaining -= outWidth;
}
else
{
Gba.Memory.Store16( dst, next );
dst += (uint)outWidth;
remaining -= outWidth;
}
old = next;
src += (uint)inWidth;
}
Gba.Cpu.Registers[0] = src;
Gba.Cpu.Registers[1] = dst;
}
private void MidiKey2Freq()
{
uint waveDataPtr = Gba.Cpu.Registers[0];
uint key = Gba.Memory.Load32( waveDataPtr + 4 );
int midiKey = (int)Gba.Cpu.Registers[1];
int pitchAdj = (int)Gba.Cpu.Registers[2];
float exponent = (180f - midiKey - pitchAdj / 256f) / 12f;
Gba.Cpu.Registers[0] = (uint)(key / MathF.Pow( 2f, exponent ));
}
}