mirror of
https://github.com/BX-Team/DivineMC.git
synced 2025-12-19 14:59:25 +00:00
implement shuttle
This commit is contained in:
4
.github/workflows/build-1217.yml
vendored
4
.github/workflows/build-1217.yml
vendored
@@ -62,8 +62,8 @@ jobs:
|
|||||||
PARALLELISM=$(($(nproc) * 2))
|
PARALLELISM=$(($(nproc) * 2))
|
||||||
./gradlew applyAllPatches --stacktrace --parallel --max-workers=$PARALLELISM --build-cache --no-daemon
|
./gradlew applyAllPatches --stacktrace --parallel --max-workers=$PARALLELISM --build-cache --no-daemon
|
||||||
|
|
||||||
- name: Build Paperclip Jar
|
- name: Build Shuttle Jar
|
||||||
run: ./gradlew createMojmapPaperclipJar --stacktrace --parallel --no-daemon
|
run: ./gradlew createMojmapShuttleJar --stacktrace --parallel --no-daemon
|
||||||
|
|
||||||
- name: Prepare Release Info
|
- name: Prepare Release Info
|
||||||
run: bash scripts/releaseInfo.sh
|
run: bash scripts/releaseInfo.sh
|
||||||
|
|||||||
6
.github/workflows/build-pr.yml
vendored
6
.github/workflows/build-pr.yml
vendored
@@ -26,11 +26,11 @@ jobs:
|
|||||||
- name: Apply Patches
|
- name: Apply Patches
|
||||||
run: ./gradlew applyAllPatches --stacktrace --no-daemon
|
run: ./gradlew applyAllPatches --stacktrace --no-daemon
|
||||||
|
|
||||||
- name: Build Paperclip Jar
|
- name: Build Shuttle Jar
|
||||||
run: ./gradlew createMojmapPaperclipJar --stacktrace --no-daemon
|
run: ./gradlew createMojmapShuttleJar --stacktrace --no-daemon
|
||||||
|
|
||||||
- name: Upload Artifacts
|
- name: Upload Artifacts
|
||||||
uses: actions/upload-artifact@main
|
uses: actions/upload-artifact@main
|
||||||
with:
|
with:
|
||||||
name: DivineMC
|
name: DivineMC
|
||||||
path: divinemc-server/build/libs/divinemc-paperclip-*-mojmap.jar
|
path: divinemc-server/build/libs/divinemc-shuttle-*-mojmap.jar
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ Run the following commands in the root directory:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
> ./gradlew applyAllPatches # apply all patches
|
> ./gradlew applyAllPatches # apply all patches
|
||||||
> ./gradlew createMojmapPaperclipJar # build the server jar
|
> ./gradlew createMojmapShuttleJar # build the server jar
|
||||||
```
|
```
|
||||||
|
|
||||||
For anything else you can refer to our [contribution guide](https://bxteam.org/docs/divinemc/development/contributing).
|
For anything else you can refer to our [contribution guide](https://bxteam.org/docs/divinemc/development/contributing).
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
||||||
import org.gradle.api.tasks.testing.logging.TestLogEvent
|
import org.gradle.api.tasks.testing.logging.TestLogEvent
|
||||||
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
java
|
java
|
||||||
@@ -91,6 +92,7 @@ subprojects {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven(paperMavenPublicUrl)
|
maven(paperMavenPublicUrl)
|
||||||
maven("https://jitpack.io")
|
maven("https://jitpack.io")
|
||||||
|
maven("https://s01.oss.sonatype.org/content/repositories/snapshots")
|
||||||
}
|
}
|
||||||
|
|
||||||
extensions.configure<PublishingExtension> {
|
extensions.configure<PublishingExtension> {
|
||||||
@@ -105,6 +107,76 @@ subprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.register<Jar>("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") {
|
tasks.register("printMinecraftVersion") {
|
||||||
doLast {
|
doLast {
|
||||||
println(providers.gradleProperty("mcVersion").get().trim())
|
println(providers.gradleProperty("mcVersion").get().trim())
|
||||||
|
|||||||
@@ -18,10 +18,28 @@ index 394443d00e661715439be1e56dddc129947699a4..480ad57a6b7b74e6b83e9c6ceb69ea1f
|
|||||||
public CrashReport(String title, Throwable exception) {
|
public CrashReport(String title, Throwable exception) {
|
||||||
io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception); // Paper
|
io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception); // Paper
|
||||||
diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
|
diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
|
||||||
index b06c2c4aa77edafb374f7cf0406cf4d29c6e7f9f..a476b53e0c5b18d9b0abceb4ffeb5ab4c5d7d6d9 100644
|
index b06c2c4aa77edafb374f7cf0406cf4d29c6e7f9f..9d89af099c6e0f50c73a9372e1ef7f1b0ef932c5 100644
|
||||||
--- a/net/minecraft/server/Main.java
|
--- a/net/minecraft/server/Main.java
|
||||||
+++ b/net/minecraft/server/Main.java
|
+++ b/net/minecraft/server/Main.java
|
||||||
@@ -125,7 +125,6 @@ public class Main {
|
@@ -64,6 +64,17 @@ 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);
|
||||||
|
+
|
||||||
|
+ 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"
|
||||||
|
)
|
||||||
|
@@ -125,7 +136,6 @@ public class Main {
|
||||||
dedicatedServerSettings.forceSave();
|
dedicatedServerSettings.forceSave();
|
||||||
RegionFileVersion.configure(dedicatedServerSettings.getProperties().regionFileComression);
|
RegionFileVersion.configure(dedicatedServerSettings.getProperties().regionFileComression);
|
||||||
Path path2 = Paths.get("eula.txt");
|
Path path2 = Paths.get("eula.txt");
|
||||||
@@ -29,7 +47,7 @@ index b06c2c4aa77edafb374f7cf0406cf4d29c6e7f9f..a476b53e0c5b18d9b0abceb4ffeb5ab4
|
|||||||
// Paper start - load config files early for access below if needed
|
// 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 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"));
|
org.bukkit.configuration.file.YamlConfiguration spigotConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionSet.valueOf("spigot-settings"));
|
||||||
@@ -148,19 +147,6 @@ public class Main {
|
@@ -148,19 +158,6 @@ public class Main {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -231,18 +231,112 @@ index 62e2d5704c348955bc8284dc2d54c933b7bcdd06..341f13e57896f03058ea3ec68e69b7cb
|
|||||||
public void executeAsync(final Runnable runnable) {
|
public void executeAsync(final Runnable runnable) {
|
||||||
MCUtil.scheduleAsyncTask(this.catching(runnable, "asynchronous"));
|
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
|
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
|
||||||
index c92ae5af5186a7c36f685272a13cfcfad21e8e69..24013949b6646016267132396e61d6cd4af8e374 100644
|
index 748bd9650da4a209743b7a5dde584b2e19c5a578..7e90142cb65937103aa99fd011540086449c45c8 100644
|
||||||
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
|
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
|
||||||
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
|
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
|
||||||
@@ -247,7 +247,7 @@ public class Main {
|
@@ -1,14 +1,8 @@
|
||||||
System.setProperty("library.jansi.version", "Paper"); // Paper - set meaningless jansi version to prevent git builds from crashing on Windows
|
package org.bukkit.craftbukkit;
|
||||||
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);
|
import java.io.File;
|
||||||
+ org.bxteam.divinemc.DivineBootstrap.boot(options); // DivineMC - Replace with DivineBootstrap
|
-import java.io.IOException;
|
||||||
} catch (Throwable t) {
|
import java.text.SimpleDateFormat;
|
||||||
t.printStackTrace();
|
-import java.util.Calendar;
|
||||||
|
-import java.util.Date;
|
||||||
|
-import java.util.logging.Level;
|
||||||
|
-import java.util.logging.Logger;
|
||||||
|
import joptsimple.OptionParser;
|
||||||
|
-import joptsimple.OptionSet;
|
||||||
|
import joptsimple.util.PathConverter;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
@@ -24,7 +18,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 +174,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
|
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
|
index 2e7c3d4befeb6256ce81ecaa9ed4e8fbcb21651e..a839dbbb72f48b8f8736d9f4693c528686570732 100644
|
||||||
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
|
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ index 592e8a4c04ef5acda9fdfd1405d8ff4952396ada..c8c7fa0304e8eaf0d444fc0c9a36c00b
|
|||||||
|
|
||||||
Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper
|
Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper
|
||||||
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
|
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
|
||||||
index cc4f4fbbe1cacce5f0f5500a8dda199aa0bcdf31..03fe7d98252b93f4625359f50552e3cf60e82b9c 100644
|
index 7e90142cb65937103aa99fd011540086449c45c8..7653ba0259ddc930dc4e2af84636641d3dba6e7f 100644
|
||||||
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
|
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
|
||||||
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
|
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
|
||||||
@@ -172,6 +172,14 @@ public class Main {
|
@@ -166,6 +166,14 @@ public class Main {
|
||||||
.describedAs("Yml file");
|
.describedAs("Yml file");
|
||||||
// Purpur end - Purpur config files
|
// Purpur end - Purpur config files
|
||||||
|
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ index 70413fddd23ca1165cb5090cce4fddcb1bbca93f..ae70b84e6473fa2ed94416bf4bef8849
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
java.util.List<Path> files = ((java.util.List<File>) optionSet.valuesOf("add-plugin")).stream().map(File::toPath).toList();
|
java.util.List<Path> files = ((java.util.List<File>) optionSet.valuesOf("add-plugin")).stream().map(File::toPath).toList();
|
||||||
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
|
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
|
||||||
index 03fe7d98252b93f4625359f50552e3cf60e82b9c..ff350b5744b586cad7c2f5e0a04770e76f039d39 100644
|
index 7653ba0259ddc930dc4e2af84636641d3dba6e7f..1a3a5e8a9119af8ed9d13a61bc5dc7b3ee0b7a65 100644
|
||||||
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
|
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
|
||||||
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
|
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
|
||||||
@@ -180,6 +180,14 @@ public class Main {
|
@@ -174,6 +174,14 @@ public class Main {
|
||||||
.describedAs("Yml file");
|
.describedAs("Yml file");
|
||||||
// DivineMC end - Configuration
|
// DivineMC end - Configuration
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +1,92 @@
|
|||||||
package org.bxteam.divinemc;
|
package org.bxteam.divinemc;
|
||||||
|
|
||||||
import io.papermc.paper.ServerBuildInfo;
|
import io.papermc.paper.ServerBuildInfo;
|
||||||
|
import joptsimple.OptionParser;
|
||||||
import joptsimple.OptionSet;
|
import joptsimple.OptionSet;
|
||||||
import net.minecraft.SharedConstants;
|
import net.minecraft.SharedConstants;
|
||||||
import net.minecraft.server.Eula;
|
import net.minecraft.server.Eula;
|
||||||
import net.minecraft.server.Main;
|
import org.bukkit.craftbukkit.CraftServer;
|
||||||
|
import org.bukkit.craftbukkit.Main;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@SuppressWarnings("DuplicatedCode")
|
||||||
public class DivineBootstrap {
|
public class DivineBootstrap {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger("DivineBootstrap");
|
private static final Logger LOGGER = LoggerFactory.getLogger("DivineBootstrap");
|
||||||
|
|
||||||
public static void boot(final OptionSet options) {
|
public static OptionSet bootstrap(String[] args) {
|
||||||
SharedConstants.tryDetectVersion();
|
OptionParser parser = Main.main(args);
|
||||||
|
OptionSet options = parser.parse(args);
|
||||||
|
|
||||||
io.papermc.paper.ServerBuildInfo info = io.papermc.paper.ServerBuildInfo.buildInfo();
|
if ((options == null) || (options.has("?"))) {
|
||||||
if (io.papermc.paper.ServerBuildInfoImpl.IS_EXPERIMENTAL) {
|
try {
|
||||||
LOGGER.warn("Running an experimental version of {}, please proceed with caution.", info.brandName());
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
Path path2 = Paths.get("eula.txt");
|
boolean skip = Boolean.getBoolean("Paper.IgnoreJavaVersion");
|
||||||
Eula eula = new Eula(path2);
|
String javaVersionName = System.getProperty("java.version");
|
||||||
boolean eulaAgreed = Boolean.getBoolean("com.mojang.eula.agree");
|
boolean isPreRelease = javaVersionName.contains("-");
|
||||||
if (eulaAgreed) {
|
if (isPreRelease) {
|
||||||
LOGGER.error("You have used the Spigot command line EULA agreement flag.");
|
if (!skip) {
|
||||||
LOGGER.error("By using this setting you are indicating your agreement to Mojang's EULA (https://aka.ms/MinecraftEULA).");
|
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.");
|
||||||
LOGGER.error("If you do not agree to the above EULA please stop your server and remove this flag immediately.");
|
System.exit(70);
|
||||||
}
|
}
|
||||||
if (!eula.hasAgreedToEULA() && !eulaAgreed) {
|
|
||||||
LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
System.out.println("Loading libraries, please wait..."); // Restore CraftBukkit log
|
|
||||||
getStartupVersionMessages().forEach(LOGGER::info);
|
|
||||||
|
|
||||||
Main.main(options);
|
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();
|
||||||
|
Path path2 = Paths.get("eula.txt");
|
||||||
|
Eula eula = new Eula(path2);
|
||||||
|
boolean eulaAgreed = Boolean.getBoolean("com.mojang.eula.agree");
|
||||||
|
if (eulaAgreed) {
|
||||||
|
LOGGER.error("You have used the Spigot command line EULA agreement flag.");
|
||||||
|
LOGGER.error("By using this setting you are indicating your agreement to Mojang's EULA (https://aka.ms/MinecraftEULA).");
|
||||||
|
LOGGER.error("If you do not agree to the above EULA please stop your server and remove this flag immediately.");
|
||||||
|
}
|
||||||
|
if (!eula.hasAgreedToEULA() && !eulaAgreed) {
|
||||||
|
LOGGER.info("You need to agree to the EULA in order to run the server. Go to eula.txt for more info.");
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
getStartupVersionMessages().forEach(LOGGER::info);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
t.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<String> getStartupVersionMessages() {
|
private static List<String> getStartupVersionMessages() {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ mcVersion=1.21.7
|
|||||||
purpurRef=c4e5604ca8e4a36d24f4eaa90de7fee768fb60b7
|
purpurRef=c4e5604ca8e4a36d24f4eaa90de7fee768fb60b7
|
||||||
experimental=false
|
experimental=false
|
||||||
|
|
||||||
org.gradle.configuration-cache=true
|
#org.gradle.configuration-cache=true
|
||||||
org.gradle.caching = true
|
org.gradle.caching = true
|
||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
org.gradle.vfs.watch = false
|
org.gradle.vfs.watch = false
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ make_latest=$([ "$experimental" = "true" ] && echo "false" || echo "true")
|
|||||||
|
|
||||||
rm -f $changelog
|
rm -f $changelog
|
||||||
|
|
||||||
mv divinemc-server/build/libs/divinemc-paperclip-"$version"-mojmap.jar "$jarName"
|
mv divinemc-server/build/libs/divinemc-shuttle-"$version"-mojmap.jar "$jarName"
|
||||||
{
|
{
|
||||||
echo "name=$divinemcid"
|
echo "name=$divinemcid"
|
||||||
echo "tag=$tagid"
|
echo "tag=$tagid"
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ if (!file(".git").exists()) {
|
|||||||
|
|
||||||
rootProject.name = "DivineMC"
|
rootProject.name = "DivineMC"
|
||||||
|
|
||||||
for (name in listOf("divinemc-api", "divinemc-server")) {
|
for (name in listOf("divinemc-api", "divinemc-server", "shuttle")) {
|
||||||
val projName = name.lowercase(Locale.ENGLISH)
|
val projName = name.lowercase(Locale.ENGLISH)
|
||||||
include(projName)
|
include(projName)
|
||||||
findProject(":$projName")!!.projectDir = file(name)
|
findProject(":$projName")!!.projectDir = file(name)
|
||||||
|
|||||||
57
shuttle/build.gradle.kts
Normal file
57
shuttle/build.gradle.kts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
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<JavaCompile>().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")
|
||||||
|
}
|
||||||
169
shuttle/src/main/java/org/bxteam/shuttle/Shuttle.java
Normal file
169
shuttle/src/main/java/org/bxteam/shuttle/Shuttle.java
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
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<String> 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> T readResourceContent(String resourcePath, ResourceParser<T> 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<String> versionProvider) throws IOException {
|
||||||
|
new PatchBuilder().start(versionProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldExitAfterPatching() {
|
||||||
|
return Boolean.getBoolean(PATCH_ONLY_PROPERTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeLibraryLoadingPhase(Provider<String> 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> {
|
||||||
|
T parse(BufferedReader reader) throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Provider<T> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
shuttle/src/main/java/org/bxteam/shuttle/logger/Logger.java
Normal file
73
shuttle/src/main/java/org/bxteam/shuttle/logger/Logger.java
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
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<String> 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<String> 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<JarEntry> 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) { }
|
||||||
|
}
|
||||||
248
shuttle/src/main/java/org/bxteam/shuttle/patch/PatchBuilder.java
Normal file
248
shuttle/src/main/java/org/bxteam/shuttle/patch/PatchBuilder.java
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
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<String> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user