Editor/Decompiler/Lumps/BaseLump.Lzma.cs
using LZMA = SevenZip.Compression.LZMA;
namespace BspImport.Decompiler.Lumps;
public partial class BaseLump
{
/// <summary>
/// LZMA magic number.
/// </summary>
private const uint LZMA_ID = (('A' << 24) | ('M' << 16) | ('Z' << 8) | ('L'));
/// <summary>
/// Checks for the LZMA magic number in given Byte data.
/// </summary>
/// <param name="data">Byte data to check for LZMA compression magic number.</param>
/// <returns>True if the first 4 bytes represent the chars 'LZMA', false otherwise.</returns>
private bool IsCompressed( byte[]? data )
{
if ( data is null || data.Length < sizeof( uint ) )
return false;
if ( data.Length < Marshal.SizeOf<SourceLzmaHeader>() )
return false;
var reader = new BinaryReader( new MemoryStream( data ) );
var lzmaId = reader.ReadUInt32();
return lzmaId == LZMA_ID;
}
/// <summary>
/// Decompress the given LZMA compressed byte data.
/// </summary>
/// <param name="inData">Byte array to decompres.s</param>
/// <returns>Decompressed byte array.</returns>
private byte[] Decompress( byte[] inData )
{
// turn source lzma to standard lzma, constructs and stitches standard lzma header
var standardLzma = ConstructStandardLzma( inData );
// standard lzma format
var reader = new BinaryReader( new MemoryStream( standardLzma ) );
// get lzma properties
byte[] properties = new byte[5];
if ( reader.Read( properties, 0, 5 ) != 5 )
throw (new Exception( "Unable to read lzma properties!" ));
// setup decoder
var decoder = new LZMA.Decoder();
decoder.SetDecoderProperties( properties );
// body size before and after compression
long uncompressedSize = reader.ReadInt64();
long compressedSize = reader.GetLength();
// decompress and put into byte array
var outStream = new MemoryStream();
decoder.Code( reader.BaseStream, outStream, compressedSize, uncompressedSize, null );
//var outData = outStream.ReadByteArrayFromStream( 0, (uint)outStream.Length );
return outStream.ToArray();
}
// this sucks
private byte[] ConstructStandardLzma( byte[] sourceLzma )
{
// read source engine lzma header
var sourceHeaderReader = new StructReader<SourceLzmaHeader>();
var sourceHeaderLength = Marshal.SizeOf<SourceLzmaHeader>();
var sourceHeader = sourceHeaderReader.Read( sourceLzma );
// take lzma body only
var lzmaBody = sourceLzma[sourceHeaderLength..];
var lzmaHeaderLength = Marshal.SizeOf<LzmaHeader>();
// construct standard lzma
var standardLzma = new byte[lzmaHeaderLength + lzmaBody.Length];
// properties
sourceHeader.Properties.CopyTo( standardLzma, 0 );
// actual size
BitConverter.GetBytes( (ulong)sourceHeader.ActualSize ).CopyTo( standardLzma, 5 );
// body
lzmaBody.CopyTo( standardLzma, lzmaHeaderLength );
return standardLzma;
}
/// <summary>
/// LZMA header as used by source bsp formats.
/// </summary>
[StructLayout( LayoutKind.Sequential, Size = 17, Pack = 1 )]
private struct SourceLzmaHeader
{
public uint Id; // 4
public uint ActualSize; // 4
public uint LzmaSize; // 4
[MarshalAs( UnmanagedType.ByValArray, SizeConst = 5 )]
public byte[] Properties; // 5
}
/// <summary>
/// Standard LZMA header as used by the LZMA sdk.
/// </summary>
[StructLayout( LayoutKind.Sequential, Size = 13, Pack = 1 )]
private struct LzmaHeader
{
[MarshalAs( UnmanagedType.ByValArray, SizeConst = 5 )]
public byte[] Properties; // 5
public ulong ActualSize; // 8
}
}