9
0
mirror of https://github.com/BX-Team/DivineMC.git synced 2025-12-19 14:59:25 +00:00

remove shuttle, useless

This commit is contained in:
NONPLAYT
2025-08-08 01:55:17 +03:00
parent 1f3f2de273
commit cb2232c4bf
15 changed files with 42 additions and 1201 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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).

View File

@@ -107,76 +107,6 @@ 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") {
doLast {
println(providers.gradleProperty("mcVersion").get().trim())

View File

@@ -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<TickTa

View File

@@ -114,66 +114,32 @@ index bc7e4e5560708fea89c584b1d8b471f4966f311a..6567ff18cb1c21230565c2d92caf3a7f
.completer(new ConsoleCommandCompleter(this.server))
.option(LineReader.Option.COMPLETE_IN_WORD, true);
diff --git a/src/main/java/io/papermc/paper/PaperBootstrap.java b/src/main/java/io/papermc/paper/PaperBootstrap.java
deleted file mode 100644
index d543b1b107ab8d3eeb1fc3c1cadf489928d2786e..0000000000000000000000000000000000000000
index d543b1b107ab8d3eeb1fc3c1cadf489928d2786e..e77078e54efdfe931202ecf8c8550840b602d135 100644
--- a/src/main/java/io/papermc/paper/PaperBootstrap.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package io.papermc.paper;
-
-import java.util.List;
-import joptsimple.OptionSet;
-import net.minecraft.SharedConstants;
-import net.minecraft.server.Main;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public final class PaperBootstrap {
+++ b/src/main/java/io/papermc/paper/PaperBootstrap.java
@@ -8,7 +8,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class PaperBootstrap {
- private static final Logger LOGGER = LoggerFactory.getLogger("bootstrap");
-
- private PaperBootstrap() {
- }
-
- public static void boot(final OptionSet options) {
- SharedConstants.tryDetectVersion();
-
- getStartupVersionMessages().forEach(LOGGER::info);
-
- Main.main(options);
- }
-
- private static List<String> 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

View File

@@ -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<String> 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()
)
);
}
}

View File

@@ -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"

View File

@@ -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)

View File

@@ -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<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")
}

View File

@@ -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<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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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<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) { }
}

View File

@@ -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<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);
}
}
}