/* * This file is a part of the SteamWar software. * * Copyright (C) 2026 SteamWar.de-Serverteam * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package de.steamwar; import java.io.IOException; import java.io.InputStream; import java.nio.file.*; import java.util.ArrayList; import java.util.List; /** * Command-line tool that produces a widened copy of a JAR for use as a * compile-time stub in IntelliJ / Gradle. * * Usage: * java -jar jar-widener.jar [file.accesswidener ...] * * The output JAR is identical to the input JAR except that every class * targeted by the access widener entries has its access flags patched: * accessible → public * extendable → public + non-final * mutable → non-final field * * Intended for use as a Gradle task so IntelliJ sees the already-widened * class when you Ctrl+click NMS code, and javac compiles without complaints. */ public class Main { public static void main(String[] args) throws Exception { if (args.length < 2) { System.err.println("Usage: jar-widener [*.accesswidener ...]"); System.exit(1); } Path inputJar = Path.of(args[0]); Path outputJar = Path.of(args[1]); if (!Files.exists(inputJar)) { System.err.println("Input JAR not found: " + inputJar); System.exit(1); } // --- Collect all access widener entries --- List entries = new ArrayList<>(); if (args.length > 2) { for (int i = 2; i < args.length; i++) { Path awFile = Path.of(args[i]); if (!Files.exists(awFile)) { System.err.println("Warning: access widener file not found, skipping: " + awFile); continue; } try (InputStream in = Files.newInputStream(awFile)) { List parsed = AccessWidenerParser.parse(in); System.out.println("Loaded " + parsed.size() + " entries from " + awFile.getFileName()); entries.addAll(parsed); } } } if (entries.isEmpty()) { System.out.println("No access widener entries found — copying JAR unchanged."); Files.createDirectories(outputJar.getParent()); Files.copy(inputJar, outputJar, StandardCopyOption.REPLACE_EXISTING); return; } System.out.println("Widening " + inputJar.getFileName() + " with " + entries.size() + " total entr" + (entries.size() == 1 ? "y" : "ies") + "..."); // --- Copy input → output, transforming .class files in place --- Files.createDirectories(outputJar.getParent()); Files.copy(inputJar, outputJar, StandardCopyOption.REPLACE_EXISTING); ClassPatcher patcher = new ClassPatcher(entries); try (FileSystem fs = FileSystems.newFileSystem(outputJar)) { // Walk every .class entry in the JAR try (var stream = Files.walk(fs.getPath("/"))) { stream.filter(p -> p.toString().endsWith(".class")) .forEach(classPath -> patchClass(fs, classPath, patcher)); } } System.out.println("Done. Widened JAR written to " + outputJar); } private static void patchClass(FileSystem fs, Path classPath, ClassPatcher patcher) { // Derive internal class name from path e.g. /net/minecraft/Foo.class → net/minecraft/Foo String internalName = classPath.toString() .replaceFirst("^/", "") .replace(".class", ""); try { byte[] original = Files.readAllBytes(classPath); byte[] patched = patcher.patch(internalName, original); if (patched != null) { Files.write(classPath, patched); System.out.println(" Widened: " + internalName); } } catch (IOException e) { System.err.println(" Warning: failed to patch " + internalName + ": " + e.getMessage()); } } }