From c2fd399caf18c5611265b1cd5ed95a18c351b957 Mon Sep 17 00:00:00 2001 From: ishland Date: Mon, 1 Feb 2021 18:44:34 +0800 Subject: [PATCH] Merge staging/1.16.5 (#371) * add config for sand duping (#352) * Lithium: cache chunk gen sea level (#349) based off: https://github.com/jellysquid3/lithium-fabric/commit/a55cfd1c914789a27bb6fe20e6a56af44d305f72 * PaperPR: Inline shift fields in EnumDirection (#350) * Introducing: Yatoclip (#360) * New async nbt cache (#347) * update pom * whoops * Try to address path issue and improve Jenkins build speed * Detailed lag and crash reports (#369) Added "Suspected Plugins" to Watchdog and crash reports * Drop sand duping * Add branch specific versions * Remove copyright * Revert mysql-connector-java version bump * Small fixes * More detailed lag and crash reports * Don't use branch information when generating metadata * Fix Jenkins Builds version command * Fixup patches * Fix patch notes * Pull Request compatibility for branch detection * Fix Pull Request compatibility for branch detection * Set context classloader before launch * Inject server jar to SystemClassLoader before launch * Try fix compile in java8 * Run tests on CodeMC and Github Actions Co-authored-by: Simon Gardling Co-authored-by: Zoe Co-authored-by: Hugo Planque <12386279+HookWoods@users.noreply.github.com> --- .github/workflows/build.yml | 9 +- Jenkinsfile | 165 ++++---- PATCHES.md | 5 + Yatoclip/build.gradle.kts | 11 + .../yatopiamc/yatoclip/YatoclipLaunch.java | 28 ++ .../yatopiamc/yatoclip/PatchesMetadata.java | 51 +++ .../org/yatopiamc/yatoclip/ServerSetup.java | 360 ++++++++++++++++++ .../yatoclip/SpecialSourceLauncher.java | 89 +++++ .../java/org/yatopiamc/yatoclip/Yatoclip.java | 41 ++ .../yatopiamc/yatoclip/YatoclipLaunch.java | 24 ++ .../yatopiamc/yatoclip/YatoclipPatcher.java | 236 ++++++++++++ build.gradle.kts | 14 +- buildSrc/build.gradle.kts | 10 + .../yatoclip/gradle/MakePatchesTask.java | 269 +++++++++++++ .../yatoclip/gradle/PatchesMetadata.java | 51 +++ .../yatoclip/gradle/PropertiesUtils.java | 22 ++ .../src/main/kotlin/ConfigureSubprojects.kt | 103 ++++- buildSrc/src/main/kotlin/DependencyLoading.kt | 2 +- .../src/main/kotlin/ToothpickExtension.kt | 6 + .../src/main/kotlin/ToothpickExtensions.kt | 2 +- .../src/main/kotlin/task/RebuildPatches.kt | 7 +- .../api/0008-Suspected-plugins-report.patch | 258 +++++++++++++ .../server/0060-New-Network-System.patch | 10 +- patches/server/0001-Modify-POM.patch | 60 ++- patches/server/0002-Brandings.patch | 41 +- patches/server/0003-Utilities.patch | 2 +- ...009-Add-NBT-API-as-a-first-class-lib.patch | 2 +- ...62-lithium-cache-chunk-gen-sea-level.patch | 52 +++ ...aperPR-Inline-shift-direction-fields.patch | 55 +++ patches/server/0064-New-nbt-cache.patch | 137 +++++++ .../0065-Suspected-plugins-report.patch | 207 ++++++++++ settings.gradle.kts | 1 + subprojects/server.gradle.kts | 6 - 33 files changed, 2206 insertions(+), 130 deletions(-) create mode 100644 Yatoclip/build.gradle.kts create mode 100644 Yatoclip/src/java9/java/org/yatopiamc/yatoclip/YatoclipLaunch.java create mode 100644 Yatoclip/src/main/java/org/yatopiamc/yatoclip/PatchesMetadata.java create mode 100644 Yatoclip/src/main/java/org/yatopiamc/yatoclip/ServerSetup.java create mode 100644 Yatoclip/src/main/java/org/yatopiamc/yatoclip/SpecialSourceLauncher.java create mode 100644 Yatoclip/src/main/java/org/yatopiamc/yatoclip/Yatoclip.java create mode 100644 Yatoclip/src/main/java/org/yatopiamc/yatoclip/YatoclipLaunch.java create mode 100644 Yatoclip/src/main/java/org/yatopiamc/yatoclip/YatoclipPatcher.java create mode 100644 buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/MakePatchesTask.java create mode 100644 buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/PatchesMetadata.java create mode 100644 buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/PropertiesUtils.java create mode 100644 patches/api/0008-Suspected-plugins-report.patch create mode 100644 patches/server/0062-lithium-cache-chunk-gen-sea-level.patch create mode 100644 patches/server/0063-PaperPR-Inline-shift-direction-fields.patch create mode 100644 patches/server/0064-New-nbt-cache.patch create mode 100644 patches/server/0065-Suspected-plugins-report.patch diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 45f5cb13..e11dd13e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,11 +29,6 @@ jobs: fail-fast: false steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.4.1 - with: - access_token: ${{ github.token }} - - uses: actions/checkout@v2 - name: Set up JDK ${{ matrix.java }} uses: actions/setup-java@v1 @@ -80,12 +75,12 @@ jobs: - name: Build Yatopia run: | - ./gradlew paperclip + ./gradlew clean build yatoclip - name: Upload Artifact if: github.ref != 'refs/heads/ver/1.16.4' uses: actions/upload-artifact@v2 with: name: Yatopia-${{ matrix.java }} - path: yatopia-1.16.5-paperclip.jar + path: yatopia-1.16.5-yatoclip.jar diff --git a/Jenkinsfile b/Jenkinsfile index ed261ac7..cafa13f8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,91 +1,74 @@ -pipeline { - agent { label 'slave' } - options { timestamps() } - stages { - stage('Cleanup') { - steps { - scmSkip(deleteBuild: true, skipPattern:'.*\\[CI-SKIP\\].*') - sh 'rm -rf ./target' - sh 'rm -rf ./Paper/Paper-API ./Paper/Paper-Server ./Paper/work/Spigot/Spigot-API ./Paper/work/Spigot/Spigot-Server' - sh 'rm -rf ./Yatopia-API ./Yatopia-Server' - sh 'chmod +x ./gradlew' - } - } - stage('Init project & submodules') { - steps { - withMaven( - maven: '3', - mavenLocalRepo: '.repository', - publisherStrategy: 'EXPLICIT', - ) { - sh './gradlew initGitSubmodules' - } - } - } - stage('Decompile & apply patches') { - tools { - jdk "OpenJDK 8" - } - steps { - withMaven( - maven: '3', - mavenLocalRepo: '.repository', - publisherStrategy: 'EXPLICIT', - ) { - sh ''' - ./gradlew setupUpstream - ./gradlew applyPatches - ''' - } - } - } - stage('Build') { - tools { - jdk "OpenJDK 8" - } - steps { - withMaven( - maven: '3', - mavenLocalRepo: '.repository', - publisherStrategy: 'EXPLICIT' - ) { - withCredentials([usernamePassword(credentialsId: 'jenkins-deploy', usernameVariable: 'ORG_GRADLE_PROJECT_mavenUsername', passwordVariable: 'ORG_GRADLE_PROJECT_mavenPassword')]) { - sh ''' - ./gradlew build - ./gradlew publish - ''' - } - } - } - } - stage('Build Launcher') { - tools { - jdk "OpenJDK 8" - } - steps { - withMaven( - maven: '3', - mavenLocalRepo: '.repository', - publisherStrategy: 'EXPLICIT' - ) { - sh ''' - mkdir -p "./target" - ./gradlew paperclip - basedir=$(pwd) - paperworkdir="$basedir/Paper/work" - mcver=$(cat "$paperworkdir/BuildData/info.json" | grep minecraftVersion | cut -d '"' -f 4) - cp "yatopia-$mcver-paperclip.jar" "./target/yatopia-$mcver-paperclip-b$BUILD_NUMBER.jar" - ''' - } - } - post { - success { - archiveArtifacts "target/*.jar" - } - failure { - cleanWs() - } - } - } - } -} +pipeline { + agent { label 'slave' } + options { timestamps() } + stages { + stage('Cleanup') { + steps { + scmSkip(deleteBuild: true, skipPattern:'.*\\[CI-SKIP\\].*') + sh 'rm -rf ./target' + sh 'rm -rf ./Paper/Paper-API ./Paper/Paper-Server ./Paper/work/Spigot/Spigot-API ./Paper/work/Spigot/Spigot-Server' + sh 'rm -rf ./Yatopia-API ./Yatopia-Server' + sh 'chmod +x ./gradlew' + } + } + stage('Init project & submodules') { + steps { + withMaven( + maven: '3', + mavenLocalRepo: '.repository', + publisherStrategy: 'EXPLICIT', + ) { + sh './gradlew initGitSubmodules' + } + } + } + stage('Decompile & apply patches') { + tools { + jdk "OpenJDK 8" + } + steps { + withMaven( + maven: '3', + mavenLocalRepo: '.repository', + publisherStrategy: 'EXPLICIT', + ) { + sh ''' + ./gradlew setupUpstream + ./gradlew applyPatches + ''' + } + } + } + stage('Build') { + tools { + jdk "OpenJDK 8" + } + steps { + withMaven( + maven: '3', + mavenLocalRepo: '.repository', + publisherStrategy: 'EXPLICIT' + ) { + withCredentials([usernamePassword(credentialsId: 'jenkins-deploy', usernameVariable: 'ORG_GRADLE_PROJECT_mavenUsername', passwordVariable: 'ORG_GRADLE_PROJECT_mavenPassword')]) { + sh ''' + ./gradlew clean build yatoclip publish + mkdir -p "./target" + basedir=$(pwd) + paperworkdir="$basedir/Paper/work" + mcver=$(cat "$paperworkdir/BuildData/info.json" | grep minecraftVersion | cut -d '"' -f 4) + cp "yatopia-$mcver-yatoclip.jar" "./target/yatopia-$mcver-yatoclip-b$BUILD_NUMBER.jar" + ''' + } + } + } + post { + success { + archiveArtifacts "target/*.jar" + } + failure { + cleanWs() + } + } + } + } +} diff --git a/PATCHES.md b/PATCHES.md index 66dfb55f..3296e36b 100644 --- a/PATCHES.md +++ b/PATCHES.md @@ -8,6 +8,7 @@ This is an overview over all patches that are currently used. | Side | Patch | Author | CoAuthors | | ----- | ------------- |:-------------:| -----:| +| server | (PaperPR) Inline shift direction fields | Andrew Steinborn | | | server | AFK API | William Blake Galbreath | | | api | AFK API | William Blake Galbreath | | | server | Ability to re-add farmland mechanics from Alpha | Yive | | @@ -240,6 +241,7 @@ This is an overview over all patches that are currently used. | server | Multi-Threaded Server Ticking Vanilla | Spottedleaf | | | server | Multi-Threaded ticking CraftBukkit | Spottedleaf | | | server | Name craft scheduler threads according to the plugin using | Spottedleaf | | +| server | New nbt cache | Hugo Planque | ishland | | server | Nuke streams off BlockPosition | Ivan Pekov | | | server | Nuke streams off SectionPosition | Ivan Pekov | | | server | Optimise EntityInsentient#checkDespawn | Spottedleaf | | @@ -339,6 +341,8 @@ This is an overview over all patches that are currently used. | server | Stop wasting resources on JsonList#get | Ivan Pekov | | | server | Striders give saddle back | Ben Kerllenevich | | | server | Strip raytracing for EntityLiving#hasLineOfSight | Paul Sauve | | +| server | Suspected plugins report | ishland | | +| api | Suspected plugins report | ishland | | | server | Swap priority of checks in chunk ticking | Paul Sauve | | | server | Time scoreboard search | Spottedleaf | | | server | Timings stuff | William Blake Galbreath | | @@ -374,3 +378,4 @@ This is an overview over all patches that are currently used. | server | lithium VoronoiBiomeAccessTypeMixin | JellySquid | | | server | lithium enum_values | JellySquid | | | server | lithium reduce allocations | JellySquid | Mykyta Komarnytskyy | +| server | lithium: cache chunk gen sea level | SuperCoder7979 | | diff --git a/Yatoclip/build.gradle.kts b/Yatoclip/build.gradle.kts new file mode 100644 index 00000000..3d1d3e7a --- /dev/null +++ b/Yatoclip/build.gradle.kts @@ -0,0 +1,11 @@ +repositories { + mavenCentral() + maven("https://jitpack.io/") +} + +dependencies { + implementation("com.github.ishlandbukkit:jbsdiff:deff66b794") + implementation("com.google.code.gson:gson:2.8.6") + implementation("commons-io:commons-io:2.8.0") +} + diff --git a/Yatoclip/src/java9/java/org/yatopiamc/yatoclip/YatoclipLaunch.java b/Yatoclip/src/java9/java/org/yatopiamc/yatoclip/YatoclipLaunch.java new file mode 100644 index 00000000..1260a32e --- /dev/null +++ b/Yatoclip/src/java9/java/org/yatopiamc/yatoclip/YatoclipLaunch.java @@ -0,0 +1,28 @@ +package org.yatopiamc.yatoclip; + +import java.lang.instrument.Instrumentation; +import java.nio.file.Path; +import java.util.jar.JarFile; + +public class YatoclipLaunch { + + private static Instrumentation inst = null; + + public static void premain(String args, Instrumentation inst) { + YatoclipLaunch.inst = inst; + } + + public static void agentmain(final String agentArgs, final Instrumentation inst) { + YatoclipLaunch.inst = inst; + } + + @SuppressWarnings("unused") + static void injectClasspath(Path setup) throws Throwable { + if(inst == null) { + throw new RuntimeException("Instrumentation API handle not found"); + } + inst.appendToSystemClassLoaderSearch(new JarFile(setup.toFile())); + inst = null; + } + +} diff --git a/Yatoclip/src/main/java/org/yatopiamc/yatoclip/PatchesMetadata.java b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/PatchesMetadata.java new file mode 100644 index 00000000..3603af88 --- /dev/null +++ b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/PatchesMetadata.java @@ -0,0 +1,51 @@ +package org.yatopiamc.yatoclip; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +public class PatchesMetadata { + + public final Set patches; + public final Set relocations; + public final Set copyExcludes; + + public PatchesMetadata(Set patches, Set relocations, Set copyExcludes) { + Objects.requireNonNull(copyExcludes); + this.copyExcludes = Collections.unmodifiableSet(copyExcludes); + Objects.requireNonNull(relocations); + this.relocations = Collections.unmodifiableSet(relocations); + Objects.requireNonNull(patches); + this.patches = Collections.unmodifiableSet(patches); + } + + public static class PatchMetadata { + public final String name; + public final String originalHash; + public final String targetHash; + public final String patchHash; + + public PatchMetadata(String name, String originalHash, String targetHash, String patchHash) { + this.name = name; + this.originalHash = originalHash; + this.targetHash = targetHash; + this.patchHash = patchHash; + } + } + + public static class Relocation implements Serializable { + + public final String from; + public final String to; + public final boolean includeSubPackages; + + public Relocation(String from, String to, boolean includeSubPackages) { + Objects.requireNonNull(from); + Objects.requireNonNull(to); + this.from = from.replaceAll("\\.", "/"); + this.to = to.replaceAll("\\.", "/"); + this.includeSubPackages = includeSubPackages; + } + } +} diff --git a/Yatoclip/src/main/java/org/yatopiamc/yatoclip/ServerSetup.java b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/ServerSetup.java new file mode 100644 index 00000000..905c3795 --- /dev/null +++ b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/ServerSetup.java @@ -0,0 +1,360 @@ +package org.yatopiamc.yatoclip; + +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; +import static java.nio.file.StandardOpenOption.WRITE; + +public class ServerSetup { + + private static final String minecraftVersion; + private static final Path cacheDirectory; + private static final Gson gson = new Gson(); + + private static VersionInfo versionInfo = null; + private static BuildDataInfo buildDataInfo = null; + + static { + Properties prop = new Properties(); + try (InputStream inputStream = ServerSetup.class.getClassLoader().getResourceAsStream("yatoclip-launch.properties")) { + prop.load(inputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + minecraftVersion = prop.getProperty("minecraftVersion"); + cacheDirectory = Paths.get("cache", minecraftVersion); + cacheDirectory.toFile().mkdirs(); + } + + public static Path setup() throws IOException { + long startTime = System.nanoTime(); + checkBuildData(); + applyMappingsAndPatches(); + System.err.println(String.format("Yatoclip server setup completed in %.2fms", (System.nanoTime() - startTime) / 1_000_000.0)); + return cacheDirectory.resolve("Minecraft").resolve(minecraftVersion + "-patched.jar"); + } + + private static void applyMappingsAndPatches() throws IOException { + final Path minecraftDir = cacheDirectory.resolve("Minecraft"); + minecraftDir.toFile().mkdirs(); + final Path vanillaJar = minecraftDir.resolve(minecraftVersion + ".jar"); + if (!isValidZip(vanillaJar)) { + System.err.println("Downloading vanilla jar..."); + download(new URL(buildDataInfo.serverUrl), vanillaJar); + if (!isValidZip(vanillaJar)) throw new RuntimeException("Invalid vanilla jar"); + } + final Path classMappedJar = minecraftDir.resolve(minecraftVersion + "-cl.jar"); + final Path memberMappedJar = minecraftDir.resolve(minecraftVersion + "-m.jar"); + final Path patchedJar = minecraftDir.resolve(minecraftVersion + "-patched.jar"); + if (!isValidZip(classMappedJar) || !isValidZip(memberMappedJar)) { + SpecialSourceLauncher.resetSpecialSourceClassloader(); + final Path buildData = cacheDirectory.resolve("BuildData"); + SpecialSourceLauncher.setSpecialSourceJar(buildData.resolve("bin").resolve("SpecialSource-2.jar").toFile()); + System.err.println("Applying class mapping..."); + SpecialSourceLauncher.runProcess( + "map", "--only", ".", "--only", "net/minecraft", "--auto-lvt", "BASIC", "--auto-member", "SYNTHETIC", + "-i", vanillaJar.toAbsolutePath().toString(), + "-m", buildData.resolve("mappings").resolve(buildDataInfo.classMappings).toAbsolutePath().toString(), + "-o", classMappedJar.toAbsolutePath().toString() + ); + System.err.println("Applying member mapping..."); + SpecialSourceLauncher.runProcess( + "map", "--only", ".", "--only", "net/minecraft", "--auto-member", "LOGGER", "--auto-member", "TOKENS", + "-i", classMappedJar.toAbsolutePath().toString(), + "-m", buildData.resolve("mappings").resolve(buildDataInfo.memberMappings).toAbsolutePath().toString(), + "-o", memberMappedJar.toAbsolutePath().toString() + ); + SpecialSourceLauncher.resetSpecialSourceClassloader(); + if (!isValidZip(classMappedJar) || !isValidZip(memberMappedJar)) + throw new RuntimeException("Unable to apply mappings"); + } + + if (!YatoclipPatcher.isJarUpToDate(patchedJar)){ + System.err.println("Applying patches..."); + YatoclipPatcher.patchJar(memberMappedJar, patchedJar); + if(!YatoclipPatcher.isJarUpToDate(patchedJar)) + throw new RuntimeException("Unable to apply patches"); + } + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private static boolean isValidZip(Path zipPath) { + try { + ZipFile zipFile = new ZipFile(zipPath.toFile()); + zipFile.close(); + } catch (Throwable t) { + return false; + } + return true; + } + + private static void checkBuildData() throws IOException { + final Path buildDataDir = cacheDirectory.resolve("BuildData"); + buildDataDir.toFile().mkdirs(); + final Path versionInfoFile = buildDataDir.resolve("version.json"); + if (!tryParseVersionInfo(versionInfoFile)) { + System.err.println("Downloading version.json..."); + final URL versionInfoURI = new URL("https://hub.spigotmc.org/versions/" + minecraftVersion + ".json"); + download(versionInfoURI, versionInfoFile); + if (!tryParseVersionInfo(versionInfoFile)) throw new RuntimeException("Unable to parse versionInfo"); + } + final Path buildDataArchive = buildDataDir.resolve("BuildData.zip"); + if (!tryParseBuildData(buildDataArchive)) { + System.err.println("Downloading BuildData..."); + final URL buildDataURL = new URL("https://hub.spigotmc.org/stash/rest/api/latest/projects/SPIGOT/repos/builddata/archive?at=" + ServerSetup.versionInfo.refs.buildData + "&format=zip"); + download(buildDataURL, buildDataArchive); + if (!tryParseBuildData(buildDataArchive)) throw new RuntimeException("Unable to parse BuildData"); + } + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private static boolean tryParseBuildData(Path buildData) { + try { + ZipFile zipFile = new ZipFile(buildData.toFile()); + ((Iterator) zipFile.entries()).forEachRemaining(zipEntry -> { + if (zipEntry.isDirectory()) return; + buildData.getParent().resolve(zipEntry.getName()).getParent().toFile().mkdirs(); + try ( + final ReadableByteChannel source = Channels.newChannel(zipFile.getInputStream(zipEntry)); + final FileChannel fileChannel = FileChannel.open(buildData.getParent().resolve(zipEntry.getName()), CREATE, WRITE, TRUNCATE_EXISTING) + ) { + fileChannel.transferFrom(source, 0, Long.MAX_VALUE); + } catch (Throwable t) { + throw new RuntimeException(t); + } + }); + zipFile.close(); + try (Reader reader = Files.newBufferedReader(buildData.getParent().resolve("info.json"))){ + ServerSetup.buildDataInfo = gson.fromJson(reader, BuildDataInfo.class); + } + } catch (Throwable t) { + return false; + } + return true; + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private static boolean tryParseVersionInfo(Path versionInfo) { + try (Reader reader = Files.newBufferedReader(versionInfo)) { + ServerSetup.versionInfo = gson.fromJson(reader, VersionInfo.class); + } catch (Throwable t) { + return false; + } + return true; + } + + private static void download(URL url, Path downloadTo) throws IOException { + try ( + final ReadableByteChannel source = Channels.newChannel(url.openStream()); + final FileChannel fileChannel = FileChannel.open(downloadTo, CREATE, WRITE, TRUNCATE_EXISTING) + ) { + downloadTo.getParent().toFile().mkdirs(); + fileChannel.transferFrom(source, 0, Long.MAX_VALUE); + } + } + + static String toHex(final byte[] hash) { + final StringBuilder sb = new StringBuilder(hash.length * 2); + for (byte aHash : hash) { + sb.append(String.format("%02X", aHash & 0xFF)); + } + return sb.toString(); + } + + public static class VersionInfo { + + @SerializedName("refs") + private Refs refs; + + @SerializedName("name") + private String name; + + @SerializedName("description") + private String description; + + @SerializedName("toolsVersion") + private int toolsVersion; + + @SerializedName("javaVersions") + private List javaVersions; + + public Refs getRefs() { + return refs; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public int getToolsVersion() { + return toolsVersion; + } + + public List getJavaVersions() { + return javaVersions; + } + + @Override + public String toString() { + return + "VersionInfo{" + + "refs = '" + refs + '\'' + + ",name = '" + name + '\'' + + ",description = '" + description + '\'' + + ",toolsVersion = '" + toolsVersion + '\'' + + ",javaVersions = '" + javaVersions + '\'' + + "}"; + } + + public static class Refs { + + @SerializedName("BuildData") + private String buildData; + + @SerializedName("CraftBukkit") + private String craftBukkit; + + @SerializedName("Bukkit") + private String bukkit; + + @SerializedName("Spigot") + private String spigot; + + public String getBuildData() { + return buildData; + } + + public String getCraftBukkit() { + return craftBukkit; + } + + public String getBukkit() { + return bukkit; + } + + public String getSpigot() { + return spigot; + } + + @Override + public String toString() { + return + "Refs{" + + "buildData = '" + buildData + '\'' + + ",craftBukkit = '" + craftBukkit + '\'' + + ",bukkit = '" + bukkit + '\'' + + ",spigot = '" + spigot + '\'' + + "}"; + } + } + } + + public static class BuildDataInfo { + + @SerializedName("memberMapCommand") + private String memberMapCommand; + + @SerializedName("packageMappings") + private String packageMappings; + + @SerializedName("classMapCommand") + private String classMapCommand; + + @SerializedName("finalMapCommand") + private String finalMapCommand; + + @SerializedName("serverUrl") + private String serverUrl; + + @SerializedName("toolsVersion") + private int toolsVersion; + + @SerializedName("minecraftHash") + private String minecraftHash; + + @SerializedName("minecraftVersion") + private String minecraftVersion; + + @SerializedName("accessTransforms") + private String accessTransforms; + + @SerializedName("memberMappings") + private String memberMappings; + + @SerializedName("decompileCommand") + private String decompileCommand; + + @SerializedName("classMappings") + private String classMappings; + + public String getMemberMapCommand() { + return memberMapCommand; + } + + public String getPackageMappings() { + return packageMappings; + } + + public String getClassMapCommand() { + return classMapCommand; + } + + public String getFinalMapCommand() { + return finalMapCommand; + } + + public String getServerUrl() { + return serverUrl; + } + + public int getToolsVersion() { + return toolsVersion; + } + + public String getMinecraftHash() { + return minecraftHash; + } + + public String getMinecraftVersion() { + return minecraftVersion; + } + + public String getAccessTransforms() { + return accessTransforms; + } + + public String getMemberMappings() { + return memberMappings; + } + + public String getDecompileCommand() { + return decompileCommand; + } + + public String getClassMappings() { + return classMappings; + } + } +} diff --git a/Yatoclip/src/main/java/org/yatopiamc/yatoclip/SpecialSourceLauncher.java b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/SpecialSourceLauncher.java new file mode 100644 index 00000000..8b30d4b5 --- /dev/null +++ b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/SpecialSourceLauncher.java @@ -0,0 +1,89 @@ +package org.yatopiamc.yatoclip; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; + +public class SpecialSourceLauncher { + + private static final AtomicReference classLoader = new AtomicReference<>(new SpecialSourceClassLoader(new URL[0], SpecialSourceLauncher.class.getClassLoader().getParent())); + private static final AtomicReference mainClass = new AtomicReference<>(""); + + static void setSpecialSourceJar(File specialSourceJar) { + synchronized (classLoader) { + System.err.println("Setting up SpecialSource: " + specialSourceJar); + try { + classLoader.get().addURL(specialSourceJar.toURI().toURL()); + mainClass.set(Yatoclip.getMainClass(specialSourceJar.toPath())); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + } + + static void resetSpecialSourceClassloader() { + synchronized (classLoader) { + if(!classLoader.get().isLoaded) return; + System.err.println("Releasing SpecialSource"); + try { + classLoader.get().close(); + classLoader.set(new SpecialSourceClassLoader(new URL[0], SpecialSourceLauncher.class.getClassLoader().getParent())); + mainClass.set(""); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + public static void runProcess(String... command) throws IOException { + if (!(command != null && command.length > 0)) throw new IllegalArgumentException(); + + System.err.println("Invoking SpecialSource with arguments: " + Arrays.toString(command)); + + AtomicReference thrown = new AtomicReference<>(null); + final Thread thread = new Thread(() -> { + try { + final Class mainClass = Class.forName(SpecialSourceLauncher.mainClass.get(), true, classLoader.get()); + final Method mainMethod = mainClass.getMethod("main", String[].class); + if (!Modifier.isStatic(mainMethod.getModifiers()) || !Modifier.isPublic(mainMethod.getModifiers())) + throw new IllegalArgumentException(); + mainMethod.invoke(null, new Object[]{command}); + } catch (Throwable t) { + thrown.set(t); + } + }); + thread.setName("SpecialSource Thread"); + thread.setContextClassLoader(classLoader.get()); + thread.start(); + while (thread.isAlive()) + try { + thread.join(); + } catch (InterruptedException ignored) { + } + if (thrown.get() != null) + throw new RuntimeException(thrown.get()); + + } + + private static class SpecialSourceClassLoader extends URLClassLoader { + + private volatile boolean isLoaded = false; + + public SpecialSourceClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + @Override + protected synchronized void addURL(URL url) { + if (isLoaded) throw new IllegalStateException(); + this.isLoaded = true; + super.addURL(url); + } + } + +} diff --git a/Yatoclip/src/main/java/org/yatopiamc/yatoclip/Yatoclip.java b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/Yatoclip.java new file mode 100644 index 00000000..cb1194dd --- /dev/null +++ b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/Yatoclip.java @@ -0,0 +1,41 @@ +package org.yatopiamc.yatoclip; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.jar.JarInputStream; + +public class Yatoclip { + + public static void main(String... args) throws Throwable { + final Path setup = ServerSetup.setup(); + launch(setup, args); + } + + private static void launch(Path setup, String[] args) throws Throwable { + YatoclipLaunch.injectClasspath(setup); + final Class mainClassInstance = Class.forName("org.bukkit.craftbukkit.Main", true, ClassLoader.getSystemClassLoader()); + final Method mainMethod = mainClassInstance.getMethod("main", String[].class); + if(!Modifier.isPublic(mainMethod.getModifiers()) || !Modifier.isStatic(mainMethod.getModifiers())) throw new IllegalArgumentException(); + mainMethod.invoke(null, new Object[]{args}); + } + + static String getMainClass(Path jarPath) throws IOException { + final String mainClass; + try ( + InputStream inputStream = Files.newInputStream(jarPath); + JarInputStream jar = new JarInputStream(inputStream) + ) { + mainClass = jar.getManifest().getMainAttributes().getValue("Main-Class"); + } + return mainClass; + } + +} diff --git a/Yatoclip/src/main/java/org/yatopiamc/yatoclip/YatoclipLaunch.java b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/YatoclipLaunch.java new file mode 100644 index 00000000..1fb6ffbd --- /dev/null +++ b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/YatoclipLaunch.java @@ -0,0 +1,24 @@ +package org.yatopiamc.yatoclip; + +import java.lang.instrument.Instrumentation; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; + +public class YatoclipLaunch { + + public static void premain(String args, Instrumentation inst) { + } + + static void injectClasspath(Path setup) throws Throwable { + final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); + if(!(systemClassLoader instanceof URLClassLoader)) + throw new ClassCastException("SystemClassLoader is not an instance of URLClassLoader"); + final URLClassLoader classLoader = (URLClassLoader) systemClassLoader; + final Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + addURL.setAccessible(true); + addURL.invoke(classLoader, setup.toUri().toURL()); + } + +} diff --git a/Yatoclip/src/main/java/org/yatopiamc/yatoclip/YatoclipPatcher.java b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/YatoclipPatcher.java new file mode 100644 index 00000000..27d3fc25 --- /dev/null +++ b/Yatoclip/src/main/java/org/yatopiamc/yatoclip/YatoclipPatcher.java @@ -0,0 +1,236 @@ +package org.yatopiamc.yatoclip; + +import com.google.gson.Gson; +import io.sigpipe.jbsdiff.InvalidHeaderException; +import io.sigpipe.jbsdiff.Patch; +import org.apache.commons.compress.compressors.CompressorException; +import org.apache.commons.io.IOUtils; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.zip.Deflater; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import static java.util.Objects.requireNonNull; + +public class YatoclipPatcher { + + private static final PatchesMetadata patchesMetadata; + + static { + try ( + final InputStream in = YatoclipPatcher.class.getClassLoader().getResourceAsStream("patches/metadata.json"); + final InputStreamReader reader = new InputStreamReader(in); + ) { + patchesMetadata = new Gson().fromJson(reader, PatchesMetadata.class); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + static boolean isJarUpToDate(Path patchedJar) { + requireNonNull(patchedJar); + if (!patchedJar.toFile().isFile()) return false; + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + try (ZipFile patchedZip = new ZipFile(patchedJar.toFile())) { + for (PatchesMetadata.PatchMetadata patchMetadata : patchesMetadata.patches) { + ZipEntry zipEntry = patchedZip.getEntry(patchMetadata.name); + if (zipEntry == null || !patchMetadata.targetHash.equals(ServerSetup.toHex(digest.digest(IOUtils.toByteArray(patchedZip.getInputStream(zipEntry)))))) + return false; + } + } + return true; + } catch (Throwable t) { + System.out.println(t.toString()); + return false; + } + } + + static void patchJar(Path memberMappedJar, Path patchedJar) { + requireNonNull(memberMappedJar); + requireNonNull(patchedJar); + if(!memberMappedJar.toFile().isFile()) throw new IllegalArgumentException(new FileNotFoundException()); + try { + patchedJar.toFile().getParentFile().mkdirs(); + final ThreadLocal classMappedZip = ThreadLocal.withInitial(() -> { + try { + return new ZipFile(memberMappedJar.toFile()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + final ThreadLocal digest = ThreadLocal.withInitial(() -> { + try { + return MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + }); + ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() { + private AtomicInteger serial = new AtomicInteger(0); + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(() -> { + try { + r.run(); + } finally { + try { + classMappedZip.get().close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + thread.setName("YatoClip Worker #" + serial.incrementAndGet()); + thread.setDaemon(true); + return thread; + } + }); + try { + final Set patchDataSet = patchesMetadata.patches.stream().map((PatchesMetadata.PatchMetadata metadata) -> new PatchData(CompletableFuture.supplyAsync(() -> { + try { + return getPatchedBytes(classMappedZip.get(), digest.get(), metadata); + } catch (IOException | CompressorException | InvalidHeaderException e) { + throw new RuntimeException(e); + } + }, executorService), metadata)).collect(Collectors.toSet()); + try (ZipOutputStream patchedZip = new ZipOutputStream(new FileOutputStream(patchedJar.toFile()))) { + patchedZip.setMethod(ZipOutputStream.DEFLATED); + patchedZip.setLevel(Deflater.BEST_SPEED); + Set processed = new HashSet<>(); + for (PatchData patchData : patchDataSet) { + putNextEntrySafe(patchedZip, patchData.metadata.name); + final byte[] patchedBytes = patchData.patchedBytesFuture.join(); + patchedZip.write(patchedBytes); + patchedZip.closeEntry(); + processed.add(patchData.metadata.name); + } + + ((Iterator) classMappedZip.get().entries()).forEachRemaining(zipEntry -> { + if (zipEntry.isDirectory() || processed.contains(applyRelocations(zipEntry.getName())) || patchesMetadata.copyExcludes.contains(zipEntry.getName())) + return; + try { + InputStream in = classMappedZip.get().getInputStream(zipEntry); + putNextEntrySafe(patchedZip, zipEntry.getName()); + patchedZip.write(IOUtils.toByteArray(in)); + patchedZip.closeEntry(); + } catch (Throwable t) { + throw new RuntimeException(t); + } + }); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + executorService.shutdown(); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + private static byte[] getPatchedBytes(ZipFile classMappedZip, MessageDigest digest, PatchesMetadata.PatchMetadata patchMetadata) throws IOException, CompressorException, InvalidHeaderException { + final byte[] originalBytes; + final ZipEntry originalEntry = classMappedZip.getEntry(applyRelocationsReverse(patchMetadata.name)); + if (originalEntry != null) + try (final InputStream in = classMappedZip.getInputStream(originalEntry)) { + originalBytes = IOUtils.toByteArray(in); + } + else originalBytes = new byte[0]; + final byte[] patchBytes; + try (final InputStream in = YatoclipPatcher.class.getClassLoader().getResourceAsStream("patches/" + patchMetadata.name + ".patch")) { + if (in == null) + throw new FileNotFoundException(); + patchBytes = IOUtils.toByteArray(in); + } + if (!patchMetadata.originalHash.equals(ServerSetup.toHex(digest.digest(originalBytes))) || !patchMetadata.patchHash.equals(ServerSetup.toHex(digest.digest(patchBytes)))) + throw new FileNotFoundException("Hash do not match"); + + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + Patch.patch(originalBytes, patchBytes, byteOut); + final byte[] patchedBytes = byteOut.toByteArray(); + if (!patchMetadata.targetHash.equals(ServerSetup.toHex(digest.digest(patchedBytes)))) + throw new FileNotFoundException("Hash do not match"); + return patchedBytes; + } + + private static void putNextEntrySafe(ZipOutputStream patchedZip, String name) throws IOException { + String[] split = name.split("/"); + split = Arrays.copyOfRange(split, 0, split.length - 1); + StringBuilder sb = new StringBuilder(); + for (String s : split) { + sb.append(s).append("/"); + try { + patchedZip.putNextEntry(new ZipEntry(sb.toString())); + } catch (ZipException e) { + if (e.getMessage().startsWith("duplicate entry")) + continue; + throw e; + } + } + final ZipEntry entry = new ZipEntry(name); + patchedZip.putNextEntry(entry); + } + + private static String applyRelocations(String name) { + if (!name.endsWith(".class")) return name; + if (name.indexOf('/') == -1) + name = "/" + name; + for (PatchesMetadata.Relocation relocation : patchesMetadata.relocations) { + if (name.startsWith(relocation.from) && (relocation.includeSubPackages || name.split("/").length == name.split("/").length - 1)) { + return relocation.to + name.substring(relocation.from.length()); + } + } + return name; + } + + private static String applyRelocationsReverse(String name) { + if (!name.endsWith(".class")) return name; + if (name.indexOf('/') == -1) + name = "/" + name; + for (PatchesMetadata.Relocation relocation : patchesMetadata.relocations) { + if (name.startsWith(relocation.to) && (relocation.includeSubPackages || name.split("/").length == name.split("/").length - 1)) { + return relocation.from + name.substring(relocation.to.length()); + } + } + return name; + } + + private static class PatchData { + + public final CompletableFuture patchedBytesFuture; + public final PatchesMetadata.PatchMetadata metadata; + + + private PatchData(CompletableFuture patchedBytesFuture, PatchesMetadata.PatchMetadata metadata) { + Objects.requireNonNull(patchedBytesFuture); + Objects.requireNonNull(metadata); + this.patchedBytesFuture = patchedBytesFuture.thenApply(Objects::requireNonNull); + this.metadata = metadata; + } + } + +} diff --git a/build.gradle.kts b/build.gradle.kts index 63ca8468..57ce074c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,17 @@ toothpick { groupId = "org.yatopiamc" val versionTag = System.getenv("BUILD_NUMBER") ?: "\"${gitCmd("rev-parse", "--short", "HEAD").output}\"" - forkVersion = "git-$forkName-$versionTag" + if(!System.getenv("BRANCH_NAME").isNullOrEmpty()) { + currentBranch = System.getenv("BRANCH_NAME") + } else if (!System.getenv("GITHUB_HEAD_REF").isNullOrEmpty()) { + currentBranch = System.getenv("GITHUB_HEAD_REF") + } else if (!System.getenv("GITHUB_REF").isNullOrEmpty()) { + currentBranch = System.getenv("GITHUB_REF").substring("refs/heads/".length) + } else { + currentBranch = gitCmd("rev-parse", "--abbrev-ref", "HEAD").output.toString().trim() + if(currentBranch == "HEAD") logger.warn("You are currently in \'detached HEAD\' state, branch information isn\'t available") + } + forkVersion = "git-$forkName-$currentBranch-$versionTag" forkUrl = "https://github.com/YatopiaMC/Yatopia" minecraftVersion = "1.16.5" @@ -32,6 +42,8 @@ toothpick { project = project(":$forkNameLowercase-api") patchesDir = rootProject.projectDir.resolve("patches/api") } + + logger.lifecycle("Configured version string: $calcVersionString") } subprojects { diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 891742b3..1991567b 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -11,6 +11,7 @@ repositories { mavenCentral() jcenter() maven("https://plugins.gradle.org/m2/") + maven("https://jitpack.io/") } dependencies { @@ -18,6 +19,15 @@ dependencies { implementation("com.github.jengelman.gradle.plugins:shadow:$shadowVersion") implementation("com.github.spullara.mustache.java:compiler:$mustacheVersion") implementation("javax.mail:mail:$javaxMailVersion") + implementation("com.github.ishlandbukkit:jbsdiff:deff66b794") + implementation("com.google.code.gson:gson:2.8.6") + implementation("com.google.guava:guava:30.0-jre") + implementation("commons-io:commons-io:2.8.0") +} + +tasks.withType { + options.encoding = "UTF-8" + sourceCompatibility = "1.8" } gradlePlugin { diff --git a/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/MakePatchesTask.java b/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/MakePatchesTask.java new file mode 100644 index 00000000..ec7223fc --- /dev/null +++ b/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/MakePatchesTask.java @@ -0,0 +1,269 @@ +package org.yatopiamc.yatoclip.gradle; + +import com.google.common.base.Preconditions; +import com.google.common.base.Throwables; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.google.gson.Gson; +import io.sigpipe.jbsdiff.Diff; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.gradle.api.DefaultTask; +import org.gradle.api.internal.project.ProjectInternal; +import org.gradle.api.tasks.Copy; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskAction; +import org.gradle.internal.logging.progress.ProgressLogger; +import org.gradle.internal.logging.progress.ProgressLoggerFactory; +import org.gradle.work.Incremental; +import org.gradle.workers.WorkerExecutor; + +import javax.inject.Inject; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class MakePatchesTask extends DefaultTask { + + @OutputDirectory + private final File outputDir = ((Copy) getProject().getTasks().getByPath("processResources")).getDestinationDir().toPath().resolve("patches").toFile(); + + @InputFile + @Incremental + public File originalJar = null; + @InputFile + @Incremental + public File targetJar = null; + + public Set getRelocations() { + return relocations; + } + + public void setRelocations(Set relocations) { + this.relocations = relocations; + } + + @Input + public Set relocations; + + public File getOriginalJar() { + return originalJar; + } + + public void setOriginalJar(File originalJar) { + this.originalJar = originalJar; + } + + public File getTargetJar() { + return targetJar; + } + + public void setTargetJar(File targetJar) { + this.targetJar = targetJar; + } + + public File getOutputDir() { + return outputDir; + } + + private ProgressLoggerFactory getProgressLoggerFactory() { + return ((ProjectInternal) getProject()).getServices().get(ProgressLoggerFactory.class); + } + + @Inject + public WorkerExecutor getWorkerExecutor() { + throw new UnsupportedOperationException(); + } + + @TaskAction + public void genPatches() throws IOException, InterruptedException { + Preconditions.checkNotNull(originalJar); + Preconditions.checkNotNull(targetJar); + getLogger().lifecycle("Generating patches for " + originalJar + " -> " + targetJar); + + final ProgressLogger genPatches = getProgressLoggerFactory().newOperation(getClass()).setDescription("Generate patches"); + genPatches.started(); + + genPatches.progress("Cleanup"); + outputDir.mkdirs(); + FileUtils.cleanDirectory(outputDir); + + genPatches.progress("Reading files"); + ThreadLocal originalZip = ThreadLocal.withInitial(() -> { + try { + return new ZipFile(originalJar); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + ThreadLocal targetZip = ThreadLocal.withInitial(() -> { + try { + return new ZipFile(targetJar); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + Set patchMetadata = Sets.newConcurrentHashSet(); + ThreadLocal digestThreadLocal = ThreadLocal.withInitial(() -> { + try { + return MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + }); + ThreadLocal progressLoggerThreadLocal = ThreadLocal.withInitial(() -> { + final ProgressLogger progressLogger = getProgressLoggerFactory().newOperation(this.getClass()); + progressLogger.setDescription("Patch worker"); + progressLogger.started("Idle"); + return progressLogger; + }); + final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), + new ThreadFactoryBuilder().setNameFormat("MakePatches-%d").setThreadFactory(r -> new Thread(() -> { + boolean isExceptionOccurred = false; + try { + r.run(); + } catch (Throwable t) { + isExceptionOccurred = true; + progressLoggerThreadLocal.get().completed(t.toString(), true); + throw t; + } finally { + digestThreadLocal.remove(); + if (!isExceptionOccurred) + progressLoggerThreadLocal.get().completed(); + progressLoggerThreadLocal.remove(); + try { + originalZip.get().close(); + targetZip.get().close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + })).build()); + AtomicInteger current = new AtomicInteger(0); + final int size = targetZip.get().size(); + ((Iterator) targetZip.get().entries()).forEachRemaining(zipEntryT -> { + genPatches.progress("Submitting tasks (" + current.incrementAndGet() + "/" + size + ")"); + if (zipEntryT.isDirectory()) return; + executorService.execute(() -> { + ZipEntry zipEntry = targetZip.get().getEntry(zipEntryT.getName()); + final String child = zipEntry.getName(); + progressLoggerThreadLocal.get().progress("Reading " + zipEntry.getName()); + File outputFile = new File(outputDir, child + ".patch"); + outputFile.getParentFile().mkdirs(); + final byte[] originalBytes; + final byte[] targetBytes; + final ZipEntry oEntry = originalZip.get().getEntry(applyRelocationsReverse(child)); + try ( + final InputStream oin = oEntry != null ? originalZip.get().getInputStream(oEntry) : null; + final InputStream tin = targetZip.get().getInputStream(zipEntry); + ) { + originalBytes = oin != null ? IOUtils.toByteArray(oin) : new byte[0]; + targetBytes = IOUtils.toByteArray(tin); + } catch (Throwable e) { + Throwables.throwIfUnchecked(e); + throw new RuntimeException(e); + } + if (Arrays.equals(originalBytes, targetBytes)) return; + + progressLoggerThreadLocal.get().progress("GenPatch " + zipEntry.getName()); + try (final OutputStream out = new FileOutputStream(outputFile)) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + Diff.diff(originalBytes, targetBytes, byteArrayOutputStream); + patchMetadata.add(new PatchesMetadata.PatchMetadata(child, toHex(digestThreadLocal.get().digest(originalBytes)), toHex(digestThreadLocal.get().digest(targetBytes)), toHex(digestThreadLocal.get().digest(byteArrayOutputStream.toByteArray())))); + out.write(byteArrayOutputStream.toByteArray()); + } catch (Throwable t) { + Throwables.throwIfUnchecked(t); + throw new RuntimeException(t); + } + + progressLoggerThreadLocal.get().progress("Idle"); + }); + }); + genPatches.progress("Calculating exclusions"); + Set copyExcludes = new HashSet<>(); + ((Iterator) originalZip.get().entries()).forEachRemaining(zipEntry -> { + if(targetZip.get().getEntry(applyRelocations(zipEntry.getName())) == null) + copyExcludes.add(zipEntry.getName()); + }); + originalZip.get().close(); + targetZip.get().close(); + + genPatches.progress("Waiting for patching to finish"); + executorService.shutdown(); + while (!executorService.awaitTermination(1, TimeUnit.SECONDS)) ; + digestThreadLocal.remove(); + + genPatches.progress("Writing patches metadata"); + try (final OutputStream out = new FileOutputStream(new File(outputDir, "metadata.json")); + final Writer writer = new OutputStreamWriter(out)) { + new Gson().toJson(new PatchesMetadata(patchMetadata, relocations, copyExcludes), writer); + } + + /* + genPatches.progress("Reading jar files into memory"); + byte[] origin = Files.readAllBytes(originalJar.toPath()); + byte[] target = Files.readAllBytes(targetJar.toPath()); + + genPatches.progress("Generating patch"); + try(final OutputStream out = new BufferedOutputStream(new FileOutputStream(output))){ + Diff.diff(origin, target, out); + } + */ + + genPatches.completed(); + + } + + private String applyRelocations(String name) { + if(!name.endsWith(".class")) return name; + if (name.indexOf('/') == -1) + name = "/" + name; + for (PatchesMetadata.Relocation relocation : relocations) { + if (name.startsWith(relocation.from) && (relocation.includeSubPackages || name.split("/").length == name.split("/").length - 1)) { + return relocation.to + name.substring(relocation.from.length()); + } + } + return name; + } + + private String applyRelocationsReverse(String name) { + if(!name.endsWith(".class")) return name; + if (name.indexOf('/') == -1) + name = "/" + name; + for (PatchesMetadata.Relocation relocation : relocations) { + if (name.startsWith(relocation.to) && (relocation.includeSubPackages || name.split("/").length == name.split("/").length - 1)) { + return relocation.from + name.substring(relocation.to.length()); + } + } + return name; + } + + public static String toHex(final byte[] hash) { + final StringBuilder sb = new StringBuilder(hash.length * 2); + for (byte aHash : hash) { + sb.append(String.format("%02X", aHash & 0xFF)); + } + return sb.toString(); + } + +} diff --git a/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/PatchesMetadata.java b/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/PatchesMetadata.java new file mode 100644 index 00000000..4c7f0bbb --- /dev/null +++ b/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/PatchesMetadata.java @@ -0,0 +1,51 @@ +package org.yatopiamc.yatoclip.gradle; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +public class PatchesMetadata { + + public final Set patches; + public final Set relocations; + public final Set copyExcludes; + + public PatchesMetadata(Set patches, Set relocations, Set copyExcludes) { + Objects.requireNonNull(copyExcludes); + this.copyExcludes = Collections.unmodifiableSet(copyExcludes); + Objects.requireNonNull(relocations); + this.relocations = Collections.unmodifiableSet(relocations); + Objects.requireNonNull(patches); + this.patches = Collections.unmodifiableSet(patches); + } + + public static class PatchMetadata { + public final String name; + public final String originalHash; + public final String targetHash; + public final String patchHash; + + public PatchMetadata(String name, String originalHash, String targetHash, String patchHash) { + this.name = name; + this.originalHash = originalHash; + this.targetHash = targetHash; + this.patchHash = patchHash; + } + } + + public static class Relocation implements Serializable { + + public final String from; + public final String to; + public final boolean includeSubPackages; + + public Relocation(String from, String to, boolean includeSubPackages) { + Objects.requireNonNull(from); + Objects.requireNonNull(to); + this.from = from.replaceAll("\\.", "/"); + this.to = to.replaceAll("\\.", "/"); + this.includeSubPackages = includeSubPackages; + } + } +} diff --git a/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/PropertiesUtils.java b/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/PropertiesUtils.java new file mode 100644 index 00000000..39b35480 --- /dev/null +++ b/buildSrc/src/main/java/org/yatopiamc/yatoclip/gradle/PropertiesUtils.java @@ -0,0 +1,22 @@ +package org.yatopiamc.yatoclip.gradle; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Path; +import java.util.Properties; + +public class PropertiesUtils { + + public static void saveProperties(Properties prop, Path file, String comments){ + System.out.println("Saving properties file to " + file); + file.toFile().getParentFile().mkdirs(); + file.toFile().delete(); + try(final OutputStream out = new FileOutputStream(file.toFile())) { + prop.store(out, comments); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/buildSrc/src/main/kotlin/ConfigureSubprojects.kt b/buildSrc/src/main/kotlin/ConfigureSubprojects.kt index e5ef2a1c..16f6e83b 100644 --- a/buildSrc/src/main/kotlin/ConfigureSubprojects.kt +++ b/buildSrc/src/main/kotlin/ConfigureSubprojects.kt @@ -5,19 +5,27 @@ import kotlinx.dom.elements import kotlinx.dom.parseXml import kotlinx.dom.search import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.UnknownDomainObjectException import org.gradle.api.plugins.JavaLibraryPlugin +import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.publish.PublishingExtension import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.publish.maven.plugins.MavenPublishPlugin import org.gradle.api.publish.maven.tasks.GenerateMavenPom +import org.gradle.api.tasks.Copy import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.compile.JavaCompile import org.gradle.api.tasks.javadoc.Javadoc import org.gradle.api.tasks.testing.Test import org.gradle.kotlin.dsl.* +import org.yatopiamc.yatoclip.gradle.MakePatchesTask +import org.yatopiamc.yatoclip.gradle.PatchesMetadata +import org.yatopiamc.yatoclip.gradle.PropertiesUtils import java.nio.charset.StandardCharsets.UTF_8 import java.text.SimpleDateFormat import java.util.* +import kotlin.collections.HashSet internal fun Project.configureSubprojects() { subprojects { @@ -49,6 +57,87 @@ internal fun Project.configureSubprojects() { project.name.endsWith("api") -> configureApiProject() } } + rootProject.project("Yatoclip") { + configureYatoclipProject() + } +} + +private fun Project.configureYatoclipProject() { + try { + rootProject.toothpick.serverProject.project.extensions.getByName("relocations") + } catch (e: UnknownDomainObjectException) { + return + } + + apply() + apply() + + tasks.register("genPatches") { + originalJar = rootProject.toothpick.paperDir.resolve("work").resolve("Minecraft") + .resolve(rootProject.toothpick.minecraftVersion).resolve("${rootProject.toothpick.minecraftVersion}-m.jar") + targetJar = rootProject.toothpick.serverProject.project.tasks.getByName("shadowJar").outputs.files.singleFile + setRelocations(rootProject.toothpick.serverProject.project.extensions.getByName("relocations") as HashSet) + dependsOn(rootProject.toothpick.serverProject.project.tasks.getByName("shadowJar")) + doLast { + val prop = Properties() + prop.setProperty("minecraftVersion", rootProject.toothpick.minecraftVersion) + PropertiesUtils.saveProperties( + prop, + outputDir.toPath().parent.resolve("yatoclip-launch.properties"), + "Yatoclip launch values" + ) + } + } + + val sourceSets = extensions.getByName("sourceSets") as org.gradle.api.tasks.SourceSetContainer + + sourceSets.create("java9") { + java { + srcDir("src/java9") + } + } + + val shadowJar by tasks.getting(ShadowJar::class) { + manifest { + attributes( + "Main-Class" to "org.yatopiamc.yatoclip.Yatoclip", + "Launcher-Agent-Class" to "org.yatopiamc.yatoclip.YatoclipLaunch", + "Premain-Class" to "org.yatopiamc.yatoclip.YatoclipLaunch", + "Multi-Release" to "true" + ) + } + into("META-INF/versions/9") { + from(sourceSets.getByName("java9").output) + } + } + + tasks.register("copyJar") { + val targetName = "yatopia-${rootProject.toothpick.minecraftVersion}-yatoclip.jar" + from(shadowJar.outputs.files.singleFile) { + rename { targetName } + } + + into(rootProject.projectDir) + + doLast { + logger.lifecycle(">>> $targetName saved to root project directory") + } + + dependsOn(shadowJar) + } + + tasks.getByName("processResources").dependsOn(tasks.getByName("genPatches")) + tasks.getByName("assemble").dependsOn(tasks.getByName("copyJar")) + tasks.getByName("jar").enabled = false + val buildTask = tasks.getByName("build") + val buildTaskDependencies = HashSet(buildTask.dependsOn) + buildTask.setDependsOn(HashSet()) + buildTask.onlyIf { false } + tasks.register("yatoclip") { + buildTaskDependencies.forEach { + dependsOn(it) + } + } } private fun Project.configureServerProject() { @@ -76,7 +165,7 @@ private fun Project.configureServerProject() { "Implementation-Version" to toothpick.forkVersion, "Implementation-Vendor" to SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(Date()), "Specification-Title" to "Bukkit", - "Specification-Version" to "${project.version}", + "Specification-Version" to "${project.rootProject.toothpick.minecraftVersion}-${project.rootProject.toothpick.nmsRevision}", "Specification-Vendor" to "Bukkit Team" ) } @@ -85,11 +174,14 @@ private fun Project.configureServerProject() { into("META-INF/maven/io.papermc.paper/paper") } + val relocationSet = HashSet() + // Don't like to do this but sadly have to do this for compatibility reasons relocate("org.bukkit.craftbukkit", "org.bukkit.craftbukkit.v${toothpick.nmsPackage}") { exclude("org.bukkit.craftbukkit.Main*") } relocate("net.minecraft.server", "net.minecraft.server.v${toothpick.nmsPackage}") + relocationSet.add(PatchesMetadata.Relocation("", "net.minecraft.server.v${toothpick.nmsPackage}", false)) // Make sure we relocate deps the same as Paper et al. val pomFile = project.projectDir.resolve("pom.xml") @@ -111,9 +203,11 @@ private fun Project.configureServerProject() { if (pattern != "org.bukkit.craftbukkit" && pattern != "net.minecraft.server") { // We handle these ourselves above logger.debug("Imported relocation to server project shadowJar from ${pomFile.absolutePath}: $pattern to $shadedPattern") relocate(pattern, shadedPattern) + relocationSet.add(PatchesMetadata.Relocation(pattern, shadedPattern, true)) } } } + project.extensions.add("relocations", relocationSet) } tasks.getByName("build") { dependsOn(shadowJar) @@ -121,9 +215,8 @@ private fun Project.configureServerProject() { extensions.configure { publications { - getByName("mavenJava") { - artifactId = rootProject.name - artifact(tasks["shadowJar"]) + create("shadow") { + artifact(project.tasks.named("shadowJar")) } } } @@ -134,7 +227,7 @@ private fun Project.configureApiProject() { val jar by this.tasks.getting(Jar::class) { doFirst { buildDir.resolve("tmp/pom.properties") - .writeText("version=${project.version}") + .writeText("version=${project.rootProject.toothpick.minecraftVersion}-${project.rootProject.toothpick.nmsRevision}") } from(buildDir.resolve("tmp/pom.properties")) { into("META-INF/maven/${project.group}/${project.name}") diff --git a/buildSrc/src/main/kotlin/DependencyLoading.kt b/buildSrc/src/main/kotlin/DependencyLoading.kt index ff3b9f15..0e256ab7 100644 --- a/buildSrc/src/main/kotlin/DependencyLoading.kt +++ b/buildSrc/src/main/kotlin/DependencyLoading.kt @@ -32,7 +32,7 @@ fun DependencyHandlerScope.loadDependencies(project: Project) { val groupId = dependencyElem.search("groupId").first().textContent val artifactId = dependencyElem.search("artifactId").first().textContent val version = dependencyElem.search("version").first().textContent.applyReplacements( - "project.version" to project.version.toString(), + "project.version" to "${project.rootProject.toothpick.minecraftVersion}-${project.rootProject.toothpick.nmsRevision}", "minecraft.version" to project.toothpick.minecraftVersion ) val scope = dependencyElem.search("scope").firstOrNull()?.textContent diff --git a/buildSrc/src/main/kotlin/ToothpickExtension.kt b/buildSrc/src/main/kotlin/ToothpickExtension.kt index fd314511..d1c0c9e2 100644 --- a/buildSrc/src/main/kotlin/ToothpickExtension.kt +++ b/buildSrc/src/main/kotlin/ToothpickExtension.kt @@ -33,6 +33,12 @@ open class ToothpickExtension(objects: ObjectFactory) { lateinit var patchCreditsOutput: String lateinit var patchCreditsTemplate: String + lateinit var currentBranch : String + val currentBranchDisplayName + get() = currentBranch.replace("/${minecraftVersion}", "") + val calcVersionString + get() = if(!currentBranch.startsWith("ver/")) { "${minecraftVersion}-${nmsRevision}-${currentBranchDisplayName.replace('/', '_')}" } else "${minecraftVersion}-${nmsRevision}" + fun server(receiver: ToothpickSubproject.() -> Unit) { serverProject = ToothpickSubproject() receiver(serverProject) diff --git a/buildSrc/src/main/kotlin/ToothpickExtensions.kt b/buildSrc/src/main/kotlin/ToothpickExtensions.kt index a4a02800..1c524c0c 100644 --- a/buildSrc/src/main/kotlin/ToothpickExtensions.kt +++ b/buildSrc/src/main/kotlin/ToothpickExtensions.kt @@ -10,7 +10,7 @@ fun Project.toothpick(receiver: ToothpickExtension.() -> Unit) { receiver(toothpick) allprojects { group = toothpick.groupId - version = "${toothpick.minecraftVersion}-${toothpick.nmsRevision}" + version = toothpick.calcVersionString } configureSubprojects() initToothpickTasks() diff --git a/buildSrc/src/main/kotlin/task/RebuildPatches.kt b/buildSrc/src/main/kotlin/task/RebuildPatches.kt index b44a7159..afaa2d98 100644 --- a/buildSrc/src/main/kotlin/task/RebuildPatches.kt +++ b/buildSrc/src/main/kotlin/task/RebuildPatches.kt @@ -70,7 +70,12 @@ private fun Project.updatePatches( "--no-stat", "--zero-commit", "--full-index", "--no-signature", "-N", "-o", patchPath.absolutePath, previousUpstreamName, dir = projectDir, - printOut = true + printOut = false ) ) + gitCmd( + "add", patchPath.canonicalPath, + dir = patchPath, + printOut = true + ) } diff --git a/patches/api/0008-Suspected-plugins-report.patch b/patches/api/0008-Suspected-plugins-report.patch new file mode 100644 index 00000000..cad34cf3 --- /dev/null +++ b/patches/api/0008-Suspected-plugins-report.patch @@ -0,0 +1,258 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Fri, 29 Jan 2021 09:57:47 +0800 +Subject: [PATCH] Suspected plugins report + +Added "Suspected Plugins" to Watchdog, crash reports and exception messages + +diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +index 26685f59b235ea5b4c4fb7ae21acb5149edaa2b3..02c20a33161094b08dc2ae9353c0504561b0b452 100644 +--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java ++++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +@@ -560,7 +560,11 @@ public final class SimplePluginManager implements PluginManager { + + // Paper start + private void handlePluginException(String msg, Throwable ex, Plugin plugin) { +- server.getLogger().log(Level.SEVERE, msg, ex); ++ // Yatopia start - detailed report ++ server.getLogger().log(Level.SEVERE, msg); ++ org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(ex, _msg -> server.getLogger().log(Level.SEVERE, _msg)); ++ server.getLogger().log(Level.SEVERE, org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW, ex); ++ // Yatopia end + callEvent(new ServerExceptionEvent(new ServerPluginEnableDisableException(msg, ex, plugin))); + } + // Paper end +@@ -621,7 +625,11 @@ public final class SimplePluginManager implements PluginManager { + } catch (Throwable ex) { + // Paper start - error reporting + String msg = "Could not pass event " + event.getEventName() + " to " + registration.getPlugin().getDescription().getFullName(); +- server.getLogger().log(Level.SEVERE, msg, ex); ++ // Yatopia start - detailed report ++ server.getLogger().log(Level.SEVERE, msg); ++ org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(ex, _msg -> server.getLogger().log(Level.SEVERE, _msg)); ++ server.getLogger().log(Level.SEVERE, org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW, ex); ++ // Yatopia end + if (!(event instanceof ServerExceptionEvent)) { // We don't want to cause an endless event loop + callEvent(new ServerExceptionEvent(new ServerEventException(msg, ex, registration.getPlugin(), registration.getListener(), event))); + } +@@ -905,4 +913,10 @@ public final class SimplePluginManager implements PluginManager { + } + // Paper end + ++ // Yatopia start - Accessor ++ @NotNull ++ public Collection getPluginLoaders() { ++ return new HashSet<>(fileAssociations.values()); ++ } ++ // Yatopia end + } +diff --git a/src/main/java/org/bukkit/plugin/java/JavaPlugin.java b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java +index 04fa3991f6ce4e9dad804f28fc6c947695857089..cb11eab6e13ed1c395b8f7db033c9a2817f4089c 100644 +--- a/src/main/java/org/bukkit/plugin/java/JavaPlugin.java ++++ b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java +@@ -111,7 +111,7 @@ public abstract class JavaPlugin extends PluginBase { + * @return File containing this plugin + */ + @NotNull +- protected File getFile() { ++ public File getFile() { // Yatopia + return file; + } + +diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +index 384edf9890dfbd1cddfdcac4db1ebe9a4d761f78..7c0c63c3f5734d59aa8b57fe3eb3c1fe0e137f12 100644 +--- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java ++++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +@@ -379,7 +379,11 @@ public final class JavaPluginLoader implements PluginLoader { + try { + jPlugin.setEnabled(true); + } catch (Throwable ex) { +- server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); ++ // Yatopia start - detailed report ++ server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)"); ++ org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(ex, _msg -> server.getLogger().log(Level.SEVERE, _msg)); ++ server.getLogger().log(Level.SEVERE, org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW, ex); ++ // Yatopia end + // Paper start - Disable plugins that fail to load + server.getPluginManager().disablePlugin(jPlugin, true); // Paper - close Classloader on disable - She's dead jim + return; +@@ -414,7 +418,11 @@ public final class JavaPluginLoader implements PluginLoader { + try { + jPlugin.setEnabled(false); + } catch (Throwable ex) { +- server.getLogger().log(Level.SEVERE, "Error occurred while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); ++ // Yatopia start - detailed report ++ server.getLogger().log(Level.SEVERE, "Error occurred while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)"); ++ org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(ex, _msg -> server.getLogger().log(Level.SEVERE, _msg)); ++ server.getLogger().log(Level.SEVERE, org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW, ex); ++ // Yatopia end + } + + if (cloader instanceof PluginClassLoader) { +@@ -432,11 +440,20 @@ public final class JavaPluginLoader implements PluginLoader { + loader.close(); + } + } catch (IOException e) { ++ // Yatopia start - detailed report + server.getLogger().log(Level.WARNING, "Error closing the Plugin Class Loader for " + plugin.getDescription().getFullName()); ++ org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(e, _msg -> server.getLogger().log(Level.WARNING, _msg)); ++ server.getLogger().log(Level.WARNING, org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW, e); ++ // Yatopia end + e.printStackTrace(); + } + // Paper end + } + } + } ++ // Yatopia start - Accessor ++ public List getClassLoaders() { ++ return java.util.Collections.unmodifiableList(loaders); ++ } ++ // Yatopia end + } +diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java +index 7760be3e34fa20825faf145d9fb5b2855c1a4602..2e8f3efdb683c44b3e42bb0187bc907e64cde288 100644 +--- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java ++++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java +@@ -232,4 +232,13 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot + '}'; + } + // Paper end ++ ++ // Yatopia start - Accessor ++ public java.util.Collection> getLoadedClasses() { ++ return java.util.Collections.unmodifiableCollection( ++ new java.util.HashSet<>(classes.values()).stream() ++ .filter(clazz -> clazz.getClassLoader() == this).collect(java.util.stream.Collectors.toSet()) ++ ); ++ } ++ // Yatopia end + } +diff --git a/src/main/java/org/yatopiamc/yatopia/api/internal/StackTraceUtils.java b/src/main/java/org/yatopiamc/yatopia/api/internal/StackTraceUtils.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0aa9bc6ad0a85d469b29201b9da29165bafb874c +--- /dev/null ++++ b/src/main/java/org/yatopiamc/yatopia/api/internal/StackTraceUtils.java +@@ -0,0 +1,105 @@ ++package org.yatopiamc.yatopia.api.internal; ++ ++import com.google.common.base.Suppliers; ++import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; ++import org.bukkit.Bukkit; ++import org.bukkit.plugin.Plugin; ++import org.bukkit.plugin.PluginLoader; ++import org.bukkit.plugin.SimplePluginManager; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.bukkit.plugin.java.JavaPluginLoader; ++import org.bukkit.plugin.java.PluginClassLoader; ++ ++import java.util.Arrays; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.HashSet; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++import java.util.concurrent.TimeUnit; ++import java.util.function.Consumer; ++import java.util.function.Supplier; ++import java.util.stream.Collectors; ++ ++public class StackTraceUtils { ++ ++ public static final String EXCEPTION_DETAILS_BELOW = "Exception details below: "; ++ ++ private static final Supplier>>> loadedClassesSupplier = Suppliers.memoizeWithExpiration(StackTraceUtils::scanForPluginClasses, 5, TimeUnit.SECONDS); ++ ++ public static void print(StackTraceElement[] stackTrace, Consumer out) { ++ Set suspectedPlugins = getSuspectedPluginsFromStackTrace(stackTrace); ++ ++ printSuspectedPlugins(out, suspectedPlugins); ++ } ++ ++ public static void print(Throwable t, Consumer out) { ++ Set suspectedPlugins = getSuspectedPluginsFromStackTrace(getStackTracesFromThrowable(t).toArray(new StackTraceElement[0])); ++ ++ printSuspectedPlugins(out, suspectedPlugins); ++ } ++ ++ private static Set getStackTracesFromThrowable(Throwable t) { ++ if(t == null) return Collections.emptySet(); ++ Set elements = new ObjectOpenHashSet<>(); ++ elements.addAll(getStackTracesFromThrowable(t.getCause())); ++ elements.addAll(Arrays.stream(t.getSuppressed()).flatMap(throwable -> getStackTracesFromThrowable(throwable).stream()).collect(Collectors.toSet())); ++ elements.addAll(Arrays.asList(t.getStackTrace())); ++ return elements; ++ } ++ ++ private static void printSuspectedPlugins(Consumer out, Set suspectedPlugins) { ++ if (!suspectedPlugins.isEmpty()) { ++ out.accept("Suspected Plugins: "); ++ for (Plugin plugin : suspectedPlugins) { ++ StringBuilder builder = new StringBuilder("\t"); ++ builder.append(plugin.getName()) ++ .append("{") ++ .append(plugin.isEnabled() ? "enabled" : "disabled") ++ .append(",").append("ver=").append(plugin.getDescription().getVersion()); ++ if (!plugin.isNaggable()) ++ builder.append(",").append("nag"); ++ if (plugin instanceof JavaPlugin) ++ builder.append(",").append("path=").append(((JavaPlugin) plugin).getFile()); ++ ++ builder.append("}"); ++ out.accept(builder.toString()); ++ } ++ } else { ++ out.accept("Suspected Plugins: None"); ++ } ++ } ++ ++ private static Set getSuspectedPluginsFromStackTrace(StackTraceElement[] stackTrace) { ++ Map>> loadedClasses = loadedClassesSupplier.get(); ++ Set suspectedPlugins = new HashSet<>(); ++ for (StackTraceElement stackTraceElement : stackTrace) { ++ for (Map.Entry>> pluginSetEntry : loadedClasses.entrySet()) { ++ if (pluginSetEntry.getValue().stream().anyMatch(clazz -> clazz.getName().equals(stackTraceElement.getClassName()))) ++ suspectedPlugins.add(pluginSetEntry.getKey()); ++ } ++ } ++ return suspectedPlugins; ++ } ++ ++ private static Map>> scanForPluginClasses() { ++ Map>> loadedClasses = new Object2ObjectOpenHashMap<>(); ++ if (Bukkit.getPluginManager() instanceof SimplePluginManager) { ++ final SimplePluginManager pluginManager = (SimplePluginManager) Bukkit.getPluginManager(); ++ final Collection pluginLoaders = pluginManager.getPluginLoaders(); ++ for (PluginLoader pluginLoader : pluginLoaders) { ++ if (pluginLoader instanceof JavaPluginLoader) { ++ JavaPluginLoader javaPluginLoader = (JavaPluginLoader) pluginLoader; ++ final List classLoaders = javaPluginLoader.getClassLoaders(); ++ for (PluginClassLoader classLoader : classLoaders) { ++ loadedClasses.put(classLoader.getPlugin(), new ObjectOpenHashSet<>(classLoader.getLoadedClasses())); ++ } ++ } ++ } ++ } ++ return loadedClasses; ++ } ++ ++} +diff --git a/src/test/java/org/bukkit/AnnotationTest.java b/src/test/java/org/bukkit/AnnotationTest.java +index 4c2780c903ec354edac741b673a7174284a9849a..ed5629295324123576af992cb1b4c7ce90ffa1de 100644 +--- a/src/test/java/org/bukkit/AnnotationTest.java ++++ b/src/test/java/org/bukkit/AnnotationTest.java +@@ -49,8 +49,11 @@ public class AnnotationTest { + "co/aikar/timings/TimingHistory$2$1$2", + "co/aikar/timings/TimingHistory$3", + "co/aikar/timings/TimingHistory$4", +- "co/aikar/timings/TimingHistoryEntry$1" ++ "co/aikar/timings/TimingHistoryEntry$1", + // Paper end ++ // Yatopia start ++ "org/yatopiamc/yatopia/api/internal/StackTraceUtils" ++ // Yatopia end + }; + + @Test diff --git a/patches/removed/server/0060-New-Network-System.patch b/patches/removed/server/0060-New-Network-System.patch index cf9b78aa..12463fc7 100644 --- a/patches/removed/server/0060-New-Network-System.patch +++ b/patches/removed/server/0060-New-Network-System.patch @@ -6,15 +6,11 @@ Subject: [PATCH] New Network System Co-authored-by: Ivan Pekov diff --git a/pom.xml b/pom.xml -index 8af1a91102c5cc4c230f622e6629e46e95f17d44..9b7ed0de1054285dadff6aefc95c7207079504a6 100644 +index 6109699411c349c4965de6dbdbd9f8454bc18b10..0bbc7badb1f597357d0fce7cd0f5faf5eb1d0eb5 100644 --- a/pom.xml +++ b/pom.xml -@@ -53,9 +53,17 @@ - - io.netty - netty-all -- 4.1.50.Final -+ 4.1.58.Final +@@ -56,6 +56,14 @@ + 4.1.58.Final + diff --git a/patches/server/0001-Modify-POM.patch b/patches/server/0001-Modify-POM.patch index b52b2226..3135f375 100644 --- a/patches/server/0001-Modify-POM.patch +++ b/patches/server/0001-Modify-POM.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Modify POM diff --git a/pom.xml b/pom.xml -index 752d62eb3b87ab24260ec2c029bae0d2b0e3b908..4f56aa4ae78b9d3756983cde52bc1d1adda0c9d4 100644 +index 752d62eb3b87ab24260ec2c029bae0d2b0e3b908..e790d779d24c2c8d4a74d458839c11bc494eeef1 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,11 @@ @@ -54,6 +54,64 @@ index 752d62eb3b87ab24260ec2c029bae0d2b0e3b908..4f56aa4ae78b9d3756983cde52bc1d1a ${project.version} compile +@@ -44,7 +53,7 @@ + + io.netty + netty-all +- 4.1.50.Final ++ 4.1.58.Final + + + +@@ -61,7 +70,7 @@ + + org.jline + jline-terminal-jansi +- 3.12.1 ++ 3.19.0 + runtime + + +@@ -144,14 +153,14 @@ + + org.hamcrest + hamcrest-library +- 1.3 ++ 2.2 + test + + + + io.github.classgraph + classgraph +- 4.8.47 ++ 4.8.98 + test + + @@ -161,6 +170,12 @@ 1.1.0-SNAPSHOT compile diff --git a/patches/server/0002-Brandings.patch b/patches/server/0002-Brandings.patch index 4f3ad14a..b195260b 100644 --- a/patches/server/0002-Brandings.patch +++ b/patches/server/0002-Brandings.patch @@ -161,22 +161,28 @@ index 58d01c6f8abcd9e1792495abd08b186f9d03f834..476939bde38246eb0fd96e6a4ba8076c diff --git a/src/main/java/org/yatopiamc/yatopia/server/YatopiaVersionFetcher.java b/src/main/java/org/yatopiamc/yatopia/server/YatopiaVersionFetcher.java new file mode 100644 -index 0000000000000000000000000000000000000000..db0f7b1acf28ebe486026b8a7507ed4c7e26fb0f +index 0000000000000000000000000000000000000000..2898bfb109c63a93971bd38cc4778da1dc37c445 --- /dev/null +++ b/src/main/java/org/yatopiamc/yatopia/server/YatopiaVersionFetcher.java -@@ -0,0 +1,96 @@ +@@ -0,0 +1,123 @@ +package org.yatopiamc.yatopia.server; + +import com.destroystokyo.paper.VersionHistoryManager; +import com.google.common.base.Charsets; ++import com.google.common.io.Resources; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import java.io.BufferedReader; +import java.io.IOException; ++import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; ++import java.net.URLEncoder; ++import java.util.Arrays; ++import java.util.Objects; ++import java.util.jar.Manifest; +import javax.annotation.Nonnull; + +import com.destroystokyo.paper.util.VersionFetcher; @@ -184,8 +190,6 @@ index 0000000000000000000000000000000000000000..db0f7b1acf28ebe486026b8a7507ed4c + +public class YatopiaVersionFetcher implements VersionFetcher { + -+ private static final String GITHUB_BRANCH_NAME = "ver/1.16.5"; -+ + @Override + public long getCacheTime() { + return 3600000; @@ -194,15 +198,23 @@ index 0000000000000000000000000000000000000000..db0f7b1acf28ebe486026b8a7507ed4c + @Nonnull + @Override + public String getVersionMessage(@Nonnull String serverVersion) { ++ if(serverVersion.equals("null")) return "Custom build"; + String[] parts = serverVersion.substring("git-Yatopia-".length()).split("[-\\s]"); -+ String updateMessage = getUpdateStatusMessage("YatopiaMC/Yatopia", GITHUB_BRANCH_NAME, parts[0]); ++ String branch = String.join("-", Arrays.copyOfRange(parts, 0, parts.length - 3)); ++ String version = parts[parts.length - 3]; ++ String updateMessage = getUpdateStatusMessage("YatopiaMC/Yatopia", branch, version); + String history = getHistory(); + return history != null ? history + "\n" + updateMessage : updateMessage; + } + + private String getUpdateStatusMessage(String repo, String branch, String versionInfo) { -+ versionInfo = versionInfo.replace("\"", ""); -+ int distance = fetchDistanceFromGitHub(repo, branch, versionInfo); ++ int distance; ++ try { ++ int jenkinsBuild = Integer.parseInt(versionInfo); ++ distance = fetchDistanceFromJenkins(branch, jenkinsBuild); ++ } catch (NumberFormatException ignored) { ++ distance = fetchDistanceFromGitHub(repo, branch, versionInfo.replace("\"", "")); ++ } + + switch (distance) { + case -1: @@ -216,6 +228,21 @@ index 0000000000000000000000000000000000000000..db0f7b1acf28ebe486026b8a7507ed4c + } + } + ++ // modified from PurpurVersionFetcher ++ private static int fetchDistanceFromJenkins(String branch, int jenkinsBuild) { ++ try { ++ try (BufferedReader reader = Resources.asCharSource(new URL("https://ci.codemc.io/job/YatopiaMC/job/Yatopia/job/" + URLEncoder.encode(branch, Charsets.UTF_8.name()) + "/lastStableBuild/buildNumber"), Charsets.UTF_8).openBufferedStream()) { ++ return Integer.decode(reader.readLine()) - jenkinsBuild; ++ } catch (NumberFormatException ex) { ++ ex.printStackTrace(); ++ return -2; ++ } ++ } catch (IOException e) { ++ e.printStackTrace(); ++ return -1; ++ } ++ } ++ + // Contributed by Techcable in GH-65 + // from PaperVersionFetcher + private static int fetchDistanceFromGitHub(@Nonnull String repo, @Nonnull String branch, @Nonnull String hash) { diff --git a/patches/server/0003-Utilities.patch b/patches/server/0003-Utilities.patch index f8618a3c..fb5ef182 100644 --- a/patches/server/0003-Utilities.patch +++ b/patches/server/0003-Utilities.patch @@ -9,7 +9,7 @@ Co-authored-by: Mykyta Komarnytskyy Co-authored-by: Ivan Pekov diff --git a/pom.xml b/pom.xml -index 4f56aa4ae78b9d3756983cde52bc1d1adda0c9d4..d8ec87143370144c04502cd7bddf57f2f5e25168 100644 +index e790d779d24c2c8d4a74d458839c11bc494eeef1..04ec0b474a234c04450f04ecf7336d3bb0947420 100644 --- a/pom.xml +++ b/pom.xml @@ -176,6 +176,12 @@ diff --git a/patches/server/0009-Add-NBT-API-as-a-first-class-lib.patch b/patches/server/0009-Add-NBT-API-as-a-first-class-lib.patch index 3251eb95..eb7a984c 100644 --- a/patches/server/0009-Add-NBT-API-as-a-first-class-lib.patch +++ b/patches/server/0009-Add-NBT-API-as-a-first-class-lib.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add NBT API as a first-class lib diff --git a/pom.xml b/pom.xml -index d8ec87143370144c04502cd7bddf57f2f5e25168..8af1a91102c5cc4c230f622e6629e46e95f17d44 100644 +index 04ec0b474a234c04450f04ecf7336d3bb0947420..9402ec96f626e5e4f36f7dd678454f9efeb7e254 100644 --- a/pom.xml +++ b/pom.xml @@ -358,6 +358,10 @@ diff --git a/patches/server/0062-lithium-cache-chunk-gen-sea-level.patch b/patches/server/0062-lithium-cache-chunk-gen-sea-level.patch new file mode 100644 index 00000000..54871796 --- /dev/null +++ b/patches/server/0062-lithium-cache-chunk-gen-sea-level.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: SuperCoder7979 <25208576+SuperCoder7979@users.noreply.github.com> +Date: Fri, 22 Jan 2021 16:38:19 -0500 +Subject: [PATCH] lithium: cache chunk gen sea level + +Chunk generator settings are passed to the noise chunk generator through a supplier, which retrieves a given chunk generator settings registry key from the registry. The problem arises as this setting is retrieved from the supplier to get the sea level, which is called every single time a block is placed by the generator, for a total of 65536 registry lookups per chunk. As you can imagine this isn't all that fast and removing it speeds up the chunk generation time by a moderate amount (13% of CPU time spent in populateNoise without the patch vs 10% with the patch). My solution to this was to stick the supplier into a Lazy<> so it's computed once and then cached, but I'm open to suggestions. (The sea level can't be computed ahead of time due to a quirk in the new registry system, which would have been the best option here.) + +This code was originally made by SuperCoder7979 in a Pull Request to lithium (https://github.com/jellysquid3/lithium-fabric/pull/179) + +This code was orignally licensed under the LGPL-3.0 license. + +diff --git a/src/main/java/net/minecraft/server/ChunkGeneratorAbstract.java b/src/main/java/net/minecraft/server/ChunkGeneratorAbstract.java +index fa60285c0c48147ad09b9197bfe577f504dd34bd..de469f9b4f0fecc05dca7a5aacd1308db6f80a18 100644 +--- a/src/main/java/net/minecraft/server/ChunkGeneratorAbstract.java ++++ b/src/main/java/net/minecraft/server/ChunkGeneratorAbstract.java +@@ -17,6 +17,7 @@ import javax.annotation.Nullable; + + public final class ChunkGeneratorAbstract extends ChunkGenerator { + ++ private int cachedSeaLevel; // Yatopia - lithium cache chunk gen settings + public static final Codec d = RecordCodecBuilder.create((instance) -> { + return instance.group(WorldChunkManager.a.fieldOf("biome_source").forGetter((chunkgeneratorabstract) -> { + return chunkgeneratorabstract.b; +@@ -101,6 +102,7 @@ public final class ChunkGeneratorAbstract extends ChunkGenerator { + } else { + this.v = null; + } ++ this.cachedSeaLevel = ((GeneratorSettingBase) this.h.get()).g(); // Yatopia - lithium cache chunk gen settings + + } + +@@ -688,10 +690,18 @@ public final class ChunkGeneratorAbstract extends ChunkGenerator { + return this.x; + } + ++ // Yatopia start - lithium cache chunk gen settings ++ /** ++ * Use cached sea level instead of retrieving from the registry every time. ++ * This method is called for every block in the chunk so this will save a lot of registry lookups. ++ * ++ * @author SuperCoder79 ++ */ + @Override +- public int getSeaLevel() { +- return ((GeneratorSettingBase) this.h.get()).g(); ++ public int getSeaLevel() { // Cached method of getting sealevel ++ return this.cachedSeaLevel; + } ++ // Yatopia end + + @Override + public List getMobsFor(BiomeBase biomebase, StructureManager structuremanager, EnumCreatureType enumcreaturetype, BlockPosition blockposition) { diff --git a/patches/server/0063-PaperPR-Inline-shift-direction-fields.patch b/patches/server/0063-PaperPR-Inline-shift-direction-fields.patch new file mode 100644 index 00000000..357752c2 --- /dev/null +++ b/patches/server/0063-PaperPR-Inline-shift-direction-fields.patch @@ -0,0 +1,55 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrew Steinborn +Date: Mon, 18 Jan 2021 20:45:25 -0500 +Subject: [PATCH] (PaperPR) Inline shift direction fields + +Removes a layer of indirection for EnumDirection.getAdjacent(X|Y|Z)(), which is in the +critical section for much of the server, including the lighting engine. + +diff --git a/src/main/java/net/minecraft/server/EnumDirection.java b/src/main/java/net/minecraft/server/EnumDirection.java +index 2f1e7f0d753aace655210e32ddec59a3c46aade9..74e95bfcae5212c77383f7b8780dce293fda02dc 100644 +--- a/src/main/java/net/minecraft/server/EnumDirection.java ++++ b/src/main/java/net/minecraft/server/EnumDirection.java +@@ -49,6 +49,11 @@ public enum EnumDirection implements INamable { + }, (enumdirection, enumdirection1) -> { + throw new IllegalArgumentException("Duplicate keys"); + }, Long2ObjectOpenHashMap::new)); ++ // Paper start ++ private final int adjX; ++ private final int adjY; ++ private final int adjZ; ++ // Paper end + + private EnumDirection(int i, int j, int k, String s, EnumDirection.EnumAxisDirection enumdirection_enumaxisdirection, EnumDirection.EnumAxis enumdirection_enumaxis, BaseBlockPosition baseblockposition) { + this.g = i; +@@ -58,6 +63,11 @@ public enum EnumDirection implements INamable { + this.k = enumdirection_enumaxis; + this.l = enumdirection_enumaxisdirection; + this.m = baseblockposition; ++ // Paper start ++ this.adjX = baseblockposition.getX(); ++ this.adjY = baseblockposition.getY(); ++ this.adjZ = baseblockposition.getZ(); ++ // Paper end + } + + public static EnumDirection[] a(Entity entity) { +@@ -151,15 +161,15 @@ public enum EnumDirection implements INamable { + } + + public int getAdjacentX() { +- return this.m.getX(); ++ return this.adjX; // Paper + } + + public int getAdjacentY() { +- return this.m.getY(); ++ return this.adjY; // Paper + } + + public int getAdjacentZ() { +- return this.m.getZ(); ++ return this.adjZ; // Paper + } + + public String m() { diff --git a/patches/server/0064-New-nbt-cache.patch b/patches/server/0064-New-nbt-cache.patch new file mode 100644 index 00000000..aa88e9d3 --- /dev/null +++ b/patches/server/0064-New-nbt-cache.patch @@ -0,0 +1,137 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hugo Planque +Date: Thu, 21 Jan 2021 17:56:03 +0100 +Subject: [PATCH] New nbt cache + +The goal of this patch is to reduce I/O operations from the main thread while saving player data and also to avoid too many I/O operations while reading NBT Player file by using a cache (Which start to delete the oldest data when there is too much player compared to the map size) + +Co-authored-by: ishland + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 2a06f663ce64529842cfb0f4cf16c28ee143f110..084f7435ce50a10cd22e967f695064fb00c9b165 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -824,7 +824,9 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant { try { NBTCompressedStreamTools.a(nbttagcompound, file); + File file1 = new File(this.playerDir, entityhuman.getUniqueIDString() + ".dat"); + File file2 = new File(this.playerDir, entityhuman.getUniqueIDString() + ".dat_old"); + + SystemUtils.a(file1, file, file2); ++ } catch (Exception exception) { ++ WorldNBTStorage.LOGGER.error("Failed to save player data for {}", entityhuman.getName(), exception); // Paper ++ } ++ }; ++ synchronized (this.dataCache){ ++ this.dataCache.put(file, nbttagcompound); ++ } ++ this.executorService.execute(task); ++ // Yatopia end + } catch (Exception exception) { + WorldNBTStorage.LOGGER.error("Failed to save player data for {}", entityhuman.getName(), exception); // Paper + } +@@ -50,9 +66,18 @@ public class WorldNBTStorage { + // Spigot Start + boolean usingWrongFile = false; + boolean normalFile = file.exists() && file.isFile(); // Akarin - ensures normal file +- if ( org.bukkit.Bukkit.getOnlineMode() && !normalFile ) // Paper - Check online mode first // Akarin - ensures normal file ++ // if ( org.bukkit.Bukkit.getOnlineMode() && !normalFile ) // Paper - Check online mode first // Akarin - ensures normal file // Yatopia ++ // Yatopia start - NBT Cache system ++ NBTTagCompound playerData; ++ synchronized (this.dataCache){ ++ playerData = this.dataCache.get(file); ++ } ++ if (playerData == null && org.bukkit.Bukkit.getOnlineMode() && !normalFile ) // Paper - Check online mode first // Akarin - ensures normal file + { + file = new File( this.playerDir, java.util.UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + entityhuman.getName() ).getBytes( "UTF-8" ) ).toString() + ".dat"); ++ synchronized (this.dataCache){ ++ playerData = this.dataCache.get(file); ++ } + if ( file.exists() ) + { + usingWrongFile = true; +@@ -60,10 +85,13 @@ public class WorldNBTStorage { + } + } + // Spigot End +- +- if (normalFile) { // Akarin - avoid double I/O operation ++ // if (normalFile) { // Akarin - avoid double I/O operation // Yatopia ++ if (playerData != null) { ++ nbttagcompound = playerData; ++ } else if (normalFile) { // Akarin - avoid double I/O operation + nbttagcompound = NBTCompressedStreamTools.a(file); + } ++ // Yatopia end + // Spigot Start + if ( usingWrongFile ) + { +diff --git a/src/main/java/org/yatopiamc/yatopia/server/cache/NBTCache.java b/src/main/java/org/yatopiamc/yatopia/server/cache/NBTCache.java +new file mode 100644 +index 0000000000000000000000000000000000000000..942163396e41795b1b5624348e42a0c0325703f8 +--- /dev/null ++++ b/src/main/java/org/yatopiamc/yatopia/server/cache/NBTCache.java +@@ -0,0 +1,32 @@ ++package org.yatopiamc.yatopia.server.cache; ++ ++import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenCustomHashMap; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.NBTTagCompound; ++ ++import java.io.File; ++ ++public class NBTCache extends Object2ObjectLinkedOpenCustomHashMap { ++ ++ public NBTCache() { ++ super(100, 0.75F, new Strategy() { ++ @Override ++ public int hashCode(File k) { ++ return k.hashCode(); ++ } ++ ++ @Override ++ public boolean equals(File k, File k1) { ++ return k.equals(k1); ++ } ++ }); ++ } ++ ++ @Override ++ public NBTTagCompound put(File k, NBTTagCompound v) { ++ if (this.size() > MinecraftServer.getServer().getPlayerCount()) { ++ this.removeLast(); ++ } ++ return super.putAndMoveToFirst(k, v); ++ } ++} diff --git a/patches/server/0065-Suspected-plugins-report.patch b/patches/server/0065-Suspected-plugins-report.patch new file mode 100644 index 00000000..a9dda569 --- /dev/null +++ b/patches/server/0065-Suspected-plugins-report.patch @@ -0,0 +1,207 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: ishland +Date: Wed, 27 Jan 2021 23:35:30 +0800 +Subject: [PATCH] Suspected plugins report + +Added "Suspected Plugins" to Watchdog, crash reports and exception messages + +diff --git a/src/main/java/net/minecraft/server/CrashReport.java b/src/main/java/net/minecraft/server/CrashReport.java +index cc6e6f245ee5e73bd570cf42381bf55ee0b364d3..2f8ec64c26f5ed239b4dd9f9532c402d7081f9ed 100644 +--- a/src/main/java/net/minecraft/server/CrashReport.java ++++ b/src/main/java/net/minecraft/server/CrashReport.java +@@ -87,6 +87,8 @@ public class CrashReport { + if (this.h != null && this.h.length > 0) { + stringbuilder.append("-- Head --\n"); + stringbuilder.append("Thread: ").append(Thread.currentThread().getName()).append("\n"); ++ org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(this.h, stringbuilder::append); // Yatopia - detailed report ++ stringbuilder.append("\n"); // Yatopia + stringbuilder.append("Stacktrace:\n"); + StackTraceElement[] astacktraceelement = this.h; + int i = astacktraceelement.length; +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 084f7435ce50a10cd22e967f695064fb00c9b165..e6a23b2cf343f1e58d9de030525aba9f9f5f959b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1039,7 +1039,11 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant> { + channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + // Paper start + } catch (Exception e) { ++ // Yatopia - detailed report ++ LOGGER.error("Encountered unexpected exception sending packets"); ++ org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(e, LOGGER::error); ++ LOGGER.error(org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW); ++ // Yatopia end + LOGGER.error("NetworkException: " + player, e); + close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));; + packet.onPacketDispatchFinish(player, null); +@@ -421,6 +426,11 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + // Paper start + } catch (Exception e) { ++ // Yatopia start - detailed report ++ LOGGER.error("Encountered unexpected exception sending packets"); ++ org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(e, LOGGER::error); ++ LOGGER.error(org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW); ++ // Yatopia end + LOGGER.error("NetworkException: " + player, e); + close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));; + packet.onPacketDispatchFinish(player, null); +@@ -456,6 +466,11 @@ public class NetworkManager extends SimpleChannelInboundHandler> { + channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + // Paper start + } catch (Exception e) { ++ // Yatopia start - detailed report ++ LOGGER.error("Encountered unexpected exception sending packets"); ++ org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(e, LOGGER::error); ++ LOGGER.error(org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW); ++ // Yatopia end + LOGGER.error("NetworkException: " + player, e); + close(new ChatMessage("disconnect.genericReason", "Internal Exception: " + e.getMessage()));; + packet.onPacketDispatchFinish(player, null); +diff --git a/src/main/java/net/minecraft/server/PlayerConnectionUtils.java b/src/main/java/net/minecraft/server/PlayerConnectionUtils.java +index e698dd22607b2b2c4068c5bfb03ac53eb5bac080..4e545e467042772d49ac82492694824576dcd139 100644 +--- a/src/main/java/net/minecraft/server/PlayerConnectionUtils.java ++++ b/src/main/java/net/minecraft/server/PlayerConnectionUtils.java +@@ -45,6 +45,11 @@ public class PlayerConnectionUtils { + // Paper start + catch (Exception e) { + NetworkManager networkmanager = t0.a(); ++ // Yatopia start - detailed report ++ LOGGER.error("Encountered unexpected exception sending packets"); ++ org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(e, LOGGER::error); ++ LOGGER.error(org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW); ++ // Yatopia end + if (networkmanager.getPlayer() != null) { + LOGGER.error("Error whilst processing packet {} for {}[{}]", packet, networkmanager.getPlayer().getName(), networkmanager.getSocketAddress(), e); + } else { +diff --git a/src/main/java/net/minecraft/server/ServerConnection.java b/src/main/java/net/minecraft/server/ServerConnection.java +index 0668d383db1f3a81d1053954d72678c7ac5aecec..7b9f83e63d0f9cd83a246be33af4ab91da6b2151 100644 +--- a/src/main/java/net/minecraft/server/ServerConnection.java ++++ b/src/main/java/net/minecraft/server/ServerConnection.java +@@ -153,6 +153,11 @@ public class ServerConnection { + throw new ReportedException(CrashReport.a(exception, "Ticking memory connection")); + } + ++ // Yatopia start - detailed report ++ ServerConnection.LOGGER.error("Encountered unexpected exception sending packets"); ++ org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(exception, ServerConnection.LOGGER::error); ++ ServerConnection.LOGGER.error(org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW); ++ // Yatopia end + ServerConnection.LOGGER.warn("Failed to handle packet for {}", networkmanager.getSocketAddress(), exception); + ChatComponentText chatcomponenttext = new ChatComponentText("Internal server error"); + +diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java +index 626fab23a727073f502d934fc8f8616326ee8b52..ccf13e4daf0524c7a0c8de3b574cea8c188011ce 100644 +--- a/src/main/java/net/minecraft/server/World.java ++++ b/src/main/java/net/minecraft/server/World.java +@@ -988,6 +988,10 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + // Paper start - Prevent tile entity and entity crashes + String msg = "TileEntity threw exception at " + tileentity.world.getWorld().getName() + ":" + tileentity.position.getX() + "," + tileentity.position.getY() + "," + tileentity.position.getZ(); + System.err.println(msg); ++ // Yatopia start - detailed report ++ org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(throwable, System.err::println); ++ System.err.println(org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW); ++ // Yatopia end + throwable.printStackTrace(); + getServer().getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(msg, throwable))); + // Paper end +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +index 13e461ffb2ee2e7d0440c0f60809ea99629b843c..6f921e3fd85fa3eadb32743e5f9a991a00a99ad2 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -455,6 +455,7 @@ public class CraftScheduler implements BukkitScheduler { + parsePending(); + while (isReady(currentTick)) { + final CraftTask task = pending.remove(); ++ lastSyncTask = task; // Yatopia - detailed report + if (task.getPeriod() < CraftTask.NO_REPEATING) { + if (task.isSync()) { + runners.remove(task.getTaskId(), task); +@@ -473,12 +474,15 @@ public class CraftScheduler implements BukkitScheduler { + task.getTaskId(), + task.getOwner().getDescription().getFullName()); + if (task.getOwner() == MINECRAFT) { +- net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable); ++ // Yatopia start - detailed report ++ net.minecraft.server.MinecraftServer.LOGGER.error(msg); ++ org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(throwable, net.minecraft.server.MinecraftServer.LOGGER::error); ++ net.minecraft.server.MinecraftServer.LOGGER.error(org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW, throwable); + } else { +- task.getOwner().getLogger().log( +- Level.WARNING, +- msg, +- throwable); ++ task.getOwner().getLogger().log(Level.SEVERE, msg); ++ org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(throwable, _msg -> task.getOwner().getLogger().log(Level.SEVERE, _msg)); ++ task.getOwner().getLogger().log(Level.SEVERE, org.yatopiamc.yatopia.api.internal.StackTraceUtils.EXCEPTION_DETAILS_BELOW, throwable); ++ // Yatopia end + } + org.bukkit.Bukkit.getServer().getPluginManager().callEvent( + new ServerExceptionEvent(new ServerSchedulerException(msg, throwable, task))); +@@ -493,6 +497,7 @@ public class CraftScheduler implements BukkitScheduler { + // We don't need to parse pending + // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code) + } ++ lastSyncTask = null; // Yatopia - detailed report + final long period = task.getPeriod(); // State consistency + if (period > 0) { + task.setNextRun(currentTick + period); +@@ -635,4 +640,10 @@ public class CraftScheduler implements BukkitScheduler { + public BukkitTask runTaskTimerAsynchronously(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException { + throw new UnsupportedOperationException("Use BukkitRunnable#runTaskTimerAsynchronously(Plugin, long, long)"); + } ++ // Yatopia start - detailed report ++ private volatile CraftTask lastSyncTask = null; ++ public CraftTask getLastSyncTask() { ++ return lastSyncTask; ++ } ++ // Yatopia end + } +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index 476939bde38246eb0fd96e6a4ba8076c9d1b0ff4..d13bd96716db90a5d9ab65f3c7d025b75947b71a 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -140,6 +140,15 @@ public class WatchdogThread extends Thread + log.log(Level.SEVERE, "Handling packet for connection: " + packetListener); + } + } ++ // Yatopia start - detailed report - Scheduler ++ if (Bukkit.getScheduler() instanceof org.bukkit.craftbukkit.scheduler.CraftScheduler) { ++ final org.bukkit.craftbukkit.scheduler.CraftScheduler scheduler = (org.bukkit.craftbukkit.scheduler.CraftScheduler) Bukkit.getScheduler(); ++ final org.bukkit.craftbukkit.scheduler.CraftTask lastSyncTask = scheduler.getLastSyncTask(); ++ if (lastSyncTask != null) { ++ log.log(Level.SEVERE, "Running task " + lastSyncTask.getTaskId() + " for '" + lastSyncTask.getOwner().getDescription().getFullName() + "', TaskClass: " + lastSyncTask.getTaskClass()); ++ } ++ } else log.log(Level.SEVERE, "Unofficial scheduler, unable to get task information"); ++ // Yatopia end + } + // Tuinity end - log detailed tick information + +@@ -271,6 +280,10 @@ public class WatchdogThread extends Thread + log.log( Level.SEVERE, "\t\tLocked on:" + monitor.getLockedStackFrame() ); + } + } ++ // Yatopia start - dump plugins info ++ org.yatopiamc.yatopia.api.internal.StackTraceUtils.print(thread.getStackTrace(), msg -> log.log(Level.SEVERE, msg)); ++ log.log(Level.SEVERE, ""); ++ // Yatopia end + log.log( Level.SEVERE, "\tStack:" ); + // + for ( StackTraceElement stack : thread.getStackTrace() ) diff --git a/settings.gradle.kts b/settings.gradle.kts index 2b6949d2..d05e9373 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,6 +13,7 @@ setupSubproject("$forkNameLowercase-server") { projectDir = File("$forkName-Server") buildFileName = "../subprojects/server.gradle.kts" } +setupSubproject("Yatoclip") { } inline fun setupSubproject(name: String, block: ProjectDescriptor.() -> Unit) { include(name) diff --git a/subprojects/server.gradle.kts b/subprojects/server.gradle.kts index 5e84f929..855fb4bd 100644 --- a/subprojects/server.gradle.kts +++ b/subprojects/server.gradle.kts @@ -7,12 +7,6 @@ dependencies { } publishing { - publications { - create("server") { - from(components.getByName("java")) - } - } - repositories { maven { url = uri("https://repo.codemc.org/repository/nms-local/")