Pascal code for decompressing L2.
(Delphi added some new syntax, but the code should be readable by a programmer).
unit L2Decompress;
interface
type
TSigChars = array[0..3] of AnsiChar;
TSignature = packed record
case byte of
0: ( Chars: TSigChars );
1: ( Id: Cardinal );
end;
TL2Decompressor = class
private
const
CHUNK_SIZE = 2048; // maximum decompression size
type
TBuffer = array[0..CHUNK_SIZE - 1] of Byte; // decompressbuffer
// ref entity for chunk decompression
TRef = packed record
Byte: Byte;
Pair: array[0..1] of Byte;
end;
// one compressed chunk in compressed file
TChunk = packed record
LastChunkFlag : Byte; // Last chunk = $FF; $00 otherwise
RefCount : UInt16; // Number of refs
Refs : TArray<TRef>; // 'dictionary' [RefCount]
CompressedSize : UInt16; // Number of bytes in compressed data
CompressedData : TArray<Byte>; // compressed data [CompressedSize]
procedure LoadFromStream(S: TStream; out IsLast: Boolean);
procedure DecompressChunk(out Buffer: TBuffer; out Size: Integer);
end;
public
procedure Decompress(Src, Dst: TStream); overload;
end;
implementation
procedure TL2Decompressor.TChunk.LoadFromStream(S: TStream; out IsLast: Boolean);
// load one chunk of compressed data from stream
var
j, i: Integer;
begin
// read the chunk flag
S.ReadBuffer(LastChunkFlag, 1);
if not (LastChunkFlag in [$FF, $00]) then raise Exception.Create('LastChunkFlag error');
IsLast := LastChunkFlag = $FF;
// read the refcount
S.ReadBuffer(RefCount, 2);
SetLength(Refs, RefCount);
// and fill the refs byte by byte
for i := 0 to RefCount - 1 do
if S.Read(Refs.Byte, 1) <> 1 then raise exception.create('chunk ref read error a');
// and fill the byte-pairs
for j := 0 to 1 do
for i := 0 to RefCount - 1 do
if S.Read(Refs.Pair[j], 1) <> 1 then raise exception.create('cunks byte pair read error 2');
// read the compressed size
S.ReadBuffer(CompressedSize, SizeOf(CompressedSize));
if CompressedSize > 100000 then raise exception.Create('datasize error');
// and finally read the compressed data in to the buffer
SetLength(CompressedData, CompressedSize);
S.ReadBuffer(CompressedData[0], CompressedSize);
end;
procedure TL2Decompressor.TChunk.DecompressChunk(out Buffer: TBuffer; out Size: Integer);
// decompress one chunk of data
var
i, j, d, s, Cnt: Integer;
begin
Size := CompressedSize;
if Size > CHUNK_SIZE then raise Exception.Create('chunk decompression error (chunksize)');
// initialize the decompression buffer
FillChar(Buffer, SizeOf(Buffer), 0);
Move(CompressedData[0], Buffer[0], Size);
for i := RefCount - 1 downto 0 do begin
Cnt := 0;
for j := Size - 1 downto 0 do
if (Buffer[j] = Refs.Byte) then
Inc(Cnt);
if Size + Cnt > CHUNK_SIZE then raise Exception.Create('chunk decompression error (overflow)');
d := Size - 1 + Cnt;
for s := Size - 1 downto 0 do begin
if (Buffer = Refs.Byte) then begin
for j := 1 downto 0 do begin
Buffer[d] := Refs.Pair[j];
dec(d);
end;
end
else begin
Buffer[d] := Buffer;
Dec(d);
end;
if d < -1 then raise Exception.Create('chunk decompression error (underflow)');
end;
Inc(Size, Cnt);
end;
end;
procedure TL2Decompressor.Decompress(Src, Dst: TStream);
// decompress all chunks from sourcestream (src) to destinationstream (dst)
var
Chunk: TChunk;
OutSize: Integer;
TotalSize: Integer;
Buffer: TBuffer;
F: TFileStream;
IsLast: Boolean;
Sig: TSignature;
DecompressedSize: UInt32;
begin
// read signature
Src.ReadBuffer(Sig, 4);
if Sig.Chars <> 'GSCM' then raise Exception.Create('L2 decompression read signature error');
// read size
Src.ReadBuffer(DecompressedSize, 4);
if DecompressedSize > MegaByte then raise Exception.Create('decompress error: decompressed size too large'); // don't know limit
TotalSize := 0;
IsLast := False;
while not IsLast do begin
Chunk.LoadFromStream(Src, IsLast); // read 1 compressed chunk
OutSize := 0;
Chunk.DecompressChunk(Buffer, OutSize); // decompress 1 chunk
Inc(TotalSize, OutSize);
Dst.WriteBuffer(Buffer[0], OutSize);
end;
if TotalSize <> DecompressedSize then raise Exception.Create('Decompress OutSize mismatch');
end;
end.