Files
node-metaverse/lib/classes/BinaryReader.ts
2025-01-17 23:53:31 +00:00

443 lines
9.3 KiB
TypeScript

import { UUID } from "./UUID";
export class BinaryReader
{
public pos = 0;
public constructor(private readonly buf: Buffer)
{
}
public seek(pos: number): void
{
if (pos < 0 || pos > this.buf.length)
{
throw new RangeError(`Invalid seek position: ${pos}`);
}
this.pos = pos;
}
public getPos(): number
{
return this.pos;
}
public peekUInt8(): number
{
this.checkBounds(1);
return this.buf.readUInt8(this.pos);
}
public peekInt8(): number
{
this.checkBounds(1);
return this.buf.readInt8(this.pos);
}
public peekUInt16LE(): number
{
this.checkBounds(2);
return this.buf.readUInt16LE(this.pos);
}
public peekInt16LE(): number
{
this.checkBounds(2);
return this.buf.readInt16LE(this.pos);
}
public peekUInt16BE(): number
{
this.checkBounds(2);
return this.buf.readUInt16BE(this.pos);
}
public peekInt16BE(): number
{
this.checkBounds(2);
return this.buf.readInt16BE(this.pos);
}
public peekUInt32LE(): number
{
this.checkBounds(4);
return this.buf.readUInt32LE(this.pos);
}
public peekInt32LE(): number
{
this.checkBounds(4);
return this.buf.readInt32LE(this.pos);
}
public peekUInt32BE(): number
{
this.checkBounds(4);
return this.buf.readUInt32BE(this.pos);
}
public peekInt32BE(): number
{
this.checkBounds(4);
return this.buf.readInt32BE(this.pos);
}
public peekUInt64LE(): bigint
{
this.checkBounds(8);
return this.buf.readBigUInt64LE(this.pos);
}
public peekInt64LE(): bigint
{
this.checkBounds(8);
return this.buf.readBigInt64LE(this.pos);
}
public peekUInt64BE(): bigint
{
this.checkBounds(8);
return this.buf.readBigUInt64BE(this.pos);
}
public peekInt64BE(): bigint
{
this.checkBounds(8);
return this.buf.readBigInt64BE(this.pos);
}
public peekFloatLE(): number
{
this.checkBounds(4);
return this.buf.readFloatLE(this.pos);
}
public peekFloatBE(): number
{
this.checkBounds(4);
return this.buf.readFloatBE(this.pos);
}
public peekDoubleLE(): number
{
this.checkBounds(8);
return this.buf.readDoubleLE(this.pos);
}
public peekDoubleBE(): number
{
this.checkBounds(8);
return this.buf.readDoubleBE(this.pos);
}
public peekUUID(): UUID
{
this.checkBounds(16);
return new UUID(this.buf, this.pos);
}
public peekDate(): Date
{
this.checkBounds(8);
return new Date(Number(this.peekUInt64LE()));
}
public peekBuffer(length: number): Buffer
{
if (this.pos + length > this.buf.length)
{
throw new RangeError("Attempt to read beyond buffer length");
}
return this.buf.subarray(this.pos, this.pos + length);
}
public peekVarInt(): { value: number | bigint; bytesRead: number }
{
let value = 0n;
let bytesRead = 0;
let shift = 0n;
while (this.pos + bytesRead < this.buf.length)
{
const byte = this.buf[this.pos + bytesRead];
bytesRead++;
const byteValue = BigInt(byte & 0x7F);
value |= (byteValue << shift);
if ((byte & 0x80) === 0)
{
break;
}
shift += 7n;
if (bytesRead > 100)
{
throw new Error('VarInt is too long');
}
}
if (bytesRead === 0 || (this.pos + bytesRead) > this.buf.length)
{
throw new Error('Incomplete VarInt');
}
const decoded = (value >> 1n) ^ -(value & 1n);
if (
decoded >= BigInt(Number.MIN_SAFE_INTEGER) &&
decoded <= BigInt(Number.MAX_SAFE_INTEGER)
)
{
return {value: Number(decoded), bytesRead};
}
else
{
return {value: decoded, bytesRead};
}
}
public peekString(metadata?: { length: number | bigint, bytesRead: number }): string
{
const {value: length, bytesRead} = this.peekVarInt();
if (length < 0)
{
throw new Error('Error reading string: Length is negative');
}
this.checkBounds(bytesRead + Number(length));
const start = this.pos + bytesRead;
const end = start + Number(length);
const stringData = this.buf.subarray(start, end);
if (metadata)
{
metadata.length = length;
metadata.bytesRead = bytesRead;
}
return new TextDecoder("utf-8").decode(stringData);
}
public peekCString(): string
{
let tempPos = this.pos;
const start = tempPos;
while (tempPos < this.buf.length && this.buf[tempPos] !== 0)
{
tempPos++;
}
if (tempPos >= this.buf.length)
{
throw new RangeError("Null-terminated string not found");
}
const stringData = this.buf.subarray(start, tempPos);
return new TextDecoder("utf-8").decode(stringData);
}
public peekFixedString(length: number): string
{
this.checkBounds(length);
return this.buf.subarray(this.pos, this.pos + length).toString('utf-8');
}
// read
public readUInt8(): number
{
const num = this.peekUInt8();
this.pos++;
return num;
}
public readInt8(): number
{
const num = this.peekInt8();
this.pos++;
return num;
}
public readUInt16LE(): number
{
const num = this.peekUInt16LE();
this.pos += 2;
return num;
}
public readInt16LE(): number
{
const num = this.peekInt16LE();
this.pos += 2;
return num;
}
public readUInt16BE(): number
{
const num = this.peekUInt16BE();
this.pos += 2;
return num;
}
public readInt16BE(): number
{
const num = this.peekInt16BE();
this.pos += 2;
return num;
}
public readUInt32LE(): number
{
const num = this.peekUInt32LE();
this.pos += 4;
return num;
}
public readInt32LE(): number
{
const num = this.peekInt32LE();
this.pos += 4;
return num;
}
public readUInt32BE(): number
{
const num = this.peekUInt32BE();
this.pos += 4;
return num;
}
public readInt32BE(): number
{
const num = this.peekInt32BE();
this.pos += 4;
return num;
}
public readUInt64LE(): bigint
{
const num = this.peekUInt64LE();
this.pos += 8;
return num;
}
public readInt64LE(): bigint
{
const num = this.peekInt64LE();
this.pos += 8;
return num;
}
public readUInt64BE(): bigint
{
const num = this.peekUInt64BE();
this.pos += 8;
return num;
}
public readInt64BE(): bigint
{
const num = this.peekInt64BE();
this.pos += 8;
return num;
}
public readFloatLE(): number
{
const num = this.peekFloatLE();
this.pos += 4;
return num;
}
public readFloatBE(): number
{
const num = this.peekFloatBE();
this.pos += 4;
return num;
}
public readDoubleLE(): number
{
const num = this.peekDoubleLE();
this.pos += 8;
return num;
}
public readDoubleBE(): number
{
const num = this.peekDoubleBE();
this.pos += 8;
return num;
}
public readUUID(): UUID
{
const uuid = this.peekUUID();
this.pos += 16;
return uuid;
}
public readDate(): Date
{
const d = this.peekDate();
this.pos += 8;
return d;
}
public readBuffer(length: number): Buffer
{
const buffer = this.peekBuffer(length);
this.pos += length;
return buffer;
}
public readCString(): string
{
const str = this.peekCString();
this.pos += Buffer.byteLength(str, "utf-8") + 1; // Include null terminator
return str;
}
public readString(): string
{
const md: {
length: number | bigint,
bytesRead: number
} = {
length: 0,
bytesRead: 0
}
const str = this.peekString(md);
this.pos += md.bytesRead + Number(md.length);
return str;
}
public readFixedString(length: number): string
{
const str = this.peekFixedString(length);
this.pos += length;
return str;
}
public length(): number
{
return this.buf.length;
}
public readVarInt(): number | bigint
{
const {value, bytesRead} = this.peekVarInt();
this.pos += bytesRead;
return value;
}
private checkBounds(length: number): void
{
if (this.pos + length > this.buf.length)
{
throw new RangeError(`Attempt to read beyond buffer length: position=${this.pos}, length=${length}, bufferSize=${this.buf.length}`);
}
}
}