├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── src └── main │ ├── java │ └── com │ │ └── abdelaziz │ │ └── pluto │ │ ├── common │ │ ├── Pluto.java │ │ ├── entity │ │ │ └── WorldEntityByChunkAccess.java │ │ ├── network │ │ │ ├── ClientConnectionEncryptionExtension.java │ │ │ ├── ConfigurableAutoFlush.java │ │ │ ├── VarintByteDecoder.java │ │ │ ├── compression │ │ │ │ ├── MinecraftCompressDecoder.java │ │ │ │ └── MinecraftCompressEncoder.java │ │ │ ├── pipeline │ │ │ │ ├── MinecraftCipherDecoder.java │ │ │ │ └── MinecraftCipherEncoder.java │ │ │ └── util │ │ │ │ ├── AutoFlushUtil.java │ │ │ │ ├── VarIntUtil.java │ │ │ │ └── exception │ │ │ │ ├── QuietDecoderException.java │ │ │ │ └── WellKnownExceptions.java │ │ └── player │ │ │ └── PlutoServerPlayer.java │ │ └── mixin │ │ ├── PlutoMixinPlugin.java │ │ ├── fast_entity_access │ │ ├── ClientLevelMixin.java │ │ ├── EntitySectionAccessor.java │ │ ├── EntitySectionStorageMixin.java │ │ └── ServerLevelMixin.java │ │ ├── network │ │ ├── avoidwork │ │ │ └── ChunkMapMixin.java │ │ ├── flushconsolidation │ │ │ ├── ChunkMapMixin.java │ │ │ ├── ConnectionMixin.java │ │ │ └── ServerEntityMixin.java │ │ ├── microopt │ │ │ ├── FriendlyByteBufMixin.java │ │ │ └── ServerEntityMixin.java │ │ └── pipeline │ │ │ ├── LegacyQueryHandlerMixin.java │ │ │ ├── Varint21FrameDecoderMixin.java │ │ │ ├── compression │ │ │ └── ConnectionMixin.java │ │ │ └── encryption │ │ │ ├── ConnectionMixin.java │ │ │ └── ServerLoginPacketListenerImplMixin.java │ │ └── player │ │ └── ServerPlayerMixin.java │ └── resources │ ├── META-INF │ ├── accesstransformer.cfg │ └── mods.toml │ ├── canary.overrides.properties │ ├── logo.png │ ├── pack.mcmeta │ └── pluto.mixins.json └── updates.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Disable autocrlf on generated files, they always generate with LF 2 | # Add any extra files or paths here to make git stop saying they 3 | # are changed when only line endings change. 4 | src/generated/**/.cache/cache text eol=lf 5 | src/generated/**/*.json text eol=lf 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse 2 | bin 3 | *.launch 4 | .settings 5 | .metadata 6 | .classpath 7 | .project 8 | 9 | # idea 10 | out 11 | *.ipr 12 | *.iws 13 | *.iml 14 | .idea 15 | 16 | # gradle 17 | build 18 | .gradle 19 | 20 | # other 21 | eclipse 22 | run 23 | 24 | # Files from Forge MDK 25 | forge*changelog.txt 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### ⚠️Project Status 2 | Pluto is no longer maintained. While the repository will remain available, no further updates or fixes will be provided. Please feel free to fork or contribute if you wish to extend its functionality." 3 | 4 | # 5 | 6 | 7 | 8 | # Pluto for Minecraft Forge 9 | 10 | ![Github License](https://img.shields.io/github/license/AbdElAziz333/Pluto) 11 | ![Github Issues](https://img.shields.io/github/issues/AbdElAziz333/Pluto) 12 | ![Github Versions](https://img.shields.io/github/v/tag/AbdElAziz333/Pluto) 13 | [![Architectury Forge Loom](https://img.shields.io/badge/Built%20With-Architectury%20Forge%20Loom-9cf)](https://github.com/architectury/architectury-loom) 14 | [![This is a Fork](https://img.shields.io/badge/This%20is%20port-Support%20the%20original!-lightgray)](https://github.com/astei/krypton) 15 | 16 | Pluto is a networking stack optimization mod and unofficial fork of the Fabric mod [Krypton](https://github.com/astei/krypton) for Minecraft, this mod aims to optimize the network stack of the game in order to provide better network performance. It works on both the **client and server**, It derives from work 17 | done in the [Velocity](https://velocitypowered.com/) and [Paper](https://papermc.io) projects. 18 | 19 | # Installation 20 | 21 | ### Manual Installation 22 | 23 | ![Curseforge Versions](https://cf.way2muchnoise.eu/versions/pluto.svg) 24 | 25 | Pluto supports Minecraft Forge 1.18.2 and up, So make sure you have the latest version of Forge present and simply drop the mod into your mods folder, no other mods or additional setup is required! 26 | 27 | ### Curseforge 28 | 29 | ![Curseforge Downloads](https://cf.way2muchnoise.eu/full_682881_downloads.svg) 30 | 31 | If you are using Curseforge, you can continue downloading Pluto through my [Curseforge page](https://www.curseforge.com/minecraft/mc-mods/pluto). 32 | 33 | ### Modrinth 34 | 35 | ![Modrinth Downloads](https://img.shields.io/modrinth/dt/I2K4u1Q7?color=00AF5C&label=modrinth&style=flat&logo=modrinth) 36 | 37 | If you are using Modrinth, you can continue downloading Pluto through my [Modrinth page](https://modrinth.com/mod/pluto). 38 | 39 | ### Github Actions 40 | 41 | Github Actions builds will be available soon. 42 | 43 | # 44 | 45 | ### Support the Original Creator 46 | 47 | Pluto is impossible without the high-quality contributions made by the original Krypton developer, and as such, i would like to ask you support him, for more information you can see [this section](https://github.com/sponsors/astei). 48 | 49 | ### License 50 | 51 | Pluto is licensed under GNU LGPLv3, a free and open-source license. For more information, please see the 52 | [license file](LICENSE.txt). 53 | 54 | ### Issues and Feature Requests 55 | If you'd like to get help with the mod, feel free to open an issue here on GitHub, and if you want to propose new features or otherwise contribute to the mod, i will gladly accept pull requests, as well! 56 | 57 | # 58 | 59 | YourKit supports open source projects with innovative and intelligent tools 60 | for monitoring and profiling Java and .NET applications. 61 | YourKit is the creator of YourKit Java Profiler, 62 | YourKit .NET Profiler, 63 | and YourKit YouMonitor. 64 | Thanks to YourKit for providing a free license for this project. 65 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url = 'https://repo.spongepowered.org/repository/maven-public/' } 4 | mavenCentral() 5 | } 6 | 7 | dependencies { 8 | classpath 'org.spongepowered:mixingradle:0.7-SNAPSHOT' 9 | } 10 | } 11 | 12 | plugins { 13 | id 'eclipse' 14 | id 'idea' 15 | id 'maven-publish' 16 | id 'net.minecraftforge.gradle' version '5.1.+' 17 | id 'com.github.johnrengelman.shadow' version '7.1.2' 18 | id 'java' 19 | } 20 | 21 | apply plugin: 'org.spongepowered.mixin' 22 | 23 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 24 | 25 | version = mod_version 26 | group = mod_group_id 27 | 28 | base { 29 | archivesName = mod_id + "-mc" + minecraft_version 30 | } 31 | 32 | configurations { 33 | library 34 | shade 35 | implementation.extendsFrom library 36 | shade.extendsFrom library 37 | } 38 | 39 | java.toolchain.languageVersion = JavaLanguageVersion.of(17) 40 | 41 | println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}" 42 | minecraft { 43 | mappings channel: mapping_channel, version: mapping_version 44 | copyIdeResources = true 45 | accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') 46 | 47 | runs { 48 | configureEach { 49 | workingDirectory project.file('run') 50 | 51 | property 'forge.logging.markers', 'REGISTRIES' 52 | property 'forge.logging.console.level', 'debug' 53 | 54 | property 'mixin.env.remapRefMap', 'true' 55 | property 'mixin.env.refMapRemappingFile', "${project.projectDir}/build/createSrgToMcp/output.srg" 56 | 57 | mods { 58 | "${mod_id}" { 59 | source sourceSets.main 60 | } 61 | } 62 | } 63 | 64 | client { 65 | property 'forge.enabledGameTestNamespaces', mod_id 66 | } 67 | 68 | server { 69 | property 'forge.enabledGameTestNamespaces', mod_id 70 | args '--nogui' 71 | } 72 | 73 | gameTestServer { 74 | property 'forge.enabledGameTestNamespaces', mod_id 75 | } 76 | } 77 | } 78 | 79 | mixin { 80 | add sourceSets.main, "pluto.refmap.json" 81 | 82 | config "pluto.mixins.json" 83 | } 84 | 85 | sourceSets.main.resources { srcDir 'src/generated/resources' } 86 | 87 | repositories { 88 | maven { 89 | url "https://papermc.io/repo/repository/maven-public/" 90 | } 91 | } 92 | 93 | dependencies { 94 | minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" 95 | 96 | annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' 97 | library "com.velocitypowered:velocity-native:3.1.2-SNAPSHOT" 98 | shade "com.velocitypowered:velocity-native:3.1.2-SNAPSHOT" 99 | } 100 | 101 | reobf { 102 | shadowJar {} 103 | } 104 | 105 | shadowJar { 106 | configurations = [project.configurations.shade] 107 | from sourceSets.main.allSource 108 | exclude('io/netty/**') 109 | exclude('com/google/**') 110 | exclude('javax/annotation/**') 111 | exclude('org/**') 112 | exclude('META-INF/services/**') 113 | exclude('linux_aarch64/**') 114 | exclude('linux_x86_64/**') 115 | exclude('macos_arm64/**') 116 | classifier '' 117 | 118 | minimize() 119 | } 120 | 121 | tasks.build.dependsOn reobfShadowJar 122 | shadowJar.dependsOn('classes') 123 | jar.finalizedBy('reobfJar') 124 | 125 | artifacts { 126 | archives jar, shadowJar 127 | } 128 | 129 | tasks.named('processResources', ProcessResources).configure { 130 | var replaceProperties = [ 131 | minecraft_version: minecraft_version, minecraft_version_range: minecraft_version_range, 132 | forge_version: forge_version, forge_version_range: forge_version_range, 133 | loader_version_range: loader_version_range, 134 | mod_id: mod_id, mod_name: mod_name, mod_license: mod_license, mod_version: mod_version, 135 | credits: credits, mod_authors: mod_authors, mod_description: mod_description, 136 | ] 137 | inputs.properties replaceProperties 138 | 139 | filesMatching(['META-INF/mods.toml', 'pack.mcmeta']) { 140 | expand replaceProperties + [project: project] 141 | } 142 | } 143 | 144 | tasks.named('jar', Jar).configure { 145 | manifest { 146 | attributes([ 147 | 'Specification-Title' : mod_id, 148 | 'Specification-Vendor' : mod_authors, 149 | 'Specification-Version' : '1', // We are version 1 of ourselves 150 | 'Implementation-Title' : project.name, 151 | 'Implementation-Version' : project.jar.archiveVersion, 152 | 'Implementation-Vendor' : mod_authors, 153 | 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") 154 | ]) 155 | } 156 | finalizedBy 'reobfJar' 157 | } 158 | 159 | publishing { 160 | publications { 161 | register('mavenJava', MavenPublication) { 162 | artifact jar 163 | } 164 | } 165 | repositories { 166 | maven { 167 | url "file://${project.projectDir}/mcmodsrepo" 168 | } 169 | } 170 | } 171 | 172 | task packageSources(type: ShadowJar, dependsOn: 'classes') { 173 | configurations = [project.configurations.shade] 174 | from sourceSets.main.allSource 175 | exclude('io/netty/**') 176 | exclude('com/google/**') 177 | exclude('javax/annotation/**') 178 | exclude('org/**') 179 | exclude('META-INF/services/**') 180 | exclude('linux_aarch64/**') 181 | exclude('linux_x86_64/**') 182 | exclude('macos_arm64/**') 183 | classifier = 'sources' 184 | } 185 | 186 | artifacts { 187 | archives shadowJar 188 | archives packageSources 189 | } 190 | 191 | afterEvaluate { 192 | shadowJar.dependsOn(packageSources) 193 | shadowJar.dependsOn('classes') 194 | } 195 | 196 | shadowJar.finalizedBy(reobfShadowJar) 197 | jar.finalizedBy(packageSources) 198 | 199 | tasks.withType(JavaCompile).configureEach { 200 | options.encoding = 'UTF-8' 201 | } 202 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1G 2 | org.gradle.daemon=false 3 | 4 | minecraft_version=1.20.1 5 | minecraft_version_range=[1.20.1,1.21) 6 | forge_version=47.1.43 7 | forge_version_range=[47,) 8 | loader_version_range=[47,) 9 | mapping_channel=official 10 | mapping_version=1.20.1 11 | 12 | mod_id=pluto 13 | mod_name=Pluto 14 | mod_license=LGPL-3.0 15 | mod_version=0.0.9 16 | maven_group=com.abdelaziz.pluto 17 | credits=AbdElAziz, Tuxed 18 | mod_authors=AbdElAziz 19 | archives_base_name=pluto-mc1.20.1- 20 | mod_description=a performance mod that optimizes Minecraft's networking stack and entity tracker. 21 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AbdElAziz333/Pluto/02c6cf26a9190eab01420941007880d89424fad3/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | maven { url = 'https://maven.minecraftforge.net/' } 5 | } 6 | } 7 | 8 | rootProject.name = 'Pluto' -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/common/Pluto.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.common; 2 | 3 | import com.velocitypowered.natives.util.Natives; 4 | import net.minecraftforge.common.MinecraftForge; 5 | import net.minecraftforge.fml.common.Mod; 6 | import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; 7 | import org.apache.logging.log4j.LogManager; 8 | import org.apache.logging.log4j.Logger; 9 | 10 | @Mod(Pluto.MOD_ID) 11 | public class Pluto { 12 | public static final String MOD_ID = "pluto"; 13 | 14 | private static final Logger LOGGER = LogManager.getLogger(Pluto.class); 15 | 16 | static { 17 | // By default, Netty allocates 16MiB arenas for the PooledByteBufAllocator. This is too much 18 | // memory for Minecraft, which imposes a maximum packet size of 2MiB! We'll use 4MiB as a more 19 | // sane default. 20 | // 21 | // Note: io.netty.allocator.pageSize << io.netty.allocator.maxOrder is the formula used to 22 | // compute the chunk size. We lower maxOrder from its default of 11 to 9. (We also use a null 23 | // check, so that the user is free to choose another setting if need be.) 24 | if (System.getProperty("io.netty.allocator.maxOrder") == null) { 25 | System.setProperty("io.netty.allocator.maxOrder", "9"); 26 | } 27 | } 28 | 29 | public Pluto() { 30 | MinecraftForge.EVENT_BUS.addListener(this::commonSetup); 31 | } 32 | 33 | private void commonSetup(final FMLCommonSetupEvent event) { 34 | LOGGER.info("Compression will use " + Natives.compress.getLoadedVariant() + ", encryption will use " + Natives.cipher.getLoadedVariant()); 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/common/entity/WorldEntityByChunkAccess.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.common.entity; 2 | 3 | import net.minecraft.world.entity.Entity; 4 | 5 | import java.util.Collection; 6 | 7 | public interface WorldEntityByChunkAccess { 8 | Collection getEntitiesInChunk(final int chunkX, final int chunkZ); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/common/network/ClientConnectionEncryptionExtension.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.common.network; 2 | 3 | import javax.crypto.SecretKey; 4 | import java.security.GeneralSecurityException; 5 | 6 | public interface ClientConnectionEncryptionExtension { 7 | void setupEncryption(SecretKey key) throws GeneralSecurityException; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/common/network/ConfigurableAutoFlush.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.common.network; 2 | 3 | public interface ConfigurableAutoFlush { 4 | void setShouldAutoFlush(boolean shouldAutoFlush); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/common/network/VarintByteDecoder.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.common.network; 2 | 3 | import io.netty.util.ByteProcessor; 4 | 5 | public class VarintByteDecoder implements ByteProcessor { 6 | private int readVarint; 7 | private int bytesRead; 8 | private DecodeResult result = DecodeResult.TOO_SHORT; 9 | 10 | @Override 11 | public boolean process(byte k) { 12 | if (k == 0 && bytesRead == 0) { 13 | // tentatively say it's invalid, but there's a possibility of redemption 14 | result = DecodeResult.RUN_OF_ZEROES; 15 | return true; 16 | } 17 | if (result == DecodeResult.RUN_OF_ZEROES) { 18 | return false; 19 | } 20 | readVarint |= (k & 0x7F) << bytesRead++ * 7; 21 | if (bytesRead > 3) { 22 | result = DecodeResult.TOO_BIG; 23 | return false; 24 | } 25 | if ((k & 0x80) != 128) { 26 | result = DecodeResult.SUCCESS; 27 | return false; 28 | } 29 | return true; 30 | } 31 | 32 | public int getReadVarint() { 33 | return readVarint; 34 | } 35 | 36 | public int getBytesRead() { 37 | return bytesRead; 38 | } 39 | 40 | public DecodeResult getResult() { 41 | return result; 42 | } 43 | 44 | public void reset() { 45 | readVarint = 0; 46 | bytesRead = 0; 47 | result = DecodeResult.TOO_SHORT; 48 | } 49 | 50 | public enum DecodeResult { 51 | SUCCESS, 52 | TOO_SHORT, 53 | TOO_BIG, 54 | RUN_OF_ZEROES 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/common/network/compression/MinecraftCompressDecoder.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.common.network.compression; 2 | 3 | import com.velocitypowered.natives.compression.VelocityCompressor; 4 | import com.velocitypowered.natives.util.MoreByteBufUtils; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.ByteToMessageDecoder; 8 | import io.netty.handler.codec.DecoderException; 9 | import net.minecraft.network.FriendlyByteBuf; 10 | 11 | import java.util.List; 12 | 13 | public class MinecraftCompressDecoder extends ByteToMessageDecoder { 14 | 15 | private static final int UNCOMPRESSED_CAP = 8 * 1024 * 1024; // 8MiB 16 | 17 | private int threshold; 18 | private final boolean validate; 19 | private final VelocityCompressor compressor; 20 | 21 | public MinecraftCompressDecoder(int threshold, boolean validate, VelocityCompressor compressor) { 22 | this.threshold = threshold; 23 | this.validate = validate; 24 | this.compressor = compressor; 25 | } 26 | 27 | @Override 28 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 29 | if (in.readableBytes() != 0) { 30 | FriendlyByteBuf packetBuf = new FriendlyByteBuf(in); 31 | int claimedUncompressedSize = packetBuf.readVarInt(); 32 | 33 | if (claimedUncompressedSize == 0) { 34 | out.add(packetBuf.readBytes(packetBuf.readableBytes())); 35 | } else { 36 | if (validate) { 37 | if (claimedUncompressedSize < this.threshold) { 38 | throw new DecoderException("Badly compressed packet - size of " + claimedUncompressedSize + " is below server threshold of " + this.threshold); 39 | } 40 | 41 | if (claimedUncompressedSize > UNCOMPRESSED_CAP) { 42 | throw new DecoderException("Badly compressed packet - size of " + claimedUncompressedSize + " is larger than maximum of " + UNCOMPRESSED_CAP); 43 | } 44 | } 45 | 46 | ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), compressor, in); 47 | ByteBuf uncompressed = MoreByteBufUtils.preferredBuffer(ctx.alloc(), compressor, claimedUncompressedSize); 48 | try { 49 | compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize); 50 | out.add(uncompressed); 51 | in.clear(); 52 | } catch (Exception e) { 53 | uncompressed.release(); 54 | throw e; 55 | } finally { 56 | compatibleIn.release(); 57 | } 58 | } 59 | 60 | } 61 | } 62 | 63 | public void setThreshold(int threshold) { 64 | this.threshold = threshold; 65 | } 66 | 67 | @Override 68 | public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { 69 | compressor.close(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/common/network/compression/MinecraftCompressEncoder.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.common.network.compression; 2 | 3 | import com.velocitypowered.natives.compression.VelocityCompressor; 4 | import com.velocitypowered.natives.util.MoreByteBufUtils; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.MessageToByteEncoder; 8 | import net.minecraft.network.FriendlyByteBuf; 9 | 10 | public class MinecraftCompressEncoder extends MessageToByteEncoder { 11 | 12 | private int threshold; 13 | private final VelocityCompressor compressor; 14 | 15 | public MinecraftCompressEncoder(int threshold, VelocityCompressor compressor) { 16 | this.threshold = threshold; 17 | this.compressor = compressor; 18 | } 19 | 20 | @Override 21 | protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { 22 | FriendlyByteBuf wrappedBuf = new FriendlyByteBuf(out); 23 | int uncompressed = msg.readableBytes(); 24 | if (uncompressed < threshold) { 25 | // Under the threshold, there is nothing to do. 26 | wrappedBuf.writeVarInt(0); 27 | out.writeBytes(msg); 28 | } else { 29 | wrappedBuf.writeVarInt(uncompressed); 30 | ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), compressor, msg); 31 | try { 32 | compressor.deflate(compatibleIn, out); 33 | } finally { 34 | compatibleIn.release(); 35 | } 36 | } 37 | } 38 | 39 | @Override 40 | protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) 41 | throws Exception { 42 | // We allocate bytes to be compressed plus 1 byte. This covers two cases: 43 | // 44 | // - Compression 45 | // According to https://github.com/ebiggers/libdeflate/blob/master/libdeflate.h#L103, 46 | // if the data compresses well (and we do not have some pathological case) then the maximum 47 | // size the compressed size will ever be is the input size minus one. 48 | // - Uncompressed 49 | // This is fairly obvious - we will then have one more than the uncompressed size. 50 | int initialBufferSize = msg.readableBytes() + 1; 51 | return MoreByteBufUtils.preferredBuffer(ctx.alloc(), compressor, initialBufferSize); 52 | } 53 | 54 | @Override 55 | public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { 56 | compressor.close(); 57 | } 58 | 59 | public void setThreshold(int threshold) { 60 | this.threshold = threshold; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/common/network/pipeline/MinecraftCipherDecoder.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.common.network.pipeline; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.velocitypowered.natives.encryption.VelocityCipher; 5 | import com.velocitypowered.natives.util.MoreByteBufUtils; 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.handler.codec.MessageToMessageDecoder; 9 | 10 | import java.util.List; 11 | 12 | public class MinecraftCipherDecoder extends MessageToMessageDecoder { 13 | 14 | private final VelocityCipher cipher; 15 | 16 | public MinecraftCipherDecoder(VelocityCipher cipher) { 17 | this.cipher = Preconditions.checkNotNull(cipher, "cipher"); 18 | } 19 | 20 | @Override 21 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 22 | ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), cipher, in).slice(); 23 | try { 24 | cipher.process(compatible); 25 | out.add(compatible); 26 | } catch (Exception e) { 27 | compatible.release(); // compatible will never be used if we throw an exception 28 | throw e; 29 | } 30 | } 31 | 32 | @Override 33 | public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { 34 | cipher.close(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/common/network/pipeline/MinecraftCipherEncoder.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.common.network.pipeline; 2 | 3 | import com.google.common.base.Preconditions; 4 | import com.velocitypowered.natives.encryption.VelocityCipher; 5 | import com.velocitypowered.natives.util.MoreByteBufUtils; 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.handler.codec.MessageToMessageEncoder; 9 | 10 | import java.util.List; 11 | 12 | public class MinecraftCipherEncoder extends MessageToMessageEncoder { 13 | 14 | private final VelocityCipher cipher; 15 | 16 | public MinecraftCipherEncoder(VelocityCipher cipher) { 17 | this.cipher = Preconditions.checkNotNull(cipher, "cipher"); 18 | } 19 | 20 | @Override 21 | protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { 22 | ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), cipher, msg); 23 | try { 24 | cipher.process(compatible); 25 | out.add(compatible); 26 | } catch (Exception e) { 27 | compatible.release(); // compatible will never be used if we throw an exception 28 | throw e; 29 | } 30 | } 31 | 32 | @Override 33 | public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { 34 | cipher.close(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/common/network/util/AutoFlushUtil.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.common.network.util; 2 | 3 | import com.abdelaziz.pluto.common.network.ConfigurableAutoFlush; 4 | import net.minecraft.network.Connection; 5 | import net.minecraft.server.level.ServerPlayer; 6 | 7 | public class AutoFlushUtil { 8 | public static void setAutoFlush(ServerPlayer player, boolean val) { 9 | if (player.getClass() == ServerPlayer.class) { 10 | ConfigurableAutoFlush configurableAutoFlusher = ((ConfigurableAutoFlush) player.connection.getConnection()); 11 | configurableAutoFlusher.setShouldAutoFlush(val); 12 | } 13 | } 14 | 15 | public static void setAutoFlush(Connection conn, boolean val) { 16 | if (conn.getClass() == Connection.class) { 17 | ConfigurableAutoFlush configurableAutoFlusher = ((ConfigurableAutoFlush) conn); 18 | configurableAutoFlusher.setShouldAutoFlush(val); 19 | } 20 | } 21 | 22 | private AutoFlushUtil() { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/common/network/util/VarIntUtil.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.common.network.util; 2 | 3 | /** 4 | * Maps VarInt byte sizes to a lookup table corresponding to the number of bits in the integer, 5 | * from zero to 32. 6 | */ 7 | public class VarIntUtil { 8 | private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33]; 9 | 10 | static { 11 | for (int i = 0; i <= 32; ++i) { 12 | VARINT_EXACT_BYTE_LENGTHS[i] = (int) Math.ceil((31d - (i - 1)) / 7d); 13 | } 14 | VARINT_EXACT_BYTE_LENGTHS[32] = 1; // Special case for 0. 15 | } 16 | 17 | public static int getVarIntLength(int value) { 18 | return VARINT_EXACT_BYTE_LENGTHS[Integer.numberOfLeadingZeros(value)]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/common/network/util/exception/QuietDecoderException.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.common.network.util.exception; 2 | 3 | import io.netty.handler.codec.DecoderException; 4 | 5 | /** 6 | * A special-purpose exception thrown when we want to indicate an error decoding but do not want 7 | * to see a large stack trace in logs. 8 | */ 9 | public class QuietDecoderException extends DecoderException { 10 | 11 | public QuietDecoderException(String message) { 12 | super(message); 13 | } 14 | 15 | @Override 16 | public synchronized Throwable fillInStackTrace() { 17 | return this; 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/common/network/util/exception/WellKnownExceptions.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.common.network.util.exception; 2 | 3 | import com.abdelaziz.pluto.common.network.util.exception.QuietDecoderException; 4 | 5 | public enum WellKnownExceptions { 6 | ; 7 | 8 | public static final QuietDecoderException BAD_LENGTH_CACHED = new QuietDecoderException("Bad packet length"); 9 | public static final QuietDecoderException VARINT_BIG_CACHED = new QuietDecoderException("VarInt too big"); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/common/player/PlutoServerPlayer.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.common.player; 2 | 3 | public interface PlutoServerPlayer { 4 | void setNeedsChunksReloaded(boolean needsChunksReloaded); 5 | 6 | int getPlayerViewDistance(); 7 | 8 | boolean getNeedsChunksReloaded(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/PlutoMixinPlugin.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin; 2 | 3 | import net.minecraftforge.fml.loading.LoadingModList; 4 | import org.objectweb.asm.tree.ClassNode; 5 | import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; 6 | import org.spongepowered.asm.mixin.extensibility.IMixinInfo; 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | public class PlutoMixinPlugin implements IMixinConfigPlugin { 11 | @Override 12 | public void onLoad(String mixinPackage) { 13 | 14 | } 15 | 16 | @Override 17 | public String getRefMapperConfig() { 18 | return null; 19 | } 20 | 21 | @Override 22 | public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { 23 | // This mixin is incompatible with Immersive Portals. 24 | if (mixinClassName.contains("avoidwork.ChunkMapMixin")) { 25 | return LoadingModList.get().getModFileById("imm_ptl_core") == null; 26 | } 27 | return true; 28 | } 29 | 30 | @Override 31 | public void acceptTargets(Set myTargets, Set otherTargets) { 32 | 33 | } 34 | 35 | @Override 36 | public List getMixins() { 37 | return null; 38 | } 39 | 40 | @Override 41 | public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { 42 | 43 | } 44 | 45 | @Override 46 | public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/fast_entity_access/ClientLevelMixin.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin.fast_entity_access; 2 | 3 | import com.abdelaziz.pluto.common.entity.WorldEntityByChunkAccess; 4 | import net.minecraft.client.multiplayer.ClientLevel; 5 | import net.minecraft.world.entity.Entity; 6 | import net.minecraft.world.level.entity.TransientEntitySectionManager; 7 | import net.minecraftforge.api.distmarker.Dist; 8 | import net.minecraftforge.api.distmarker.OnlyIn; 9 | import org.spongepowered.asm.mixin.Final; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Shadow; 12 | 13 | import java.util.Collection; 14 | 15 | @Mixin(ClientLevel.class) 16 | @OnlyIn(Dist.CLIENT) 17 | public class ClientLevelMixin implements WorldEntityByChunkAccess { 18 | @Shadow 19 | @Final 20 | private TransientEntitySectionManager entityStorage; 21 | 22 | @Override 23 | public Collection getEntitiesInChunk(int chunkX, int chunkZ) { 24 | return ((WorldEntityByChunkAccess) this.entityStorage.sectionStorage).getEntitiesInChunk(chunkX, chunkZ); 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/fast_entity_access/EntitySectionAccessor.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin.fast_entity_access; 2 | 3 | import net.minecraft.util.ClassInstanceMultiMap; 4 | import net.minecraft.world.level.entity.EntitySection; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(EntitySection.class) 9 | public interface EntitySectionAccessor { 10 | @Accessor 11 | ClassInstanceMultiMap getStorage(); 12 | } -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/fast_entity_access/EntitySectionStorageMixin.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin.fast_entity_access; 2 | 3 | import com.abdelaziz.pluto.common.entity.WorldEntityByChunkAccess; 4 | import it.unimi.dsi.fastutil.longs.Long2ObjectMap; 5 | import it.unimi.dsi.fastutil.longs.LongIterator; 6 | import it.unimi.dsi.fastutil.longs.LongSortedSet; 7 | import net.minecraft.world.entity.Entity; 8 | import net.minecraft.world.level.entity.EntitySection; 9 | import net.minecraft.world.level.entity.EntitySectionStorage; 10 | import org.spongepowered.asm.mixin.Final; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Collection; 16 | import java.util.List; 17 | 18 | /** 19 | * Provides a fast way to search the section cache for entities present in a given chunk. 20 | */ 21 | @Mixin(EntitySectionStorage.class) 22 | public abstract class EntitySectionStorageMixin implements WorldEntityByChunkAccess { 23 | 24 | @Shadow 25 | @Final 26 | private Long2ObjectMap> sections; 27 | 28 | @Override 29 | public Collection getEntitiesInChunk(final int chunkX, final int chunkZ) { 30 | final LongSortedSet set = this.getChunkSections(chunkX, chunkZ); 31 | if (set.isEmpty()) { 32 | // Nothing in this map? 33 | return List.of(); 34 | } 35 | 36 | final List entities = new ArrayList<>(); 37 | final LongIterator sectionsIterator = set.iterator(); 38 | while (sectionsIterator.hasNext()) { 39 | final long key = sectionsIterator.nextLong(); 40 | final EntitySection value = this.sections.get(key); 41 | if (value != null && value.getStatus().isAccessible()) { 42 | entities.addAll(((EntitySectionAccessor) value).getStorage()); 43 | } 44 | } 45 | 46 | return entities; 47 | } 48 | 49 | @Shadow 50 | protected abstract LongSortedSet getChunkSections(int chunkX, int chunkZ); 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/fast_entity_access/ServerLevelMixin.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin.fast_entity_access; 2 | 3 | import com.abdelaziz.pluto.common.entity.WorldEntityByChunkAccess; 4 | import net.minecraft.server.level.ServerLevel; 5 | import net.minecraft.world.entity.Entity; 6 | import net.minecraft.world.level.entity.PersistentEntitySectionManager; 7 | import org.spongepowered.asm.mixin.Final; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Shadow; 10 | 11 | import java.util.Collection; 12 | 13 | @Mixin(ServerLevel.class) 14 | public class ServerLevelMixin implements WorldEntityByChunkAccess { 15 | @Shadow 16 | @Final 17 | private PersistentEntitySectionManager entityManager; 18 | 19 | @Override 20 | public Collection getEntitiesInChunk(int chunkX, int chunkZ) { 21 | return ((WorldEntityByChunkAccess) this.entityManager.sectionStorage).getEntitiesInChunk(chunkX, chunkZ); 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/network/avoidwork/ChunkMapMixin.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin.network.avoidwork; 2 | 3 | import com.abdelaziz.pluto.common.entity.WorldEntityByChunkAccess; 4 | import it.unimi.dsi.fastutil.ints.Int2ObjectMap; 5 | import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; 6 | import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; 7 | import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket; 8 | import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket; 9 | import net.minecraft.server.level.ChunkMap; 10 | import net.minecraft.server.level.ServerPlayer; 11 | import net.minecraft.world.entity.Entity; 12 | import net.minecraft.world.entity.Mob; 13 | import net.minecraft.world.level.chunk.LevelChunk; 14 | import org.apache.commons.lang3.mutable.MutableObject; 15 | import org.objectweb.asm.Opcodes; 16 | import org.spongepowered.asm.mixin.Final; 17 | import org.spongepowered.asm.mixin.Mixin; 18 | import org.spongepowered.asm.mixin.Shadow; 19 | import org.spongepowered.asm.mixin.injection.At; 20 | import org.spongepowered.asm.mixin.injection.Inject; 21 | import org.spongepowered.asm.mixin.injection.Redirect; 22 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 23 | 24 | import java.util.ArrayList; 25 | import java.util.Collection; 26 | import java.util.List; 27 | 28 | @Mixin(ChunkMap.class) 29 | public class ChunkMapMixin { 30 | @Shadow 31 | @Final 32 | private Int2ObjectMap entityMap; 33 | 34 | @Inject(method = "playerLoadedChunk", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/protocol/game/DebugPackets;sendPoiPacketsForChunk(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/ChunkPos;)V", shift = At.Shift.AFTER, by = 1)) 35 | public void sendChunkDataPackets$beSmart(ServerPlayer player, MutableObject mutableObject, LevelChunk chunk, CallbackInfo ci) { 36 | // Synopsis: when sending chunk data to the player, sendChunkDataPackets iterates over EVERY tracked entity in 37 | // the world, when it doesn't have to do so - we only need entities in the current chunk. A similar optimization 38 | // is present in Paper. 39 | final Collection entitiesInChunk = ((WorldEntityByChunkAccess) chunk.getLevel()).getEntitiesInChunk(chunk.getPos().x, chunk.getPos().z); 40 | final List attachmentsToSend = new ArrayList<>(); 41 | final List passengersToSend = new ArrayList<>(); 42 | for (Entity entity : entitiesInChunk) { 43 | final ChunkMap.TrackedEntity entityTracker = this.entityMap.get(entity.getId()); 44 | if (entityTracker != null) { 45 | entityTracker.updatePlayer(player); 46 | if (entity instanceof Mob && ((Mob) entity).getLeashHolder() != null) { 47 | attachmentsToSend.add(entity); 48 | } 49 | 50 | if (!entity.getPassengers().isEmpty()) { 51 | passengersToSend.add(entity); 52 | } 53 | } 54 | } 55 | 56 | if (!attachmentsToSend.isEmpty()) { 57 | for (Entity entity : attachmentsToSend) { 58 | player.connection.send(new ClientboundSetEntityLinkPacket(entity, ((Mob) entity).getLeashHolder())); 59 | } 60 | } 61 | 62 | if (!passengersToSend.isEmpty()) { 63 | for (Entity entity : passengersToSend) { 64 | player.connection.send(new ClientboundSetPassengersPacket(entity)); 65 | } 66 | } 67 | } 68 | 69 | @Redirect(method = "playerLoadedChunk", at = @At(value = "FIELD", target = "Lnet/minecraft/server/level/ChunkMap;entityMap:Lit/unimi/dsi/fastutil/ints/Int2ObjectMap;", opcode = Opcodes.GETFIELD)) 70 | public Int2ObjectMap sendChunkDataPackets$nullifyRest(ChunkMap tacs) { 71 | return Int2ObjectMaps.emptyMap(); 72 | } 73 | } -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/network/flushconsolidation/ChunkMapMixin.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin.network.flushconsolidation; 2 | 3 | import com.abdelaziz.pluto.common.network.util.AutoFlushUtil; 4 | import com.abdelaziz.pluto.common.player.PlutoServerPlayer; 5 | import it.unimi.dsi.fastutil.ints.Int2ObjectMap; 6 | import net.minecraft.core.SectionPos; 7 | import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; 8 | import net.minecraft.server.level.ChunkMap; 9 | import net.minecraft.server.level.PlayerMap; 10 | import net.minecraft.server.level.ServerLevel; 11 | import net.minecraft.server.level.ServerPlayer; 12 | import net.minecraft.world.level.ChunkPos; 13 | import org.apache.commons.lang3.mutable.MutableObject; 14 | import org.spongepowered.asm.mixin.Final; 15 | import org.spongepowered.asm.mixin.Mixin; 16 | import org.spongepowered.asm.mixin.Overwrite; 17 | import org.spongepowered.asm.mixin.Shadow; 18 | import org.spongepowered.asm.mixin.injection.At; 19 | import org.spongepowered.asm.mixin.injection.Inject; 20 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 21 | 22 | /** 23 | * Mixes into various methods in {@code ChunkMap} to utilize flush consolidation for sending chunks all at once to the 24 | * client, along with loading chunks in a spiral order. Helpful for heavy server activity or flying very quickly. 25 | *

26 | * Note for anyone attempting to modify this class in the future: for some reason, mojang includes both the chunk loading & chunk unloading 27 | * packets in the same method. This is why chunks must always be sent to the player when they leave an area. 28 | */ 29 | @Mixin(ChunkMap.class) 30 | public abstract class ChunkMapMixin { 31 | @Shadow 32 | @Final 33 | ServerLevel level; 34 | 35 | @Shadow 36 | int viewDistance; 37 | 38 | @Shadow 39 | @Final 40 | private Int2ObjectMap entityMap; 41 | 42 | @Shadow 43 | @Final 44 | private PlayerMap playerMap; 45 | 46 | @Shadow 47 | @Final 48 | private ChunkMap.DistanceManager distanceManager; 49 | 50 | @Shadow 51 | public static boolean isChunkInRange(int x1, int y1, int x2, int y2, int maxDistance) { 52 | // PAIL: isWithinEuclideanDistance(x1, y1, x2, y2, maxDistance) 53 | throw new AssertionError("pedantic"); 54 | } 55 | 56 | /** 57 | * This is run on login. This method is overwritten to avoid sending duplicate chunks (which mc does by default) 58 | * 59 | * @reason optimize sending chunks 60 | * @author solonovamax 61 | */ 62 | @Overwrite 63 | public void updatePlayerStatus(ServerPlayer player, boolean added) { 64 | boolean skipPlayer = this.skipPlayer(player); 65 | boolean isWatchingWorld = !this.playerMap.ignoredOrUnknown(player); 66 | 67 | int chunkPosX = SectionPos.blockToSectionCoord(player.getBlockX()); 68 | int chunkPosZ = SectionPos.blockToSectionCoord(player.getBlockZ()); 69 | 70 | AutoFlushUtil.setAutoFlush(player, false); 71 | 72 | try { 73 | if (added) { 74 | this.playerMap.addPlayer(ChunkPos.asLong(chunkPosX, chunkPosZ), player, skipPlayer); 75 | this.updatePlayerPos(player); 76 | 77 | if (!skipPlayer) { 78 | this.distanceManager.addPlayer(SectionPos.of(player), player); 79 | } 80 | 81 | // Send spiral watch packets if added 82 | sendSpiralChunkWatchPackets(player); 83 | } else { 84 | SectionPos chunkSectionPos = player.getLastSectionPos(); 85 | this.playerMap.removePlayer(chunkSectionPos.chunk().toLong(), player); 86 | 87 | if (isWatchingWorld) { 88 | this.distanceManager.removePlayer(chunkSectionPos, player); 89 | } 90 | 91 | // Loop through & unload chunks if removed 92 | unloadChunks(player, chunkPosX, chunkPosZ, viewDistance); 93 | } 94 | } finally { 95 | AutoFlushUtil.setAutoFlush(player, true); 96 | } 97 | } 98 | 99 | /** 100 | * @author Andrew Steinborn 101 | * @reason Add support for flush consolidation & optimize sending chunks 102 | */ 103 | @Overwrite 104 | public void move(ServerPlayer player) { 105 | // TODO: optimize this further by only considering entities that the player is close to. 106 | // use the FastChunkEntityAccess magic to do this. 107 | for (ChunkMap.TrackedEntity entityTracker : this.entityMap.values()) { 108 | if (entityTracker.entity == player) { 109 | entityTracker.updatePlayers(this.level.players()); 110 | } else { 111 | entityTracker.updatePlayer(player); 112 | } 113 | } 114 | 115 | SectionPos oldPos = player.getLastSectionPos(); 116 | SectionPos newPos = SectionPos.of(player); 117 | boolean isWatchingWorld = this.playerMap.ignored(player); 118 | boolean noChunkGen = this.skipPlayer(player); 119 | boolean movedSections = !oldPos.equals(newPos); 120 | 121 | if (movedSections || isWatchingWorld != noChunkGen) { 122 | this.updatePlayerPos(player); 123 | 124 | if (!isWatchingWorld) { 125 | this.distanceManager.removePlayer(oldPos, player); 126 | } 127 | 128 | if (!noChunkGen) { 129 | this.distanceManager.addPlayer(newPos, player); 130 | } 131 | 132 | if (!isWatchingWorld && noChunkGen) { 133 | this.playerMap.ignorePlayer(player); 134 | } 135 | 136 | if (isWatchingWorld && !noChunkGen) { 137 | this.playerMap.unIgnorePlayer(player); 138 | } 139 | 140 | long oldChunkPos = ChunkPos.asLong(oldPos.getX(), oldPos.getZ()); 141 | long newChunkPos = ChunkPos.asLong(newPos.getX(), newPos.getZ()); 142 | this.playerMap.updatePlayer(oldChunkPos, newChunkPos, player); 143 | } 144 | 145 | // The player *always* needs to be send chunks, as for some reason both chunk loading & unloading packets are handled 146 | // by the same method (why mojang) 147 | if (player.level == this.level) 148 | this.sendChunkWatchPackets(oldPos, player); 149 | } 150 | 151 | @Inject(method = "tick()V", at = @At("HEAD")) 152 | public void disableAutoFlushForEntityTracking(CallbackInfo info) { 153 | for (ServerPlayer player : level.players()) { 154 | AutoFlushUtil.setAutoFlush(player, false); 155 | } 156 | } 157 | 158 | @Inject(method = "tick()V", at = @At("RETURN")) 159 | public void enableAutoFlushForEntityTracking(CallbackInfo info) { 160 | for (ServerPlayer player : level.players()) { 161 | AutoFlushUtil.setAutoFlush(player, true); 162 | } 163 | } 164 | 165 | /** 166 | * @param player The player 167 | * @param pos The position of the chunk to send 168 | * @param mutableObject A new mutable object 169 | * @param oldWithinViewDistance If the chunk was previously within the player's view distance 170 | * @param newWithinViewDistance If the chunk is now within the player's view distance 171 | */ 172 | @Shadow 173 | protected abstract void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject mutableObject, 174 | boolean oldWithinViewDistance, boolean newWithinViewDistance); 175 | 176 | @Shadow 177 | protected abstract boolean skipPlayer(ServerPlayer player); 178 | 179 | @Shadow 180 | protected abstract SectionPos updatePlayerPos(ServerPlayer serverPlayerEntity); 181 | 182 | private void sendChunkWatchPackets(SectionPos oldPos, ServerPlayer player) { 183 | AutoFlushUtil.setAutoFlush(player, false); 184 | try { 185 | int oldChunkX = oldPos.x(); 186 | int oldChunkZ = oldPos.z(); 187 | 188 | int newChunkX = SectionPos.blockToSectionCoord(player.getBlockX()); 189 | int newChunkZ = SectionPos.blockToSectionCoord(player.getBlockZ()); 190 | 191 | int playerViewDistance = getPlayerViewDistance(player); // +1 for buffer 192 | 193 | if (shouldReloadAllChunks(player)) { // Player updated view distance, unload chunks & resend (only unload chunks not visible) 194 | //noinspection InstanceofIncompatibleInterface 195 | if (player instanceof PlutoServerPlayer plutoPlayer) 196 | plutoPlayer.setNeedsChunksReloaded(false); 197 | 198 | for (int curX = newChunkX - viewDistance - 1; curX <= newChunkX + viewDistance + 1; ++curX) { 199 | for (int curZ = newChunkZ - viewDistance - 1; curZ <= newChunkZ + viewDistance + 1; ++curZ) { 200 | ChunkPos chunkPos = new ChunkPos(curX, curZ); 201 | boolean inNew = isChunkInRange(curX, curZ, newChunkX, newChunkZ, playerViewDistance); 202 | 203 | this.updateChunkTracking(player, chunkPos, new MutableObject<>(), true, inNew); 204 | } 205 | } 206 | 207 | // Send new chunks 208 | sendSpiralChunkWatchPackets(player); 209 | } else if (Math.abs(oldChunkX - newChunkX) > playerViewDistance * 2 || 210 | Math.abs(oldChunkZ - newChunkZ) > playerViewDistance * 2) { 211 | // If the player is not near the old chunks, send all new chunks & unload old chunks 212 | 213 | // Unload previous chunks 214 | // Chunk unload packets are very light, so we can just do it like this 215 | unloadChunks(player, oldChunkX, oldChunkZ, viewDistance); 216 | 217 | // Send new chunks 218 | sendSpiralChunkWatchPackets(player); 219 | } else { 220 | int minSendChunkX = Math.min(newChunkX, oldChunkX) - playerViewDistance - 1; 221 | int minSendChunkZ = Math.min(newChunkZ, oldChunkZ) - playerViewDistance - 1; 222 | int maxSendChunkX = Math.max(newChunkX, oldChunkX) + playerViewDistance + 1; 223 | int maxSendChunkZ = Math.max(newChunkZ, oldChunkZ) + playerViewDistance + 1; 224 | 225 | // We're sending *all* chunks in the range of where the player was, to where the player currently is. 226 | // This is because the #updateChunkTracking method will also unload chunks. 227 | // For chunks outside of the view distance, it does nothing. 228 | for (int curX = minSendChunkX; curX <= maxSendChunkX; ++curX) { 229 | for (int curZ = minSendChunkZ; curZ <= maxSendChunkZ; ++curZ) { 230 | ChunkPos chunkPos = new ChunkPos(curX, curZ); 231 | boolean inOld = isChunkInRange(curX, curZ, oldChunkX, oldChunkZ, playerViewDistance); 232 | boolean inNew = isChunkInRange(curX, curZ, newChunkX, newChunkZ, playerViewDistance); 233 | this.updateChunkTracking(player, chunkPos, new MutableObject<>(), inOld, inNew); 234 | } 235 | } 236 | } 237 | } finally { 238 | AutoFlushUtil.setAutoFlush(player, true); 239 | } 240 | } 241 | 242 | /** 243 | * Sends watch packets to the client in a spiral for a player, which has *no* chunks loaded in the area. 244 | */ 245 | private void sendSpiralChunkWatchPackets(ServerPlayer player) { 246 | int chunkPosX = SectionPos.blockToSectionCoord(player.getBlockX()); 247 | int chunkPosZ = SectionPos.blockToSectionCoord(player.getBlockZ()); 248 | 249 | 250 | // + 1 because mc adds 1 when it sends chunks 251 | int playerViewDistance = getPlayerViewDistance(player) + 1; 252 | 253 | int x = 0, z = 0, dx = 0, dz = -1; 254 | int t = playerViewDistance * 2; 255 | int maxI = t * t * 2; 256 | for (int i = 0; i < maxI; i++) { 257 | if ((-playerViewDistance <= x) && (x <= playerViewDistance) && (-playerViewDistance <= z) && (z <= playerViewDistance)) { 258 | boolean inNew = isChunkInRange(chunkPosX, chunkPosZ, chunkPosX + x, chunkPosZ + z, playerViewDistance); 259 | 260 | this.updateChunkTracking(player, 261 | new ChunkPos(chunkPosX + x, chunkPosZ + z), 262 | new MutableObject<>(), false, inNew 263 | ); 264 | } 265 | if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z))) { 266 | t = dx; 267 | dx = -dz; 268 | dz = t; 269 | } 270 | x += dx; 271 | z += dz; 272 | } 273 | } 274 | 275 | private void unloadChunks(ServerPlayer player, int chunkPosX, int chunkPosZ, int distance) { 276 | for (int curX = chunkPosX - distance - 1; curX <= chunkPosX + distance + 1; ++curX) { 277 | for (int curZ = chunkPosZ - distance - 1; curZ <= chunkPosZ + distance + 1; ++curZ) { 278 | ChunkPos chunkPos = new ChunkPos(curX, curZ); 279 | 280 | this.updateChunkTracking(player, chunkPos, new MutableObject<>(), true, false); 281 | } 282 | } 283 | } 284 | 285 | private int getPlayerViewDistance(ServerPlayer playerEntity) { 286 | //noinspection InstanceofIncompatibleInterface 287 | return playerEntity instanceof PlutoServerPlayer plutoPlayerEntity 288 | ? plutoPlayerEntity.getPlayerViewDistance() != -1 289 | // if -1, the view distance hasn't been set 290 | // We *actually* need to send view distance + 1, because mc doesn't render chunks adjacent to unloaded ones 291 | ? Math.min(this.viewDistance, 292 | plutoPlayerEntity.getPlayerViewDistance() + 293 | 1) 294 | : this.viewDistance : this.viewDistance; 295 | } 296 | 297 | private boolean shouldReloadAllChunks(ServerPlayer playerEntity) { 298 | //noinspection InstanceofIncompatibleInterface 299 | return playerEntity instanceof PlutoServerPlayer plutoPlayerEntity && plutoPlayerEntity.getNeedsChunksReloaded(); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/network/flushconsolidation/ConnectionMixin.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin.network.flushconsolidation; 2 | 3 | import com.abdelaziz.pluto.common.network.ConfigurableAutoFlush; 4 | import io.netty.channel.Channel; 5 | import net.minecraft.network.Connection; 6 | import org.objectweb.asm.Opcodes; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.Redirect; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | 14 | import java.util.concurrent.atomic.AtomicBoolean; 15 | 16 | /** 17 | * Optimizes ClientConnection by adding the ability to skip auto-flushing and using void promises where possible. 18 | */ 19 | @Mixin(Connection.class) 20 | public abstract class ConnectionMixin implements ConfigurableAutoFlush { 21 | @Shadow 22 | private Channel channel; 23 | 24 | private AtomicBoolean autoFlush; 25 | 26 | @Inject(method = "", at = @At("RETURN")) 27 | private void initAddedFields(CallbackInfo ci) { 28 | this.autoFlush = new AtomicBoolean(true); 29 | } 30 | 31 | @Redirect(method = "tick", at = @At(value = "FIELD", target = "Lnet/minecraft/network/Connection;channel:Lio/netty/channel/Channel;", opcode = Opcodes.GETFIELD)) 32 | public Channel disableForcedFlushEveryTick(Connection clientConnection) { 33 | return null; 34 | } 35 | 36 | @Override 37 | public void setShouldAutoFlush(boolean shouldAutoFlush) { 38 | boolean prev = this.autoFlush.getAndSet(shouldAutoFlush); 39 | if (!prev && shouldAutoFlush) { 40 | this.channel.flush(); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/network/flushconsolidation/ServerEntityMixin.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin.network.flushconsolidation; 2 | 3 | import net.minecraft.server.level.ServerEntity; 4 | import net.minecraft.server.level.ServerPlayer; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 9 | 10 | import static com.abdelaziz.pluto.common.network.util.AutoFlushUtil.setAutoFlush; 11 | 12 | @Mixin(ServerEntity.class) 13 | public class ServerEntityMixin { 14 | 15 | @Inject(at = @At("HEAD"), method = "addPairing") 16 | public void addPairing$disableAutoFlush(ServerPlayer player, CallbackInfo ci) { 17 | setAutoFlush(player, false); 18 | } 19 | 20 | @Inject(at = @At("RETURN"), method = "addPairing") 21 | public void addPairing$reenableAutoFlush(ServerPlayer player, CallbackInfo ci) { 22 | setAutoFlush(player, true); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/network/microopt/FriendlyByteBufMixin.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin.network.microopt; 2 | 3 | import com.abdelaziz.pluto.common.network.util.VarIntUtil; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.ByteBufUtil; 6 | import io.netty.handler.codec.EncoderException; 7 | import net.minecraft.network.FriendlyByteBuf; 8 | import org.spongepowered.asm.mixin.Final; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Overwrite; 11 | import org.spongepowered.asm.mixin.Shadow; 12 | 13 | import java.nio.charset.Charset; 14 | import java.nio.charset.StandardCharsets; 15 | 16 | /** 17 | * Multiple micro-optimizations for packet writing. 18 | */ 19 | @Mixin(FriendlyByteBuf.class) 20 | public abstract class FriendlyByteBufMixin extends ByteBuf { 21 | 22 | @Shadow 23 | @Final 24 | private ByteBuf source; 25 | 26 | @Shadow 27 | public abstract int writeCharSequence(CharSequence charSequence, Charset charset); 28 | 29 | /** 30 | * @author Andrew 31 | * @reason Use optimized VarInt byte size lookup table 32 | */ 33 | @Overwrite 34 | public static int getVarIntSize(int value) { 35 | return VarIntUtil.getVarIntLength(value); 36 | } 37 | 38 | /** 39 | * @author Andrew 40 | * @reason Use {@link ByteBuf#writeCharSequence(CharSequence, Charset)} instead for improved performance along with 41 | * computing the byte size ahead of time with {@link ByteBufUtil#utf8Bytes(CharSequence)} 42 | */ 43 | @Overwrite 44 | public FriendlyByteBuf writeUtf(String string, int i) { 45 | int utf8Bytes = ByteBufUtil.utf8Bytes(string); 46 | if (utf8Bytes > i) { 47 | throw new EncoderException("String too big (was " + utf8Bytes + " bytes encoded, max " + i + ")"); 48 | } else { 49 | this.writeVarInt(utf8Bytes); 50 | this.writeCharSequence(string, StandardCharsets.UTF_8); 51 | return new FriendlyByteBuf(source); 52 | } 53 | } 54 | 55 | /** 56 | * @author Andrew 57 | * @reason optimized VarInt writing 58 | */ 59 | @Overwrite 60 | public FriendlyByteBuf writeVarInt(int value) { 61 | // Peel the one and two byte count cases explicitly as they are the most common VarInt sizes 62 | // that the proxy will write, to improve inlining. 63 | if ((value & (0xFFFFFFFF << 7)) == 0) { 64 | source.writeByte(value); 65 | } else if ((value & (0xFFFFFFFF << 14)) == 0) { 66 | int w = (value & 0x7F | 0x80) << 8 | (value >>> 7); 67 | source.writeShort(w); 68 | } else { 69 | writeVarIntFull(source, value); 70 | } 71 | return new FriendlyByteBuf(source); 72 | } 73 | 74 | private static void writeVarIntFull(ByteBuf buf, int value) { 75 | // See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/ 76 | if ((value & (0xFFFFFFFF << 7)) == 0) { 77 | buf.writeByte(value); 78 | } else if ((value & (0xFFFFFFFF << 14)) == 0) { 79 | int w = (value & 0x7F | 0x80) << 8 | (value >>> 7); 80 | buf.writeShort(w); 81 | } else if ((value & (0xFFFFFFFF << 21)) == 0) { 82 | int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14); 83 | buf.writeMedium(w); 84 | } else if ((value & (0xFFFFFFFF << 28)) == 0) { 85 | int w = (value & 0x7F | 0x80) << 24 | (((value >>> 7) & 0x7F | 0x80) << 16) 86 | | ((value >>> 14) & 0x7F | 0x80) << 8 | (value >>> 21); 87 | buf.writeInt(w); 88 | } else { 89 | int w = (value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16 90 | | ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80); 91 | buf.writeInt(w); 92 | buf.writeByte(value >>> 28); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/network/microopt/ServerEntityMixin.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin.network.microopt; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import net.minecraft.server.level.ServerEntity; 5 | import net.minecraft.world.entity.Entity; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Redirect; 9 | 10 | import java.util.List; 11 | 12 | @Mixin(ServerEntity.class) 13 | public class ServerEntityMixin { 14 | 15 | @Redirect(method = "", at = @At(value = "INVOKE", target = "Ljava/util/Collections;emptyList()Ljava/util/List;")) 16 | public List construct$initialPassengersListIsGuavaImmutableList() { 17 | // This is a tiny micro-optimization, but in most cases, the passengers list for an entity is typically empty. 18 | // Furthermore, it is using Guava's ImmutableList type, but the constructor uses the JDK (pre-Java 9) empty 19 | // collections. By using Guava's collection type here, this check can often be simplified to a simple reference 20 | // equality check, which is very cheap. 21 | return ImmutableList.of(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/network/pipeline/LegacyQueryHandlerMixin.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin.network.pipeline; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import net.minecraft.server.network.LegacyQueryHandler; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | /** 12 | * Mixes into {@link LegacyQueryHandler} to fix a security issue. 13 | */ 14 | @Mixin(LegacyQueryHandler.class) 15 | public abstract class LegacyQueryHandlerMixin { 16 | @Inject(method = "channelRead", at = @At(value = "HEAD"), cancellable = true) 17 | public void channelRead(ChannelHandlerContext ctx, Object msg, CallbackInfo ci) throws Exception { 18 | if (!ctx.channel().isActive()) { 19 | ((ByteBuf) msg).clear(); 20 | ci.cancel(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/network/pipeline/Varint21FrameDecoderMixin.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin.network.pipeline; 2 | 3 | import com.abdelaziz.pluto.common.network.VarintByteDecoder; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import net.minecraft.network.Varint21FrameDecoder; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Overwrite; 9 | 10 | import java.util.List; 11 | 12 | import static com.abdelaziz.pluto.common.network.util.exception.WellKnownExceptions.BAD_LENGTH_CACHED; 13 | import static com.abdelaziz.pluto.common.network.util.exception.WellKnownExceptions.VARINT_BIG_CACHED; 14 | 15 | /** 16 | * Overrides the SplitterHandler to use optimized packet splitting from Velocity 1.1.0. In addition this applies a 17 | * security fix to stop "nullping" attacks. 18 | */ 19 | @Mixin(Varint21FrameDecoder.class) 20 | public class Varint21FrameDecoderMixin { 21 | private final VarintByteDecoder reader = new VarintByteDecoder(); 22 | 23 | /** 24 | * @author Andrew Steinborn 25 | * @reason Use optimized Velocity varint decoder that reduces bounds checking 26 | */ 27 | @Overwrite 28 | public void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 29 | if (!ctx.channel().isActive()) { 30 | in.clear(); 31 | return; 32 | } 33 | 34 | reader.reset(); 35 | 36 | int varintEnd = in.forEachByte(reader); 37 | if (varintEnd == -1) { 38 | // We tried to go beyond the end of the buffer. This is probably a good sign that the 39 | // buffer was too short to hold a proper varint. 40 | if (reader.getResult() == VarintByteDecoder.DecodeResult.RUN_OF_ZEROES) { 41 | // Special case where the entire packet is just a run of zeroes. We ignore them all. 42 | in.clear(); 43 | } 44 | return; 45 | } 46 | 47 | if (reader.getResult() == VarintByteDecoder.DecodeResult.RUN_OF_ZEROES) { 48 | // this will return to the point where the next varint starts 49 | in.readerIndex(varintEnd); 50 | } else if (reader.getResult() == VarintByteDecoder.DecodeResult.SUCCESS) { 51 | int readVarint = reader.getReadVarint(); 52 | int bytesRead = reader.getBytesRead(); 53 | if (readVarint < 0) { 54 | in.clear(); 55 | throw BAD_LENGTH_CACHED; 56 | } else if (readVarint == 0) { 57 | // skip over the empty packet(s) and ignore it 58 | in.readerIndex(varintEnd + 1); 59 | } else { 60 | int minimumRead = bytesRead + readVarint; 61 | if (in.isReadable(minimumRead)) { 62 | out.add(in.retainedSlice(varintEnd + 1, readVarint)); 63 | in.skipBytes(minimumRead); 64 | } 65 | } 66 | } else if (reader.getResult() == VarintByteDecoder.DecodeResult.TOO_BIG) { 67 | in.clear(); 68 | throw VARINT_BIG_CACHED; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/network/pipeline/compression/ConnectionMixin.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin.network.pipeline.compression; 2 | 3 | import com.abdelaziz.pluto.common.network.compression.MinecraftCompressDecoder; 4 | import com.abdelaziz.pluto.common.network.compression.MinecraftCompressEncoder; 5 | import com.velocitypowered.natives.compression.VelocityCompressor; 6 | import com.velocitypowered.natives.util.Natives; 7 | import io.netty.channel.Channel; 8 | import net.minecraft.network.Connection; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Shadow; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | @Mixin(Connection.class) 16 | public class ConnectionMixin { 17 | @Shadow 18 | private Channel channel; 19 | 20 | @Inject(method = "setupCompression", at = @At("HEAD"), cancellable = true) 21 | public void setCompressionThreshold(int compressionThreshold, boolean validate, CallbackInfo ci) { 22 | if (compressionThreshold == -1) { 23 | this.channel.pipeline().remove("decompress"); 24 | this.channel.pipeline().remove("compress"); 25 | } else { 26 | MinecraftCompressDecoder decoder = (MinecraftCompressDecoder) channel.pipeline() 27 | .get("decompress"); 28 | MinecraftCompressEncoder encoder = (MinecraftCompressEncoder) channel.pipeline() 29 | .get("compress"); 30 | if (decoder != null && encoder != null) { 31 | decoder.setThreshold(compressionThreshold); 32 | encoder.setThreshold(compressionThreshold); 33 | } else { 34 | VelocityCompressor compressor = Natives.compress.get().create(4); 35 | 36 | encoder = new MinecraftCompressEncoder(compressionThreshold, compressor); 37 | decoder = new MinecraftCompressDecoder(compressionThreshold, validate, compressor); 38 | 39 | channel.pipeline().addBefore("decoder", "decompress", decoder); 40 | channel.pipeline().addBefore("encoder", "compress", encoder); 41 | } 42 | } 43 | 44 | ci.cancel(); 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/network/pipeline/encryption/ConnectionMixin.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin.network.pipeline.encryption; 2 | 3 | import com.abdelaziz.pluto.common.network.ClientConnectionEncryptionExtension; 4 | import com.abdelaziz.pluto.common.network.pipeline.MinecraftCipherDecoder; 5 | import com.abdelaziz.pluto.common.network.pipeline.MinecraftCipherEncoder; 6 | import com.velocitypowered.natives.encryption.VelocityCipher; 7 | import com.velocitypowered.natives.util.Natives; 8 | import io.netty.channel.Channel; 9 | import net.minecraft.network.Connection; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Shadow; 12 | 13 | import javax.crypto.SecretKey; 14 | import java.security.GeneralSecurityException; 15 | 16 | @Mixin(Connection.class) 17 | public class ConnectionMixin implements ClientConnectionEncryptionExtension { 18 | @Shadow 19 | private boolean encrypted; 20 | @Shadow 21 | private Channel channel; 22 | 23 | @Override 24 | public void setupEncryption(SecretKey key) throws GeneralSecurityException { 25 | if (!this.encrypted) { 26 | VelocityCipher decryption = Natives.cipher.get().forDecryption(key); 27 | VelocityCipher encryption = Natives.cipher.get().forEncryption(key); 28 | 29 | this.encrypted = true; 30 | this.channel.pipeline().addBefore("splitter", "decrypt", new MinecraftCipherDecoder(decryption)); 31 | this.channel.pipeline().addBefore("prepender", "encrypt", new MinecraftCipherEncoder(encryption)); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/network/pipeline/encryption/ServerLoginPacketListenerImplMixin.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin.network.pipeline.encryption; 2 | 3 | import com.abdelaziz.pluto.common.network.ClientConnectionEncryptionExtension; 4 | import net.minecraft.network.Connection; 5 | import net.minecraft.server.network.ServerLoginPacketListenerImpl; 6 | import org.spongepowered.asm.mixin.Final; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Redirect; 11 | 12 | import javax.crypto.Cipher; 13 | import javax.crypto.SecretKey; 14 | import java.security.GeneralSecurityException; 15 | import java.security.Key; 16 | 17 | @Mixin(ServerLoginPacketListenerImpl.class) 18 | public class ServerLoginPacketListenerImplMixin { 19 | @Shadow 20 | @Final 21 | public Connection connection; 22 | 23 | @Redirect(method = "handleKey", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Crypt;getCipher(ILjava/security/Key;)Ljavax/crypto/Cipher;")) 24 | private Cipher onKey$initializeVelocityCipher(int ignored1, Key secretKey) throws GeneralSecurityException { 25 | // Hijack this portion of the cipher initialization and set up our own encryption handler. 26 | ((ClientConnectionEncryptionExtension) this.connection).setupEncryption((SecretKey) secretKey); 27 | 28 | // Turn the operation into a no-op. 29 | return null; 30 | } 31 | 32 | @Redirect(method = "handleKey", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/Connection;setEncryptionKey(Ljavax/crypto/Cipher;Ljavax/crypto/Cipher;)V")) 33 | public void onKey$ignoreMinecraftEncryptionPipelineInjection(Connection connection, Cipher ignored1, Cipher ignored2) { 34 | // Turn the operation into a no-op. 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/com/abdelaziz/pluto/mixin/player/ServerPlayerMixin.java: -------------------------------------------------------------------------------- 1 | package com.abdelaziz.pluto.mixin.player; 2 | 3 | import com.abdelaziz.pluto.common.player.PlutoServerPlayer; 4 | import net.minecraft.network.protocol.game.ServerboundClientInformationPacket; 5 | import net.minecraft.server.level.ServerPlayer; 6 | import org.spongepowered.asm.mixin.Implements; 7 | import org.spongepowered.asm.mixin.Interface; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Unique; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | 14 | @Mixin(ServerPlayer.class) 15 | @Implements(@Interface(iface = PlutoServerPlayer.class, prefix = "pluto$", unique = true)) 16 | public class ServerPlayerMixin implements PlutoServerPlayer { 17 | @Unique 18 | private int playerViewDistance = -1; 19 | 20 | @Unique 21 | private boolean needsChunksReloaded = false; 22 | 23 | @Inject(method = "updateOptions", at = @At("HEAD")) 24 | public void updateOptions(ServerboundClientInformationPacket packet, CallbackInfo ci) { 25 | needsChunksReloaded = (playerViewDistance != packet.viewDistance()); 26 | playerViewDistance = packet.viewDistance(); 27 | } 28 | 29 | @Override 30 | public boolean getNeedsChunksReloaded() { 31 | return needsChunksReloaded; 32 | } 33 | 34 | @Override 35 | public void setNeedsChunksReloaded(boolean needsChunksReloaded) { 36 | this.needsChunksReloaded = needsChunksReloaded; 37 | } 38 | 39 | @Override 40 | public int getPlayerViewDistance() { 41 | return playerViewDistance; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/accesstransformer.cfg: -------------------------------------------------------------------------------- 1 | public net.minecraft.server.level.ChunkMap$TrackedEntity 2 | public net.minecraft.server.level.ChunkMap$DistanceManager 3 | 4 | public net.minecraft.network.Varint21FrameDecoder decode 5 | public net.minecraft.server.level.ChunkMap sendWatchPackets 6 | public-f net.minecraft.server.level.ChunkMap$TrackedEntity f_140472_ 7 | 8 | public-f net.minecraft.world.level.entity.TransientEntitySectionManager f_157638_ 9 | public-f net.minecraft.world.level.entity.PersistentEntitySectionManager f_157495_ -------------------------------------------------------------------------------- /src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader="javafml" 2 | loaderVersion="[43,)" 3 | license = "LGPL-3.0" 4 | issueTrackerURL = "https://github.com/AbdElAziz333/Pluto/issues" 5 | [[mods]] 6 | modId = "pluto" 7 | version = "${file.jarVersion}" 8 | displayName = "Pluto" 9 | updateJSONURL = "https://github.com/AbdElAziz333/Pluto/blob/mc1.19.2/dev/updates.json" 10 | displayURL = "https://www.curseforge.com/minecraft/mc-mods/pluto" 11 | logoFile = "logo.png" 12 | credits = "AbdElAziz, Tuxed" 13 | authors = "AbdElAziz" 14 | displayTest = "MATCH_VERSION" 15 | 16 | description = ''' 17 | Pluto is a performance mod that optimizes Minecraft's networking stack and entity tracker. 18 | ''' 19 | 20 | [[dependencies.pluto]] 21 | modId="forge" 22 | mandatory=true 23 | versionRange="[43,)" 24 | ordering="NONE" 25 | side="BOTH" 26 | 27 | [[dependencies.pluto]] 28 | modId="minecraft" 29 | mandatory = true 30 | versionRange = "[1.19.2,)" 31 | ordering = "NONE" 32 | side="BOTH" -------------------------------------------------------------------------------- /src/main/resources/canary.overrides.properties: -------------------------------------------------------------------------------- 1 | mixin.world.player_chunk_tick=false -------------------------------------------------------------------------------- /src/main/resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AbdElAziz333/Pluto/02c6cf26a9190eab01420941007880d89424fad3/src/main/resources/logo.png -------------------------------------------------------------------------------- /src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "pluto resources", 4 | "pack_format": 9, 5 | "forge:resource_pack_format": 9, 6 | "forge:data_pack_format": 10 7 | } 8 | } -------------------------------------------------------------------------------- /src/main/resources/pluto.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "com.abdelaziz.pluto.mixin", 4 | "compatibilityLevel": "JAVA_17", 5 | "plugin": "com.abdelaziz.pluto.mixin.PlutoMixinPlugin", 6 | "minVersion": "0.8.2", 7 | "injectors": { 8 | "defaultRequire": 1 9 | }, 10 | "refmap": "pluto.refmap.json", 11 | "mixins": [ 12 | "fast_entity_access.EntitySectionAccessor", 13 | "fast_entity_access.EntitySectionStorageMixin", 14 | "fast_entity_access.ServerLevelMixin", 15 | "network.avoidwork.ChunkMapMixin", 16 | "network.flushconsolidation.ChunkMapMixin", 17 | "network.flushconsolidation.ConnectionMixin", 18 | "network.flushconsolidation.ServerEntityMixin", 19 | "network.microopt.FriendlyByteBufMixin", 20 | "network.microopt.ServerEntityMixin", 21 | "network.pipeline.LegacyQueryHandlerMixin", 22 | "network.pipeline.Varint21FrameDecoderMixin", 23 | "network.pipeline.compression.ConnectionMixin", 24 | "network.pipeline.encryption.ConnectionMixin", 25 | "network.pipeline.encryption.ServerLoginPacketListenerImplMixin", 26 | "player.ServerPlayerMixin" 27 | ], 28 | "client": [ 29 | "fast_entity_access.ClientLevelMixin" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /updates.json: -------------------------------------------------------------------------------- 1 | { 2 | "homepage": "https://www.curseforge.com/minecraft/mc-mods/pluto", 3 | "promos": { 4 | "1.19.3-latest": "0.0.9", 5 | "1.19.3-recommended": "0.0.9", 6 | "1.19.2-latest": "0.0.9", 7 | "1.19.2-recommended": "0.0.9", 8 | "1.18.2-latest": "0.0.6", 9 | "1.18.2-recommended": "0.0.6" 10 | } 11 | } 12 | --------------------------------------------------------------------------------