From aae442ae480b39c978d53bc438116a437279447f Mon Sep 17 00:00:00 2001 From: M_Viper Date: Wed, 7 Oct 2026 07:32:07 +0000 Subject: [PATCH] Upload via GUI (194 Dateien) --- agent/src/de/aeromc/capes/Agent.java | 64 +++- agent/src/de/aeromc/capes/Design.java | 141 +++++++ agent/src/de/aeromc/capes/DesignUmbau.java | 352 ++++++++++++++++++ agent/src/de/aeromc/capes/ModsUmbau.java | 138 +++++++ agent/tools/Shade.java | 160 ++++---- installer/Installer.cs | 26 ++ installer/MainWindow.xaml.cs | 11 +- .../obj/AeroMCSetup.csproj.nuget.dgspec.json | 2 +- installer/obj/Release/net48/AeroMC-Setup.exe | Bin 259072 -> 259584 bytes .../Release/net48/AeroMCSetup.AssemblyInfo.cs | 6 +- .../AeroMCSetup.AssemblyInfoInputs.cache | 2 +- installer/obj/project.assets.json | 2 +- installer/obj/project.nuget.cache | 2 +- 13 files changed, 803 insertions(+), 103 deletions(-) create mode 100644 agent/src/de/aeromc/capes/Design.java create mode 100644 agent/src/de/aeromc/capes/DesignUmbau.java create mode 100644 agent/src/de/aeromc/capes/ModsUmbau.java diff --git a/agent/src/de/aeromc/capes/Agent.java b/agent/src/de/aeromc/capes/Agent.java index 6593332..68dd511 100644 --- a/agent/src/de/aeromc/capes/Agent.java +++ b/agent/src/de/aeromc/capes/Agent.java @@ -3,15 +3,22 @@ package de.aeromc.capes; import java.lang.instrument.Instrumentation; /** - * AeroMC-Umhang-Agent. + * AeroMC-Agent. * * 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 - * 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. + * Loader) und kann zwei Dinge, einzeln zuschaltbar: * - * 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 { /** Unter diesem Schlüssel liegt der Nachschlage-Dienst in den System-Properties. */ @@ -28,15 +35,42 @@ public final class Agent { } private static void starte(String argument, Instrumentation inst) { - 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 - // über JDK-Typen, damit es mit jedem Classloader und Modulsystem klappt. - System.getProperties().put(SCHLUESSEL, new Nachschlagen(basis)); - inst.addTransformer(new Umbau()); - System.out.println("[AeroMC] Umhang-Agent aktiv"); - } catch (Throwable t) { - System.out.println("[AeroMC] Umhang-Agent konnte nicht starten: " + t); + 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 { + // Die eingefügten Befehle in authlib holen sich den Dienst von hier – nur + // über JDK-Typen, damit es mit jedem Classloader und Modulsystem klappt. + System.getProperties().put(SCHLUESSEL, new Nachschlagen(basis)); + inst.addTransformer(new Umbau()); + System.out.println("[AeroMC] Umhang-Agent aktiv"); + } catch (Throwable 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); + } } } } diff --git a/agent/src/de/aeromc/capes/Design.java b/agent/src/de/aeromc/capes/Design.java new file mode 100644 index 0000000..2d51c28 --- /dev/null +++ b/agent/src/de/aeromc/capes/Design.java @@ -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 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) Design::titel); + p.put(ICON, (Function) 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) 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 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 groessen = new ArrayList<>(); + List 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; + } + } +} diff --git a/agent/src/de/aeromc/capes/DesignUmbau.java b/agent/src/de/aeromc/capes/DesignUmbau.java new file mode 100644 index 0000000..a7847df --- /dev/null +++ b/agent/src/de/aeromc/capes/DesignUmbau.java @@ -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 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 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 befehle(MethodNode m) { + List 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 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 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; + } +} diff --git a/agent/src/de/aeromc/capes/ModsUmbau.java b/agent/src/de/aeromc/capes/ModsUmbau.java new file mode 100644 index 0000000..fe6fb4f --- /dev/null +++ b/agent/src/de/aeromc/capes/ModsUmbau.java @@ -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; + } +} diff --git a/agent/tools/Shade.java b/agent/tools/Shade.java index 32e52cb..b5bff3a 100644 --- a/agent/tools/Shade.java +++ b/agent/tools/Shade.java @@ -1,80 +1,80 @@ -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.nio.file.Files; -import java.util.Enumeration; -import java.util.jar.Attributes; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.JarOutputStream; -import java.util.jar.Manifest; - -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.commons.ClassRemapper; -import org.objectweb.asm.commons.Remapper; - -/** - * Baut die Agent-Datei: eigene Klassen + ASM, dabei org/objectweb/asm nach - * de/aeromc/capes/asm umbenannt. So kann das ASM von Fabric/Forge (das auf - * demselben Klassenpfad liegt) nie mit unserem verwechselt werden. - * - * Aufruf: java Shade <ziel.jar> <klassenordner> <asm.jar> - */ -public class Shade { - static final Remapper UMBENENNEN = new Remapper() { - @Override - public String map(String name) { - return name.startsWith("org/objectweb/asm/") ? "de/aeromc/capes/asm/" + name.substring(18) : name; - } - }; - - static byte[] umbenennen(byte[] klasse) { - ClassReader r = new ClassReader(klasse); - ClassWriter w = new ClassWriter(0); - r.accept(new ClassRemapper(w, UMBENENNEN), 0); - return w.toByteArray(); - } - - static byte[] lies(InputStream in) throws Exception { - ByteArrayOutputStream b = new ByteArrayOutputStream(); - byte[] p = new byte[8192]; - int n; - while ((n = in.read(p)) > 0) b.write(p, 0, n); - return b.toByteArray(); - } - - public static void main(String[] a) throws Exception { - Manifest mf = new Manifest(); - Attributes at = mf.getMainAttributes(); - at.put(Attributes.Name.MANIFEST_VERSION, "1.0"); - at.putValue("Premain-Class", "de.aeromc.capes.Agent"); - at.putValue("Agent-Class", "de.aeromc.capes.Agent"); - at.putValue("Can-Retransform-Classes", "false"); - at.putValue("Implementation-Title", "AeroMC Umhang-Agent"); - try (JarOutputStream out = new JarOutputStream(new FileOutputStream(a[0]), mf)) { - // eigene Klassen - File basis = new File(a[1]); - for (File f : Files.walk(basis.toPath()).map(p -> p.toFile()).filter(f -> f.getName().endsWith(".class")).toArray(File[]::new)) { - String name = basis.toPath().relativize(f.toPath()).toString().replace('\\', '/'); - out.putNextEntry(new JarEntry(name)); - out.write(umbenennen(Files.readAllBytes(f.toPath()))); - out.closeEntry(); - } - // ASM (umbenannt), ohne Modul-Infos und Signaturen - try (JarFile asm = new JarFile(a[2])) { - Enumeration e = asm.entries(); - while (e.hasMoreElements()) { - JarEntry je = e.nextElement(); - String n = je.getName(); - if (!n.startsWith("org/objectweb/asm/") || !n.endsWith(".class")) continue; - byte[] neu = umbenennen(lies(asm.getInputStream(je))); - out.putNextEntry(new JarEntry("de/aeromc/capes/asm/" + n.substring(18))); - out.write(neu); - out.closeEntry(); - } - } - } - } -} +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.Enumeration; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.commons.ClassRemapper; +import org.objectweb.asm.commons.Remapper; + +/** + * Baut die Agent-Datei: eigene Klassen + ASM, dabei org/objectweb/asm nach + * de/aeromc/capes/asm umbenannt. So kann das ASM von Fabric/Forge (das auf + * demselben Klassenpfad liegt) nie mit unserem verwechselt werden. + * + * Aufruf: java Shade <ziel.jar> <klassenordner> <asm.jar> [weitere ASM-Jars, z. B. asm-tree] + */ +public class Shade { + static final Remapper UMBENENNEN = new Remapper() { + @Override + public String map(String name) { + return name.startsWith("org/objectweb/asm/") ? "de/aeromc/capes/asm/" + name.substring(18) : name; + } + }; + + static byte[] umbenennen(byte[] klasse) { + ClassReader r = new ClassReader(klasse); + ClassWriter w = new ClassWriter(0); + r.accept(new ClassRemapper(w, UMBENENNEN), 0); + return w.toByteArray(); + } + + static byte[] lies(InputStream in) throws Exception { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + byte[] p = new byte[8192]; + int n; + while ((n = in.read(p)) > 0) b.write(p, 0, n); + return b.toByteArray(); + } + + public static void main(String[] a) throws Exception { + Manifest mf = new Manifest(); + Attributes at = mf.getMainAttributes(); + at.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + at.putValue("Premain-Class", "de.aeromc.capes.Agent"); + at.putValue("Agent-Class", "de.aeromc.capes.Agent"); + at.putValue("Can-Retransform-Classes", "false"); + at.putValue("Implementation-Title", "AeroMC Agent (Umhaenge, Spieldesign)"); + try (JarOutputStream out = new JarOutputStream(new FileOutputStream(a[0]), mf)) { + // eigene Klassen + File basis = new File(a[1]); + for (File f : Files.walk(basis.toPath()).map(p -> p.toFile()).filter(f -> f.getName().endsWith(".class")).toArray(File[]::new)) { + String name = basis.toPath().relativize(f.toPath()).toString().replace('\\', '/'); + out.putNextEntry(new JarEntry(name)); + out.write(umbenennen(Files.readAllBytes(f.toPath()))); + out.closeEntry(); + } + // ASM (umbenannt), ohne Modul-Infos und Signaturen + for (int j = 2; j < a.length; j++) try (JarFile asm = new JarFile(a[j])) { + Enumeration e = asm.entries(); + while (e.hasMoreElements()) { + JarEntry je = e.nextElement(); + String n = je.getName(); + if (!n.startsWith("org/objectweb/asm/") || !n.endsWith(".class")) continue; + byte[] neu = umbenennen(lies(asm.getInputStream(je))); + out.putNextEntry(new JarEntry("de/aeromc/capes/asm/" + n.substring(18))); + out.write(neu); + out.closeEntry(); + } + } + } + } +} diff --git a/installer/Installer.cs b/installer/Installer.cs index b45863f..7a7b68e 100644 --- a/installer/Installer.cs +++ b/installer/Installer.cs @@ -181,9 +181,35 @@ public static class Installer k.SetValue("NoRepair", 1, RegistryValueKind.DWord); } + progress.Report((0.98, "Installation wird abgeschlossen …")); + Vorwaermen(exe); + progress.Report((1.0, "Fertig")); } + /// + /// 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. + /// + 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 TopLevelNames(ZipArchive zip) => zip.Entries .Select(e => e.FullName.Replace('\\', '/').Split('/')[0]) diff --git a/installer/MainWindow.xaml.cs b/installer/MainWindow.xaml.cs index b7606ca..6a55996 100644 --- a/installer/MainWindow.xaml.cs +++ b/installer/MainWindow.xaml.cs @@ -300,7 +300,16 @@ public partial class MainWindow : Window await RunInstall(DirBox.Text.Trim(), DesktopLinkBox.IsChecked == true, update: false); break; 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(); break; } diff --git a/installer/obj/AeroMCSetup.csproj.nuget.dgspec.json b/installer/obj/AeroMCSetup.csproj.nuget.dgspec.json index 31aecad..fc86f45 100644 --- a/installer/obj/AeroMCSetup.csproj.nuget.dgspec.json +++ b/installer/obj/AeroMCSetup.csproj.nuget.dgspec.json @@ -5,7 +5,7 @@ }, "projects": { "C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj": { - "version": "1.2.1", + "version": "1.2.3", "restore": { "projectUniqueName": "C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj", "projectName": "AeroMC-Setup", diff --git a/installer/obj/Release/net48/AeroMC-Setup.exe b/installer/obj/Release/net48/AeroMC-Setup.exe index a468912c8aab34967afe812c3b819e00e0224ec2..4efa8c1ae0ad467e2812b3f996515dbca2958669 100644 GIT binary patch delta 27452 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%0A!ZUP#aKji75R?)gv}jii|un7V862t1me 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 EKUU%Hi~s-t diff --git a/installer/obj/Release/net48/AeroMCSetup.AssemblyInfo.cs b/installer/obj/Release/net48/AeroMCSetup.AssemblyInfo.cs index 53e4a4b..7d9e514 100644 --- a/installer/obj/Release/net48/AeroMCSetup.AssemblyInfo.cs +++ b/installer/obj/Release/net48/AeroMCSetup.AssemblyInfo.cs @@ -14,11 +14,11 @@ using System.Reflection; [assembly: System.Reflection.AssemblyConfigurationAttribute("Release")] [assembly: System.Reflection.AssemblyCopyrightAttribute("Copyright (c) M_Viper. Alle Rechte vorbehalten.")] [assembly: System.Reflection.AssemblyDescriptionAttribute("Installiert AeroMC")] -[assembly: System.Reflection.AssemblyFileVersionAttribute("1.2.1.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.2.1")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.2.3.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.2.3")] [assembly: System.Reflection.AssemblyProductAttribute("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")] // Von der MSBuild WriteCodeFragment-Klasse generiert. diff --git a/installer/obj/Release/net48/AeroMCSetup.AssemblyInfoInputs.cache b/installer/obj/Release/net48/AeroMCSetup.AssemblyInfoInputs.cache index 54445a8..c4d0b92 100644 --- a/installer/obj/Release/net48/AeroMCSetup.AssemblyInfoInputs.cache +++ b/installer/obj/Release/net48/AeroMCSetup.AssemblyInfoInputs.cache @@ -1 +1 @@ -a17a2bc3b05635d3c29adc5c6884bde0a77826489d54b55a9b7744bfaa1ef206 +111db8ee3dc7193961107c73afd8913fff02b92fd4e49b8b7e4bf051181600bd diff --git a/installer/obj/project.assets.json b/installer/obj/project.assets.json index 8f5d57f..1c28bfb 100644 --- a/installer/obj/project.assets.json +++ b/installer/obj/project.assets.json @@ -418,7 +418,7 @@ "C:\\Users\\Viper\\.nuget\\packages\\": {} }, "project": { - "version": "1.2.1", + "version": "1.2.3", "restore": { "projectUniqueName": "C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj", "projectName": "AeroMC-Setup", diff --git a/installer/obj/project.nuget.cache b/installer/obj/project.nuget.cache index 9e61586..2a18427 100644 --- a/installer/obj/project.nuget.cache +++ b/installer/obj/project.nuget.cache @@ -1,6 +1,6 @@ { "version": 2, - "dgSpecHash": "zBMXx+S6DFU=", + "dgSpecHash": "5nf4o8NXPS0=", "success": true, "projectFilePath": "C:\\Users\\Viper\\Desktop\\AeroMC-Launcher\\installer\\AeroMCSetup.csproj", "expectedPackageFiles": [