/** * Class for packaging various data types into an array of bytes. * Copyright 2020 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * * Acedia is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, or * (at your option) any later version. * * Acedia is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Acedia. If not, see . */ class BitStreamReader extends Object; // Array of bytes to read from var private array stream; var private int currentBytePointer; var private int currentBitPointer; // Write `byte` from `stream[currentBytePointer]` to avoid unnecessary // array access calls. var private byte currentByte; var private const array bitMask; var private const array bitMaskBefore; var private const array bitMaskAfter; var private int tempInt; var private string tempString; var private byte temp; var private byte result; var private byte rightBoundary; var private byte remainingToRead; var private byte byte1, byte2, byte3, byte4; public final function Initialize(array newStream) { stream = newStream; currentBytePointer = 0; currentBitPointer = 0; if (stream.length > 0) { currentByte = stream[0]; } } private final function ShiftBytePointer() { currentBitPointer = 0; currentBytePointer += 1; if (currentBytePointer < stream.length) { currentByte = stream[currentBytePointer]; } else { currentByte = 0; } } public final function byte ReadBit() { if (currentBytePointer >= stream.length) return 0; if ((currentByte & bitMask[currentBitPointer]) > 0) { currentBitPointer += 1; if (currentBitPointer >= 8) { ShiftBytePointer(); } return 1; } currentBitPointer += 1; if (currentBitPointer >= 8) { ShiftBytePointer(); } return 0; } public final function bool ReadBool() { if (currentBytePointer >= stream.length) return false; if ((currentByte & bitMask[currentBitPointer]) > 0) { currentBitPointer += 1; if (currentBitPointer >= 8) { ShiftBytePointer(); } return true; } currentBitPointer += 1; if (currentBitPointer >= 8) { ShiftBytePointer(); } return false; } public final function byte ReadByte(optional int bitLimit) { if (currentBytePointer >= stream.length) return 0; // Default `bitLimit` if (bitLimit <= 0 || bitLimit > 8) { bitLimit = 8; } // Throw away already read part result = currentByte & bitMaskAfter[currentBitPointer]; if (bitLimit <= 8 - currentBitPointer) { // If all we are asked to read is contained in cuurent `byte` - // just throw away excessive info by shifting to the right result = result >>> (8 - currentBitPointer - bitLimit); currentBitPointer += bitLimit; if (currentBitPointer >= 8) { ShiftBytePointer(); } return result; } // Otherwise shift already read part up rightBoundary = bitLimit - (8 - currentBitPointer); result = result << rightBoundary; // Move to the next byte ShiftBytePointer(); currentBitPointer = rightBoundary; // Record required part from the second byte temp = currentByte & bitMaskBefore[rightBoundary]; temp = temp >>> (8 - rightBoundary); result = result | temp; return result; } public final function int ReadInt(optional int bitLimit) { // Default `bitLimit` if (bitLimit <= 0 || bitLimit > 32) { bitLimit = 32; } byte1 = ReadByte(bitLimit); if (bitLimit <= 8) { return byte1; } bitLimit -= 8; byte2 = ReadByte(bitLimit); if (bitLimit <= 8) { tempInt = byte1 << bitLimit; tempInt = tempInt | byte2; return tempInt; } bitLimit -= 8; byte3 = ReadByte(bitLimit); if (bitLimit <= 8) { tempInt = byte1 << (bitLimit + 8); tempInt = tempInt | (byte2 << bitLimit); tempInt = tempInt | byte3; return tempInt; } bitLimit -= 8; byte4 = ReadByte(bitLimit); tempInt = byte1 << (bitLimit + 16); tempInt = tempInt | (byte2 << (bitLimit + 8)); tempInt = tempInt | (byte3 << bitLimit); tempInt = tempInt | byte4; return tempInt; } public final function float ReadFloat( int precisionLevel, optional byte bitLimit) { if (bitLimit < 0 || bitLimit > 32) { bitLimit = 32; } precisionLevel = Max(0, precisionLevel); return float(ReadInt(bitLimit)) * (0.1 ** precisionLevel); } public final function string ReadString(int length) { if (length <= 0) return ""; while (length > 0) { length -= 1; tempString $= Chr(ReadByte()); } return tempString; } // String representation of `class`es has a more limited character range, // which allows us to fit every chgaracter in `class`' name into 6 bits, // saving 25% space. // Allowed characters are: digits, upper and lower case letters, dot '.' // and underscore '_'. // Any other symbol is converted into an underscore. private final function byte CompressClassCharacter(byte source) { // 26 upper case character if (source >= 65 && source <= 90) { return source - 65; } // 26 lower case character // 52 total if (source >= 97 && source <= 122) { return 26 + (source - 97); } // 10 digits // 62 total if (source >= 48 && source <= 57) { return 52 + (source - 48); } // dot if (source == 46) { return 62; } // underscore and everything else return 63; } private final function byte DecompressClassCharacter(byte source) { if (source >= 63) return 95; // 26 upper case character if (source <= 25) { return source + 65; } // 26 lower case character if (source <= 51) { return source + 71; } // 10 digits if (source <= 61) { return source - 4; } // dot // if (source == 62) return 46; } public final function string ReadClassName(int length) { if (length <= 0) return ""; while (length > 0) { length -= 1; tempString $= Chr(DecompressClassCharacter(ReadByte(6))); } return tempString; } defaultproperties { bitMask(0) = 128 // 1 0 0 0 0 0 0 0 bitMask(1) = 64 // 0 1 0 0 0 0 0 0 bitMask(2) = 32 // 0 0 1 0 0 0 0 0 bitMask(3) = 16 // 0 0 0 1 0 0 0 0 bitMask(4) = 8 // 0 0 0 0 1 0 0 0 bitMask(5) = 4 // 0 0 0 0 0 1 0 0 bitMask(6) = 2 // 0 0 0 0 0 0 1 0 bitMask(7) = 1 // 0 0 0 0 0 0 0 1 bitMaskBefore(0) = 128 // 1 0 0 0 0 0 0 0 bitMaskBefore(1) = 192 // 1 1 0 0 0 0 0 0 bitMaskBefore(2) = 224 // 1 1 1 0 0 0 0 0 bitMaskBefore(3) = 240 // 1 1 1 1 0 0 0 0 bitMaskBefore(4) = 248 // 1 1 1 1 1 0 0 0 bitMaskBefore(5) = 252 // 1 1 1 1 1 1 0 0 bitMaskBefore(6) = 254 // 1 1 1 1 1 1 1 0 bitMaskBefore(7) = 255 // 1 1 1 1 1 1 1 1 bitMaskAfter(0) = 255 // 1 1 1 1 1 1 1 1 bitMaskAfter(1) = 127 // 0 1 1 1 1 1 1 1 bitMaskAfter(2) = 63 // 0 0 1 1 1 1 1 1 bitMaskAfter(3) = 31 // 0 0 0 1 1 1 1 1 bitMaskAfter(4) = 15 // 0 0 0 0 1 1 1 1 bitMaskAfter(5) = 7 // 0 0 0 0 0 1 1 1 bitMaskAfter(6) = 3 // 0 0 0 0 0 0 1 1 bitMaskAfter(7) = 1 // 0 0 0 0 0 0 0 1 }