diff --git a/.github/workflows/build-1218.yml b/.github/workflows/build-1218.yml index 4e84cb2..3271634 100644 --- a/.github/workflows/build-1218.yml +++ b/.github/workflows/build-1218.yml @@ -62,8 +62,8 @@ jobs: PARALLELISM=$(($(nproc) * 2)) ./gradlew applyAllPatches --stacktrace --parallel --max-workers=$PARALLELISM --build-cache --no-daemon - - name: Build Shuttle Jar - run: ./gradlew createMojmapShuttleJar --stacktrace --parallel --no-daemon + - name: Build Paperclip Jar + run: ./gradlew createMojmapPaperclipJar --stacktrace --parallel --no-daemon - name: Prepare Release Info run: bash scripts/releaseInfo.sh diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 230c0b0..40b747a 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -26,11 +26,11 @@ jobs: - name: Apply Patches run: ./gradlew applyAllPatches --stacktrace --no-daemon - - name: Build Shuttle Jar - run: ./gradlew createMojmapShuttleJar --stacktrace --no-daemon + - name: Build Paperclip Jar + run: ./gradlew createMojmapPaperclipJar --stacktrace --no-daemon - name: Upload Artifacts uses: actions/upload-artifact@main with: name: DivineMC - path: divinemc-server/build/libs/divinemc-shuttle-*-mojmap.jar + path: divinemc-server/build/libs/divinemc-paperclip-*-mojmap.jar diff --git a/README.md b/README.md index c90cdb1..8f914e8 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Run the following commands in the root directory: ```bash > ./gradlew applyAllPatches # apply all patches -> ./gradlew createMojmapShuttleJar # build the server jar +> ./gradlew createMojmapPaperclipJar # build the server jar ``` For anything else you can refer to our [contribution guide](https://bxteam.org/docs/divinemc/development/contributing). diff --git a/build.gradle.kts b/build.gradle.kts index 5821d2b..4b0f275 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -107,76 +107,6 @@ subprojects { } } -tasks.register("createMojmapShuttleJar") { - dependsOn(":divinemc-server:createMojmapPaperclipJar", "shuttle:shadowJar") - - outputs.upToDateWhen { false } - - val paperclipJarTask = project(":divinemc-server").tasks.getByName("createMojmapPaperclipJar") - val shuttleJarTask = project(":shuttle").tasks.getByName("shadowJar") - - val paperclipJar = paperclipJarTask.outputs.files.singleFile - val shuttleJar = shuttleJarTask.outputs.files.singleFile - val outputDir = paperclipJar.parentFile - val tempDir = File(outputDir, "tempJarWork") - val newJarName = "divinemc-shuttle-${properties["version"]}-mojmap.jar" - - doFirst { - val time = measureTimeMillis { - println("Recompiling Paperclip with Shuttle sources...") - - tempDir.deleteRecursively() - tempDir.mkdirs() - - copy { - from(zipTree(paperclipJar)) - into(tempDir) - } - - val oldPackagePath = "io/papermc/paperclip/" - tempDir.walkTopDown() - .filter { it.isFile && it.relativeTo(tempDir).path.startsWith(oldPackagePath) } - .forEach { it.delete() } - - val shuttlePackagePath = "org/bxteam/shuttle/" - copy { - from(zipTree(shuttleJar)) - include("$shuttlePackagePath**") - into(tempDir) - } - - tempDir.walkBottomUp() - .filter { it.isDirectory && it.listFiles().isNullOrEmpty() } - .forEach { it.delete() } - - val metaInfDir = File(tempDir, "META-INF") - metaInfDir.mkdirs() - File(metaInfDir, "main-class").writeText("net.minecraft.server.Main") - } - println("Finished build in ${time}ms") - } - - archiveFileName.set(newJarName) - destinationDirectory.set(outputDir) - from(tempDir) - - manifest { - attributes( - "Main-Class" to "org.bxteam.shuttle.Shuttle", - "Enable-Native-Access" to "ALL-UNNAMED", - "Premain-Class" to "org.bxteam.shuttle.patch.InstrumentationManager", - "Agent-Class" to "org.bxteam.shuttle.patch.InstrumentationManager", - "Launcher-Agent-Class" to "org.bxteam.shuttle.patch.InstrumentationManager", - "Can-Redefine-Classes" to true, - "Can-Retransform-Classes" to true - ) - } - - doLast { - tempDir.deleteRecursively() - } -} - tasks.register("printMinecraftVersion") { doLast { println(providers.gradleProperty("mcVersion").get().trim()) diff --git a/divinemc-server/minecraft-patches/features/0001-Rebrand.patch b/divinemc-server/minecraft-patches/features/0001-Rebrand.patch index 6a25e7d..3d5a28a 100644 --- a/divinemc-server/minecraft-patches/features/0001-Rebrand.patch +++ b/divinemc-server/minecraft-patches/features/0001-Rebrand.patch @@ -31,29 +31,19 @@ index 97844ec1ccc986eb3d3a185d78a03ceeca49fc1a..5e40ec3fbe6e6d5f98ad98df7d4c27d6 @Override diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java -index b06c2c4aa77edafb374f7cf0406cf4d29c6e7f9f..c3ac5ba5d8084fefc0e9f58b09b521ef516126eb 100644 +index b06c2c4aa77edafb374f7cf0406cf4d29c6e7f9f..0e07db9311d480c97251be6277f9d5d103ee9f22 100644 --- a/net/minecraft/server/Main.java +++ b/net/minecraft/server/Main.java -@@ -64,41 +64,39 @@ import org.slf4j.Logger; - public class Main { - private static final Logger LOGGER = LogUtils.getLogger(); - -+ public static void main(String[] arguments) { -+ OptionSet optionSet = org.bxteam.divinemc.DivineBootstrap.bootstrap(arguments); -+ +@@ -69,36 +69,29 @@ public class Main { + ) + @DontObfuscate + public static void main(final OptionSet optionSet) { // CraftBukkit - replaces main(String[] args) ++ // DivineMC start - Rebrand + io.papermc.paper.ServerBuildInfo info = io.papermc.paper.ServerBuildInfo.buildInfo(); + if (io.papermc.paper.ServerBuildInfoImpl.IS_EXPERIMENTAL) { + LOGGER.warn("Running an experimental version of {}, please proceed with caution.", info.brandName()); + } + -+ main(optionSet); -+ } -+ - @SuppressForbidden( - reason = "System.out needed before bootstrap" - ) - @DontObfuscate - public static void main(final OptionSet optionSet) { // CraftBukkit - replaces main(String[] args) + Path path2 = Paths.get("eula.txt"); + Eula eula = new Eula(path2); + boolean eulaAgreed = Boolean.getBoolean("com.mojang.eula.agree"); @@ -66,7 +56,7 @@ index b06c2c4aa77edafb374f7cf0406cf4d29c6e7f9f..c3ac5ba5d8084fefc0e9f58b09b521ef + LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info."); + return; + } -+ ++ // DivineMC end - Rebrand io.papermc.paper.util.LogManagerShutdownThread.hook(); // Paper - Improved watchdog support SharedConstants.tryDetectVersion(); - /* CraftBukkit start - Replace everything @@ -100,7 +90,7 @@ index b06c2c4aa77edafb374f7cf0406cf4d29c6e7f9f..c3ac5ba5d8084fefc0e9f58b09b521ef Path path = (Path) optionSet.valueOf("pidFile"); // CraftBukkit if (path != null) { writePidFile(path); -@@ -124,8 +122,6 @@ public class Main { +@@ -124,8 +117,6 @@ public class Main { DedicatedServerSettings dedicatedServerSettings = new DedicatedServerSettings(optionSet); // CraftBukkit - CLI argument support dedicatedServerSettings.forceSave(); RegionFileVersion.configure(dedicatedServerSettings.getProperties().regionFileComression); @@ -109,7 +99,7 @@ index b06c2c4aa77edafb374f7cf0406cf4d29c6e7f9f..c3ac5ba5d8084fefc0e9f58b09b521ef // Paper start - load config files early for access below if needed org.bukkit.configuration.file.YamlConfiguration bukkitConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionSet.valueOf("bukkit-settings")); org.bukkit.configuration.file.YamlConfiguration spigotConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionSet.valueOf("spigot-settings")); -@@ -148,19 +144,6 @@ public class Main { +@@ -148,19 +139,6 @@ public class Main { return; } @@ -130,7 +120,7 @@ index b06c2c4aa77edafb374f7cf0406cf4d29c6e7f9f..c3ac5ba5d8084fefc0e9f58b09b521ef String awtException = io.papermc.paper.util.ServerEnvironment.awtDependencyCheck(); if (awtException != null) { diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 3de43e4edb33bb2c657a315ad2676ce44ee3bd6a..2d01252a66e59f69ff69055b83d7e881f2f3e5cd 100644 +index b68749354f2a55e41f2b39f96ac9d1190092d672..e04be8b21514db5b7ecb2419b4b37caf29960c5a 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -1184,6 +1184,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop getStartupVersionMessages() { -- final String javaSpecVersion = System.getProperty("java.specification.version"); -- final String javaVmName = System.getProperty("java.vm.name"); -- final String javaVmVersion = System.getProperty("java.vm.version"); -- final String javaVendor = System.getProperty("java.vendor"); -- final String javaVendorVersion = System.getProperty("java.vendor.version"); -- final String osName = System.getProperty("os.name"); -- final String osVersion = System.getProperty("os.version"); -- final String osArch = System.getProperty("os.arch"); -- -- final ServerBuildInfo bi = ServerBuildInfo.buildInfo(); -- return List.of( -- String.format( -- "Running Java %s (%s %s; %s %s) on %s %s (%s)", -- javaSpecVersion, -- javaVmName, -- javaVmVersion, -- javaVendor, -- javaVendorVersion, -- osName, -- osVersion, -- osArch -- ), -- String.format( -- "Loading %s %s for Minecraft %s", -- bi.brandName(), -- bi.asString(ServerBuildInfo.StringRepresentation.VERSION_FULL), -- bi.minecraftVersionId() -- ) -- ); -- } --} ++ private static final Logger LOGGER = LoggerFactory.getLogger("PaperBootstrap"); // DivineMC - Rebrand + + private PaperBootstrap() { + } +@@ -49,7 +49,13 @@ public final class PaperBootstrap { + bi.brandName(), + bi.asString(ServerBuildInfo.StringRepresentation.VERSION_FULL), + bi.minecraftVersionId() ++ ), ++ // DivineMC start - Rebrand ++ String.format( ++ "Running JVM args %s", ++ java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments().toString() + ) ++ // DivineMC end - Rebrand + ); + } + } diff --git a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java index 1b0ee48e28aaa68ddb1f28c23d3c5f5f40505c98..4d3518795a238d87ca4f3df0bee074ab5bcc2734 100644 --- a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java @@ -230,98 +196,6 @@ index 62e2d5704c348955bc8284dc2d54c933b7bcdd06..341f13e57896f03058ea3ec68e69b7cb @Override public void executeAsync(final Runnable runnable) { MCUtil.scheduleAsyncTask(this.catching(runnable, "asynchronous")); -diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 748bd9650da4a209743b7a5dde584b2e19c5a578..3a4239d6f5768f7e2b6025477670dd2eb9f8cbc4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java -+++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -24,7 +24,7 @@ public class Main { - } - // Paper end - Reset loggers after shutdown - -- public static void main(String[] args) { -+ public static OptionParser main(String[] args) { // DivineMC - Rebrand - if (System.getProperty("jdk.nio.maxCachedBufferSize") == null) System.setProperty("jdk.nio.maxCachedBufferSize", "262144"); // Paper - cap per-thread NIO cache size; https://www.evanjones.ca/java-bytebuffer-leak.html - OptionParser parser = new OptionParser() { - { -@@ -180,77 +180,6 @@ public class Main { - } - }; - -- OptionSet options = null; -- -- try { -- options = parser.parse(args); -- } catch (joptsimple.OptionException ex) { -- Logger.getLogger(Main.class.getName()).log(Level.SEVERE, ex.getLocalizedMessage()); -- } -- -- if ((options == null) || (options.has("?"))) { -- try { -- parser.printHelpOn(System.out); -- } catch (IOException ex) { -- Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); -- } -- } else if (options.has("v")) { -- System.out.println(CraftServer.class.getPackage().getImplementationVersion()); -- } else { -- // Do you love Java using + and ! as string based identifiers? I sure do! -- String path = new File(".").getAbsolutePath(); -- if (path.contains("!") || path.contains("+")) { -- System.err.println("Cannot run server in a directory with ! or + in the pathname. Please rename the affected folders and try again."); -- return; -- } -- -- // Paper start - Improve java version check -- boolean skip = Boolean.getBoolean("Paper.IgnoreJavaVersion"); -- String javaVersionName = System.getProperty("java.version"); -- // J2SE SDK/JRE Version String Naming Convention -- boolean isPreRelease = javaVersionName.contains("-"); -- if (isPreRelease) { -- if (!skip) { -- System.err.println("Unsupported Java detected (" + javaVersionName + "). You are running an unsupported, non official, version. Only general availability versions of Java are supported. Please update your Java version. See https://docs.papermc.io/paper/faq#unsupported-java-detected-what-do-i-do for more information."); -- return; -- } -- -- System.err.println("Unsupported Java detected ("+ javaVersionName + "), but the check was skipped. Proceed with caution! "); -- } -- // Paper end - Improve java version check -- -- try { -- if (options.has("nojline")) { -- System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); -- useJline = false; -- } -- -- if (options.has("noconsole")) { -- Main.useConsole = false; -- useJline = false; // Paper -- System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper -- } -- -- if (false && Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { // Purpur - Disable outdated build check -- Date buildDate = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(Main.class.getPackage().getImplementationVendor()); // Paper -- -- Calendar deadline = Calendar.getInstance(); -- deadline.add(Calendar.DAY_OF_YEAR, -14); -- if (buildDate.before(deadline.getTime())) { -- // Paper start - This is some stupid bullshit -- System.err.println("*** Warning, you've not updated in a while! ***"); -- System.err.println("*** Please download a new build from https://papermc.io/downloads/paper ***"); -- // Paper end -- } -- } -- -- System.setProperty("library.jansi.version", "Paper"); // Paper - set meaningless jansi version to prevent git builds from crashing on Windows -- System.setProperty("jdk.console", "java.base"); // Paper - revert default console provider back to java.base so we can have our own jline -- -- io.papermc.paper.PaperBootstrap.boot(options); -- } catch (Throwable t) { -- t.printStackTrace(); -- } -- } -+ return parser; // DivineMC - Rebrand - } - } diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java index 2e7c3d4befeb6256ce81ecaa9ed4e8fbcb21651e..a839dbbb72f48b8f8736d9f4693c528686570732 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/DivineBootstrap.java b/divinemc-server/src/main/java/org/bxteam/divinemc/DivineBootstrap.java deleted file mode 100644 index bb12442..0000000 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/DivineBootstrap.java +++ /dev/null @@ -1,111 +0,0 @@ -package org.bxteam.divinemc; - -import io.papermc.paper.ServerBuildInfo; -import joptsimple.OptionParser; -import joptsimple.OptionSet; -import net.minecraft.SharedConstants; -import org.bukkit.craftbukkit.CraftServer; -import org.bukkit.craftbukkit.Main; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.util.List; - -public class DivineBootstrap { - private static final Logger LOGGER = LoggerFactory.getLogger("DivineBootstrap"); - - public static OptionSet bootstrap(String[] args) { - OptionParser parser = Main.main(args); - OptionSet options = parser.parse(args); - - if ((options == null) || (options.has("?"))) { - try { - parser.printHelpOn(System.out); - } catch (IOException ex) { - LOGGER.warn(ex.getMessage()); - } - } else if (options.has("v")) { - System.out.println(CraftServer.class.getPackage().getImplementationVersion()); - } else { - String path = new File(".").getAbsolutePath(); - if (path.contains("!") || path.contains("+")) { - System.err.println("Cannot run server in a directory with ! or + in the pathname. Please rename the affected folders and try again."); - System.exit(70); - } - - boolean skip = Boolean.getBoolean("Paper.IgnoreJavaVersion"); - String javaVersionName = System.getProperty("java.version"); - boolean isPreRelease = javaVersionName.contains("-"); - if (isPreRelease) { - if (!skip) { - System.err.println("Unsupported Java detected (" + javaVersionName + "). You are running an unsupported, non official, version. Only general availability versions of Java are supported. Please update your Java version. See https://docs.papermc.io/paper/faq#unsupported-java-detected-what-do-i-do for more information."); - System.exit(70); - } - - System.err.println("Unsupported Java detected ("+ javaVersionName + "), but the check was skipped. Proceed with caution! "); - } - - try { - if (options.has("nojline")) { - System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); - Main.useJline = false; - } - - if (options.has("noconsole")) { - Main.useConsole = false; - Main.useJline = false; - System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper - } - - System.setProperty("library.jansi.version", "Paper"); - System.setProperty("jdk.console", "java.base"); - - SharedConstants.tryDetectVersion(); - getStartupVersionMessages().forEach(LOGGER::info); - } catch (Throwable t) { - LOGGER.error("Failed to initialize the server", t); - } - } - - return options; - } - - private static List getStartupVersionMessages() { - final String javaSpecVersion = System.getProperty("java.specification.version"); - final String javaVmName = System.getProperty("java.vm.name"); - final String javaVmVersion = System.getProperty("java.vm.version"); - final String javaVendor = System.getProperty("java.vendor"); - final String javaVendorVersion = System.getProperty("java.vendor.version"); - final String osName = System.getProperty("os.name"); - final String osVersion = System.getProperty("os.version"); - final String osArch = System.getProperty("os.arch"); - - final ServerBuildInfo bi = ServerBuildInfo.buildInfo(); - return List.of( - String.format( - "Running Java %s (%s %s; %s %s) on %s %s (%s)", - javaSpecVersion, - javaVmName, - javaVmVersion, - javaVendor, - javaVendorVersion, - osName, - osVersion, - osArch - ), - String.format( - "Loading %s %s for Minecraft %s", - bi.brandName(), - bi.asString(ServerBuildInfo.StringRepresentation.VERSION_FULL), - bi.minecraftVersionId() - ), - String.format( - "Running JVM args %s", - ManagementFactory.getRuntimeMXBean().getInputArguments().toString() - ) - ); - } -} diff --git a/scripts/releaseInfo.sh b/scripts/releaseInfo.sh index 455bd7c..78d563f 100644 --- a/scripts/releaseInfo.sh +++ b/scripts/releaseInfo.sh @@ -28,7 +28,7 @@ make_latest=$([ "$experimental" = "true" ] && echo "false" || echo "true") rm -f $changelog -mv divinemc-server/build/libs/divinemc-shuttle-"$version"-mojmap.jar "$jarName" +mv divinemc-server/build/libs/divinemc-paperclip-"$version"-mojmap.jar "$jarName" { echo "name=$divinemcid" echo "tag=$tagid" diff --git a/settings.gradle.kts b/settings.gradle.kts index 474fed7..67c5e4d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -35,7 +35,7 @@ if (!file(".git").exists()) { rootProject.name = "DivineMC" -for (name in listOf("divinemc-api", "divinemc-server", "shuttle")) { +for (name in listOf("divinemc-api", "divinemc-server")) { val projName = name.lowercase(Locale.ENGLISH) include(projName) findProject(":$projName")!!.projectDir = file(name) diff --git a/shuttle/build.gradle.kts b/shuttle/build.gradle.kts deleted file mode 100644 index b6ecb04..0000000 --- a/shuttle/build.gradle.kts +++ /dev/null @@ -1,57 +0,0 @@ -plugins { - id("java") - id("application") - id("maven-publish") - id("com.gradleup.shadow") version "8.3.8" -} - -val mainClass = "org.bxteam.shuttle.Shuttle" - -repositories { - mavenCentral() -} - -dependencies { - implementation("io.sigpipe:jbsdiff:1.0") -} - -java { - toolchain.languageVersion.set(JavaLanguageVersion.of(21)) - withSourcesJar() -} - -tasks.withType().configureEach { - options.encoding = "UTF-8" - options.release = 21 -} - -tasks.jar { - val jar = tasks.named("shadowJar") - dependsOn(jar) - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - from(zipTree(jar.map { it.outputs.files.singleFile })) - - manifest { - attributes( - "Main-Class" to mainClass, - "Enable-Native-Access" to "ALL-UNNAMED", - "Premain-Class" to "org.bxteam.shuttle.patch.InstrumentationManager", - "Agent-Class" to "org.bxteam.shuttle.patch.InstrumentationManager", - "Launcher-Agent-Class" to "org.bxteam.shuttle.patch.InstrumentationManager", - "Can-Redefine-Classes" to true, - "Can-Retransform-Classes" to true - ) - } -} - -project.setProperty("mainClassName", mainClass) - -tasks.shadowJar { - val prefix = "paperclip.libs" - listOf("org.apache", "org.tukaani", "io.sigpipe").forEach { pack -> - relocate(pack, "$prefix.$pack") - } - - exclude("META-INF/LICENSE.txt") - exclude("META-INF/NOTICE.txt") -} diff --git a/shuttle/src/main/java/org/bxteam/shuttle/Shuttle.java b/shuttle/src/main/java/org/bxteam/shuttle/Shuttle.java deleted file mode 100644 index 715c958..0000000 --- a/shuttle/src/main/java/org/bxteam/shuttle/Shuttle.java +++ /dev/null @@ -1,170 +0,0 @@ -package org.bxteam.shuttle; - -import org.bxteam.shuttle.logger.Logger; -import org.bxteam.shuttle.patch.LibraryLoader; -import org.bxteam.shuttle.patch.PatchBuilder; - -import java.io.*; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -public final class Shuttle { - private static final String MAIN_CLASS_RESOURCE = "/META-INF/main-class"; - private static final String VERSION_RESOURCE = "/version.json"; - private static final String PATCH_ONLY_PROPERTY = "paperclip.patchonly"; - private static final String BUNDLER_MAIN_CLASS_PROPERTY = "bundlerMainClass"; - private static final String BUNDLER_REPO_DIR_PROPERTY = "bundlerRepoDir"; - private static final String MAIN_THREAD_NAME = "main"; - - public static void main(String[] arguments) { - new Shuttle().run(arguments); - } - - private void run(String[] arguments) { - try { - String defaultMainClassName = readMainClass(); - String mainClassName = System.getProperty(BUNDLER_MAIN_CLASS_PROPERTY, defaultMainClassName); - String repoDir = System.getProperty(BUNDLER_REPO_DIR_PROPERTY, ""); - - setupDirectories(repoDir); - - Provider versionProvider = this::readVersionFromResource; - - executePatchingPhase(versionProvider); - - if (shouldExitAfterPatching()) { - System.exit(0); - } - - executeLibraryLoadingPhase(versionProvider); - - startTargetApplication(mainClassName, arguments); - } catch (Exception e) { - Logger.error("Failed to extract server libraries, exiting", e); - System.exit(1); - } - } - - private String readMainClass() throws IOException { - return readResourceContent(MAIN_CLASS_RESOURCE, BufferedReader::readLine); - } - - private String readVersionFromResource() throws IOException { - String jsonContent = readResourceContent(VERSION_RESOURCE, this::readAllLines); - return extractVersionFromJson(jsonContent); - } - - private T readResourceContent(String resourcePath, ResourceParser parser) throws IOException { - try (InputStream inputStream = getClass().getResourceAsStream(resourcePath)) { - if (inputStream == null) { - throw new IOException("Resource not found: " + resourcePath); - } - - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { - return parser.parse(reader); - } - } - } - - private String readAllLines(BufferedReader reader) throws IOException { - StringBuilder content = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - content.append(line); - } - return content.toString(); - } - - private String extractVersionFromJson(String jsonContent) throws IOException { - String prefix = "\"id\": \""; - int startIndex = jsonContent.indexOf(prefix); - - if (startIndex == -1) { - throw new IOException("Version ID not found in JSON content"); - } - - startIndex += prefix.length(); - int endIndex = jsonContent.indexOf("\"", startIndex); - - if (endIndex == -1) { - throw new IOException("Malformed version ID in JSON content"); - } - - return jsonContent.substring(startIndex, endIndex); - } - - private void setupDirectories(String repoDir) throws IOException { - if (!repoDir.isEmpty()) { - Path outputDir = Paths.get(repoDir); - Files.createDirectories(outputDir); - } - } - - private void executePatchingPhase(Provider versionProvider) throws IOException { - new PatchBuilder().start(versionProvider); - } - - private boolean shouldExitAfterPatching() { - return Boolean.getBoolean(PATCH_ONLY_PROPERTY); - } - - private void executeLibraryLoadingPhase(Provider versionProvider) throws IOException { - new LibraryLoader().start(versionProvider); - } - - private void startTargetApplication(String mainClassName, String[] arguments) { - if (mainClassName == null || mainClassName.trim().isEmpty()) { - Logger.warn("No main class specified, exiting"); - return; - } - - Logger.info("Starting " + mainClassName); - - Thread applicationThread = new Thread(() -> { - try { - invokeMainMethod(mainClassName, arguments); - } catch (Throwable e) { - ExceptionHandler.INSTANCE.rethrow(e); - } - }, MAIN_THREAD_NAME); - - applicationThread.start(); - } - - private void invokeMainMethod(String mainClassName, String[] arguments) throws Throwable { - Class mainClass = Class.forName(mainClassName); - MethodHandle mainHandle = MethodHandles.lookup() - .findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class)) - .asFixedArity(); - - mainHandle.invoke((Object) arguments); - } - - @FunctionalInterface - private interface ResourceParser { - T parse(BufferedReader reader) throws IOException; - } - - @FunctionalInterface - public interface Provider { - T get() throws IOException; - } - - private static final class ExceptionHandler { - static final ExceptionHandler INSTANCE = new ExceptionHandler(); - - private ExceptionHandler() { - throw new UnsupportedOperationException("Utility class cannot be instantiated"); - } - - void rethrow(Throwable exception) { - throw new RuntimeException("Exception in target application", exception); - } - } -} diff --git a/shuttle/src/main/java/org/bxteam/shuttle/logger/Logger.java b/shuttle/src/main/java/org/bxteam/shuttle/logger/Logger.java deleted file mode 100644 index fc2765b..0000000 --- a/shuttle/src/main/java/org/bxteam/shuttle/logger/Logger.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.bxteam.shuttle.logger; - -import java.time.LocalTime; -import java.time.format.DateTimeFormatter; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.logging.ConsoleHandler; -import java.util.logging.Formatter; - -public class Logger { - private static final java.util.logging.Logger LOGGER = java.util.logging.Logger.getLogger("Shuttle"); - private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); - - static { - LOGGER.setUseParentHandlers(false); - - ConsoleHandler consoleHandler = new ConsoleHandler(); - consoleHandler.setFormatter(new ShuttleFormatter()); - consoleHandler.setLevel(Level.ALL); - LOGGER.addHandler(consoleHandler); - - LOGGER.setLevel(Level.INFO); - } - - public static void info(String message) { - LOGGER.info(message); - } - - public static void warn(String message) { - LOGGER.warning(message); - } - - public static void error(String message) { - LOGGER.severe(message); - } - - public static void error(String message, Throwable throwable) { - LOGGER.log(Level.SEVERE, message, throwable); - } - - public static void debug(String message) { - LOGGER.fine(message); - } - - public static void setLevel(Level level) { - LOGGER.setLevel(level); - LOGGER.getHandlers()[0].setLevel(level); - } - - private static class ShuttleFormatter extends Formatter { - @Override - public String format(LogRecord record) { - String time = LocalTime.now().format(TIME_FORMATTER); - String level = record.getLevel().getName(); - String message = record.getMessage(); - - StringBuilder sb = new StringBuilder(); - sb.append("[").append(time).append(" ").append(level).append("]: "); - sb.append("[Shuttle] "); - sb.append(message); - sb.append(System.lineSeparator()); - - if (record.getThrown() != null) { - java.io.StringWriter sw = new java.io.StringWriter(); - java.io.PrintWriter pw = new java.io.PrintWriter(sw); - record.getThrown().printStackTrace(pw); - sb.append(sw.toString()); - } - - return sb.toString(); - } - } -} diff --git a/shuttle/src/main/java/org/bxteam/shuttle/patch/InstrumentationManager.java b/shuttle/src/main/java/org/bxteam/shuttle/patch/InstrumentationManager.java deleted file mode 100644 index 6671d5f..0000000 --- a/shuttle/src/main/java/org/bxteam/shuttle/patch/InstrumentationManager.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.bxteam.shuttle.patch; - -import java.lang.instrument.Instrumentation; - -public final class InstrumentationManager { - private static volatile Instrumentation instrumentation; - private static final Object LOCK = new Object(); - - private InstrumentationManager() { - throw new UnsupportedOperationException("Utility class cannot be instantiated"); - } - - public static void premain(final String agentArgs, final Instrumentation inst) { - agentmain(agentArgs, inst); - } - - public static void agentmain(final String agentArgs, final Instrumentation inst) { - if (inst == null) { - throw new IllegalArgumentException("Instrumentation instance cannot be null"); - } - - synchronized (LOCK) { - if (instrumentation == null) { - instrumentation = inst; - } - } - } - - public static Instrumentation getInstrumentation() { - final Instrumentation inst = instrumentation; - if (inst == null) { - throw new IllegalStateException( - "Instrumentation has not been initialized. " + - "Ensure the agent is properly loaded via -javaagent or attach mechanism." - ); - } - return inst; - } -} diff --git a/shuttle/src/main/java/org/bxteam/shuttle/patch/LibraryLoader.java b/shuttle/src/main/java/org/bxteam/shuttle/patch/LibraryLoader.java deleted file mode 100644 index d466496..0000000 --- a/shuttle/src/main/java/org/bxteam/shuttle/patch/LibraryLoader.java +++ /dev/null @@ -1,255 +0,0 @@ -package org.bxteam.shuttle.patch; - -import org.bxteam.shuttle.Shuttle; -import org.bxteam.shuttle.logger.Logger; - -import java.io.*; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLConnection; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Enumeration; -import java.util.List; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -public class LibraryLoader { - private static final String LIBRARIES_DIR = "libraries"; - private static final String CACHE_DIR = "cache"; - private static final String LIBRARIES_LIST_RESOURCE = "/META-INF/libraries.list"; - private static final String LIBRARIES_PREFIX = "META-INF/libraries/"; - private static final String PATCH_EXTENSION = ".patch"; - private static final int BUFFER_SIZE = 8192; - - private static final List MAVEN_REPOSITORIES = List.of( - "https://repo.papermc.io/repository/maven-public/", - "https://jitpack.io", - "https://s01.oss.sonatype.org/content/repositories/snapshots/", - "https://repo1.maven.org/maven2/", - "https://libraries.minecraft.net/" - ); - - private static void copyStream(InputStream input, OutputStream output) throws IOException { - byte[] buffer = new byte[BUFFER_SIZE]; - int bytesRead; - while ((bytesRead = input.read(buffer)) != -1) { - output.write(buffer, 0, bytesRead); - } - } - - public void start(Shuttle.Provider versionProvider) throws IOException { - try { - start(versionProvider.get()); - } catch (IOException e) { - throw e; - } catch (Exception e) { - throw new IOException("Error during library loading process", e); - } - } - - public void start(String mcVersion) throws IOException { - if (mcVersion == null || mcVersion.trim().isEmpty()) { - throw new IllegalArgumentException("Minecraft version cannot be null or empty"); - } - - Logger.info("Unpacking and linking library jars"); - - try { - createLibrariesDirectory(); - - Path currentJar = getCurrentJarPath(); - extractLibrariesFromJar(currentJar); - - Path vanillaBundler = Paths.get(CACHE_DIR, "vanilla-bundler-" + mcVersion + ".jar"); - if (Files.exists(vanillaBundler)) { - extractLibrariesFromJar(vanillaBundler); - } - } catch (Exception e) { - throw new IOException("Failed to load libraries for version " + mcVersion, e); - } - } - - private void createLibrariesDirectory() throws IOException { - Path librariesDir = Paths.get(LIBRARIES_DIR); - if (!Files.exists(librariesDir)) { - Files.createDirectories(librariesDir); - } - } - - private Path getCurrentJarPath() throws IOException { - try { - return Paths.get(LibraryLoader.class.getProtectionDomain() - .getCodeSource().getLocation().toURI()); - } catch (URISyntaxException e) { - throw new IOException("Failed to get current JAR path", e); - } - } - - private void extractLibrariesFromJar(Path jarPath) throws IOException { - try (JarFile jarFile = new JarFile(jarPath.toFile())) { - Enumeration entries = jarFile.entries(); - - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - - if (entry.getName().startsWith(LIBRARIES_PREFIX)) { - processLibraryEntry(jarFile, entry); - } - } - } - } - - private void processLibraryEntry(JarFile jarFile, JarEntry entry) throws IOException { - String relativePath = entry.getName().substring(LIBRARIES_PREFIX.length()); - File extractedFile = new File(LIBRARIES_DIR, relativePath); - - if (entry.isDirectory()) { - Files.createDirectories(extractedFile.toPath()); - } else if (entry.getName().endsWith(PATCH_EXTENSION)) { - processPatchEntry(entry); - } else { - extractLibraryFile(jarFile, entry, extractedFile); - } - } - - private void extractLibraryFile(JarFile jarFile, JarEntry entry, File extractedFile) throws IOException { - File parentDir = extractedFile.getParentFile(); - if (parentDir != null && !parentDir.exists()) { - parentDir.mkdirs(); - } - - try (InputStream input = jarFile.getInputStream(entry); - FileOutputStream output = new FileOutputStream(extractedFile)) { - copyStream(input, output); - } - - addToClasspath(extractedFile); - } - - private void processPatchEntry(JarEntry entry) throws IOException { - try (InputStream inputStream = LibraryLoader.class.getResourceAsStream(LIBRARIES_LIST_RESOURCE); - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { - - if (inputStream == null) { - throw new IOException("Libraries list resource not found: " + LIBRARIES_LIST_RESOURCE); - } - - String entryDirectoryName = extractDirectoryName(entry.getName()); - boolean foundArtifact = false; - String line; - - while ((line = reader.readLine()) != null) { - LibraryInfo libraryInfo = parseLibraryLine(line); - if (libraryInfo == null) continue; - - String libraryDirectoryName = extractDirectoryName(libraryInfo.jarPath); - - if (entryDirectoryName.equalsIgnoreCase(libraryDirectoryName)) { - foundArtifact = true; - handleLibraryFromPatch(libraryInfo); - } - } - - if (!foundArtifact) { - String artifactName = extractArtifactName(entry.getName()); - Logger.error("Unable to find library: " + artifactName); - throw new RuntimeException("Missing library: " + artifactName); - } - } - } - - private String extractDirectoryName(String path) { - return path.replaceFirst("^" + LIBRARIES_PREFIX, "") - .replaceFirst("/[^/]+$", ""); - } - - private String extractArtifactName(String entryName) { - String[] parts = entryName.split("/"); - String fileName = parts[parts.length - 1]; - return fileName.replace(PATCH_EXTENSION, ""); - } - - private LibraryInfo parseLibraryLine(String line) { - if (line == null || line.trim().isEmpty()) { - return null; - } - - String[] parts = line.split("\\s+"); - if (parts.length < 3) { - return null; - } - - return new LibraryInfo(parts[1], parts[2]); - } - - private void handleLibraryFromPatch(LibraryInfo libraryInfo) throws IOException { - File libraryFile = new File(LIBRARIES_DIR, libraryInfo.jarPath); - - if (!libraryFile.exists()) { - File downloadedFile = downloadLibrary(libraryInfo.jarPath); - - if (downloadedFile == null) { - throw new IOException("Failed to download missing library: " + libraryInfo.artifact); - } - - libraryFile = downloadedFile; - } - - addToClasspath(libraryFile); - } - - private File downloadLibrary(String artifactPath) { - for (String repository : MAVEN_REPOSITORIES) { - try { - String downloadUrl = repository + artifactPath; - File downloadedFile = new File(LIBRARIES_DIR, artifactPath); - - if (downloadFile(downloadUrl, downloadedFile)) { - return downloadedFile; - } - } catch (Exception e) { - // Continue trying other repositories - } - } - - return null; - } - - private boolean downloadFile(String urlString, File outputFile) { - try { - File parentDir = outputFile.getParentFile(); - if (parentDir != null && !parentDir.exists()) { - parentDir.mkdirs(); - } - - URL url = new URL(urlString); - URLConnection connection = url.openConnection(); - connection.setConnectTimeout(30000); - connection.setReadTimeout(60000); - - try (InputStream input = connection.getInputStream(); - FileOutputStream output = new FileOutputStream(outputFile)) { - copyStream(input, output); - } - - return outputFile.exists() && outputFile.length() > 0; - - } catch (Exception e) { - if (outputFile.exists()) { - outputFile.delete(); - } - - return false; - } - } - - private void addToClasspath(File jarFile) throws IOException { - try (JarFile jar = new JarFile(jarFile)) { - InstrumentationManager.getInstrumentation().appendToSystemClassLoaderSearch(jar); - } - } - - private record LibraryInfo(String artifact, String jarPath) { } -} diff --git a/shuttle/src/main/java/org/bxteam/shuttle/patch/PatchBuilder.java b/shuttle/src/main/java/org/bxteam/shuttle/patch/PatchBuilder.java deleted file mode 100644 index 1a8b74c..0000000 --- a/shuttle/src/main/java/org/bxteam/shuttle/patch/PatchBuilder.java +++ /dev/null @@ -1,248 +0,0 @@ -package org.bxteam.shuttle.patch; - -import io.sigpipe.jbsdiff.InvalidHeaderException; -import io.sigpipe.jbsdiff.Patch; -import org.apache.commons.compress.compressors.CompressorException; -import org.bxteam.shuttle.Shuttle; -import org.bxteam.shuttle.logger.Logger; - -import java.io.*; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -public class PatchBuilder { - private static final String CACHE_DIR = "cache"; - private static final String VERSIONS_DIR = "versions"; - private static final String META_INF_PREFIX = "/META-INF/"; - private static final int BUFFER_SIZE = 8192; - - private static String readResourceField(int index, String resourceName) { - final String resourcePath = META_INF_PREFIX + resourceName; - - try (InputStream inputStream = PatchBuilder.class.getResourceAsStream(resourcePath); - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { - - if (inputStream == null) { - throw new IOException("Resource not found: " + resourcePath); - } - - String line = reader.readLine(); - if (line == null) { - throw new IOException("Empty resource file: " + resourcePath); - } - - String[] parts = line.split("\\s+"); - if (parts.length <= index) { - throw new IOException("Invalid resource format or index out of bounds: " + resourcePath); - } - - return parts[index].trim(); - - } catch (IOException e) { - throw new RuntimeException("Unable to read " + resourceName + " at index " + index, e); - } - } - - public static String computeFileSha256(File file) throws NoSuchAlgorithmException, IOException { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - - try (FileInputStream fis = new FileInputStream(file); - BufferedInputStream bis = new BufferedInputStream(fis)) { - - byte[] buffer = new byte[BUFFER_SIZE]; - int bytesRead; - - while ((bytesRead = bis.read(buffer)) != -1) { - digest.update(buffer, 0, bytesRead); - } - } - - byte[] hashBytes = digest.digest(); - StringBuilder hexString = new StringBuilder(); - for (byte b : hashBytes) { - hexString.append(String.format("%02x", b)); - } - - return hexString.toString(); - } - - public void start(Shuttle.Provider versionProvider) throws IOException { - try { - start(versionProvider.get()); - } catch (IOException e) { - throw e; - } catch (Exception e) { - throw new IOException("Error during patch building process", e); - } - } - - public void start(String mcVersion) throws IOException { - if (mcVersion == null || mcVersion.trim().isEmpty()) { - throw new IllegalArgumentException("Minecraft version cannot be null or empty"); - } - - Logger.info("Loading Minecraft version " + mcVersion); - - try { - createDirectories(); - - String sha256Hash = readResourceField(0, "download-context"); - String vanillaUrl = readResourceField(1, "download-context"); - - Path vanillaBundler = downloadVanillaBundler(mcVersion, vanillaUrl, sha256Hash); - String patchedJarName = extractPatchedJarName(); - - applyPatches(mcVersion, vanillaBundler, patchedJarName); - } catch (Exception e) { - throw new IOException("Failed to build patched jar for version " + mcVersion, e); - } - } - - private void createDirectories() throws IOException { - Files.createDirectories(Paths.get(VERSIONS_DIR)); - Files.createDirectories(Paths.get(CACHE_DIR)); - } - - private Path downloadVanillaBundler(String mcVersion, String vanillaUrl, String expectedSha256) throws IOException { - Path vanillaBundler = Paths.get(CACHE_DIR, "vanilla-bundler-" + mcVersion + ".jar"); - - boolean needsDownload = !Files.exists(vanillaBundler); - - if (!needsDownload) { - try { - String actualSha256 = computeFileSha256(vanillaBundler.toFile()); - needsDownload = !expectedSha256.equals(actualSha256); - if (needsDownload) { - Logger.info("SHA-256 mismatch, re-downloading vanilla jar"); - } - } catch (NoSuchAlgorithmException e) { - throw new IOException("SHA-256 algorithm not available", e); - } - } - - if (needsDownload) { - Logger.info("Downloading vanilla jar..."); - downloadFile(vanillaUrl, vanillaBundler); - } - - return vanillaBundler; - } - - /** - * Downloads a file from a URL. - */ - private void downloadFile(String urlString, Path outputPath) throws IOException { - URL url = new URL(urlString); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - connection.setConnectTimeout(30000); - connection.setReadTimeout(60000); - - try (BufferedInputStream in = new BufferedInputStream(connection.getInputStream()); - FileOutputStream fos = new FileOutputStream(outputPath.toFile()); - BufferedOutputStream out = new BufferedOutputStream(fos)) { - - byte[] buffer = new byte[BUFFER_SIZE]; - int bytesRead; - while ((bytesRead = in.read(buffer)) != -1) { - out.write(buffer, 0, bytesRead); - } - } finally { - connection.disconnect(); - } - } - - private String extractPatchedJarName() { - String versionListEntry = readResourceField(2, "versions.list"); - String[] parts = versionListEntry.split("/"); - if (parts.length < 2) { - throw new RuntimeException("Invalid versions.list format"); - } - return parts[1]; - } - - private void applyPatches(String mcVersion, Path vanillaBundler, String patchedJarName) throws IOException { - Logger.info("Applying patches..."); - - Path vanillaJar = extractVanillaJar(mcVersion, vanillaBundler); - - File patchFile = extractPatchFile(mcVersion); - - Path outputJar = Paths.get(VERSIONS_DIR, mcVersion, patchedJarName); - Files.createDirectories(outputJar.getParent()); - - try { - applyPatch(vanillaJar.toFile(), patchFile, outputJar.toFile()); - addToClasspath(outputJar.toFile()); - } catch (Exception e) { - throw new IOException("Failed to apply patch", e); - } - } - - private Path extractVanillaJar(String mcVersion, Path vanillaBundler) throws IOException { - Path vanillaJar = Paths.get(CACHE_DIR, "vanilla-" + mcVersion + ".jar"); - - try (JarFile jarFile = new JarFile(vanillaBundler.toFile())) { - JarEntry entry = jarFile.getJarEntry("META-INF/versions/" + mcVersion + "/server-" + mcVersion + ".jar"); - - if (entry == null) { - throw new IOException("Vanilla jar entry not found in bundler for version " + mcVersion); - } - - try (InputStream inputStream = jarFile.getInputStream(entry)) { - Files.copy(inputStream, vanillaJar, StandardCopyOption.REPLACE_EXISTING); - } - } - - return vanillaJar; - } - - private File extractPatchFile(String mcVersion) throws IOException { - String resourcePath = "/META-INF/versions/" + mcVersion + "/server-" + mcVersion + ".jar.patch"; - return extractResourceToFile(resourcePath, CACHE_DIR); - } - - private File extractResourceToFile(String resourcePath, String outputDir) throws IOException { - try (InputStream resourceStream = PatchBuilder.class.getResourceAsStream(resourcePath)) { - if (resourceStream == null) { - throw new IOException("Resource not found: " + resourcePath); - } - - Path outputDirectory = Paths.get(outputDir); - Files.createDirectories(outputDirectory); - - String fileName = Paths.get(resourcePath).getFileName().toString(); - Path outputPath = outputDirectory.resolve(fileName); - - Files.copy(resourceStream, outputPath, StandardCopyOption.REPLACE_EXISTING); - return outputPath.toFile(); - } - } - - private void applyPatch(File vanillaJar, File patchFile, File outputJar) throws IOException, CompressorException, InvalidHeaderException { - byte[] vanillaBytes = Files.readAllBytes(vanillaJar.toPath()); - byte[] patchBytes = Files.readAllBytes(patchFile.toPath()); - - try (FileOutputStream outputStream = new FileOutputStream(outputJar)) { - Patch.patch(vanillaBytes, patchBytes, outputStream); - } - - if (!outputJar.exists()) { - throw new IOException("Patched jar was not created successfully"); - } - } - - private void addToClasspath(File jarFile) throws IOException { - try (JarFile jar = new JarFile(jarFile)) { - InstrumentationManager.getInstrumentation().appendToSystemClassLoaderSearch(jar); - } - } -}