Skip to content

Instantly share code, notes, and snippets.

@Sascha-T
Last active July 26, 2023 13:52
Show Gist options
  • Save Sascha-T/d864211e6c72674cfff0cd5d73b9deb0 to your computer and use it in GitHub Desktop.
Save Sascha-T/d864211e6c72674cfff0cd5d73b9deb0 to your computer and use it in GitHub Desktop.
quirky NBT library in typescript for browser and node
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar
14 rue de Plaisance, 75014 Paris, France
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
// Authored by Sascha T.
// Under WTFPL
// = One-file NBT library
/* No warranty under any circumstances
Especially in case of:
- a fire
- sudden death
- summoning of a demon
- any other act of God/Allah/Satan/Shiva/(your deity of choice)/quantum mechanics
*/
export namespace NBT {
export interface ByteReader {
read(): number;
}
export interface ByteWriter {
write(a: number): void;
}
abstract class Tag {
abstract write(write: ByteWriter): void;
abstract read(read: ByteReader): void;
abstract type(): number;
}
export type ByteView = ByteReader & ByteWriter;
export function viewFromByteArray(array: Uint8Array): ByteView {
let idx = 0;
return {
write(a: number) {
array[idx++] = a;
},
read() {
return array[idx++];
}
}
}
export function viewFromArray(array: number[]): ByteView {
let idx = 0;
return {
write(a: number) {
if (idx < array.length)
array[idx++] = a;
else
array.push(a);
},
read() {
return array[idx++];
}
}
}
export namespace Tags {
export enum Types {
END = 0x00,
BYTE = 0x01,
SHORT = 0x02,
INT = 0x03,
LONG = 0x04,
FLOAT = 0x05,
DOUBLE = 0x06,
BYTE_ARRAY = 0x07,
STRING = 0x08,
LIST = 0x09,
COMPOUND = 0x0A,
INT_ARRAY = 0x0B,
LONG_ARRAY = 0x0C
}
export class End implements Tag {
constructor() {
}
read(read: NBT.ByteReader): void {
}
write(write: NBT.ByteWriter): void {
}
type(): number {
return Types.END;
}
}
export class Byte implements Tag {
value: number;
constructor(value: number) {
this.value = value;
}
read(read: NBT.ByteReader): void {
this.value = read.read();
}
write(write: NBT.ByteWriter): void {
write.write(this.value);
}
type(): number {
return Types.BYTE;
}
}
export class Short implements Tag {
value: number;
constructor(value: number) {
this.value = value;
}
read(read: NBT.ByteReader): void {
this.value = (read.read() >> 8) | (read.read());
}
write(write: NBT.ByteWriter): void {
write.write((this.value & 0xFF00) >> 8);
write.write(this.value & 0xFF);
}
type(): number {
return Types.SHORT;
}
}
export class Int implements Tag {
value: number;
constructor(value: number) {
this.value = value;
}
read(read: NBT.ByteReader): void {
this.value = ((read.read() >> 24) | (read.read() >> 16) | (read.read() >> 8) | (read.read())) >>> 0;
}
write(write: NBT.ByteWriter): void {
write.write((this.value & 0xFF000000) >> 24);
write.write((this.value & 0xFF0000) >> 16);
write.write((this.value & 0xFF00) >> 8);
write.write(this.value & 0xFF);
}
type(): number {
return Types.INT;
}
}
export class Long implements Tag {
value: bigint;
constructor(value: bigint) {
this.value = value;
}
read(read: NBT.ByteReader): void {
this.value =
(BigInt(read.read()) << BigInt(56)) |
(BigInt(read.read()) << BigInt(48)) |
(BigInt(read.read()) << BigInt(40)) |
(BigInt(read.read()) << BigInt(32)) |
(BigInt(read.read()) << BigInt(24)) |
(BigInt(read.read()) << BigInt(16)) |
(BigInt(read.read()) << BigInt(8)) |
BigInt(read.read())
}
write(write: NBT.ByteWriter): void {
write.write(Number((this.value & BigInt("0xFF00000000000000")) >> BigInt(56)))
write.write(Number((this.value & BigInt("0xFF000000000000")) >> BigInt(48)))
write.write(Number((this.value & BigInt("0xFF0000000000")) >> BigInt(40)))
write.write(Number((this.value & BigInt("0xFF00000000")) >> BigInt(32)))
write.write(Number((this.value & BigInt("0xFF000000")) >> BigInt(24)))
write.write(Number((this.value & BigInt("0xFF0000")) >> BigInt(16)))
write.write(Number((this.value & BigInt("0xFF00")) >> BigInt(8)))
write.write(Number(this.value & BigInt("0xFF")))
}
type(): number {
return Types.LONG;
}
}
export class Float implements Tag {
value: number;
constructor(value: number) {
this.value = value;
}
read(read: NBT.ByteReader): void {
let value = ((read.read() >> 24) | (read.read() >> 16) | (read.read() >> 8) | (read.read())) >>> 0;
this.value = new Float32Array(new Uint32Array([value]).buffer)[0];
}
write(write: NBT.ByteWriter): void {
let value = new Uint32Array(new Float32Array([this.value]).buffer)[0]
write.write((value & 0xFF000000) >> 24);
write.write((value & 0xFF0000) >> 16);
write.write((value & 0xFF00) >> 8);
write.write(value & 0xFF);
}
type(): number {
return Types.FLOAT;
}
}
export class Double implements Tag {
value: number;
constructor(value: number) {
this.value = value;
}
read(read: NBT.ByteReader): void {
let value =
(BigInt(read.read()) << BigInt(56)) |
(BigInt(read.read()) << BigInt(48)) |
(BigInt(read.read()) << BigInt(40)) |
(BigInt(read.read()) << BigInt(32)) |
(BigInt(read.read()) << BigInt(24)) |
(BigInt(read.read()) << BigInt(16)) |
(BigInt(read.read()) << BigInt(8)) |
BigInt(read.read())
this.value = new Float64Array(new BigUint64Array([value]).buffer)[0];
}
write(write: NBT.ByteWriter): void {
let value = new BigUint64Array(new Float64Array([this.value]).buffer)[0];
write.write(Number((value & BigInt("0xFF00000000000000")) >> BigInt(56)))
write.write(Number((value & BigInt("0xFF000000000000")) >> BigInt(48)))
write.write(Number((value & BigInt("0xFF0000000000")) >> BigInt(40)))
write.write(Number((value & BigInt("0xFF00000000")) >> BigInt(32)))
write.write(Number((value & BigInt("0xFF000000")) >> BigInt(24)))
write.write(Number((value & BigInt("0xFF0000")) >> BigInt(16)))
write.write(Number((value & BigInt("0xFF00")) >> BigInt(8)))
write.write(Number(value & BigInt("0xFF")))
}
type(): number {
return Types.DOUBLE;
}
}
export class ByteArray implements Tag {
array: Uint8Array;
constructor(array: Uint8Array) {
this.array = array;
}
read(read: NBT.ByteReader): void {
let size = ((read.read() >> 24) | (read.read() >> 16) | (read.read() >> 8) | (read.read())) >>> 0;
this.array = new Uint8Array(size);
for (let i = 0; i < size; i++)
this.array[i] = read.read();
}
write(write: NBT.ByteWriter): void {
let size = this.array.length;
write.write((size & 0xFF000000) >> 24);
write.write((size & 0xFF0000) >> 16);
write.write((size & 0xFF00) >> 8);
write.write(size & 0xFF);
for (let i = 0; i < size; i++)
write.write(this.array[i]);
}
type(): number {
return Types.BYTE_ARRAY;
}
}
export class StringTag implements Tag {
string: string
constructor(str: string) {
this.string = str;
}
read(read: NBT.ByteReader): void {
let size = read.read() << 8 | read.read();
let chars = [];
for (let i = 0; i < size; i++)
chars.push(String.fromCharCode(read.read()))
this.string = chars.join();
}
write(write: NBT.ByteWriter): void {
write.write(this.string.length >> 8);
write.write(this.string.length & 0xFF)
for (let char of this.string)
write.write(char.charCodeAt(0))
}
type(): number {
return Types.STRING;
}
}
export class ListTag implements Tag {
type(): number {
return Types.LIST;
}
list: Tag[];
tagType: number;
constructor(list: Tag[], tagType: number) {
this.list = list;
this.tagType = tagType;
}
read(read: NBT.ByteReader): void {
this.tagType = read.read();
let length = ((read.read() >> 24) | (read.read() >> 16) | (read.read() >> 8) | (read.read())) >>> 0;
this.list = [];
for (let i = 0; i < length; i++)
this.list.push(readTagUnnamed(read, this.tagType));
}
write(write: NBT.ByteWriter): void {
write.write(this.tagType);
write.write((this.list.length & 0xFF000000) >> 24);
write.write((this.list.length & 0xFF0000) >> 16);
write.write((this.list.length & 0xFF00) >> 8);
write.write(this.list.length & 0xFF);
for (let i = 0; i < this.list.length; i++)
writeTag(write, this.list[i], undefined)
}
}
type CompoundMap = {[key: string]: Tag};
export class CompoundTag implements Tag {
map: CompoundMap = {};
constructor(map: CompoundMap) {
this.map = map;
}
read(read: NBT.ByteReader): void {
let run = true;
while(run) {
let [tag, name] = readTagNamed(read);
if(tag.type() === Types.END) {
run = false;
continue;
}
this.map[name] = tag;
}
}
type(): number {
return Types.COMPOUND;
}
write(write: NBT.ByteWriter): void {
for(let name in this.map)
writeTag(write, this.map[name], name);
}
}
export class IntArray implements Tag {
ints: number[]
constructor(ints: number[]) {
this.ints = ints;
}
read(read: NBT.ByteReader): void {
let length = ((read.read() >> 24) | (read.read() >> 16) | (read.read() >> 8) | (read.read())) >>> 0;
for (let i = 0; i < length; i++)
this.ints.push(((read.read() >> 24) | (read.read() >> 16) | (read.read() >> 8) | (read.read())) >>> 0)
}
type(): number {
return Types.INT_ARRAY;
}
write(write: NBT.ByteWriter): void {
write.write((this.ints.length & 0xFF000000) >> 24);
write.write((this.ints.length & 0xFF0000) >> 16);
write.write((this.ints.length & 0xFF00) >> 8);
write.write(this.ints.length & 0xFF);
for (let x = 0; x < this.ints.length; x++) {
let i = this.ints[x];
write.write((i & 0xFF000000) >> 24);
write.write((i & 0xFF0000) >> 16);
write.write((i & 0xFF00) >> 8);
write.write(i & 0xFF);
}
}
}
export class LongArray implements Tag {
ints: bigint[]
constructor(ints: bigint[]) {
this.ints = ints;
}
read(read: NBT.ByteReader): void {
let length = ((read.read() >> 24) | (read.read() >> 16) | (read.read() >> 8) | (read.read())) >>> 0;
for (let i = 0; i < length; i++)
this.ints.push((BigInt(read.read()) << BigInt(56)) |
(BigInt(read.read()) << BigInt(48)) |
(BigInt(read.read()) << BigInt(40)) |
(BigInt(read.read()) << BigInt(32)) |
(BigInt(read.read()) << BigInt(24)) |
(BigInt(read.read()) << BigInt(16)) |
(BigInt(read.read()) << BigInt(8)) |
BigInt(read.read()))
}
type(): number {
return Types.LONG_ARRAY;
}
write(write: NBT.ByteWriter): void {
write.write((this.ints.length & 0xFF000000) >> 24);
write.write((this.ints.length & 0xFF0000) >> 16);
write.write((this.ints.length & 0xFF00) >> 8);
write.write(this.ints.length & 0xFF);
for (let i = 0; i < this.ints.length; i++) {
let ivalue = this.ints[i];
write.write(Number((ivalue & BigInt("0xFF00000000000000")) >> BigInt(56)))
write.write(Number((ivalue & BigInt("0xFF000000000000")) >> BigInt(48)))
write.write(Number((ivalue & BigInt("0xFF0000000000")) >> BigInt(40)))
write.write(Number((ivalue & BigInt("0xFF00000000")) >> BigInt(32)))
write.write(Number((ivalue & BigInt("0xFF000000")) >> BigInt(24)))
write.write(Number((ivalue & BigInt("0xFF0000")) >> BigInt(16)))
write.write(Number((ivalue & BigInt("0xFF00")) >> BigInt(8)))
write.write(Number(ivalue & BigInt("0xFF")))
}
}
}
// @todo: Long array
let map: { [k: number]: () => Tag } = {
0x00: () => new End(),
0x01: () => new Byte(0),
0x02: () => new Short(0),
0x03: () => new Int(0),
0x04: () => new Long(BigInt(0)),
0x05: () => new Float(0),
0x06: () => new Double(0),
0x07: () => new ByteArray(new Uint8Array(0)),
0x08: () => new StringTag(""),
0x09: () => new ListTag([], 0),
0x0A: () => new CompoundTag({}),
0x0B: () => new IntArray([]),
0x0C: () => new LongArray([])
}
export function readTagUnnamed(reader: ByteReader, type: number): Tag {
let tag = map[type]();
tag.read(reader);
return tag;
}
export function readTagNamed(reader: ByteReader): [Tag, string] {
console.log("Reading tag...")
let value = reader.read();
let tag = map[value]();
console.log("\tTag type: " + value)
if(tag.type() === Types.END)
return [tag, ""];
let len = reader.read() << 8 | reader.read();
let name = ""
for (let i = 0; i < len; i++)
name += String.fromCharCode(reader.read())
console.log(`\tTag name: "${name}" (${len})`)
tag.read(reader);
return [tag, name]
}
export function writeTag(writer: ByteWriter, tag: Tag, named?: string) {
writer.write(tag.type());
if (named !== undefined) {
writer.write(named.length >> 8)
writer.write(named.length && 0xFF)
for (let char of named)
writer.write(char.charCodeAt(0))
}
tag.write(writer);
}
export function byte(num: number) {
return new Tags.Byte(num);
}
export function short(num: number) {
return new Tags.Short(num);
}
export function int(num: number) {
return new Tags.Int(num);
}
export function long(num: bigint) {
return new Tags.Long(num);
}
export function float(num: number) {
return new Float(num);
}
export function double(num: number) {
return new Double(num);
}
export function byteArray(data: Uint8Array) {
return new ByteArray(data);
}
export function string(data: string) {
return new StringTag(data);
}
export function list(list: Tag[]) {
return new ListTag(list, list.length > 0 ? list[0].type() : 0);
}
export function compound(cmp: CompoundMap) {
return new CompoundTag(cmp);
}
export function intArray(list: number[]) {
return new IntArray(list);
}
export function longArray(list: bigint[]) {
return new LongArray(list);
}
}
}
@Sascha-T
Copy link
Author

didn't test half of it so good luck ❤️❤️😍❤️😍❤️❤️😍😍❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment