# 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 ```pascal 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 `offset`will now be at `9`. - - - ## Read16SignedBits ```pascal function Read16SignedBits(constref data: TByteArray; var offset: Integer): Integer; ``` Reads the next 16 bits from `data` the same way {ref}`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 ```pascal function Read8SignedBits(constref data: TByteArray; var offset: Integer): Integer; ``` Reads the next 8 bits from `data` the same way {ref}`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 ```pascal 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 ```pascal function ReadBigSmart2(constref data: TByteArray; var offset: Integer): Integer; ``` {ref}`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 ```pascal 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 ```pascal function ReadUnsignedShortSmart(constref data: TByteArray; var offset: Integer): Integer; ``` {ref}`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 ```pascal function ReadUnsignedIntSmartShortCompat(constref data: TByteArray; var offset: Integer): Integer; ``` Reads a value that can exceed what {ref}`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 ```pascal 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 {ref}`TDBRowDefinition.Create` reads a row's table id - unlike the other smart encodings above, the byte count is not capped at two. - - - ## TByteArray.ReadCString ```pascal 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 ```pascal function Char.ToHex(): Integer; ``` The value of a single hexadecimal digit, in either case. Anything else reads as `0`. - - - ## CacheString2Hash ```pascal 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 {ref}`TReferenceTable.ArchiveID`. - - - ## CacheString2UTF8 ```pascal function CacheString2UTF8(const s: String): String; ``` Turns a string as the cache stores it, one byte per character, into UTF-8. - - - ## TColor.ToHSLEx ```pascal 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 {ref}`TColor` is BGR, so the blue channel holds what this conversion treats as red. - - - ## ArithmeticShr ```pascal 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.