164 lines
5.7 KiB
JavaScript
164 lines
5.7 KiB
JavaScript
'use strict';
|
||
|
||
/*
|
||
* servers.js – Serverliste einer Instanz (servers.dat)
|
||
* -----------------------------------------------------
|
||
* Minecraft legt die Liste der gespeicherten Server als unkomprimiertes NBT
|
||
* unter <spielordner>/servers.dat ab. Aufbau:
|
||
*
|
||
* Compound "" Wurzel
|
||
* List "servers" Liste von Compounds
|
||
* String "name" Anzeigename
|
||
* String "ip" Adresse (optional mit :Port)
|
||
* String "icon" Base64-PNG des Serversymbols (optional)
|
||
* Byte "acceptTextures" Ressourcenpakete annehmen (optional)
|
||
*
|
||
* Beim Schreiben wird die vorhandene Datei zuerst gesichert – eine kaputte
|
||
* servers.dat würde alle gespeicherten Server kosten.
|
||
*/
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const nbt = require('./nbt');
|
||
|
||
function datei(gameDir) {
|
||
return path.join(gameDir, 'servers.dat');
|
||
}
|
||
|
||
/*
|
||
* Liest die Serverliste.
|
||
* -> { ok, server: [{ name, ip, icon, acceptTextures }] }
|
||
*/
|
||
function lies(gameDir) {
|
||
const p = datei(gameDir);
|
||
if (!fs.existsSync(p)) return { ok: true, server: [], leer: true };
|
||
let roh;
|
||
try { roh = fs.readFileSync(p); } catch (err) { return { ok: false, message: err.message }; }
|
||
if (!roh.length) return { ok: true, server: [], leer: true };
|
||
|
||
try {
|
||
const { wert } = nbt.lese(roh);
|
||
const liste = Array.isArray(wert.servers) ? wert.servers : [];
|
||
return {
|
||
ok: true,
|
||
server: liste.map((s) => ({
|
||
name: String(s.name || ''),
|
||
ip: String(s.ip || ''),
|
||
icon: typeof s.icon === 'string' ? s.icon : '',
|
||
acceptTextures: Number(s.acceptTextures || 0),
|
||
})),
|
||
};
|
||
} catch (err) {
|
||
return { ok: false, message: 'servers.dat konnte nicht gelesen werden: ' + err.message };
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Schreibt die Serverliste zurück.
|
||
* Die alte Datei wird vorher als servers.dat.bak gesichert.
|
||
*/
|
||
function schreibe(gameDir, server) {
|
||
const p = datei(gameDir);
|
||
try {
|
||
fs.mkdirSync(gameDir, { recursive: true });
|
||
|
||
const eintraege = (server || []).map((s) => {
|
||
const c = {
|
||
__typen: { name: nbt.TAG.STRING, ip: nbt.TAG.STRING },
|
||
name: String(s.name || ''),
|
||
ip: String(s.ip || ''),
|
||
};
|
||
// icon und acceptTextures nur mitschreiben, wenn vorhanden –
|
||
// so wie Minecraft es selbst tut
|
||
if (s.icon) { c.__typen.icon = nbt.TAG.STRING; c.icon = String(s.icon); }
|
||
if (s.acceptTextures) { c.__typen.acceptTextures = nbt.TAG.BYTE; c.acceptTextures = Number(s.acceptTextures); }
|
||
return c;
|
||
});
|
||
|
||
const liste = eintraege;
|
||
liste.__typ = nbt.TAG.COMPOUND;
|
||
const wurzel = { __typen: { servers: nbt.TAG.LIST }, servers: liste };
|
||
const daten = nbt.schreibe('', wurzel);
|
||
|
||
// erst sichern, dann über eine Zwischendatei ersetzen
|
||
if (fs.existsSync(p)) {
|
||
try { fs.copyFileSync(p, p + '.bak'); } catch { /* Sicherung ist Kür */ }
|
||
}
|
||
const tmp = p + '.neu';
|
||
fs.writeFileSync(tmp, daten);
|
||
fs.renameSync(tmp, p);
|
||
return { ok: true, anzahl: eintraege.length };
|
||
} catch (err) {
|
||
return { ok: false, message: 'servers.dat konnte nicht geschrieben werden: ' + err.message };
|
||
}
|
||
}
|
||
|
||
// Adresse normalisieren und grob prüfen
|
||
function pruefeAdresse(ip) {
|
||
const s = String(ip || '').trim();
|
||
if (!s) return { ok: false, message: 'Bitte eine Serveradresse angeben.' };
|
||
if (/\s/.test(s)) return { ok: false, message: 'Die Adresse darf keine Leerzeichen enthalten.' };
|
||
const m = s.match(/^([^:]+)(?::(\d+))?$/);
|
||
if (!m) return { ok: false, message: 'Die Adresse sieht nicht wie ein Server aus.' };
|
||
if (m[2] && (Number(m[2]) < 1 || Number(m[2]) > 65535)) {
|
||
return { ok: false, message: 'Der Port muss zwischen 1 und 65535 liegen.' };
|
||
}
|
||
return { ok: true, ip: s };
|
||
}
|
||
|
||
function fuegeHinzu(gameDir, eintrag) {
|
||
const geprueft = pruefeAdresse(eintrag && eintrag.ip);
|
||
if (!geprueft.ok) return geprueft;
|
||
|
||
const gelesen = lies(gameDir);
|
||
if (!gelesen.ok) return gelesen;
|
||
const liste = gelesen.server;
|
||
// dieselbe Adresse nicht doppelt aufnehmen
|
||
if (liste.some((s) => s.ip.toLowerCase() === geprueft.ip.toLowerCase())) {
|
||
return { ok: false, message: 'Dieser Server steht bereits in der Liste.' };
|
||
}
|
||
liste.push({
|
||
name: String((eintrag && eintrag.name) || geprueft.ip).trim() || geprueft.ip,
|
||
ip: geprueft.ip,
|
||
icon: '',
|
||
acceptTextures: 0,
|
||
});
|
||
return schreibe(gameDir, liste);
|
||
}
|
||
|
||
function entferne(gameDir, ip) {
|
||
const gelesen = lies(gameDir);
|
||
if (!gelesen.ok) return gelesen;
|
||
const vorher = gelesen.server.length;
|
||
const liste = gelesen.server.filter((s) => s.ip.toLowerCase() !== String(ip || '').toLowerCase());
|
||
if (liste.length === vorher) return { ok: false, message: 'Server nicht gefunden.' };
|
||
return schreibe(gameDir, liste);
|
||
}
|
||
|
||
/*
|
||
* Nur den Anzeigenamen ändern – die Adresse bleibt gleich.
|
||
* ip identifiziert den Eintrag (wie beim Entfernen).
|
||
*/
|
||
function benenneUm(gameDir, ip, neuerName) {
|
||
const name = String(neuerName || '').trim();
|
||
if (!name) return { ok: false, message: 'Bitte einen Namen angeben.' };
|
||
const gelesen = lies(gameDir);
|
||
if (!gelesen.ok) return gelesen;
|
||
const liste = gelesen.server;
|
||
const treffer = liste.find((s) => s.ip.toLowerCase() === String(ip || '').toLowerCase());
|
||
if (!treffer) return { ok: false, message: 'Server nicht gefunden.' };
|
||
if (treffer.name === name) return { ok: true, unveraendert: true };
|
||
treffer.name = name;
|
||
return schreibe(gameDir, liste);
|
||
}
|
||
|
||
/*
|
||
* Einen Server in eine andere Instanz übertragen – der eigentliche Zweck:
|
||
* denselben Server nicht in jeder Instanz neu eintippen zu müssen.
|
||
*/
|
||
function uebertrage(zielGameDir, eintrag) {
|
||
return fuegeHinzu(zielGameDir, eintrag);
|
||
}
|
||
|
||
module.exports = { lies, schreibe, fuegeHinzu, entferne, benenneUm, uebertrage, pruefeAdresse, datei };
|