Files
AeroMc-Launcher/src/servers.js
T

164 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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 };