Upload via GUI (39 Dateien)
This commit is contained in:
+174
@@ -0,0 +1,174 @@
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* nbt.js – minimaler NBT-Leser und -Schreiber
|
||||
* --------------------------------------------
|
||||
* Nur so viel, wie servers.dat braucht. Diese Datei ist unkomprimiertes NBT
|
||||
* mit einem Wurzel-Compound, darin eine Liste "servers" aus Compounds mit
|
||||
* name, ip, icon und acceptTextures.
|
||||
*
|
||||
* Bewusst kein Fremdpaket: Der benötigte Ausschnitt ist klein und gut
|
||||
* prüfbar, eine Abhängigkeit für zwei Datentypen wäre unverhältnismäßig.
|
||||
*
|
||||
* Alle Zahlen liegen in NBT als Big-Endian vor, Zeichenketten als
|
||||
* modifiziertes UTF-8 mit vorangestellter Länge (2 Byte).
|
||||
*/
|
||||
|
||||
const TAG = {
|
||||
END: 0, BYTE: 1, SHORT: 2, INT: 3, LONG: 4, FLOAT: 5, DOUBLE: 6,
|
||||
BYTE_ARRAY: 7, STRING: 8, LIST: 9, COMPOUND: 10, INT_ARRAY: 11, LONG_ARRAY: 12,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lesen
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class Leser {
|
||||
constructor(buf) { this.buf = buf; this.pos = 0; }
|
||||
pruefe(n) { if (this.pos + n > this.buf.length) throw new Error('NBT endet unerwartet'); }
|
||||
byte() { this.pruefe(1); return this.buf.readInt8(this.pos++); }
|
||||
ubyte() { this.pruefe(1); return this.buf.readUInt8(this.pos++); }
|
||||
short() { this.pruefe(2); const v = this.buf.readInt16BE(this.pos); this.pos += 2; return v; }
|
||||
ushort() { this.pruefe(2); const v = this.buf.readUInt16BE(this.pos); this.pos += 2; return v; }
|
||||
int() { this.pruefe(4); const v = this.buf.readInt32BE(this.pos); this.pos += 4; return v; }
|
||||
long() { this.pruefe(8); const v = this.buf.readBigInt64BE(this.pos); this.pos += 8; return v; }
|
||||
float() { this.pruefe(4); const v = this.buf.readFloatBE(this.pos); this.pos += 4; return v; }
|
||||
double() { this.pruefe(8); const v = this.buf.readDoubleBE(this.pos); this.pos += 8; return v; }
|
||||
text() {
|
||||
const laenge = this.ushort();
|
||||
this.pruefe(laenge);
|
||||
const s = this.buf.toString('utf8', this.pos, this.pos + laenge);
|
||||
this.pos += laenge;
|
||||
return s;
|
||||
}
|
||||
bytes(n) { this.pruefe(n); const b = this.buf.subarray(this.pos, this.pos + n); this.pos += n; return Buffer.from(b); }
|
||||
}
|
||||
|
||||
function leseWert(l, typ) {
|
||||
switch (typ) {
|
||||
case TAG.BYTE: return l.byte();
|
||||
case TAG.SHORT: return l.short();
|
||||
case TAG.INT: return l.int();
|
||||
case TAG.LONG: return l.long();
|
||||
case TAG.FLOAT: return l.float();
|
||||
case TAG.DOUBLE: return l.double();
|
||||
case TAG.STRING: return l.text();
|
||||
case TAG.BYTE_ARRAY: return l.bytes(l.int());
|
||||
case TAG.INT_ARRAY: { const n = l.int(); const a = []; for (let i = 0; i < n; i++) a.push(l.int()); return a; }
|
||||
case TAG.LONG_ARRAY: { const n = l.int(); const a = []; for (let i = 0; i < n; i++) a.push(l.long()); return a; }
|
||||
case TAG.LIST: {
|
||||
const innerTyp = l.ubyte();
|
||||
const n = l.int();
|
||||
const a = [];
|
||||
for (let i = 0; i < n; i++) a.push(leseWert(l, innerTyp));
|
||||
a.__typ = innerTyp; // Typ merken, damit Schreiben ihn wiederherstellt
|
||||
return a;
|
||||
}
|
||||
case TAG.COMPOUND: {
|
||||
const o = {};
|
||||
o.__typen = {};
|
||||
for (;;) {
|
||||
const t = l.ubyte();
|
||||
if (t === TAG.END) break;
|
||||
const name = l.text();
|
||||
o.__typen[name] = t;
|
||||
o[name] = leseWert(l, t);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
default: throw new Error('Unbekannter NBT-Typ: ' + typ);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Liest unkomprimiertes NBT.
|
||||
* -> { name, wert }
|
||||
*/
|
||||
function lese(buf) {
|
||||
const l = new Leser(buf);
|
||||
const typ = l.ubyte();
|
||||
if (typ !== TAG.COMPOUND) throw new Error('NBT beginnt nicht mit einem Compound');
|
||||
const name = l.text();
|
||||
return { name, wert: leseWert(l, TAG.COMPOUND) };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Schreiben
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class Schreiber {
|
||||
constructor() { this.teile = []; }
|
||||
byte(v) { const b = Buffer.alloc(1); b.writeInt8(v, 0); this.teile.push(b); }
|
||||
ubyte(v) { const b = Buffer.alloc(1); b.writeUInt8(v, 0); this.teile.push(b); }
|
||||
short(v) { const b = Buffer.alloc(2); b.writeInt16BE(v, 0); this.teile.push(b); }
|
||||
int(v) { const b = Buffer.alloc(4); b.writeInt32BE(v, 0); this.teile.push(b); }
|
||||
long(v) { const b = Buffer.alloc(8); b.writeBigInt64BE(BigInt(v), 0); this.teile.push(b); }
|
||||
float(v) { const b = Buffer.alloc(4); b.writeFloatBE(v, 0); this.teile.push(b); }
|
||||
double(v) { const b = Buffer.alloc(8); b.writeDoubleBE(v, 0); this.teile.push(b); }
|
||||
text(s) {
|
||||
const d = Buffer.from(String(s), 'utf8');
|
||||
const l = Buffer.alloc(2); l.writeUInt16BE(d.length, 0);
|
||||
this.teile.push(l, d);
|
||||
}
|
||||
roh(b) { this.teile.push(b); }
|
||||
fertig() { return Buffer.concat(this.teile); }
|
||||
}
|
||||
|
||||
// Typ raten, wenn er nicht aus dem Einlesen bekannt ist
|
||||
function typVon(wert) {
|
||||
if (typeof wert === 'string') return TAG.STRING;
|
||||
if (typeof wert === 'bigint') return TAG.LONG;
|
||||
if (typeof wert === 'boolean') return TAG.BYTE;
|
||||
if (Buffer.isBuffer(wert)) return TAG.BYTE_ARRAY;
|
||||
if (Array.isArray(wert)) return TAG.LIST;
|
||||
if (wert && typeof wert === 'object') return TAG.COMPOUND;
|
||||
if (Number.isInteger(wert)) return TAG.INT;
|
||||
return TAG.DOUBLE;
|
||||
}
|
||||
|
||||
function schreibeWert(s, typ, wert) {
|
||||
switch (typ) {
|
||||
case TAG.BYTE: s.byte(wert ? Number(wert) : 0); break;
|
||||
case TAG.SHORT: s.short(Number(wert) || 0); break;
|
||||
case TAG.INT: s.int(Number(wert) || 0); break;
|
||||
case TAG.LONG: s.long(wert || 0); break;
|
||||
case TAG.FLOAT: s.float(Number(wert) || 0); break;
|
||||
case TAG.DOUBLE: s.double(Number(wert) || 0); break;
|
||||
case TAG.STRING: s.text(wert == null ? '' : wert); break;
|
||||
case TAG.BYTE_ARRAY: { const b = Buffer.isBuffer(wert) ? wert : Buffer.alloc(0); s.int(b.length); s.roh(b); break; }
|
||||
case TAG.INT_ARRAY: { s.int(wert.length); for (const v of wert) s.int(v); break; }
|
||||
case TAG.LONG_ARRAY: { s.int(wert.length); for (const v of wert) s.long(v); break; }
|
||||
case TAG.LIST: {
|
||||
const liste = wert || [];
|
||||
// leere Listen bekommen END als Typ – so macht es Minecraft auch
|
||||
const innerTyp = liste.__typ || (liste.length ? typVon(liste[0]) : TAG.END);
|
||||
s.ubyte(innerTyp);
|
||||
s.int(liste.length);
|
||||
for (const v of liste) schreibeWert(s, innerTyp, v);
|
||||
break;
|
||||
}
|
||||
case TAG.COMPOUND: {
|
||||
const typen = (wert && wert.__typen) || {};
|
||||
for (const schluessel of Object.keys(wert || {})) {
|
||||
if (schluessel === '__typen' || schluessel === '__typ') continue;
|
||||
const t = typen[schluessel] || typVon(wert[schluessel]);
|
||||
s.ubyte(t);
|
||||
s.text(schluessel);
|
||||
schreibeWert(s, t, wert[schluessel]);
|
||||
}
|
||||
s.ubyte(TAG.END);
|
||||
break;
|
||||
}
|
||||
default: throw new Error('Unbekannter NBT-Typ beim Schreiben: ' + typ);
|
||||
}
|
||||
}
|
||||
|
||||
function schreibe(name, wert) {
|
||||
const s = new Schreiber();
|
||||
s.ubyte(TAG.COMPOUND);
|
||||
s.text(name || '');
|
||||
schreibeWert(s, TAG.COMPOUND, wert);
|
||||
return s.fertig();
|
||||
}
|
||||
|
||||
module.exports = { lese, schreibe, TAG };
|
||||
Reference in New Issue
Block a user