Upload via GUI (39 Dateien)

This commit is contained in:
Git Manager GUI
2026-08-02 17:01:18 +02:00
parent a803f297ea
commit eff6403dd5
26 changed files with 3536 additions and 134 deletions
+174
View File
@@ -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 };