274 lines
7.8 KiB
Ucode
274 lines
7.8 KiB
Ucode
/**
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
class BitStreamReader extends Object;
|
|
|
|
// Array of bytes to read from
|
|
var private array<byte> 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<byte> bitMask;
|
|
var private const array<byte> bitMaskBefore;
|
|
var private const array<byte> 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<byte> 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
|
|
} |