Upload via GUI (194 Dateien)
This commit is contained in:
@@ -3,15 +3,22 @@ package de.aeromc.capes;
|
|||||||
import java.lang.instrument.Instrumentation;
|
import java.lang.instrument.Instrumentation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AeroMC-Umhang-Agent.
|
* AeroMC-Agent.
|
||||||
*
|
*
|
||||||
* Wird vom Launcher per -javaagent an Minecraft angehängt (jede Version, jeder
|
* Wird vom Launcher per -javaagent an Minecraft angehängt (jede Version, jeder
|
||||||
* Loader). Er klinkt sich in Mojangs authlib ein – über sie lädt Minecraft für
|
* Loader) und kann zwei Dinge, einzeln zuschaltbar:
|
||||||
* jeden Spieler Skin und Umhang – und setzt dort den AeroMC-Umhang des Spielers
|
|
||||||
* ein, falls er einen hat. Alles Weitere (Herunterladen, Zwischenspeichern,
|
|
||||||
* Anzeigen, auch HD) erledigt Minecraft selbst wie bei einem offiziellen Umhang.
|
|
||||||
*
|
*
|
||||||
* Argument: Basisadresse des Umhang-Servers (…/wp-json/aeromc/v1).
|
* Umhänge: klinkt sich in Mojangs authlib ein – über sie lädt Minecraft für
|
||||||
|
* jeden Spieler Skin und Umhang – und setzt dort den AeroMC-Umhang
|
||||||
|
* des Spielers ein, falls er einen hat. Alles Weitere (Herunterladen,
|
||||||
|
* Zwischenspeichern, Anzeigen, auch HD) erledigt Minecraft selbst.
|
||||||
|
* Spieldesign: AeroMC-Ladebildschirm, Fenstertitel und -symbol (siehe Design).
|
||||||
|
*
|
||||||
|
* Argument, Teile durch "|" getrennt:
|
||||||
|
* <Basisadresse des Umhang-Servers> (…/wp-json/aeromc/v1)
|
||||||
|
* capes=0 Umhänge aus
|
||||||
|
* design=<Ordner> Spieldesign an, Grafiken aus diesem Ordner
|
||||||
|
* Ein Argument nur mit Adresse (ältere Launcher) heißt: nur Umhänge.
|
||||||
*/
|
*/
|
||||||
public final class Agent {
|
public final class Agent {
|
||||||
/** Unter diesem Schlüssel liegt der Nachschlage-Dienst in den System-Properties. */
|
/** Unter diesem Schlüssel liegt der Nachschlage-Dienst in den System-Properties. */
|
||||||
@@ -28,8 +35,27 @@ public final class Agent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void starte(String argument, Instrumentation inst) {
|
private static void starte(String argument, Instrumentation inst) {
|
||||||
|
String basis = Nachschlagen.STANDARD_BASIS;
|
||||||
|
boolean umhaenge = true;
|
||||||
|
String design = null;
|
||||||
|
String mods = null;
|
||||||
|
if (argument != null) {
|
||||||
|
for (String teil : argument.split("\\|")) {
|
||||||
|
if (teil.startsWith("http")) basis = teil;
|
||||||
|
else if (teil.equals("capes=0")) umhaenge = false;
|
||||||
|
else if (teil.startsWith("design=") && teil.length() > 7) design = teil.substring(7);
|
||||||
|
else if (teil.startsWith("mods=") && teil.length() > 5) mods = teil.substring(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mods != null) {
|
||||||
|
try {
|
||||||
|
Design.installiereMods(mods, inst);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
System.out.println("[AeroMC] Mod-Schalter konnte nicht starten: " + t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (umhaenge) {
|
||||||
try {
|
try {
|
||||||
String basis = argument != null && argument.startsWith("http") ? argument : Nachschlagen.STANDARD_BASIS;
|
|
||||||
// Die eingefügten Befehle in authlib holen sich den Dienst von hier – nur
|
// Die eingefügten Befehle in authlib holen sich den Dienst von hier – nur
|
||||||
// über JDK-Typen, damit es mit jedem Classloader und Modulsystem klappt.
|
// über JDK-Typen, damit es mit jedem Classloader und Modulsystem klappt.
|
||||||
System.getProperties().put(SCHLUESSEL, new Nachschlagen(basis));
|
System.getProperties().put(SCHLUESSEL, new Nachschlagen(basis));
|
||||||
@@ -39,4 +65,12 @@ public final class Agent {
|
|||||||
System.out.println("[AeroMC] Umhang-Agent konnte nicht starten: " + t);
|
System.out.println("[AeroMC] Umhang-Agent konnte nicht starten: " + t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (design != null) {
|
||||||
|
try {
|
||||||
|
Design.installiere(design, inst);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
System.out.println("[AeroMC] Spieldesign konnte nicht starten: " + t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
package de.aeromc.capes;
|
||||||
|
|
||||||
|
import java.lang.instrument.ClassFileTransformer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.ProtectionDomain;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
import org.objectweb.asm.tree.InsnList;
|
||||||
|
import org.objectweb.asm.tree.InsnNode;
|
||||||
|
import org.objectweb.asm.tree.IntInsnNode;
|
||||||
|
import org.objectweb.asm.tree.LdcInsnNode;
|
||||||
|
import org.objectweb.asm.tree.MethodInsnNode;
|
||||||
|
import org.objectweb.asm.tree.MethodNode;
|
||||||
|
import org.objectweb.asm.tree.TypeInsnNode;
|
||||||
|
import org.objectweb.asm.tree.VarInsnNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut beim Laden um (siehe Design):
|
||||||
|
*
|
||||||
|
* 1. Ladebildschirm – erkannt am Text "textures/gui/title/mojangstudios.png"
|
||||||
|
* (ab 1.16) bzw. "…/mojang.png" (1.14/1.15), denn die Klassennamen sind je
|
||||||
|
* Version und Loader anders (verschleiert, Intermediary, SRG, Mojang-Namen):
|
||||||
|
* ab 1.16: color(255, 239, 50, 61) -> AeroMC-Nachtblau
|
||||||
|
* color(alpha, 255, 255, 255) -> Balkenfarbe
|
||||||
|
* 1.14/1.15: weiße Füllung (0xFFFFFF / -1) -> Nachtblau,
|
||||||
|
* Balken-Rot (226, 40, 55) -> Balkenfarbe
|
||||||
|
* 2. Logo-Textur – die innere Klasse des Ladebildschirms ("…$…") liest das
|
||||||
|
* Logo mit NativeImage.read(InputStream); davor wird der Strom getauscht.
|
||||||
|
* 3. LWJGL-GLFW – Fenstertitel (glfwCreateWindow/glfwSetWindowTitle) und
|
||||||
|
* Fenstersymbol (glfwSetWindowIcon).
|
||||||
|
*
|
||||||
|
* Eingefügt bzw. ersetzt wird nur geradliniger Code ohne Sprünge mit gleicher
|
||||||
|
* Stapelwirkung – die vorhandenen Stack-Map-Frames bleiben gültig.
|
||||||
|
*/
|
||||||
|
final class DesignUmbau implements ClassFileTransformer {
|
||||||
|
private static final byte[] MERKMAL = "textures/gui/title/mojang".getBytes(StandardCharsets.UTF_8);
|
||||||
|
private static final String LOGO_NEU = "textures/gui/title/mojangstudios.png";
|
||||||
|
private static final String LOGO_ALT = "textures/gui/title/mojang.png";
|
||||||
|
private static final byte[] TITEL_MERKMAL = "textures/gui/title/minecraft.png".getBytes(StandardCharsets.UTF_8);
|
||||||
|
private static final int KEIN_WERT = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
/** Name der Ladebildschirm-Klasse, sobald sie gesehen wurde. */
|
||||||
|
private volatile String ladeKlasse;
|
||||||
|
private volatile boolean ladeAlt;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] transform(ClassLoader loader, String name, Class<?> umdefiniert, ProtectionDomain pd, byte[] klasse) {
|
||||||
|
if (name == null || umdefiniert != null) return null;
|
||||||
|
try {
|
||||||
|
if ("org/lwjgl/glfw/GLFW".equals(name)) return glfw(klasse);
|
||||||
|
if (name.startsWith("java/") || name.startsWith("jdk/") || name.startsWith("sun/")
|
||||||
|
|| name.startsWith("de/aeromc/") || name.startsWith("org/lwjgl/")) return null;
|
||||||
|
String lk = ladeKlasse;
|
||||||
|
if (lk != null && name.startsWith(lk + "$")) return logoStrom(klasse, ladeAlt);
|
||||||
|
if (enthaelt(klasse, MERKMAL)) return ladeBildschirm(name, klasse);
|
||||||
|
if (enthaelt(klasse, TITEL_MERKMAL)) return titelLogo(name, klasse);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
System.out.println("[AeroMC] Spieldesign: " + name + " nicht angepasst: " + t);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 1. Ladebildschirm ----------
|
||||||
|
|
||||||
|
private byte[] ladeBildschirm(String name, byte[] klasse) {
|
||||||
|
ClassNode cn = lies(klasse);
|
||||||
|
boolean neu = hatText(cn, LOGO_NEU), alt = hatText(cn, LOGO_ALT);
|
||||||
|
if (!neu && !alt) return null;
|
||||||
|
ladeKlasse = name;
|
||||||
|
ladeAlt = alt && !neu;
|
||||||
|
int r = Design.HINTERGRUND >> 16 & 255, g = Design.HINTERGRUND >> 8 & 255, b = Design.HINTERGRUND & 255;
|
||||||
|
int br = Design.BALKEN >> 16 & 255, bg = Design.BALKEN >> 8 & 255, bb = Design.BALKEN & 255;
|
||||||
|
int hintergrund = 0, balken = 0, vollbild = 0, balkenBreite = 0;
|
||||||
|
for (MethodNode m : cn.methods) {
|
||||||
|
List<AbstractInsnNode> a = befehle(m);
|
||||||
|
for (int i = 0; i < a.size(); i++) {
|
||||||
|
if (!ladeAlt) {
|
||||||
|
// color(255, 239, 50, 61) – Mojang-Rot
|
||||||
|
if (i + 4 < a.size() && wert(a.get(i)) == 255 && wert(a.get(i + 1)) == 239
|
||||||
|
&& wert(a.get(i + 2)) == 50 && wert(a.get(i + 3)) == 61 && aufruf(a.get(i + 4), "(IIII)I")) {
|
||||||
|
setze(m, a, i + 1, r); setze(m, a, i + 2, g); setze(m, a, i + 3, b);
|
||||||
|
hintergrund++;
|
||||||
|
}
|
||||||
|
// color(alpha, 255, 255, 255) – weißer Balken
|
||||||
|
if (i + 4 < a.size() && a.get(i).getOpcode() == Opcodes.ILOAD && wert(a.get(i + 1)) == 255
|
||||||
|
&& wert(a.get(i + 2)) == 255 && wert(a.get(i + 3)) == 255 && aufruf(a.get(i + 4), "(IIII)I")) {
|
||||||
|
setze(m, a, i + 1, br); setze(m, a, i + 2, bg); setze(m, a, i + 3, bb);
|
||||||
|
balken++;
|
||||||
|
}
|
||||||
|
// Logo-Fläche auf Vollbild: Höhe = min(Breite·0,75; Höhe)·0,25 und Breite = 4·Höhe
|
||||||
|
// wird zu Höhe = min(Breite·9/16; Höhe) und Breite = 16/9·Höhe (Bild im 16:9-Format)
|
||||||
|
AbstractInsnNode n = a.get(i);
|
||||||
|
if (n instanceof LdcInsnNode && ((LdcInsnNode) n).cst instanceof Double) {
|
||||||
|
double d = (Double) ((LdcInsnNode) n).cst;
|
||||||
|
double ersatzWert = d == 0.75 ? 9.0 / 16.0 : d == 0.25 ? 1.0 : d == 4.0 ? 16.0 / 9.0 : -1;
|
||||||
|
if (ersatzWert > 0) {
|
||||||
|
LdcInsnNode e = new LdcInsnNode(ersatzWert);
|
||||||
|
m.instructions.set(n, e);
|
||||||
|
a.set(i, e);
|
||||||
|
vollbild++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fortschrittsbalken: Minecraft macht ihn so breit wie das Logo – jetzt also
|
||||||
|
// bildschirmbreit; beim Aufruf auf einen Teil der Breite in der Mitte kürzen
|
||||||
|
if (n instanceof MethodInsnNode && ((MethodInsnNode) n).owner.equals(name)
|
||||||
|
&& ((MethodInsnNode) n).desc.endsWith("IIIIF)V")) {
|
||||||
|
m.instructions.insertBefore(n, balkenKuerzen(m));
|
||||||
|
balkenBreite++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AbstractInsnNode n = a.get(i);
|
||||||
|
// weiße Füllung beim Ein-/Ausblenden: 0xFFFFFF | alpha << 24
|
||||||
|
if (n instanceof LdcInsnNode && Integer.valueOf(0xFFFFFF).equals(((LdcInsnNode) n).cst)) {
|
||||||
|
m.instructions.set(n, ersatz(Design.HINTERGRUND, a, i));
|
||||||
|
hintergrund++;
|
||||||
|
}
|
||||||
|
// fill(0, 0, breite, hoehe, -1) – weißer Hintergrund
|
||||||
|
if (i + 5 < a.size() && n.getOpcode() == Opcodes.ICONST_0 && a.get(i + 1).getOpcode() == Opcodes.ICONST_0
|
||||||
|
&& a.get(i + 2).getOpcode() == Opcodes.ILOAD && a.get(i + 3).getOpcode() == Opcodes.ILOAD
|
||||||
|
&& a.get(i + 4).getOpcode() == Opcodes.ICONST_M1 && aufruf(a.get(i + 5), "(IIIII)V")) {
|
||||||
|
m.instructions.set(a.get(i + 4), ersatz(0xFF000000 | Design.HINTERGRUND, a, i + 4));
|
||||||
|
hintergrund++;
|
||||||
|
}
|
||||||
|
// Balken-Rot (226, 40, 55) als Gleitkommazahlen
|
||||||
|
if (n instanceof LdcInsnNode && ((LdcInsnNode) n).cst instanceof Float) {
|
||||||
|
float f = (Float) ((LdcInsnNode) n).cst;
|
||||||
|
float neuWert = f == 226f ? br : f == 40f ? bg : f == 55f ? bb : -1f;
|
||||||
|
if (neuWert >= 0) {
|
||||||
|
LdcInsnNode e = new LdcInsnNode(neuWert);
|
||||||
|
m.instructions.set(n, e);
|
||||||
|
a.set(i, e);
|
||||||
|
balken++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("[AeroMC] Ladebildschirm angepasst (" + (ladeAlt ? "1.14/1.15" : "ab 1.16")
|
||||||
|
+ ", Hintergrund " + hintergrund + ", Balken " + balken + ", Vollbild " + vollbild + ", Balkenbreite " + balkenBreite + ")");
|
||||||
|
return schreibe(cn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vor dem Aufruf drawProgressBar(…, x1, y1, x2, y2, f): die vier Zahlen und f in
|
||||||
|
* neue lokale Variablen legen, x1/x2 um die Mitte auf BALKEN_ANTEIL der Breite
|
||||||
|
* zusammenziehen und alles wieder auf den Stapel legen.
|
||||||
|
*/
|
||||||
|
private static InsnList balkenKuerzen(MethodNode m) {
|
||||||
|
int b = m.maxLocals;
|
||||||
|
m.maxLocals += 7;
|
||||||
|
int x1 = b, y1 = b + 1, x2 = b + 2, y2 = b + 3, f = b + 4, mitte = b + 5, halb = b + 6;
|
||||||
|
InsnList l = new InsnList();
|
||||||
|
l.add(new VarInsnNode(Opcodes.FSTORE, f));
|
||||||
|
l.add(new VarInsnNode(Opcodes.ISTORE, y2));
|
||||||
|
l.add(new VarInsnNode(Opcodes.ISTORE, x2));
|
||||||
|
l.add(new VarInsnNode(Opcodes.ISTORE, y1));
|
||||||
|
l.add(new VarInsnNode(Opcodes.ISTORE, x1));
|
||||||
|
// mitte = (x1 + x2) / 2
|
||||||
|
l.add(new VarInsnNode(Opcodes.ILOAD, x1));
|
||||||
|
l.add(new VarInsnNode(Opcodes.ILOAD, x2));
|
||||||
|
l.add(new InsnNode(Opcodes.IADD));
|
||||||
|
l.add(new InsnNode(Opcodes.ICONST_2));
|
||||||
|
l.add(new InsnNode(Opcodes.IDIV));
|
||||||
|
l.add(new VarInsnNode(Opcodes.ISTORE, mitte));
|
||||||
|
// halb = (int) ((x2 - x1) * anteil / 2)
|
||||||
|
l.add(new VarInsnNode(Opcodes.ILOAD, x2));
|
||||||
|
l.add(new VarInsnNode(Opcodes.ILOAD, x1));
|
||||||
|
l.add(new InsnNode(Opcodes.ISUB));
|
||||||
|
l.add(new InsnNode(Opcodes.I2F));
|
||||||
|
l.add(new LdcInsnNode(Design.BALKEN_ANTEIL / 2f));
|
||||||
|
l.add(new InsnNode(Opcodes.FMUL));
|
||||||
|
l.add(new InsnNode(Opcodes.F2I));
|
||||||
|
l.add(new VarInsnNode(Opcodes.ISTORE, halb));
|
||||||
|
// (mitte - halb, y1, mitte + halb, y2, f)
|
||||||
|
l.add(new VarInsnNode(Opcodes.ILOAD, mitte));
|
||||||
|
l.add(new VarInsnNode(Opcodes.ILOAD, halb));
|
||||||
|
l.add(new InsnNode(Opcodes.ISUB));
|
||||||
|
l.add(new VarInsnNode(Opcodes.ILOAD, y1));
|
||||||
|
l.add(new VarInsnNode(Opcodes.ILOAD, mitte));
|
||||||
|
l.add(new VarInsnNode(Opcodes.ILOAD, halb));
|
||||||
|
l.add(new InsnNode(Opcodes.IADD));
|
||||||
|
l.add(new VarInsnNode(Opcodes.ILOAD, y2));
|
||||||
|
l.add(new VarInsnNode(Opcodes.FLOAD, f));
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 1b. Titel-Logo im Hauptmenü ----------
|
||||||
|
// Minecraft zeichnet vom Logo nur einen 44 Punkte hohen Streifen – zu knapp für
|
||||||
|
// Pyramide, großen Schriftzug und Untertitel. Hier wird er auf 64 Punkte erweitert:
|
||||||
|
// bis 1.19.4: blit(u 0, v 0, 155, 44) + blit(u 0, v 45, 155, 44) -> Höhe 64, zweites Stück ab v 65
|
||||||
|
// ab 1.20: blit(…, 256, 44, Textur 256, 64) -> Höhe 64
|
||||||
|
private byte[] titelLogo(String name, byte[] klasse) {
|
||||||
|
ClassNode cn = lies(klasse);
|
||||||
|
int n = 0;
|
||||||
|
for (MethodNode m : cn.methods) {
|
||||||
|
List<AbstractInsnNode> a = befehle(m);
|
||||||
|
for (int i = 0; i + 4 < a.size(); i++) {
|
||||||
|
int w0 = wert(a.get(i)), w1 = wert(a.get(i + 1)), w2 = wert(a.get(i + 2)), w3 = wert(a.get(i + 3));
|
||||||
|
boolean aufrufDanach = a.get(i + 4) instanceof MethodInsnNode;
|
||||||
|
if (w0 == 0 && w1 == 0 && w2 == 155 && w3 == 44 && aufrufDanach) {
|
||||||
|
setze(m, a, i + 3, 64); n++;
|
||||||
|
} else if (w0 == 0 && w1 == 45 && w2 == 155 && w3 == 44 && aufrufDanach) {
|
||||||
|
setze(m, a, i + 1, 65); setze(m, a, i + 3, 64); n++;
|
||||||
|
} else if (w0 == 256 && w1 == 44 && w2 == 256 && w3 == 64) {
|
||||||
|
setze(m, a, i + 1, 64); n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (n == 0) return null;
|
||||||
|
System.out.println("[AeroMC] Titel-Logo vergroessert (" + n + ")");
|
||||||
|
return schreibe(cn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 2. Logo-Textur ----------
|
||||||
|
|
||||||
|
private byte[] logoStrom(byte[] klasse, boolean alt) {
|
||||||
|
ClassNode cn = lies(klasse);
|
||||||
|
int n = 0;
|
||||||
|
for (MethodNode m : cn.methods) {
|
||||||
|
for (AbstractInsnNode i : befehle(m)) {
|
||||||
|
if (i.getOpcode() != Opcodes.INVOKESTATIC) continue;
|
||||||
|
MethodInsnNode mi = (MethodInsnNode) i;
|
||||||
|
if (!mi.desc.startsWith("(Ljava/io/InputStream;)L") || mi.owner.startsWith("java/")) continue;
|
||||||
|
// Stapel: [strom] -> [strom, helfer] -> [helfer, strom] -> apply -> [neuer strom]
|
||||||
|
InsnList neu = new InsnList();
|
||||||
|
helfer(neu, alt ? Design.LOGO_ALT : Design.LOGO);
|
||||||
|
neu.add(new InsnNode(Opcodes.SWAP));
|
||||||
|
neu.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, "java/util/function/Function", "apply",
|
||||||
|
"(Ljava/lang/Object;)Ljava/lang/Object;", true));
|
||||||
|
neu.add(new TypeInsnNode(Opcodes.CHECKCAST, "java/io/InputStream"));
|
||||||
|
m.instructions.insertBefore(i, neu);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (n == 0) return null;
|
||||||
|
System.out.println("[AeroMC] Ladelogo ersetzt");
|
||||||
|
return schreibe(cn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 3. Fenstertitel und -symbol ----------
|
||||||
|
|
||||||
|
private byte[] glfw(byte[] klasse) {
|
||||||
|
ClassNode cn = lies(klasse);
|
||||||
|
int n = 0;
|
||||||
|
for (MethodNode m : cn.methods) {
|
||||||
|
String schluessel = null, typ = null;
|
||||||
|
if ("glfwCreateWindow".equals(m.name) && "(IILjava/lang/CharSequence;JJ)J".equals(m.desc)) {
|
||||||
|
schluessel = Design.TITEL; typ = "java/lang/CharSequence";
|
||||||
|
} else if ("glfwSetWindowTitle".equals(m.name) && "(JLjava/lang/CharSequence;)V".equals(m.desc)) {
|
||||||
|
schluessel = Design.TITEL; typ = "java/lang/CharSequence";
|
||||||
|
} else if ("glfwSetWindowIcon".equals(m.name) && "(JLorg/lwjgl/glfw/GLFWImage$Buffer;)V".equals(m.desc)) {
|
||||||
|
schluessel = Design.ICON; typ = "org/lwjgl/glfw/GLFWImage$Buffer";
|
||||||
|
}
|
||||||
|
if (schluessel == null) continue;
|
||||||
|
// am Anfang: argument = helfer.apply(argument) (Argument liegt in beiden Fällen in Slot 2)
|
||||||
|
InsnList neu = new InsnList();
|
||||||
|
helfer(neu, schluessel);
|
||||||
|
neu.add(new VarInsnNode(Opcodes.ALOAD, 2));
|
||||||
|
neu.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, "java/util/function/Function", "apply",
|
||||||
|
"(Ljava/lang/Object;)Ljava/lang/Object;", true));
|
||||||
|
neu.add(new TypeInsnNode(Opcodes.CHECKCAST, typ));
|
||||||
|
neu.add(new VarInsnNode(Opcodes.ASTORE, 2));
|
||||||
|
m.instructions.insert(neu);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
if (n == 0) return null;
|
||||||
|
System.out.println("[AeroMC] Fenstertitel und -symbol vorbereitet (" + n + ")");
|
||||||
|
return schreibe(cn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Hilfen ----------
|
||||||
|
|
||||||
|
/** legt (Function) System.getProperties().get(schluessel) auf den Stapel */
|
||||||
|
private static void helfer(InsnList l, String schluessel) {
|
||||||
|
l.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/System", "getProperties", "()Ljava/util/Properties;", false));
|
||||||
|
l.add(new LdcInsnNode(schluessel));
|
||||||
|
l.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/util/Properties", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", false));
|
||||||
|
l.add(new TypeInsnNode(Opcodes.CHECKCAST, "java/util/function/Function"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClassNode lies(byte[] klasse) {
|
||||||
|
ClassNode cn = new ClassNode();
|
||||||
|
new ClassReader(klasse).accept(cn, 0);
|
||||||
|
return cn;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] schreibe(ClassNode cn) {
|
||||||
|
ClassWriter w = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||||
|
cn.accept(w);
|
||||||
|
return w.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** echte Befehle eines Methodenrumpfs (ohne Labels, Zeilennummern, Frames) */
|
||||||
|
private static List<AbstractInsnNode> befehle(MethodNode m) {
|
||||||
|
List<AbstractInsnNode> l = new ArrayList<>();
|
||||||
|
for (AbstractInsnNode i = m.instructions.getFirst(); i != null; i = i.getNext()) {
|
||||||
|
if (i.getOpcode() >= 0) l.add(i);
|
||||||
|
}
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hatText(ClassNode cn, String text) {
|
||||||
|
for (MethodNode m : cn.methods) {
|
||||||
|
for (AbstractInsnNode i : befehle(m)) {
|
||||||
|
if (i instanceof LdcInsnNode && text.equals(((LdcInsnNode) i).cst)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Zahlenwert eines Konstanten-Befehls, sonst KEIN_WERT */
|
||||||
|
private static int wert(AbstractInsnNode i) {
|
||||||
|
int op = i.getOpcode();
|
||||||
|
if (op >= Opcodes.ICONST_M1 && op <= Opcodes.ICONST_5) return op - Opcodes.ICONST_0;
|
||||||
|
if (op == Opcodes.BIPUSH || op == Opcodes.SIPUSH) return ((IntInsnNode) i).operand;
|
||||||
|
if (i instanceof LdcInsnNode && ((LdcInsnNode) i).cst instanceof Integer) return (Integer) ((LdcInsnNode) i).cst;
|
||||||
|
return KEIN_WERT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean aufruf(AbstractInsnNode i, String desc) {
|
||||||
|
return (i.getOpcode() == Opcodes.INVOKESTATIC || i.getOpcode() == Opcodes.INVOKEVIRTUAL)
|
||||||
|
&& desc.equals(((MethodInsnNode) i).desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setze(MethodNode m, List<AbstractInsnNode> a, int index, int v) {
|
||||||
|
m.instructions.set(a.get(index), ersatz(v, a, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** neuer Konstanten-Befehl; ersetzt auch den Eintrag in der Befehlsliste */
|
||||||
|
private static AbstractInsnNode ersatz(int v, List<AbstractInsnNode> a, int index) {
|
||||||
|
AbstractInsnNode n = v >= -32768 && v <= 32767 ? new IntInsnNode(Opcodes.SIPUSH, v) : new LdcInsnNode(v);
|
||||||
|
a.set(index, n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean enthaelt(byte[] daten, byte[] muster) {
|
||||||
|
outer:
|
||||||
|
for (int i = 0, ende = daten.length - muster.length; i <= ende; i++) {
|
||||||
|
for (int k = 0; k < muster.length; k++) {
|
||||||
|
if (daten[i + k] != muster[k]) continue outer;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package de.aeromc.capes;
|
||||||
|
|
||||||
|
import java.lang.instrument.ClassFileTransformer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.ProtectionDomain;
|
||||||
|
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
import org.objectweb.asm.tree.FieldInsnNode;
|
||||||
|
import org.objectweb.asm.tree.FrameNode;
|
||||||
|
import org.objectweb.asm.tree.InsnList;
|
||||||
|
import org.objectweb.asm.tree.InsnNode;
|
||||||
|
import org.objectweb.asm.tree.JumpInsnNode;
|
||||||
|
import org.objectweb.asm.tree.LabelNode;
|
||||||
|
import org.objectweb.asm.tree.LdcInsnNode;
|
||||||
|
import org.objectweb.asm.tree.MethodInsnNode;
|
||||||
|
import org.objectweb.asm.tree.MethodNode;
|
||||||
|
import org.objectweb.asm.tree.TypeInsnNode;
|
||||||
|
import org.objectweb.asm.tree.VarInsnNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mod-Schalter im Pausemenü (Minecraft 1.14 … 26.x, jeder Loader):
|
||||||
|
* Der Knopf „Feedback geben“ wird zum Knopf „Mods verwalten“.
|
||||||
|
*
|
||||||
|
* 1. Seine Adresse (aka.ms/javafeedback bzw. snapshotfeedback) wird zu
|
||||||
|
* "aeromc://mods" – je nach Version steht sie im Pausemenü selbst
|
||||||
|
* (bis 1.20) oder in CommonLinks (ab 1.21).
|
||||||
|
* 2. Die Lambdas, über die Minecraft einen Link öffnet (im Pausemenü bzw. in
|
||||||
|
* ConfirmLinkScreen), fragen vorher den Helfer: Ist es "aeromc://mods",
|
||||||
|
* öffnet der Launcher das Mod-Fenster und Minecraft zeigt keine Rückfrage.
|
||||||
|
* Alle anderen Links laufen unverändert weiter.
|
||||||
|
*
|
||||||
|
* Beschriftung bzw. Symbol des Knopfs kommen aus dem Ressourcenpaket.
|
||||||
|
*/
|
||||||
|
final class ModsUmbau implements ClassFileTransformer {
|
||||||
|
static final String ZIEL = "aeromc://mods";
|
||||||
|
private static final String[] ADRESSEN = {"https://aka.ms/javafeedback?ref=game", "https://aka.ms/snapshotfeedback?ref=game"};
|
||||||
|
private static final byte[] MERKMAL_FEEDBACK = "aka.ms/".getBytes(StandardCharsets.UTF_8);
|
||||||
|
private static final byte[] MERKMAL_LINK = "chat.link.confirm".getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] transform(ClassLoader loader, String name, Class<?> umdefiniert, ProtectionDomain pd, byte[] klasse) {
|
||||||
|
if (name == null || umdefiniert != null || name.startsWith("java/") || name.startsWith("jdk/")
|
||||||
|
|| name.startsWith("sun/") || name.startsWith("de/aeromc/") || name.startsWith("org/lwjgl/")) return null;
|
||||||
|
try {
|
||||||
|
boolean feedback = enthaelt(klasse, MERKMAL_FEEDBACK);
|
||||||
|
boolean link = enthaelt(klasse, MERKMAL_LINK);
|
||||||
|
if (!feedback && !link) return null;
|
||||||
|
ClassNode cn = new ClassNode();
|
||||||
|
new ClassReader(klasse).accept(cn, 0);
|
||||||
|
int adressen = 0, waechter = 0;
|
||||||
|
for (MethodNode m : cn.methods) {
|
||||||
|
for (AbstractInsnNode i = m.instructions.getFirst(); i != null; i = i.getNext()) {
|
||||||
|
if (i instanceof LdcInsnNode && istFeedbackAdresse(((LdcInsnNode) i).cst)) {
|
||||||
|
((LdcInsnNode) i).cst = ZIEL;
|
||||||
|
adressen++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (adressen == 0 && !link) return null;
|
||||||
|
for (MethodNode m : cn.methods) {
|
||||||
|
if ((m.access & Opcodes.ACC_SYNTHETIC) == 0 || Type.getReturnType(m.desc) != Type.VOID_TYPE) continue;
|
||||||
|
int slot = adressParameter(m);
|
||||||
|
boolean konstante = slot < 0 && enthaeltZiel(m);
|
||||||
|
if (slot < 0 && !konstante) continue;
|
||||||
|
if (frameAmAnfang(m)) continue; // nicht sicher erweiterbar
|
||||||
|
InsnList w = new InsnList();
|
||||||
|
w.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/System", "getProperties", "()Ljava/util/Properties;", false));
|
||||||
|
w.add(new LdcInsnNode(Design.MODS));
|
||||||
|
w.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/util/Properties", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", false));
|
||||||
|
w.add(new TypeInsnNode(Opcodes.CHECKCAST, "java/util/function/Function"));
|
||||||
|
if (slot >= 0) w.add(new VarInsnNode(Opcodes.ALOAD, slot)); else w.add(new LdcInsnNode(ZIEL));
|
||||||
|
w.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, "java/util/function/Function", "apply", "(Ljava/lang/Object;)Ljava/lang/Object;", true));
|
||||||
|
w.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;"));
|
||||||
|
LabelNode weiter = new LabelNode();
|
||||||
|
w.add(new JumpInsnNode(Opcodes.IF_ACMPNE, weiter));
|
||||||
|
w.add(new InsnNode(Opcodes.RETURN));
|
||||||
|
w.add(weiter);
|
||||||
|
w.add(new FrameNode(Opcodes.F_SAME, 0, null, 0, null));
|
||||||
|
m.instructions.insert(w);
|
||||||
|
waechter++;
|
||||||
|
}
|
||||||
|
if (adressen == 0 && waechter == 0) return null;
|
||||||
|
System.out.println("[AeroMC] Mod-Schalter vorbereitet (Adressen " + adressen + ", Links " + waechter + ")");
|
||||||
|
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||||
|
cn.accept(cw);
|
||||||
|
return cw.toByteArray();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
System.out.println("[AeroMC] Mod-Schalter: " + name + " nicht angepasst: " + t);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean istFeedbackAdresse(Object cst) {
|
||||||
|
for (String a : ADRESSEN) if (a.equals(cst)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Slot des ersten Parameters vom Typ String oder URI, sonst -1 */
|
||||||
|
private static int adressParameter(MethodNode m) {
|
||||||
|
int slot = (m.access & Opcodes.ACC_STATIC) != 0 ? 0 : 1;
|
||||||
|
for (Type t : Type.getArgumentTypes(m.desc)) {
|
||||||
|
String d = t.getDescriptor();
|
||||||
|
if ("Ljava/lang/String;".equals(d) || "Ljava/net/URI;".equals(d)) return slot;
|
||||||
|
slot += t.getSize();
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean enthaeltZiel(MethodNode m) {
|
||||||
|
for (AbstractInsnNode i = m.instructions.getFirst(); i != null; i = i.getNext()) {
|
||||||
|
if (i instanceof LdcInsnNode && ZIEL.equals(((LdcInsnNode) i).cst)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** steht vor dem ersten echten Befehl schon ein Frame (Sprungziel am Anfang)? */
|
||||||
|
private static boolean frameAmAnfang(MethodNode m) {
|
||||||
|
for (AbstractInsnNode i = m.instructions.getFirst(); i != null; i = i.getNext()) {
|
||||||
|
if (i instanceof FrameNode) return true;
|
||||||
|
if (i.getOpcode() >= 0) return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean enthaelt(byte[] daten, byte[] muster) {
|
||||||
|
outer:
|
||||||
|
for (int i = 0, ende = daten.length - muster.length; i <= ende; i++) {
|
||||||
|
for (int k = 0; k < muster.length; k++) if (daten[i + k] != muster[k]) continue outer;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ import org.objectweb.asm.commons.Remapper;
|
|||||||
* de/aeromc/capes/asm umbenannt. So kann das ASM von Fabric/Forge (das auf
|
* de/aeromc/capes/asm umbenannt. So kann das ASM von Fabric/Forge (das auf
|
||||||
* demselben Klassenpfad liegt) nie mit unserem verwechselt werden.
|
* demselben Klassenpfad liegt) nie mit unserem verwechselt werden.
|
||||||
*
|
*
|
||||||
* Aufruf: java Shade <ziel.jar> <klassenordner> <asm.jar>
|
* Aufruf: java Shade <ziel.jar> <klassenordner> <asm.jar> [weitere ASM-Jars, z. B. asm-tree]
|
||||||
*/
|
*/
|
||||||
public class Shade {
|
public class Shade {
|
||||||
static final Remapper UMBENENNEN = new Remapper() {
|
static final Remapper UMBENENNEN = new Remapper() {
|
||||||
@@ -52,7 +52,7 @@ public class Shade {
|
|||||||
at.putValue("Premain-Class", "de.aeromc.capes.Agent");
|
at.putValue("Premain-Class", "de.aeromc.capes.Agent");
|
||||||
at.putValue("Agent-Class", "de.aeromc.capes.Agent");
|
at.putValue("Agent-Class", "de.aeromc.capes.Agent");
|
||||||
at.putValue("Can-Retransform-Classes", "false");
|
at.putValue("Can-Retransform-Classes", "false");
|
||||||
at.putValue("Implementation-Title", "AeroMC Umhang-Agent");
|
at.putValue("Implementation-Title", "AeroMC Agent (Umhaenge, Spieldesign)");
|
||||||
try (JarOutputStream out = new JarOutputStream(new FileOutputStream(a[0]), mf)) {
|
try (JarOutputStream out = new JarOutputStream(new FileOutputStream(a[0]), mf)) {
|
||||||
// eigene Klassen
|
// eigene Klassen
|
||||||
File basis = new File(a[1]);
|
File basis = new File(a[1]);
|
||||||
@@ -63,7 +63,7 @@ public class Shade {
|
|||||||
out.closeEntry();
|
out.closeEntry();
|
||||||
}
|
}
|
||||||
// ASM (umbenannt), ohne Modul-Infos und Signaturen
|
// ASM (umbenannt), ohne Modul-Infos und Signaturen
|
||||||
try (JarFile asm = new JarFile(a[2])) {
|
for (int j = 2; j < a.length; j++) try (JarFile asm = new JarFile(a[j])) {
|
||||||
Enumeration<JarEntry> e = asm.entries();
|
Enumeration<JarEntry> e = asm.entries();
|
||||||
while (e.hasMoreElements()) {
|
while (e.hasMoreElements()) {
|
||||||
JarEntry je = e.nextElement();
|
JarEntry je = e.nextElement();
|
||||||
|
|||||||
@@ -181,9 +181,35 @@ public static class Installer
|
|||||||
k.SetValue("NoRepair", 1, RegistryValueKind.DWord);
|
k.SetValue("NoRepair", 1, RegistryValueKind.DWord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
progress.Report((0.98, "Installation wird abgeschlossen …"));
|
||||||
|
Vorwaermen(exe);
|
||||||
|
|
||||||
progress.Report((1.0, "Fertig"));
|
progress.Report((1.0, "Fertig"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Startet AeroMC einmal kurz mit "--aeromc-vorwaermen" – die App beendet sich
|
||||||
|
/// damit sofort wieder (siehe src/main.js). Windows Defender prüft eine neue,
|
||||||
|
/// noch unbekannte EXE beim ersten Start mehrere Sekunden lang (gemessen: ~8 s);
|
||||||
|
/// das passiert so während des Fortschrittsbalkens und nicht erst beim Klick
|
||||||
|
/// auf "Fertigstellen". Das Ergebnis merkt sich Windows, der echte Start ist
|
||||||
|
/// danach sofort da.
|
||||||
|
/// </summary>
|
||||||
|
private static void Vorwaermen(string exe)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var p = Process.Start(new ProcessStartInfo(exe, "--aeromc-vorwaermen")
|
||||||
|
{
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
WorkingDirectory = Path.GetDirectoryName(exe)!,
|
||||||
|
});
|
||||||
|
if (p != null && !p.WaitForExit(30000)) { try { p.Kill(); } catch { } }
|
||||||
|
}
|
||||||
|
catch { /* nur eine Beschleunigung – Fehler sind egal */ }
|
||||||
|
}
|
||||||
|
|
||||||
private static List<string> TopLevelNames(ZipArchive zip) =>
|
private static List<string> TopLevelNames(ZipArchive zip) =>
|
||||||
zip.Entries
|
zip.Entries
|
||||||
.Select(e => e.FullName.Replace('\\', '/').Split('/')[0])
|
.Select(e => e.FullName.Replace('\\', '/').Split('/')[0])
|
||||||
|
|||||||
@@ -300,7 +300,16 @@ public partial class MainWindow : Window
|
|||||||
await RunInstall(DirBox.Text.Trim(), DesktopLinkBox.IsChecked == true, update: false);
|
await RunInstall(DirBox.Text.Trim(), DesktopLinkBox.IsChecked == true, update: false);
|
||||||
break;
|
break;
|
||||||
case PageNumProgress:
|
case PageNumProgress:
|
||||||
if (LaunchBox.IsChecked == true) StartApp();
|
// Start im Hintergrund: Process.Start kann blockieren, solange Windows
|
||||||
|
// die EXE prüft – das Fenster darf dabei nie einfrieren
|
||||||
|
if (LaunchBox.IsChecked == true)
|
||||||
|
{
|
||||||
|
_busy = true;
|
||||||
|
NextButton.IsEnabled = false;
|
||||||
|
NextButton.Content = "AeroMC startet …";
|
||||||
|
await Task.Run(StartApp);
|
||||||
|
_busy = false;
|
||||||
|
}
|
||||||
Close();
|
Close();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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": "1.2.1",
|
"version": "1.2.3",
|
||||||
"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.2.1.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.2.3.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.2.1")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.2.3")]
|
||||||
[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.2.1.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.2.3.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 @@
|
|||||||
a17a2bc3b05635d3c29adc5c6884bde0a77826489d54b55a9b7744bfaa1ef206
|
111db8ee3dc7193961107c73afd8913fff02b92fd4e49b8b7e4bf051181600bd
|
||||||
|
|||||||
@@ -418,7 +418,7 @@
|
|||||||
"C:\\Users\\Viper\\.nuget\\packages\\": {}
|
"C:\\Users\\Viper\\.nuget\\packages\\": {}
|
||||||
},
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.3",
|
||||||
"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": "zBMXx+S6DFU=",
|
"dgSpecHash": "5nf4o8NXPS0=",
|
||||||
"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": [
|
||||||
|
|||||||
Reference in New Issue
Block a user