mirror of
https://github.com/SabreTools/SabreTools.IO.git
synced 2026-07-08 17:57:02 +00:00
143 lines
4.9 KiB
C#
143 lines
4.9 KiB
C#
using System;
|
|
using SabreTools.Numerics.Extensions;
|
|
|
|
namespace SabreTools.IO.Compression.RVZPack
|
|
{
|
|
/// <summary>
|
|
/// Decompressor for RVZ packed format.
|
|
/// RVZ uses run-length encoding to store real data and junk data efficiently:
|
|
/// - Real data: size (4 bytes) + data bytes
|
|
/// - Junk data: size with high bit set (4 bytes) + 68-byte seed -> regenerate using LFG
|
|
/// </summary>
|
|
public class Decompressor
|
|
{
|
|
/// <summary>
|
|
/// Packed RVZ data to decompress
|
|
/// </summary>
|
|
private readonly byte[] _packedData;
|
|
|
|
/// <summary>
|
|
/// Expected size of packed data (for validation)
|
|
/// </summary>
|
|
private readonly uint _rvzPackedSize;
|
|
|
|
/// <summary>
|
|
/// Offset in the virtual disc, used by <see cref="_lfg"/>
|
|
/// </summary>
|
|
private long _dataOffset;
|
|
|
|
/// <summary>
|
|
/// Lagged Fibonacci generator for junk processing
|
|
/// </summary>
|
|
private readonly LaggedFibonacciGenerator _lfg;
|
|
|
|
/// <summary>
|
|
/// Current position into <see cref="_packedData"/>
|
|
/// </summary>
|
|
private int _inPosition = 0;
|
|
|
|
/// <summary>
|
|
/// The number of writable bytes in the current segment
|
|
/// </summary>
|
|
private uint _currentSize = 0;
|
|
|
|
/// <summary>
|
|
/// Indicates if the current segment is junk data
|
|
/// </summary>
|
|
private bool _currentIsJunk = false;
|
|
|
|
/// <summary>
|
|
/// Creates a new RVZ pack decompressor.
|
|
/// </summary>
|
|
/// <param name="packedData">The packed RVZ data</param>
|
|
/// <param name="rvzPackedSize">Expected size of packed data (for validation)</param>
|
|
/// <param name="dataOffset">Offset in the virtual disc (for LFG alignment)</param>
|
|
public Decompressor(byte[] packedData, uint rvzPackedSize, long dataOffset)
|
|
{
|
|
_packedData = packedData;
|
|
_rvzPackedSize = rvzPackedSize;
|
|
_dataOffset = dataOffset;
|
|
_lfg = new LaggedFibonacciGenerator();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decompresses the packed data into the output buffer.
|
|
/// </summary>
|
|
/// <param name="output">Destination buffer</param>
|
|
/// <param name="outputOffset">Offset in destination buffer</param>
|
|
/// <param name="count">Number of bytes to decompress</param>
|
|
/// <returns>Number of bytes actually decompressed</returns>
|
|
public int Decompress(byte[] output, int outputOffset, int count)
|
|
{
|
|
int totalWritten = 0;
|
|
|
|
while (totalWritten < count && !IsDone())
|
|
{
|
|
if (_currentSize == 0)
|
|
{
|
|
if (!ReadNextSegment())
|
|
break;
|
|
}
|
|
|
|
int bytesToWrite = Math.Min((int)_currentSize, count - totalWritten);
|
|
if (_currentIsJunk)
|
|
{
|
|
_lfg.GetBytes(bytesToWrite, output, outputOffset + totalWritten);
|
|
}
|
|
else
|
|
{
|
|
Array.Copy(_packedData, _inPosition, output, outputOffset + totalWritten, bytesToWrite);
|
|
_inPosition += bytesToWrite;
|
|
}
|
|
|
|
_currentSize -= (uint)bytesToWrite;
|
|
totalWritten += bytesToWrite;
|
|
_dataOffset += bytesToWrite;
|
|
}
|
|
|
|
return totalWritten;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if decompression is complete.
|
|
/// </summary>
|
|
public bool IsDone() => _currentSize == 0 && _inPosition >= _rvzPackedSize;
|
|
|
|
/// <summary>
|
|
/// Read the next segment of packed data
|
|
/// </summary>
|
|
/// <returns>True if the segment was read and cached, false otherwise</returns>
|
|
private bool ReadNextSegment()
|
|
{
|
|
if (_inPosition + 4 > _packedData.Length)
|
|
return false;
|
|
|
|
// Size field is big-endian u32; high bit signals junk data
|
|
uint sizeField = _packedData.ReadUInt32BigEndian(ref _inPosition);
|
|
|
|
_currentIsJunk = (sizeField & 0x80000000) != 0;
|
|
_currentSize = sizeField & 0x7FFFFFFF;
|
|
|
|
if (_currentIsJunk)
|
|
{
|
|
if (_inPosition + (LaggedFibonacciGenerator.SEED_SIZE * 4) > _packedData.Length)
|
|
return false;
|
|
|
|
byte[] seed = new byte[LaggedFibonacciGenerator.SEED_SIZE * 4];
|
|
Array.Copy(_packedData, _inPosition, seed, 0, seed.Length);
|
|
_inPosition += seed.Length;
|
|
|
|
_lfg.SetSeed(seed);
|
|
|
|
// Advance LFG to the correct position within the buffer.
|
|
// Dolphin: lfg.m_position_bytes = data_offset % (LFG_K * sizeof(u32))
|
|
int offsetInBuffer = (int)(_dataOffset % LaggedFibonacciGenerator.BUFFER_BYTES);
|
|
if (offsetInBuffer > 0)
|
|
_lfg.Forward(offsetInBuffer);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|