Files
AeroMc-Launcher/agent/src/de/aeromc/capes/Design.java
T
2026-10-07 07:32:07 +00:00

142 lines
6.1 KiB
Java
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.
package de.aeromc.capes;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.function.Function;
/**
* AeroMC-Look im Spiel (Minecraft 1.14 … 26.x):
*
* – Ladebildschirm: AeroMC-Nachtblau statt Mojang-Rot, hellblauer Balken und
* AeroMC-Logo statt Mojang-Logo. Das Logo lädt Minecraft am Ressourcenpaket
* vorbei direkt aus dem Spiel – deshalb tauscht der Agent den Datenstrom aus.
* – Fenster: Titel "AeroMC - Minecraft …" und AeroMC-Symbol.
*
* Titel-Logo, Untertitel, Sprüche und Knöpfe kommen über ein Ressourcenpaket,
* das der Launcher anlegt (src/spieldesign.js).
*
* Die umgebauten Stellen im Spiel holen sich ihre Helfer – wie beim Umhang –
* über die System-Properties und nur über JDK-Typen (java.util.function.Function),
* damit es mit jedem Classloader klappt (Vanilla, Fabric, Forge, NeoForge).
*/
final class Design {
static final String LOGO = "aeromc.design.logo";
static final String LOGO_ALT = "aeromc.design.logoAlt";
static final String TITEL = "aeromc.design.titel";
static final String ICON = "aeromc.design.icon";
static final String MODS = "aeromc.mods";
/** Hintergrund des Ladebildschirms (wie das AeroMC-Design im Launcher). */
static final int HINTERGRUND = 0x0A0E1A;
/** Fortschrittsbalken. */
static final int BALKEN = 0x4FD2FF;
/** Breite des Balkens als Anteil der Bildschirmbreite (das Logo ist jetzt bildschirmbreit). */
static final float BALKEN_ANTEIL = 0.56f;
private static final int[] ICON_GROESSEN = {16, 32, 48, 64, 128, 256};
/** Pixelspeicher der Symbole festhalten, bis GLFW sie übernommen hat. */
private static final List<ByteBuffer> FESTHALTEN = new ArrayList<>();
private Design() {}
static void installiere(String ordner, Instrumentation inst) {
File dir = new File(ordner);
Properties p = System.getProperties();
p.put(LOGO, strom(new File(dir, "ladelogo.png")));
p.put(LOGO_ALT, strom(new File(dir, "ladelogo_alt.png")));
p.put(TITEL, (Function<Object, Object>) Design::titel);
p.put(ICON, (Function<Object, Object>) bilder -> symbol(dir, bilder));
inst.addTransformer(new DesignUmbau());
System.out.println("[AeroMC] Spieldesign aktiv");
}
/**
* Mod-Schalter: Klick auf „Mods verwalten“ im Pausemenü (Adresse aeromc://mods)
* meldet sich beim Launcher, der dann das Mod-Fenster über dem Spiel öffnet.
* Rückgabe TRUE = erledigt (Minecraft öffnet keinen Link), sonst FALSE.
*/
static void installiereMods(String adresse, Instrumentation inst) {
System.getProperties().put(MODS, (Function<Object, Object>) link -> {
if (link == null || !link.toString().startsWith("aeromc:")) return Boolean.FALSE;
Thread t = new Thread(() -> {
try {
java.net.HttpURLConnection c = (java.net.HttpURLConnection) new java.net.URL(adresse).openConnection();
c.setConnectTimeout(2000);
c.setReadTimeout(4000);
c.getResponseCode();
c.disconnect();
} catch (Throwable e) {
System.out.println("[AeroMC] Launcher fuer den Mod-Schalter nicht erreichbar: " + e);
}
}, "AeroMC-Mod-Schalter");
t.setDaemon(true);
t.start();
return Boolean.TRUE;
});
inst.addTransformer(new ModsUmbau());
System.out.println("[AeroMC] Mod-Schalter aktiv");
}
/** Ersetzt den Datenstrom des Mojang-Logos durch das AeroMC-Logo (bei Fehlern bleibt das Original). */
private static Function<Object, Object> strom(File datei) {
return original -> {
try {
return new ByteArrayInputStream(Files.readAllBytes(datei.toPath()));
} catch (Throwable t) {
System.out.println("[AeroMC] Ladelogo nicht lesbar: " + t);
return original;
}
};
}
private static Object titel(Object original) {
if (original == null) return null;
String s = original.toString();
return s.startsWith("AeroMC") ? original : "AeroMC - " + s;
}
/**
* Baut aus den mitgelieferten Rohdaten (icon_N.rgba) einen neuen
* GLFWImage.Buffer. LWJGL wird über den Classloader des übergebenen Buffers
* angesprochen – der Agent selbst kennt LWJGL nicht.
*/
private static Object symbol(File dir, Object original) {
if (original == null) return null;
try {
List<int[]> groessen = new ArrayList<>();
List<byte[]> daten = new ArrayList<>();
for (int n : ICON_GROESSEN) {
File f = new File(dir, "icon_" + n + ".rgba");
if (!f.isFile()) continue;
byte[] b = Files.readAllBytes(f.toPath());
if (b.length != n * n * 4) continue;
groessen.add(new int[] {n, n});
daten.add(b);
}
if (daten.isEmpty()) return original;
ClassLoader cl = original.getClass().getClassLoader();
Class<?> bild = Class.forName("org.lwjgl.glfw.GLFWImage", true, cl);
Object puffer = bild.getMethod("malloc", int.class).invoke(null, daten.size());
Method hole = puffer.getClass().getMethod("get", int.class);
Method setze = bild.getMethod("set", int.class, int.class, ByteBuffer.class);
for (int i = 0; i < daten.size(); i++) {
ByteBuffer px = ByteBuffer.allocateDirect(daten.get(i).length);
px.put(daten.get(i)).flip();
synchronized (FESTHALTEN) { FESTHALTEN.add(px); }
setze.invoke(hole.invoke(puffer, i), groessen.get(i)[0], groessen.get(i)[1], px);
}
return puffer;
} catch (Throwable t) {
System.out.println("[AeroMC] Fenstersymbol nicht gesetzt: " + t);
return original;
}
}
}