Upload via GUI (138 Dateien)

This commit is contained in:
2026-10-03 14:20:38 +00:00
parent e83c951256
commit 972a3e73da
13 changed files with 3889 additions and 2615 deletions
+171
View File
@@ -1864,6 +1864,176 @@ ipcMain.handle('skin:currentData', async () => {
}
});
// ---------- Umhänge (offiziell von Mojang + eigene AeroMC-Umhänge) ----------
// Bilder immer als data:-Adresse in die Oberfläche (fremde Server schicken
// keine CORS-Kopfzeilen – die 3D-Ansicht dürfte sie sonst nicht laden)
async function bildAlsDataUri(url) {
const res = await fetch(url, { headers: { 'User-Agent': 'AeroMC-Launcher' } });
if (!res.ok) throw new Error('HTTP ' + res.status);
const daten = Buffer.from(await res.arrayBuffer());
if (daten.length > 6 * 1024 * 1024) throw new Error('Bild zu groß');
return 'data:image/png;base64,' + daten.toString('base64');
}
// PNG mit Seitenverhältnis 2:1 (64×32 … 1024×512) – wie der Server es verlangt
function umhangPngAusBase64(base64) {
const daten = Buffer.from(String(base64 || ''), 'base64');
if (daten.length < 33 || !daten.subarray(0, 8).equals(PNG_KENNUNG)) return null;
const breite = daten.readUInt32BE(16);
const hoehe = daten.readUInt32BE(20);
if (![64, 128, 256, 512, 1024].includes(breite) || breite !== 2 * hoehe) return null;
return daten;
}
// Umhang eines beliebigen Spielers fürs Anzeigen: eigener AeroMC-Umhang hat
// Vorrang, sonst der offizielle aus Mojangs Profil. 5 Minuten gemerkt.
const umhangCache = new Map(); // uuid -> { dataUri, quelle, zeit }
async function umhangFuer(uuid) {
const id = String(uuid || '').replace(/-/g, '').toLowerCase();
if (!/^[0-9a-f]{32}$/.test(id)) return null;
const gemerkt = umhangCache.get(id);
if (gemerkt && Date.now() - gemerkt.zeit < 5 * 60 * 1000) return gemerkt;
let ergebnis = { dataUri: null, quelle: null, zeit: Date.now() };
try {
const aero = await friends.getCapes([id]);
const eintrag = aero.ok && aero.capes && aero.capes[id];
if (eintrag && eintrag.url) ergebnis = { dataUri: await bildAlsDataUri(eintrag.url), quelle: 'aeromc', zeit: Date.now() };
} catch { /* weiter mit Mojang */ }
if (!ergebnis.dataUri) {
try {
const res = await fetch('https://sessionserver.mojang.com/session/minecraft/profile/' + id, { headers: { 'User-Agent': 'AeroMC-Launcher' } });
if (res.ok) {
const profil = await res.json();
const eig = (profil.properties || []).find((p) => p.name === 'textures');
const tex = eig ? JSON.parse(Buffer.from(eig.value, 'base64').toString('utf8')) : {};
const cape = tex.textures && tex.textures.CAPE;
if (cape && cape.url) ergebnis = { dataUri: await bildAlsDataUri(skins.aufHttps(cape.url)), quelle: 'mojang', zeit: Date.now() };
}
} catch { /* kein Umhang */ }
}
umhangCache.set(id, ergebnis);
return ergebnis;
}
ipcMain.handle('cape:forUuid', async (_e, uuid) => {
const r = await umhangFuer(uuid);
return r && r.dataUri ? { dataUri: r.dataUri, quelle: r.quelle } : null;
});
// Übersicht fürs Skin-Fenster: offizielle Umhänge + eigener AeroMC-Umhang
ipcMain.handle('cape:info', async () => {
const konto = aktivesFreundeKonto();
const token = await aktivesToken();
if (!konto || !token) return { ok: false, message: 'Kein angemeldetes Konto.' };
const ergebnis = { ok: true, name: konto.username, offiziell: [], aeromc: null, aeromcFehler: '' };
try {
const p = await skins.holeProfil(token);
if (p.ok) ergebnis.offiziell = p.capes || [];
} catch { /* ohne offizielle Liste weiter */ }
const aero = await friends.getCapes([konto.uuid]);
if (aero.ok) ergebnis.aeromc = aero.capes[String(konto.uuid).replace(/-/g, '').toLowerCase()] || null;
else { ergebnis.aeromcFehler = aero.message; ergebnis.aeromcFehlt = !!aero.fehlt; }
return ergebnis;
});
ipcMain.handle('cape:setOfficial', async (_e, capeId) => {
const token = await aktivesToken();
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
try {
const res = await skins.setzeUmhang(token, capeId || null);
if (res.ok) { umhangCache.clear(); broadcast('skin:changed', { quelle: 'umhang' }); }
return res;
} catch (err) { return { ok: false, message: 'Umhang konnte nicht gesetzt werden: ' + err.message }; }
});
// Der Umhang-Agent muss als echte Datei vorliegen (Java liest nicht aus app.asar):
// einmal nach userData kopieren, Dateiname mit Größe, damit ein Update ihn ersetzt.
// Rückgabe: "pfad=serveradresse" für -javaagent, oder '' wenn es nicht klappt.
function umhangAgent() {
try {
const quelle = path.join(__dirname, 'assets', 'agent', 'aeromc-capes.jar');
const groesse = fs.statSync(quelle).size;
const ordner = path.join(app.getPath('userData'), 'agent');
const ziel = path.join(ordner, `aeromc-capes-${groesse}.jar`);
if (!fs.existsSync(ziel) || fs.statSync(ziel).size !== groesse) {
fs.mkdirSync(ordner, { recursive: true });
fs.writeFileSync(ziel, fs.readFileSync(quelle));
for (const alt of fs.readdirSync(ordner)) {
if (alt !== path.basename(ziel) && /^aeromc-capes-\d+\.jar$/.test(alt)) { try { fs.unlinkSync(path.join(ordner, alt)); } catch { /* noch in Benutzung */ } }
}
}
return `${ziel}=${friends.BASIS_URL}`;
} catch (err) {
console.error('Umhang-Agent nicht verfügbar:', err.message);
return '';
}
}
// verständliche Meldung, wenn der Server das neue Plugin noch nicht hat
function umhangFehler(res) {
if (res && res.code === 'rest_no_route') return { ok: false, message: 'Der Server kennt noch keine Umhänge (Plugin „AeroMC Freunde“ 1.5.0 nötig).' };
return res;
}
ipcMain.handle('cape:upload', async (_e, base64) => {
const konto = aktivesFreundeKonto();
if (!konto) return { ok: false, message: 'Kein angemeldetes Konto.' };
if (!umhangPngAusBase64(base64)) return { ok: false, message: 'Ungültiger Umhang (PNG mit 64×32 bis 1024×512 Pixeln nötig).' };
const res = await friends.uploadCape(konto.uuid, base64);
if (res.ok) { umhangCache.clear(); broadcast('skin:changed', { quelle: 'umhang' }); }
return res.ok ? { ok: true, cape: res.data && res.data.cape } : umhangFehler(res);
});
ipcMain.handle('cape:remove', async () => {
const konto = aktivesFreundeKonto();
if (!konto) return { ok: false, message: 'Kein angemeldetes Konto.' };
const res = await friends.removeCape(konto.uuid);
if (res.ok) { umhangCache.clear(); broadcast('skin:changed', { quelle: 'umhang' }); }
return res.ok ? res : umhangFehler(res);
});
// für den Editor: eigener Umhang, sonst der getragene offizielle
ipcMain.handle('cape:currentData', async () => {
const konto = aktivesFreundeKonto();
if (!konto) return { ok: false, message: 'Kein angemeldetes Konto.' };
umhangCache.delete(String(konto.uuid).replace(/-/g, '').toLowerCase());
const r = await umhangFuer(konto.uuid);
if (!r || !r.dataUri) return { ok: false, keiner: true, message: 'Dein Konto hat noch keinen Umhang.' };
return { ok: true, dataUri: r.dataUri, quelle: r.quelle };
});
// Bild wählen: art 'umhang' = fertige Umhang-Datei (PNG), 'bild' = beliebiges Motiv zum Einsetzen
ipcMain.handle('cape:chooseFile', async (e, art) => {
const motiv = art === 'bild';
const res = await dialog.showOpenDialog(BrowserWindow.fromWebContents(e.sender) || mainWindow, {
title: motiv ? 'Bild für den Umhang wählen' : 'Umhang-Datei öffnen',
properties: ['openFile'],
filters: motiv
? [{ name: 'Bilder', extensions: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp'] }]
: [{ name: 'PNG-Bild', extensions: ['png'] }],
});
if (res.canceled || !res.filePaths.length) return { ok: false, canceled: true };
const pfad = res.filePaths[0];
try {
const st = fs.statSync(pfad);
if (st.size > 15 * 1024 * 1024) return { ok: false, message: 'Die Datei ist zu groß (über 15 MB).' };
const endung = path.extname(pfad).slice(1).toLowerCase().replace('jpg', 'jpeg');
return { ok: true, pfad, dataUri: `data:image/${endung || 'png'};base64,` + fs.readFileSync(pfad).toString('base64') };
} catch (err) { return { ok: false, message: 'Datei nicht lesbar: ' + err.message }; }
});
ipcMain.handle('cape:saveData', async (e, base64) => {
const daten = umhangPngAusBase64(base64);
if (!daten) return { ok: false, message: 'Ungültige Umhang-Daten.' };
const res = await dialog.showSaveDialog(BrowserWindow.fromWebContents(e.sender) || mainWindow, {
title: 'Umhang speichern',
defaultPath: path.join(app.getPath('pictures'), 'umhang.png'),
filters: [{ name: 'PNG-Bild', extensions: ['png'] }],
});
if (res.canceled || !res.filePath) return { ok: false, canceled: true };
try { fs.writeFileSync(res.filePath, daten); return { ok: true, pfad: res.filePath }; }
catch (err) { return { ok: false, message: 'Speichern fehlgeschlagen: ' + err.message }; }
});
ipcMain.handle('skin:readData', (_e, dateiPfad) => {
const geprueft = skins.pruefeDatei(dateiPfad);
if (!geprueft.ok) return geprueft;
@@ -3602,6 +3772,7 @@ ipcMain.handle('instances:launch', async (_e, id, accountId, launchOpts) => {
if (settings.autoModUpdates) modUpdates = await modUpdatesVorDemStart(id).catch(() => null);
const opts = {
javaAgent: settings.aeromcCapes === false ? '' : umhangAgent(),
versionId: inst.minecraft.version,
loader: inst.minecraft.loader || 'vanilla',
loaderVersion: inst.minecraft.loaderVersion || '',