CacheReader Helpers

The primitives every decoder in this library is built out of: big-endian integer reads that advance a shared offset, the cache’s variable width number encodings, and the colour and shift conversions its formats rely on.


Read N Bits

function Read8Bits(constref data: TByteArray; var offset: Integer): Integer;
function Read16Bits(constref data: TByteArray; var offset: Integer): Integer;
function Read24Bits(constref data: TByteArray; var offset: Integer): Integer;
function Read32Bits(constref data: TByteArray; var offset: Integer): Integer;
function Read64Bits(constref data: TByteArray; var offset: Integer): Int64;

Reads the next N amount of bits from data as an Integer.

offset is the position where we want to start reading from data and gets permanently modified so future reads continue reading from where the integer that was read finished.

All reads are big-endian (most significant byte first) and offset always advances by the number of bytes consumed (N/8), landing exactly on the first byte of whatever comes next.

For example, imagine the following data represented in hexadecimal and that our starting offset is 3:

Data:   4A F3 02 C1 01 06 FF AB 2A C2 DF D7 66
Offset:          ^^

If we use Read16Bits the following happens:

Data:   4A F3 02 C1 01 06 FF AB 2A C2 DF D7 66
Offset:          ¯¯ ¯¯ ^^

Our result will be $C101 (49409 in decimal), and offset will now be 5. Now right after we use Read32Bits:

Data:   4A F3 02 C1 01 06 FF AB 2A C2 DF D7 66
Offset:                ¯¯ ¯¯ ¯¯ ¯¯ ^^

The result will be $06FFAB2A (117370154 in decimal) and our offsetwill now be at 9.


Read16SignedBits

function Read16SignedBits(constref data: TByteArray; var offset: Integer): Integer;

Reads the next 16 bits from data the same way Read16Bits does, but reinterprets the result as a signed two’s complement value instead of an unsigned one.

offset behaves identically to the other ReadNBits functions: it advances by 2 bytes, landing on the first byte of whatever comes next.

Unsigned 16-bit values range from 0 to 65535. Signed 16-bit values represent that same range of bit patterns as -32768 to 32767 instead, by treating any value > 32767 as negative: subtracting 65536 from it recovers the correctly signed result.

For example, the raw bytes $FFFF read as an unsigned Read16Bits give 65535, but read as Read16SignedBits give -1.


Read8SignedBits

function Read8SignedBits(constref data: TByteArray; var offset: Integer): Integer;

Reads the next 8 bits from data the same way Read8Bits does, but reinterprets the result as a signed two’s complement value instead of an unsigned one.

Unsigned 8-bit values range from 0 to 255. Signed 8-bit values represent that same range of bit patterns as -128 to 127 instead, by treating any value > 127 as negative: subtracting 256 from it recovers the correctly signed result.


ReadBigSmart

function ReadBigSmart(constref data: TByteArray; var offset: Integer): Integer;

Reads an id that may or may not fit in 16 bits.

The top bit of the first byte says which: clear means the id was written as 2 bytes, set means it was written as 4 with that flag bit masked back off. offset advances by whichever was read.


ReadBigSmart2

function ReadBigSmart2(constref data: TByteArray; var offset: Integer): Integer;

ReadBigSmart for fields that are allowed to be absent.

The 2 byte form reserves 32767 to mean “no id”, which is returned as -1. The 4 byte form has no such reservation and is read exactly as ReadBigSmart reads it.


ReadShortSmart

function ReadShortSmart(constref data: TByteArray; var offset: Integer): Integer;

Reads a signed value written in as few bytes as it needed.

The top bit of the first byte picks the width: clear is a single byte biased by -64, covering -64 to 63; set is two bytes biased by -$C000, covering -16384 to 16383.


ReadUnsignedShortSmart

function ReadUnsignedShortSmart(constref data: TByteArray; var offset: Integer): Integer;

ReadShortSmart without the bias, so the value is never negative.

A single byte covers 0 to 127 and two bytes, with the top bit stripped, cover 0 to 32767.


ReadUnsignedIntSmartShortCompat

function ReadUnsignedIntSmartShortCompat(constref data: TByteArray; var offset: Integer): Integer;

Reads a value that can exceed what ReadUnsignedShortSmart alone can express.

32767 is a continuation marker rather than a value: every one of them adds 32767 to the running total and another smart is read, until one comes back that is not the marker.


ReadVarInt2

function ReadVarInt2(constref data: TByteArray; var offset: Integer): Integer;

Reads a value written as a run of 7-bit groups, least significant group first.

Each byte contributes its low 7 bits to the result, in order; a set top bit says another byte follows. This is how TDBRowDefinition.Create reads a row’s table id - unlike the other smart encodings above, the byte count is not capped at two.


TByteArray.ReadCString

function TByteArray.ReadCString(var offset: Integer): String;

Reads the NUL terminated string starting at offset.

offset is left on the byte after the terminator, so a following read continues with whatever comes next. Running off the end of the array ends the string as a terminator would.


Char.ToHex

function Char.ToHex(): Integer;

The value of a single hexadecimal digit, in either case. Anything else reads as 0.


CacheString2Hash

function CacheString2Hash(const s: String): Integer;

The hash the cache stores in place of a name.

Archives and files are named by this hash rather than by text, so looking one up by name means hashing the name and comparing - see TReferenceTable.ArchiveID.


CacheString2UTF8

function CacheString2UTF8(const s: String): String;

Turns a string as the cache stores it, one byte per character, into UTF-8.


TColor.ToHSLEx

function TColor.ToHSLEx(): TColorHSLEx;

Splits a colour into hue, saturation and lightness.

The result carries each component twice: as the 0.0 to 1.0 doubles the conversion works in, and as the 0 to 255 integers the cache’s own colour packing expects. Ground colours need both - the doubles feed the hue multiplier that weights blending, the integers are packed straight into a palette index.

The channels are read back to front on purpose: the cache stores its colours as RGB while TColor is BGR, so the blue channel holds what this conversion treats as red.


ArithmeticShr

function ArithmeticShr(value, bits: Integer): Integer;

Right shift that keeps the sign of value.

Pascal’s shr is a logical shift, so shifting a negative number turns it into a large positive one. Where a value’s sign is meaningful - a surface normal pointing away from the light, say - this halves it the way the original client’s >> does.