Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e1fb616aa | ||
|
|
642d229d7e | ||
|
|
e782cc3058 |
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
Alle nennenswerten Änderungen an AeroMC werden hier festgehalten.
|
Alle nennenswerten Änderungen an AeroMC werden hier festgehalten.
|
||||||
|
|
||||||
|
## [1.1.8] - 2026-10-04
|
||||||
|
|
||||||
|
### Behoben
|
||||||
|
- Das Skin-Fenster und „Aktuellen Skin laden“ im Skin-Editor zeigten bei manchen Konten einen Standard-Skin (z. B. Efe) statt des eigenen Skins – angezeigt wird jetzt der Skin, den andere Spieler im Spiel sehen; direkt nach einer eigenen Skin-Änderung noch kurz der neue Stand aus dem Konto, bis Mojang ihn überall verteilt hat
|
||||||
|
|
||||||
## [1.1.7] - 2026-10-03
|
## [1.1.7] - 2026-10-03
|
||||||
|
|
||||||
### Neu
|
### Neu
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
Ein Minecraft-Launcher für Windows mit getrennten Instanzen, Mod-Unterstützung, Spielaufzeichnung, Freundesystem und Microsoft-Anmeldung.
|
Ein Minecraft-Launcher für Windows mit getrennten Instanzen, Mod-Unterstützung, Spielaufzeichnung, Freundesystem und Microsoft-Anmeldung.
|
||||||
|
|
||||||
Aktueller Stand: Version 1.1.7
|
Aktueller Stand: Version 1.1.8
|
||||||
|
|
||||||
<img src="docs/aeromc.png" alt="AeroMC – Instanzliste mit Seitenleiste und Skin-Vorschau" width="820" />
|
<img src="docs/aeromc.png" alt="AeroMC – Instanzliste mit Seitenleiste und Skin-Vorschau" width="820" />
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
},
|
},
|
||||||
"projects": {
|
"projects": {
|
||||||
"C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj": {
|
"C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj": {
|
||||||
"version": "0.0.0",
|
"version": "1.1.8",
|
||||||
"restore": {
|
"restore": {
|
||||||
"projectUniqueName": "C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj",
|
"projectUniqueName": "C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj",
|
||||||
"projectName": "AeroMC-Setup",
|
"projectName": "AeroMC-Setup",
|
||||||
|
|||||||
Binary file not shown.
@@ -14,11 +14,11 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
|
||||||
[assembly: System.Reflection.AssemblyCopyrightAttribute("Copyright (c) M_Viper. Alle Rechte vorbehalten.")]
|
[assembly: System.Reflection.AssemblyCopyrightAttribute("Copyright (c) M_Viper. Alle Rechte vorbehalten.")]
|
||||||
[assembly: System.Reflection.AssemblyDescriptionAttribute("Installiert AeroMC")]
|
[assembly: System.Reflection.AssemblyDescriptionAttribute("Installiert AeroMC")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.1.7.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.1.8.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.1.7")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.1.8")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("AeroMC Setup")]
|
[assembly: System.Reflection.AssemblyProductAttribute("AeroMC Setup")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("AeroMC Setup")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("AeroMC Setup")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.1.7.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.1.8.0")]
|
||||||
[assembly: System.Resources.NeutralResourcesLanguageAttribute("de-DE")]
|
[assembly: System.Resources.NeutralResourcesLanguageAttribute("de-DE")]
|
||||||
|
|
||||||
// Von der MSBuild WriteCodeFragment-Klasse generiert.
|
// Von der MSBuild WriteCodeFragment-Klasse generiert.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
acf80df582ccd59574a6332950a42ff0628e88f4288eb303a60e8577b37c7bef
|
820d60fa6c1c1080954d3f18089a7c45daf7f39440b819f670e61aded52999a5
|
||||||
|
|||||||
@@ -418,7 +418,7 @@
|
|||||||
"C:\\Users\\Viper\\.nuget\\packages\\": {}
|
"C:\\Users\\Viper\\.nuget\\packages\\": {}
|
||||||
},
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"version": "0.0.0",
|
"version": "1.1.8",
|
||||||
"restore": {
|
"restore": {
|
||||||
"projectUniqueName": "C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj",
|
"projectUniqueName": "C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj",
|
||||||
"projectName": "AeroMC-Setup",
|
"projectName": "AeroMC-Setup",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"dgSpecHash": "FnRoda2hVew=",
|
"dgSpecHash": "UguGrVny76E=",
|
||||||
"success": true,
|
"success": true,
|
||||||
"projectFilePath": "C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj",
|
"projectFilePath": "C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj",
|
||||||
"expectedPackageFiles": [
|
"expectedPackageFiles": [
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "aeromc",
|
"name": "aeromc",
|
||||||
"version": "1.1.7",
|
"version": "1.1.8",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "aeromc",
|
"name": "aeromc",
|
||||||
"version": "1.1.7",
|
"version": "1.1.8",
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xhayper/discord-rpc": "^1.3.4",
|
"@xhayper/discord-rpc": "^1.3.4",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "aeromc",
|
"name": "aeromc",
|
||||||
"productName": "AeroMC",
|
"productName": "AeroMC",
|
||||||
"version": "1.1.7",
|
"version": "1.1.8",
|
||||||
"description": "AeroMC – Instanz-basierter Minecraft-Launcher (à la MultiMC/Prism)",
|
"description": "AeroMC – Instanz-basierter Minecraft-Launcher (à la MultiMC/Prism)",
|
||||||
"homepage": "https://m-viper.de",
|
"homepage": "https://m-viper.de",
|
||||||
"launcherWebsite": "https://aeromc.viper.ipv64.net",
|
"launcherWebsite": "https://aeromc.viper.ipv64.net",
|
||||||
|
|||||||
+20
-5
@@ -1788,10 +1788,25 @@ ipcMain.handle('skin:modelFor', async (_e, uuid) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Zeitpunkt der letzten eigenen Skin-Änderung: danach hinkt der Sitzungsserver
|
||||||
|
// (= was im Spiel zu sehen ist) noch kurz hinterher
|
||||||
|
let letzterSkinWechsel = 0;
|
||||||
|
const SKIN_NACHLAUF_MS = 3 * 60 * 1000;
|
||||||
|
|
||||||
|
// Kontoprofil + der Skin, den andere im Spiel sehen (siehe skins.waehleAnzeigeSkin)
|
||||||
|
async function profilMitSpielSkin(token) {
|
||||||
|
const p = await skins.holeProfil(token);
|
||||||
|
if (!p.ok) return p;
|
||||||
|
const kuerzlich = Date.now() - letzterSkinWechsel < SKIN_NACHLAUF_MS;
|
||||||
|
let spiel = null;
|
||||||
|
if (!kuerzlich) { try { spiel = await skins.holeSpielTexturen(p.id); } catch { /* dann eben das Kontoprofil */ } }
|
||||||
|
return skins.waehleAnzeigeSkin(p, spiel, kuerzlich);
|
||||||
|
}
|
||||||
|
|
||||||
ipcMain.handle('skin:profile', async () => {
|
ipcMain.handle('skin:profile', async () => {
|
||||||
const token = await aktivesToken();
|
const token = await aktivesToken();
|
||||||
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
|
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
|
||||||
try { return await skins.holeProfil(token); }
|
try { return await profilMitSpielSkin(token); }
|
||||||
catch (err) { return { ok: false, message: 'Profil konnte nicht geladen werden: ' + err.message }; }
|
catch (err) { return { ok: false, message: 'Profil konnte nicht geladen werden: ' + err.message }; }
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1819,7 +1834,7 @@ ipcMain.handle('skin:apply', async (_e, dateiPfad, variante) => {
|
|||||||
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
|
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
|
||||||
try {
|
try {
|
||||||
const res = await skins.setzeSkin(token, dateiPfad, variante);
|
const res = await skins.setzeSkin(token, dateiPfad, variante);
|
||||||
if (res && res.ok) { armformCache.clear(); broadcast('skin:changed'); }
|
if (res && res.ok) { letzterSkinWechsel = Date.now(); armformCache.clear(); broadcast('skin:changed'); }
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
catch (err) { return { ok: false, message: 'Skin konnte nicht gesetzt werden: ' + err.message }; }
|
catch (err) { return { ok: false, message: 'Skin konnte nicht gesetzt werden: ' + err.message }; }
|
||||||
@@ -1830,7 +1845,7 @@ ipcMain.handle('skin:reset', async () => {
|
|||||||
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
|
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
|
||||||
try {
|
try {
|
||||||
const res = await skins.setzeSkinZurueck(token);
|
const res = await skins.setzeSkinZurueck(token);
|
||||||
if (res && res.ok) { armformCache.clear(); broadcast('skin:changed'); }
|
if (res && res.ok) { letzterSkinWechsel = Date.now(); armformCache.clear(); broadcast('skin:changed'); }
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
catch (err) { return { ok: false, message: 'Zurücksetzen fehlgeschlagen: ' + err.message }; }
|
catch (err) { return { ok: false, message: 'Zurücksetzen fehlgeschlagen: ' + err.message }; }
|
||||||
@@ -1852,7 +1867,7 @@ ipcMain.handle('skin:currentData', async () => {
|
|||||||
const token = await aktivesToken();
|
const token = await aktivesToken();
|
||||||
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
|
if (!token) return { ok: false, message: 'Kein angemeldetes Konto.' };
|
||||||
try {
|
try {
|
||||||
const p = await skins.holeProfil(token);
|
const p = await profilMitSpielSkin(token);
|
||||||
if (!p.ok) return p;
|
if (!p.ok) return p;
|
||||||
if (!p.skinUrl) return { ok: false, standard: true, variante: p.variante, message: 'Das Konto nutzt den Standard-Skin.' };
|
if (!p.skinUrl) return { ok: false, standard: true, variante: p.variante, message: 'Das Konto nutzt den Standard-Skin.' };
|
||||||
const res = await fetch(p.skinUrl);
|
const res = await fetch(p.skinUrl);
|
||||||
@@ -2063,7 +2078,7 @@ ipcMain.handle('skin:applyData', async (_e, base64, variante) => {
|
|||||||
try {
|
try {
|
||||||
fs.writeFileSync(tmp, daten);
|
fs.writeFileSync(tmp, daten);
|
||||||
const res = await skins.setzeSkin(token, tmp, variante);
|
const res = await skins.setzeSkin(token, tmp, variante);
|
||||||
if (res && res.ok) { armformCache.clear(); broadcast('skin:changed', { quelle: 'editor' }); }
|
if (res && res.ok) { letzterSkinWechsel = Date.now(); armformCache.clear(); broadcast('skin:changed', { quelle: 'editor' }); }
|
||||||
return res;
|
return res;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { ok: false, message: 'Skin konnte nicht gesetzt werden: ' + err.message };
|
return { ok: false, message: 'Skin konnte nicht gesetzt werden: ' + err.message };
|
||||||
|
|||||||
+36
-1
@@ -141,4 +141,39 @@ async function setzeUmhang(accessToken, capeId) {
|
|||||||
return { ok: true };
|
return { ok: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { setzeSkin, setzeSkinZurueck, holeProfil, pruefeDatei, aufHttps, setzeUmhang };
|
/*
|
||||||
|
* Texturen, die andere Spieler im Spiel sehen (öffentliches Profil beim
|
||||||
|
* Sitzungsserver). Das kann vom Kontoprofil oben abweichen: Dort liefert
|
||||||
|
* Mojang bei manchen Konten einen Standard-Skin (z. B. Efe) als "ACTIVE",
|
||||||
|
* obwohl im Spiel längst ein eigener Skin getragen wird.
|
||||||
|
*/
|
||||||
|
async function holeSpielTexturen(uuid) {
|
||||||
|
const id = String(uuid || '').replace(/-/g, '').toLowerCase();
|
||||||
|
if (!/^[0-9a-f]{32}$/.test(id)) return null;
|
||||||
|
const res = await fetch('https://sessionserver.mojang.com/session/minecraft/profile/' + id, {
|
||||||
|
headers: { 'User-Agent': UA, Accept: 'application/json' },
|
||||||
|
});
|
||||||
|
if (!res.ok) return null;
|
||||||
|
const profil = await res.json();
|
||||||
|
const eigenschaft = (profil.properties || []).find((p) => p.name === 'textures');
|
||||||
|
if (!eigenschaft) return null;
|
||||||
|
const texturen = JSON.parse(Buffer.from(eigenschaft.value, 'base64').toString('utf8')).textures || {};
|
||||||
|
const skin = texturen.SKIN;
|
||||||
|
return {
|
||||||
|
skinUrl: skin && skin.url ? aufHttps(skin.url) : '',
|
||||||
|
variante: skin && skin.metadata && skin.metadata.model === 'slim' ? 'slim' : 'classic',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Welcher Skin wird als "aktuell" gezeigt? Grundsätzlich der aus dem Spiel –
|
||||||
|
* nur direkt nach einer eigenen Änderung das Kontoprofil, weil der
|
||||||
|
* Sitzungsserver dann noch kurz den alten Stand liefert.
|
||||||
|
*/
|
||||||
|
function waehleAnzeigeSkin(kontoProfil, spiel, kuerzlichGeaendert) {
|
||||||
|
if (!kontoProfil || !kontoProfil.ok || kuerzlichGeaendert) return kontoProfil;
|
||||||
|
if (!spiel || !spiel.skinUrl || spiel.skinUrl === kontoProfil.skinUrl) return kontoProfil;
|
||||||
|
return Object.assign({}, kontoProfil, { skinUrl: spiel.skinUrl, variante: spiel.variante, skinAusSpiel: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { setzeSkin, setzeSkinZurueck, holeProfil, pruefeDatei, aufHttps, setzeUmhang, holeSpielTexturen, waehleAnzeigeSkin };
|
||||||
|
|||||||
Reference in New Issue
Block a user