142 lines
6.1 KiB
Java
142 lines
6.1 KiB
Java
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;
|
||
}
|
||
}
|
||
}
|