diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..097f9f9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b00c3bd --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "daily" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..585b53c --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Compiled class file +*.class + +# Log file +*.log +*.log.gz + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# Project files +/.idea/ +/.gradle/ +/build/ +/out/ +/run/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..8abedfd --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# ViaProxy +Standalone proxy which uses ViaVersion to translate between minecraft versions. + +## Supported Server versions +- Classic (c0.0.15 - c0.30 including [CPE](https://wiki.vg/Classic_Protocol_Extension)) +- Alpha (a1.0.15 - a1.2.6) +- Beta (b1.0 - b1.8.1) +- Release (1.0.0 - 1.19.3) +- April Fools (3D Shareware, 20w14infinite) +- Combat Snapshots (Combat Test 8c) + +## Supported Client versions +- Release (1.7.2 - 1.19.3) +- April Fools (3D Shareware) + +## Releases +### Gradle/Maven +To use ViaProxy with Gradle/Maven you can use this [Maven server](https://maven.lenni0451.net/#/releases/net/raphimc/ViaProxy) or [Jitpack](https://jitpack.io/#RaphiMC/ViaProxy). +You can also find instructions how to implement it into your build script there. + +### Jar File +If you just want the latest jar file you can download it from this [Jenkins](https://build.lenni0451.net/job/ViaProxy/). + +## Usage +TODO diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..7434328 --- /dev/null +++ b/build.gradle @@ -0,0 +1,110 @@ +plugins { + id "java" + id "maven-publish" +} + +java.toolchain.languageVersion = JavaLanguageVersion.of(8) +compileJava.options.encoding = compileTestJava.options.encoding = javadoc.options.encoding = "UTF-8" + +group = project.maven_group +archivesBaseName = project.maven_name +version = project.maven_version + +configurations { + include + + implementation.extendsFrom include + api.extendsFrom include +} + +repositories { + mavenCentral() + maven { + name = "Jitpack" + url = "https://jitpack.io" + } + maven { + name = "Lenni0451" + url "https://maven.lenni0451.net/releases" + } + maven { + name = "ViaVersion" + url "https://repo.viaversion.com" + } +} + +dependencies { + include "com.viaversion:viaversion:4.5.2-SNAPSHOT" + include("com.viaversion:viabackwards-common:4.5.2-SNAPSHOT") { + exclude group: "com.viaversion", module: "viaversion" + exclude group: "io.netty", module: "netty-all" + exclude group: "com.google.guava", module: "guava" + } + include "com.viaversion:viarewind-core:2.0.3-SNAPSHOT" + include "net.raphimc:ViaLegacy:2.0.3" + include("net.raphimc:ViaProtocolHack:2.0.1") { + exclude group: "org.slf4j", module: "slf4j-api" + } + + include "com.formdev:flatlaf:3.0" + include "com.google.guava:guava:31.1-jre" + include "net.sf.jopt-simple:jopt-simple:5.0.4" + include "org.apache.logging.log4j:log4j-core:2.19.0" + include "org.apache.logging.log4j:log4j-slf4j-impl:2.19.0" + include "com.github.GeyserMC:MCAuthLib:1.4" + include "net.lenni0451.classtransform:mixinstranslator:1.7.5" + include "net.lenni0451.classtransform:mixinsdummy:1.7.5" + include "net.lenni0451.classtransform:additionalclassprovider:1.7.5" + include "net.lenni0451:Reflect:1.0.2" + include "net.lenni0451:LambdaEvents:2.0.3" + include "net.raphimc.netminecraft:all:2.2.2" +} + +java { + withSourcesJar() +} + +jar { + dependsOn configurations.include + from { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + configurations.include.collect { + zipTree(it) + } + } { + exclude "META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA" + } + + manifest { + attributes( + "Main-Class": "net.raphimc.viaproxy.ViaProxy", + "Multi-Release": "true" + ) + } + + from("LICENSE") { + rename { "${it}_${project.archivesBaseName}" } + } +} + +publishing { + repositories { + maven { + name = "reposilite" + url = "https://maven.lenni0451.net/releases" + credentials(PasswordCredentials) + authentication { + basic(BasicAuthentication) + } + } + } + publications { + maven(MavenPublication) { + groupId = project.maven_group + artifactId = project.maven_name + version = project.maven_version + + from components.java + } + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..67de5dd --- /dev/null +++ b/gradle.properties @@ -0,0 +1,9 @@ +# Gradle properties +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.configureondemand=true + +# Project properties +maven_name=ViaProxy +maven_group=net.raphimc +maven_version=3.0.0 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ae04661 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..a69d9cb --- /dev/null +++ b/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f127cfd --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..4968670 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,8 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } +} + +rootProject.name = "ViaProxy" diff --git a/src/main/java/net/raphimc/viaproxy/ViaProxy.java b/src/main/java/net/raphimc/viaproxy/ViaProxy.java new file mode 100644 index 0000000..89a6586 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/ViaProxy.java @@ -0,0 +1,114 @@ +package net.raphimc.viaproxy; + +import io.netty.channel.Channel; +import io.netty.channel.group.ChannelGroup; +import io.netty.channel.group.DefaultChannelGroup; +import io.netty.util.ResourceLeakDetector; +import io.netty.util.concurrent.GlobalEventExecutor; +import net.lenni0451.classtransform.TransformerManager; +import net.lenni0451.classtransform.additionalclassprovider.GuavaClassPathProvider; +import net.lenni0451.classtransform.mixinstranslator.MixinsTranslator; +import net.lenni0451.classtransform.utils.loader.EnumLoaderPriority; +import net.lenni0451.classtransform.utils.loader.InjectionClassLoader; +import net.lenni0451.reflect.ClassLoaders; +import net.raphimc.netminecraft.constants.MCPipeline; +import net.raphimc.netminecraft.netty.connection.NetServer; +import net.raphimc.viaproxy.cli.ConsoleHandler; +import net.raphimc.viaproxy.cli.options.Options; +import net.raphimc.viaproxy.plugins.PluginManager; +import net.raphimc.viaproxy.protocolhack.ProtocolHack; +import net.raphimc.viaproxy.proxy.ProxyConnection; +import net.raphimc.viaproxy.proxy.client2proxy.Client2ProxyChannelInitializer; +import net.raphimc.viaproxy.proxy.client2proxy.Client2ProxyHandler; +import net.raphimc.viaproxy.ui.ViaProxyUI; +import net.raphimc.viaproxy.util.logging.Logger; + +import javax.swing.*; +import java.awt.*; + +public class ViaProxy { + + public static NetServer currentProxyServer; + public static Thread loaderThread; + public static ChannelGroup c2pChannels; + + public static void main(String[] args) throws Throwable { + final TransformerManager transformerManager = new TransformerManager(new GuavaClassPathProvider()); + transformerManager.addTransformerPreprocessor(new MixinsTranslator()); + transformerManager.addTransformer("net.raphimc.viaproxy.injection.transformer.**"); + transformerManager.addTransformer("net.raphimc.viaproxy.injection.mixins.**"); + final InjectionClassLoader injectionClassLoader = new InjectionClassLoader(transformerManager, ClassLoaders.getSystemClassPath()); + injectionClassLoader.setPriority(EnumLoaderPriority.PARENT_FIRST); + injectionClassLoader.executeMain(ViaProxy.class.getName(), "injectedMain", args); + } + + public static void injectedMain(String[] args) throws InterruptedException { + Logger.setup(); + ConsoleHandler.hookConsole(); + Logger.LOGGER.info("Initializing ViaProxy..."); + setNettyParameters(); + MCPipeline.useOptimizedPipeline(); + c2pChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + loaderThread = new Thread(() -> { + ProtocolHack.init(); + PluginManager.loadPlugins(); + }, "ViaProtocolHack-Loader"); + + if (args.length == 0 && !GraphicsEnvironment.isHeadless()) { + loaderThread.start(); + final ViaProxyUI[] ui = new ViaProxyUI[1]; + SwingUtilities.invokeLater(() -> ui[0] = new ViaProxyUI()); + loaderThread.join(); + ui[0].setReady(); + Logger.LOGGER.info("ViaProxy started successfully!"); + return; + } + + try { + Options.parse(args); + } catch (Throwable t) { + Logger.LOGGER.fatal("[" + t.getClass().getSimpleName() + "] " + t.getMessage()); + return; + } + + loaderThread.start(); + loaderThread.join(); + Logger.LOGGER.info("ViaProxy started successfully!"); + startProxy(); + } + + public static void startProxy() { + if (currentProxyServer != null) { + throw new IllegalStateException("Proxy is already running"); + } + currentProxyServer = new NetServer(Client2ProxyHandler::new, Client2ProxyChannelInitializer::new); + Logger.LOGGER.info("Binding proxy server to " + Options.BIND_ADDRESS + ":" + Options.BIND_PORT); + currentProxyServer.bind(Options.BIND_ADDRESS, Options.BIND_PORT, false); + } + + public static void stopProxy() { + if (currentProxyServer != null) { + Logger.LOGGER.info("Stopping proxy server"); + currentProxyServer.getChannel().close(); + currentProxyServer = null; + + for (Channel channel : c2pChannels) { + try { + ProxyConnection.fromChannel(channel).kickClient("§cViaProxy has been stopped"); + } catch (Throwable ignored) { + } + } + } + } + + private static void setNettyParameters() { + ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED); + if (System.getProperty("io.netty.allocator.maxOrder") == null) { + System.setProperty("io.netty.allocator.maxOrder", "9"); + } + if (Options.NETTY_THREADS > 0) { + System.setProperty("io.netty.eventLoopThreads", Integer.toString(Options.NETTY_THREADS)); + } + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/cli/ConsoleFormatter.java b/src/main/java/net/raphimc/viaproxy/cli/ConsoleFormatter.java new file mode 100644 index 0000000..c1db349 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/cli/ConsoleFormatter.java @@ -0,0 +1,87 @@ +package net.raphimc.viaproxy.cli; + +public class ConsoleFormatter { + + private static final String PREFIX = "\033["; + private static final String SUFFIX = "m"; + + public static String convert(final String s) { + StringBuilder out = new StringBuilder(); + char[] chars = s.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + char next = i + 1 < chars.length ? chars[i + 1] : '\0'; + if (c == '§') { + if (next != '\0') { + if (isColor(next)) out.append(convertToAnsi('r')); + out.append(convertToAnsi(next)); + i++; + } + } else { + out.append(c); + } + } + return out + convertToAnsi('r'); + } + + //The ANSI codes are an approximation. True color is used to get the exact color. + private static String convertToAnsi(final char color) { + switch (Character.toLowerCase(color)) { + case '0': //Black + return PREFIX + getColor("30", 0x00_00_00) + SUFFIX; + case '1': //Dark Blue + return PREFIX + getColor("34", 0x00_00_AA) + SUFFIX; + case '2': //Dark Green + return PREFIX + getColor("32", 0x00_AA_00) + SUFFIX; + case '3': //Dark Aqua + return PREFIX + getColor("36", 0x00_AA_AA) + SUFFIX; + case '4': //Dark Red + return PREFIX + getColor("31", 0xAA_00_00) + SUFFIX; + case '5': //Dark Purple + return PREFIX + getColor("35", 0xAA_00_AA) + SUFFIX; + case '6': //Gold + return PREFIX + getColor("33", 0xFF_AA_00) + SUFFIX; + case '7': //Gray + return PREFIX + getColor("37", 0xAA_AA_AA) + SUFFIX; + case '8': //Dark Gray + return PREFIX + getColor("90", 0x55_55_55) + SUFFIX; + case '9': //Blue + return PREFIX + getColor("94", 0x55_55_FF) + SUFFIX; + case 'a': //Green + return PREFIX + getColor("92", 0x55_FF_55) + SUFFIX; + case 'b': //Aqua + return PREFIX + getColor("96", 0x55_FF_FF) + SUFFIX; + case 'c': //Red + return PREFIX + getColor("91", 0xFF_55_55) + SUFFIX; + case 'd': //Light Purple + return PREFIX + getColor("95", 0xFF_55_FF) + SUFFIX; + case 'e': //Yellow + return PREFIX + getColor("93", 0xFF_FF_55) + SUFFIX; + case 'f': //White + return PREFIX + getColor("97", 0xFF_FF_FF) + SUFFIX; + case 'k': //Obfuscated + return ""; //Not supported in terminal + case 'l': //Bold + return PREFIX + "1" + SUFFIX; + case 'm': //Strikethrough + return PREFIX + "9" + SUFFIX; + case 'n': //Underline + return PREFIX + "4" + SUFFIX; + case 'o': //Italic + return PREFIX + "3" + SUFFIX; + case 'r': //Reset + default: + return PREFIX + 0 + SUFFIX; + } + } + + private static boolean isColor(char color) { + color = Character.toLowerCase(color); + return color >= '0' && color <= '9' || color >= 'a' && color <= 'f'; + } + + private static String getColor(final String ansi, final int rgb) { + return String.format("38;2;%d;%d;%d", (rgb >> 16) & 255, (rgb >> 8) & 255, rgb & 255); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/cli/ConsoleHandler.java b/src/main/java/net/raphimc/viaproxy/cli/ConsoleHandler.java new file mode 100644 index 0000000..ad56edd --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/cli/ConsoleHandler.java @@ -0,0 +1,42 @@ +package net.raphimc.viaproxy.cli; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.connection.UserConnectionImpl; +import net.raphimc.viaprotocolhack.commands.UserCommandSender; +import net.raphimc.viaproxy.plugins.PluginManager; +import net.raphimc.viaproxy.plugins.events.ConsoleCommandEvent; +import net.raphimc.viaproxy.util.ArrayHelper; + +import java.util.Arrays; +import java.util.Scanner; + +public class ConsoleHandler { + + public static void hookConsole() { + new Thread(ConsoleHandler::listen, "Console-Handler").start(); + } + + private static void listen() { + final Scanner scanner = new Scanner(System.in); + while (scanner.hasNextLine()) { + final String line = scanner.nextLine(); + final String[] parts = line.split(" "); + if (parts.length == 0) continue; + final String command = parts[0]; + final ArrayHelper args = new ArrayHelper(Arrays.copyOfRange(parts, 1, parts.length)); + + if (command.equalsIgnoreCase("gc")) { + System.gc(); + System.out.println("GC Done"); + } else if (command.equalsIgnoreCase("via")) { + Via.getManager().getCommandHandler().onCommand(new UserCommandSender(new UserConnectionImpl(null, true)), args.getAsArray()); + } else { + if (PluginManager.EVENT_MANAGER.call(new ConsoleCommandEvent(command, args.getAsArray())).isCancelled()) continue; + System.out.println("Invalid Command!"); + System.out.println(" via | Run a viaversion command"); + System.out.println(" gc | Run the garbage collector"); + } + } + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/cli/options/BetterHelpFormatter.java b/src/main/java/net/raphimc/viaproxy/cli/options/BetterHelpFormatter.java new file mode 100644 index 0000000..535903b --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/cli/options/BetterHelpFormatter.java @@ -0,0 +1,21 @@ +package net.raphimc.viaproxy.cli.options; + +import joptsimple.BuiltinHelpFormatter; +import joptsimple.OptionDescriptor; +import joptsimple.internal.Classes; +import joptsimple.internal.Strings; + +public class BetterHelpFormatter extends BuiltinHelpFormatter { + + public BetterHelpFormatter() { + super(250, 4); + } + + @Override + protected String extractTypeIndicator(OptionDescriptor descriptor) { + String indicator = descriptor.argumentTypeIndicator(); + if (indicator != null && indicator.startsWith("[")) return indicator.substring(1, indicator.length() - 1); + return !Strings.isNullOrEmpty(indicator) && !String.class.getName().equals(indicator) ? Classes.shortNameOf(indicator) : "String"; + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/cli/options/Options.java b/src/main/java/net/raphimc/viaproxy/cli/options/Options.java new file mode 100644 index 0000000..d4001a4 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/cli/options/Options.java @@ -0,0 +1,72 @@ +package net.raphimc.viaproxy.cli.options; + +import joptsimple.*; +import net.raphimc.vialegacy.util.VersionEnum; +import net.raphimc.viaproxy.util.logging.Logger; + +import java.io.IOException; + +import static java.util.Arrays.asList; + +public class Options { + + public static String BIND_ADDRESS = "0.0.0.0"; + public static int BIND_PORT = 25568; + + public static boolean SRV_MODE; // Example: lenni0451.net_25565_1.8.x.viaproxy.127.0.0.1.nip.io + public static boolean INTERNAL_SRV_MODE; // Example: ip\7port\7version\7mppass + public static boolean ONLINE_MODE; + public static int NETTY_THREADS = 0; + public static int COMPRESSION_THRESHOLD = 256; + + public static String CONNECT_ADDRESS; + public static int CONNECT_PORT; + public static VersionEnum PROTOCOL_VERSION; + + public static boolean OPENAUTHMOD_AUTH; + public static boolean LOCAL_SOCKET_AUTH; + public static boolean BETACRAFT_AUTH; + + public static void parse(final String[] args) throws IOException { + VersionEnum.init(); // We need to init the version list already here + + final OptionParser parser = new OptionParser(); + final OptionSpec help = parser.acceptsAll(asList("help", "h", "?"), "Get a list of all arguments").forHelp(); + + final OptionSpec bindAddress = parser.acceptsAll(asList("bind_address", "bind_ip", "ba"), "The address the proxy should bind to").withRequiredArg().ofType(String.class).defaultsTo(BIND_ADDRESS); + final OptionSpec bindPort = parser.acceptsAll(asList("bind_port", "bp"), "The port the proxy should bind to").withRequiredArg().ofType(Integer.class).defaultsTo(BIND_PORT); + final OptionSpec srvMode = parser.acceptsAll(asList("srv_mode", "srv", "s"), "Enable srv mode"); + final OptionSpec iSrvMode = parser.acceptsAll(asList("internal_srv_mode", "isrv"), "Enable internal srv mode").availableUnless(srvMode); + final OptionSpec onlineMode = parser.acceptsAll(asList("online_mode", "om", "o"), "Enable online mode"); + final OptionSpec nettyThreads = parser.acceptsAll(asList("netty_threads", "t"), "The amount of netty threads to use").withRequiredArg().ofType(Integer.class).defaultsTo(NETTY_THREADS); + final OptionSpec compressionThreshold = parser.acceptsAll(asList("compression_threshold", "ct", "c"), "The threshold for packet compression").withRequiredArg().ofType(Integer.class).defaultsTo(COMPRESSION_THRESHOLD); + final OptionSpec connectAddress = parser.acceptsAll(asList("connect_address", "target_ip", "ca", "a"), "The address of the target server").withRequiredArg().ofType(String.class).required(); + final OptionSpec connectPort = parser.acceptsAll(asList("connect_port", "target_port", "cp", "p"), "The port of the target server").withRequiredArg().ofType(Integer.class).defaultsTo(CONNECT_PORT); + final OptionSpec version = parser.acceptsAll(asList("version", "v"), "The version of the target server").withRequiredArg().withValuesConvertedBy(new VersionEnumConverter()).required(); + final OptionSpec openAuthModAuth = parser.acceptsAll(asList("openauthmod_auth", "oam_auth"), "Enable OpenAuthMod authentication"); + final OptionSpec localSocketAuth = parser.accepts("local_socket_auth", "Enable authentication over a local socket"); + final OptionSpec betaCraftAuth = parser.accepts("betacraft_auth", "Use BetaCraft authentication servers for classic"); + + final OptionSet options = parser.parse(args); + if (options.has(help)) { + parser.formatHelpWith(new BetterHelpFormatter()); + parser.printHelpOn(Logger.SYSOUT); + System.exit(0); + } + + BIND_ADDRESS = options.valueOf(bindAddress); + BIND_PORT = options.valueOf(bindPort); + SRV_MODE = options.has(srvMode); + INTERNAL_SRV_MODE = options.has(iSrvMode); + ONLINE_MODE = options.has(onlineMode); + NETTY_THREADS = options.valueOf(nettyThreads); + CONNECT_ADDRESS = options.valueOf(connectAddress); + CONNECT_PORT = options.valueOf(connectPort); + PROTOCOL_VERSION = options.valueOf(version); + COMPRESSION_THRESHOLD = options.valueOf(compressionThreshold); + OPENAUTHMOD_AUTH = options.has(openAuthModAuth); + LOCAL_SOCKET_AUTH = options.has(localSocketAuth); + BETACRAFT_AUTH = options.has(betaCraftAuth); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/cli/options/VersionEnumConverter.java b/src/main/java/net/raphimc/viaproxy/cli/options/VersionEnumConverter.java new file mode 100644 index 0000000..dff7d08 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/cli/options/VersionEnumConverter.java @@ -0,0 +1,31 @@ +package net.raphimc.viaproxy.cli.options; + +import joptsimple.ValueConversionException; +import joptsimple.ValueConverter; +import net.raphimc.vialegacy.util.VersionEnum; + +public class VersionEnumConverter implements ValueConverter { + + @Override + public VersionEnum convert(String s) { + for (VersionEnum version : VersionEnum.getAllVersions()) { + if (version.getName().equalsIgnoreCase(s)) return version; + } + throw new ValueConversionException("Unable to find version '" + s + "'"); + } + + @Override + public Class valueType() { + return VersionEnum.class; + } + + @Override + public String valuePattern() { + StringBuilder s = new StringBuilder(); + for (VersionEnum version : VersionEnum.getAllVersions()) { + s.append((s.length() == 0) ? "" : ", ").append(version.getName()); + } + return "[" + s + "]"; + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/ClassicWorldHeightInjection.java b/src/main/java/net/raphimc/viaproxy/injection/ClassicWorldHeightInjection.java new file mode 100644 index 0000000..4b1e237 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/ClassicWorldHeightInjection.java @@ -0,0 +1,173 @@ +package net.raphimc.viaproxy.injection; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.minecraft.chunks.Chunk; +import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; +import com.viaversion.viaversion.api.protocol.remapper.PacketRemapper; +import com.viaversion.viaversion.api.type.Type; +import com.viaversion.viaversion.libs.opennbt.tag.builtin.*; +import com.viaversion.viaversion.protocols.protocol1_17to1_16_4.Protocol1_17To1_16_4; +import com.viaversion.viaversion.protocols.protocol1_17to1_16_4.types.Chunk1_17Type; +import net.raphimc.vialegacy.protocols.classic.protocola1_0_15toc0_28_30.model.ClassicLevel; +import net.raphimc.vialegacy.protocols.classic.protocola1_0_15toc0_28_30.providers.ClassicWorldHeightProvider; +import net.raphimc.vialegacy.protocols.classic.protocola1_0_15toc0_28_30.storage.ClassicLevelStorage; +import net.raphimc.vialegacy.util.VersionEnum; + +import java.util.*; + +public class ClassicWorldHeightInjection { + + public static PacketRemapper handleJoinGame(final Protocol1_17To1_16_4 protocol, final PacketRemapper parentRemapper) { + return new PacketRemapper() { + @Override + public void registerMap() { + handler(wrapper -> { + parentRemapper.remap(wrapper); + if (wrapper.isCancelled()) return; + + if (VersionEnum.fromUserConnection(wrapper.user()).isOlderThanOrEqualTo(VersionEnum.c0_28toc0_30)) { + for (Tag dimension : wrapper.get(Type.NBT, 0).get("minecraft:dimension_type").get("value")) { + changeDimensionTagHeight(wrapper.user(), ((CompoundTag) dimension).get("element")); + } + changeDimensionTagHeight(wrapper.user(), wrapper.get(Type.NBT, 1)); + } + }); + } + }; + } + + public static PacketRemapper handleRespawn(final Protocol1_17To1_16_4 protocol, final PacketRemapper parentRemapper) { + return new PacketRemapper() { + @Override + public void registerMap() { + handler(wrapper -> { + parentRemapper.remap(wrapper); + if (wrapper.isCancelled()) return; + + if (VersionEnum.fromUserConnection(wrapper.user()).isOlderThanOrEqualTo(VersionEnum.c0_28toc0_30)) { + changeDimensionTagHeight(wrapper.user(), wrapper.get(Type.NBT, 0)); + } + }); + } + }; + } + + public static PacketRemapper handleChunkData(final Protocol1_17To1_16_4 protocol, final PacketRemapper parentRemapper) { + return new PacketRemapper() { + @Override + public void registerMap() { + handler(wrapper -> { + parentRemapper.remap(wrapper); + if (wrapper.isCancelled()) return; + + if (VersionEnum.fromUserConnection(wrapper.user()).isOlderThanOrEqualTo(VersionEnum.c0_28toc0_30)) { + wrapper.resetReader(); + final Chunk chunk = wrapper.read(new Chunk1_17Type(16)); + wrapper.write(new Chunk1_17Type(chunk.getSections().length), chunk); + + final ClassicWorldHeightProvider heightProvider = Via.getManager().getProviders().get(ClassicWorldHeightProvider.class); + if (chunk.getSections().length < heightProvider.getMaxChunkSectionCount(wrapper.user())) { // Increase available sections to match new world height + final ChunkSection[] newArray = new ChunkSection[heightProvider.getMaxChunkSectionCount(wrapper.user())]; + System.arraycopy(chunk.getSections(), 0, newArray, 0, chunk.getSections().length); + chunk.setSections(newArray); + } + + final BitSet chunkMask = new BitSet(); + for (int i = 0; i < chunk.getSections().length; i++) { + if (chunk.getSections()[i] != null) chunkMask.set(i); + } + chunk.setChunkMask(chunkMask); + + final int[] newBiomeData = new int[chunk.getSections().length * 4 * 4 * 4]; + System.arraycopy(chunk.getBiomeData(), 0, newBiomeData, 0, chunk.getBiomeData().length); + for (int i = 64; i < chunk.getSections().length * 4; i++) { // copy top layer of old biome data all the way to max world height + System.arraycopy(chunk.getBiomeData(), chunk.getBiomeData().length - 16, newBiomeData, i * 16, 16); + } + chunk.setBiomeData(newBiomeData); + + chunk.setHeightMap(new CompoundTag()); // rip heightmap :( + } + }); + } + }; + } + + public static PacketRemapper handleUpdateLight(final Protocol1_17To1_16_4 protocol, final PacketRemapper parentRemapper) { + final PacketRemapper classicLightHandler = new PacketRemapper() { + @Override + public void registerMap() { + map(Type.VAR_INT); // x + map(Type.VAR_INT); // y + map(Type.BOOLEAN); // trust edges + handler(wrapper -> { + wrapper.read(Type.VAR_INT); // sky light mask + wrapper.read(Type.VAR_INT); // block light mask + final int emptySkyLightMask = wrapper.read(Type.VAR_INT); // empty sky light mask + final int emptyBlockLightMask = wrapper.read(Type.VAR_INT); // empty block light mask + + final ClassicLevel level = wrapper.user().get(ClassicLevelStorage.class).getClassicLevel(); + final ClassicWorldHeightProvider heightProvider = Via.getManager().getProviders().get(ClassicWorldHeightProvider.class); + + int sectionYCount = level.getSizeY() >> 4; + if (level.getSizeY() % 16 != 0) sectionYCount++; + if (sectionYCount > heightProvider.getMaxChunkSectionCount(wrapper.user())) { + sectionYCount = heightProvider.getMaxChunkSectionCount(wrapper.user()); + } + + final List lightArrays = new ArrayList<>(); + while (wrapper.isReadable(Type.BYTE_ARRAY_PRIMITIVE, 0)) { + lightArrays.add(wrapper.read(Type.BYTE_ARRAY_PRIMITIVE)); + } + + int skyLightCount = 16; + int blockLightCount = sectionYCount; + if (lightArrays.size() == 16 + 0 + 2) { + blockLightCount = 0; + } else if (lightArrays.size() == 16 + sectionYCount + 2) { + } else if (lightArrays.size() == sectionYCount + sectionYCount + 2) { + skyLightCount = sectionYCount; + } + skyLightCount += 2; // Chunk below 0 and above 255 + + final BitSet skyLightMask = new BitSet(); + final BitSet blockLightMask = new BitSet(); + skyLightMask.set(0, skyLightCount); + blockLightMask.set(0, blockLightCount); + + wrapper.write(Type.LONG_ARRAY_PRIMITIVE, skyLightMask.toLongArray()); + wrapper.write(Type.LONG_ARRAY_PRIMITIVE, blockLightMask.toLongArray()); + wrapper.write(Type.LONG_ARRAY_PRIMITIVE, new long[emptySkyLightMask]); + wrapper.write(Type.LONG_ARRAY_PRIMITIVE, new long[emptyBlockLightMask]); + + wrapper.write(Type.VAR_INT, skyLightCount); + for (int i = 0; i < skyLightCount; i++) { + wrapper.write(Type.BYTE_ARRAY_PRIMITIVE, lightArrays.remove(0)); + } + wrapper.write(Type.VAR_INT, blockLightCount); + for (int i = 0; i < blockLightCount; i++) { + wrapper.write(Type.BYTE_ARRAY_PRIMITIVE, lightArrays.remove(0)); + } + }); + } + }; + + return new PacketRemapper() { + @Override + public void registerMap() { + handler(wrapper -> { + if (VersionEnum.fromUserConnection(wrapper.user()).isOlderThanOrEqualTo(VersionEnum.c0_28toc0_30)) { + classicLightHandler.remap(wrapper); + } else { + parentRemapper.remap(wrapper); + } + }); + } + }; + } + + private static void changeDimensionTagHeight(final UserConnection user, final CompoundTag tag) { + tag.put("height", new IntTag(Via.getManager().getProviders().get(ClassicWorldHeightProvider.class).getMaxChunkSectionCount(user) << 4)); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinAbstractFenceConnectionHandler.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinAbstractFenceConnectionHandler.java new file mode 100644 index 0000000..bfbfb27 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinAbstractFenceConnectionHandler.java @@ -0,0 +1,34 @@ +package net.raphimc.viaproxy.injection.mixins; + +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.minecraft.BlockFace; +import com.viaversion.viaversion.api.minecraft.Position; +import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.blockconnections.AbstractFenceConnectionHandler; +import net.raphimc.vialegacy.util.VersionEnum; +import org.spongepowered.asm.mixin.*; + +@Mixin(value = AbstractFenceConnectionHandler.class, remap = false) +public abstract class MixinAbstractFenceConnectionHandler { + + @Shadow + protected abstract boolean connects(BlockFace side, int blockState, boolean pre1_12); + + @Shadow + public abstract int getBlockData(UserConnection user, Position position); + + /** + * @author RK_01 + * @reason Fixes version comparisons + */ + @Overwrite + public byte getStates(UserConnection user, Position position, int blockState) { + byte states = 0; + boolean pre1_12 = VersionEnum.fromUserConnection(user).isOlderThan(VersionEnum.r1_12); + if (connects(BlockFace.EAST, getBlockData(user, position.getRelative(BlockFace.EAST)), pre1_12)) states |= 1; + if (connects(BlockFace.NORTH, getBlockData(user, position.getRelative(BlockFace.NORTH)), pre1_12)) states |= 2; + if (connects(BlockFace.SOUTH, getBlockData(user, position.getRelative(BlockFace.SOUTH)), pre1_12)) states |= 4; + if (connects(BlockFace.WEST, getBlockData(user, position.getRelative(BlockFace.WEST)), pre1_12)) states |= 8; + return states; + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinChatItemRewriter.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinChatItemRewriter.java new file mode 100644 index 0000000..77ba7cb --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinChatItemRewriter.java @@ -0,0 +1,30 @@ +package net.raphimc.viaproxy.injection.mixins; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.minecraft.nbt.BinaryTagIO; +import com.viaversion.viaversion.libs.opennbt.tag.builtin.CompoundTag; +import com.viaversion.viaversion.protocols.protocol1_12to1_11_1.ChatItemRewriter; +import net.raphimc.vialegacy.util.ViaStringTagReader1_11_2; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Mixin(value = ChatItemRewriter.class, remap = false) +public abstract class MixinChatItemRewriter { + + @Redirect(method = "toClient", at = @At(value = "INVOKE", target = "Ljava/util/regex/Pattern;matcher(Ljava/lang/CharSequence;)Ljava/util/regex/Matcher;")) + private static Matcher rewriteShowItem(Pattern pattern, CharSequence input) { + try { + final CompoundTag tag = ViaStringTagReader1_11_2.getTagFromJson(input.toString()); + input = BinaryTagIO.writeString(tag); + } catch (Throwable e) { + Via.getPlatform().getLogger().log(Level.WARNING, "Error converting 1.11.2 nbt to 1.12.2 nbt: '" + input + "'", e); + } + return Pattern.compile("$^").matcher(input); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinChunk1_8Type.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinChunk1_8Type.java new file mode 100644 index 0000000..c04a51c --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinChunk1_8Type.java @@ -0,0 +1,37 @@ +package net.raphimc.viaproxy.injection.mixins; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.minecraft.chunks.*; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.types.Chunk1_8Type; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.ArrayList; +import java.util.logging.Level; + +@Mixin(value = Chunk1_8Type.class, remap = false) +public abstract class MixinChunk1_8Type { + + @Shadow + public static Chunk deserialize(int chunkX, int chunkZ, boolean fullChunk, boolean skyLight, int bitmask, byte[] data) { + return null; + } + + @Redirect(method = "read(Lio/netty/buffer/ByteBuf;Lcom/viaversion/viaversion/protocols/protocol1_9_3to1_9_1_2/storage/ClientWorld;)Lcom/viaversion/viaversion/api/minecraft/chunks/Chunk;", at = @At(value = "INVOKE", target = "Lcom/viaversion/viaversion/protocols/protocol1_9to1_8/types/Chunk1_8Type;deserialize(IIZZI[B)Lcom/viaversion/viaversion/api/minecraft/chunks/Chunk;")) + private Chunk fixAegis(int chunkX, int chunkZ, boolean fullChunk, boolean skyLight, int bitmask, byte[] data) { + try { + return deserialize(chunkX, chunkZ, fullChunk, skyLight, bitmask, data); + } catch (Throwable e) { + Via.getPlatform().getLogger().log(Level.WARNING, "The server sent an invalid chunk data packet, returning an empty chunk", e); + final ChunkSection[] airSections = new ChunkSection[16]; + for (int i = 0; i < airSections.length; i++) { + airSections[i] = new ChunkSectionImpl(true); + airSections[i].palette(PaletteType.BLOCKS).addId(0); + } + return new BaseChunk(chunkX, chunkZ, fullChunk, false, 65535, airSections, new int[256], new ArrayList<>()); + } + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinCommandBlockProvider.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinCommandBlockProvider.java new file mode 100644 index 0000000..96c2598 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinCommandBlockProvider.java @@ -0,0 +1,16 @@ +package net.raphimc.viaproxy.injection.mixins; + +import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.CommandBlockProvider; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; + +@Mixin(value = CommandBlockProvider.class, remap = false) +public abstract class MixinCommandBlockProvider { + + @ModifyConstant(method = "sendPermission", constant = @Constant(intValue = 26)) + private int modifyOpLevel() { + return 28; // Op Level 4 + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinCommonBoss.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinCommonBoss.java new file mode 100644 index 0000000..281d737 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinCommonBoss.java @@ -0,0 +1,15 @@ +package net.raphimc.viaproxy.injection.mixins; + +import com.viaversion.viaversion.legacy.bossbar.CommonBoss; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(value = CommonBoss.class, remap = false) +public abstract class MixinCommonBoss { + + @Redirect(method = {"", "setHealth"}, at = @At(value = "INVOKE", target = "Lcom/google/common/base/Preconditions;checkArgument(ZLjava/lang/Object;)V")) + private void removeBoundChecks(boolean expression, Object errorMessage) { + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinEntityPackets1_17.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinEntityPackets1_17.java new file mode 100644 index 0000000..993f99f --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinEntityPackets1_17.java @@ -0,0 +1,25 @@ +package net.raphimc.viaproxy.injection.mixins; + +import com.viaversion.viaversion.api.protocol.Protocol; +import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType; +import com.viaversion.viaversion.api.protocol.remapper.PacketRemapper; +import com.viaversion.viaversion.protocols.protocol1_16_2to1_16_1.ClientboundPackets1_16_2; +import com.viaversion.viaversion.protocols.protocol1_17to1_16_4.Protocol1_17To1_16_4; +import com.viaversion.viaversion.protocols.protocol1_17to1_16_4.packets.EntityPackets; +import net.raphimc.viaproxy.injection.ClassicWorldHeightInjection; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(value = EntityPackets.class, remap = false) +public abstract class MixinEntityPackets1_17 { + + @Redirect(method = "registerPackets", at = @At(value = "INVOKE", target = "Lcom/viaversion/viaversion/protocols/protocol1_17to1_16_4/Protocol1_17To1_16_4;registerClientbound(Lcom/viaversion/viaversion/api/protocol/packet/ClientboundPacketType;Lcom/viaversion/viaversion/api/protocol/remapper/PacketRemapper;)V")) + private void handleClassicWorldHeight(Protocol1_17To1_16_4 instance, ClientboundPacketType packetType, PacketRemapper packetRemapper) { + if (packetType == ClientboundPackets1_16_2.JOIN_GAME) packetRemapper = ClassicWorldHeightInjection.handleJoinGame(instance, packetRemapper); + if (packetType == ClientboundPackets1_16_2.RESPAWN) packetRemapper = ClassicWorldHeightInjection.handleRespawn(instance, packetRemapper); + + ((Protocol) instance).registerClientbound(packetType, packetRemapper); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinEntityPackets_6_1.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinEntityPackets_6_1.java new file mode 100644 index 0000000..32d3b25 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinEntityPackets_6_1.java @@ -0,0 +1,25 @@ +package net.raphimc.viaproxy.injection.mixins; + +import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; +import com.viaversion.viaversion.api.type.Type; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.Protocol1_9To1_8; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(targets = "com.viaversion.viaversion.protocols.protocol1_9to1_8.packets.EntityPackets$6$1", remap = false) +public abstract class MixinEntityPackets_6_1 { + + @SuppressWarnings({"UnresolvedMixinReference", "MixinAnnotationTarget"}) + @Inject(method = "transform(Lcom/viaversion/viaversion/api/protocol/packet/PacketWrapper;Ljava/lang/Short;)Ljava/lang/Integer;", at = @At(value = "INVOKE", target = "Lcom/viaversion/viaversion/api/data/entity/EntityTracker;clientEntityId()I"), cancellable = true) + private void fixOutOfBoundsSlot(PacketWrapper wrapper, Short slot, CallbackInfoReturnable cir) throws Exception { + final int entityId = wrapper.get(Type.VAR_INT, 0); + final int clientPlayerId = wrapper.user().getEntityTracker(Protocol1_9To1_8.class).clientEntityId(); + if (slot < 0 || slot > 4 || (entityId == clientPlayerId && slot > 3)) { + wrapper.cancel(); + cir.setReturnValue(0); + } + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinEntityTracker1_9.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinEntityTracker1_9.java new file mode 100644 index 0000000..25e0d7b --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinEntityTracker1_9.java @@ -0,0 +1,30 @@ +package net.raphimc.viaproxy.injection.mixins; + +import com.viaversion.viaversion.api.minecraft.metadata.Metadata; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.storage.EntityTracker1_9; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.*; + +@Mixin(value = EntityTracker1_9.class, remap = false) +public abstract class MixinEntityTracker1_9 { + + @Redirect(method = "handleMetadata", at = @At(value = "INVOKE", target = "Ljava/lang/Math;min(FF)F"), slice = @Slice(from = @At(value = "INVOKE", target = "Lcom/viaversion/viaversion/api/configuration/ViaVersionConfig;isBossbarAntiflicker()Z"))) + private float removeMin(float a, float b) { + return a; + } + + @Redirect(method = "handleMetadata", at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F"), slice = @Slice(from = @At(value = "INVOKE", target = "Lcom/viaversion/viaversion/api/configuration/ViaVersionConfig;isBossbarAntiflicker()Z"))) + private float removeMax(float a, float b) { + return b; + } + + @Redirect(method = "handleMetadata", at = @At(value = "INVOKE", target = "Lcom/viaversion/viaversion/api/minecraft/metadata/Metadata;getValue()Ljava/lang/Object;"), slice = @Slice(from = @At(value = "INVOKE", target = "Lcom/viaversion/viaversion/api/configuration/ViaVersionConfig;isBossbarAntiflicker()Z"))) + private Object remapNaNToZero(Metadata instance) { + if (instance.getValue() instanceof Float && ((Float) instance.getValue()).isNaN()) { + return 0F; + } + + return instance.getValue(); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinGlassConnectionHandler.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinGlassConnectionHandler.java new file mode 100644 index 0000000..7a9ec19 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinGlassConnectionHandler.java @@ -0,0 +1,32 @@ +package net.raphimc.viaproxy.injection.mixins; + +import com.viaversion.viaversion.api.connection.ProtocolInfo; +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.minecraft.Position; +import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.blockconnections.AbstractFenceConnectionHandler; +import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.blockconnections.GlassConnectionHandler; +import net.raphimc.vialegacy.util.VersionEnum; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +@Mixin(value = GlassConnectionHandler.class, remap = false) +public abstract class MixinGlassConnectionHandler extends AbstractFenceConnectionHandler { + + protected MixinGlassConnectionHandler(String blockConnections) { + super(blockConnections); + } + + /** + * @author RK_01 + * @reason Fixes version comparisons + */ + @Overwrite + public byte getStates(UserConnection user, Position position, int blockState) { + byte states = super.getStates(user, position, blockState); + if (states != 0) return states; + + ProtocolInfo protocolInfo = user.getProtocolInfo(); + return VersionEnum.fromUserConnection(user).isOlderThanOrEqualTo(VersionEnum.r1_8) && protocolInfo.getServerProtocolVersion() != -1 ? 0xF : states; + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinInventoryPackets_3_1.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinInventoryPackets_3_1.java new file mode 100644 index 0000000..9a15189 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinInventoryPackets_3_1.java @@ -0,0 +1,18 @@ +package net.raphimc.viaproxy.injection.mixins; + +import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(targets = "com.viaversion.viaversion.protocols.protocol1_14to1_13_2.packets.InventoryPackets$3$1", remap = false) +public abstract class MixinInventoryPackets_3_1 { + + @SuppressWarnings({"UnresolvedMixinReference", "MixinAnnotationTarget"}) + @Inject(method = "handle", at = @At(value = "FIELD", target = "Lcom/viaversion/viaversion/api/type/Type;BOOLEAN:Lcom/viaversion/viaversion/api/type/types/BooleanType;", ordinal = 2, shift = At.Shift.BEFORE)) + private void removeExtraData(PacketWrapper wrapper, CallbackInfo ci) { + wrapper.clearInputBuffer(); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinMetadataRewriter1_9To1_8.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinMetadataRewriter1_9To1_8.java new file mode 100644 index 0000000..d1a95d9 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinMetadataRewriter1_9To1_8.java @@ -0,0 +1,25 @@ +package net.raphimc.viaproxy.injection.mixins; + +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.minecraft.entities.EntityType; +import com.viaversion.viaversion.api.minecraft.metadata.Metadata; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.Protocol1_9To1_8; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.metadata.MetadataRewriter1_9To1_8; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; + +@Mixin(value = MetadataRewriter1_9To1_8.class, remap = false) +public abstract class MixinMetadataRewriter1_9To1_8 { + + @Inject(method = "handleMetadata", at = @At(value = "FIELD", target = "Lcom/viaversion/viaversion/protocols/protocol1_9to1_8/metadata/MetaIndex;PLAYER_HAND:Lcom/viaversion/viaversion/protocols/protocol1_9to1_8/metadata/MetaIndex;", ordinal = 0, shift = At.Shift.BEFORE), cancellable = true) + private void preventMetadataForClientPlayer(int entityId, EntityType type, Metadata metadata, List metadatas, UserConnection connection, CallbackInfo ci) { + if (connection.getEntityTracker(Protocol1_9To1_8.class).clientEntityId() == entityId) { + ci.cancel(); + } + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinProtocolVersion.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinProtocolVersion.java new file mode 100644 index 0000000..a5a7c60 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinProtocolVersion.java @@ -0,0 +1,70 @@ +package net.raphimc.viaproxy.injection.mixins; + +import com.google.common.collect.ImmutableSet; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import com.viaversion.viaversion.api.protocol.version.VersionRange; +import com.viaversion.viaversion.util.Pair; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.*; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.*; + +@Mixin(value = ProtocolVersion.class, remap = false) +public abstract class MixinProtocolVersion { + + @Unique + private static Set skips; + + @Unique + private static Map> remaps; + + @Inject(method = "", at = @At("HEAD")) + private static void initMaps(CallbackInfo ci) { + skips = ImmutableSet.of("1.4.6/7", "1.5.1", "1.5.2", "1.6.1", "1.6.2", "1.6.3", "1.6.4"); + remaps = new HashMap<>(); + remaps.put("1.7-1.7.5", new Pair<>("1.7.2-1.7.5", new VersionRange("1.7", 2, 5))); + remaps.put("1.9.3/4", new Pair<>("1.9.3-1.9.4", null)); + remaps.put("1.11.1/2", new Pair<>("1.11.1-1.11.2", null)); + remaps.put("1.16.4/5", new Pair<>("1.16.4-1.16.5", null)); + remaps.put("1.18/1.18.1", new Pair<>("1.18-1.18.1", null)); + remaps.put("1.19.1/2", new Pair<>("1.19.1-1.19.2", null)); + } + + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lcom/viaversion/viaversion/api/protocol/version/ProtocolVersion;register(ILjava/lang/String;)Lcom/viaversion/viaversion/api/protocol/version/ProtocolVersion;")) + private static ProtocolVersion unregisterAndRenameVersions(int version, String name) { + if (skips.contains(name)) return null; + final Pair remapEntry = remaps.get(name); + if (remapEntry != null) { + if (remapEntry.key() != null) name = remapEntry.key(); + } + + return ProtocolVersion.register(version, name); + } + + @SuppressWarnings({"UnresolvedMixinReference", "MixinAnnotationTarget", "InvalidInjectorMethodSignature"}) // Optional injection + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lcom/viaversion/viaversion/api/protocol/version/ProtocolVersion;register(IILjava/lang/String;)Lcom/viaversion/viaversion/api/protocol/version/ProtocolVersion;"), require = 0) + private static ProtocolVersion unregisterAndRenameVersions(int version, int snapshotVersion, String name) { + if (skips.contains(name)) return null; + final Pair remapEntry = remaps.get(name); + if (remapEntry != null) { + if (remapEntry.key() != null) name = remapEntry.key(); + } + + return ProtocolVersion.register(version, snapshotVersion, name); + } + + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lcom/viaversion/viaversion/api/protocol/version/ProtocolVersion;register(ILjava/lang/String;Lcom/viaversion/viaversion/api/protocol/version/VersionRange;)Lcom/viaversion/viaversion/api/protocol/version/ProtocolVersion;")) + private static ProtocolVersion unregisterAndRenameVersions(int version, String name, VersionRange versionRange) { + if (skips.contains(name)) return null; + final Pair remapEntry = remaps.get(name); + if (remapEntry != null) { + if (remapEntry.key() != null) name = remapEntry.key(); + if (remapEntry.value() != null) versionRange = remapEntry.value(); + } + + return ProtocolVersion.register(version, name, versionRange); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinWorldPackets1_13.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinWorldPackets1_13.java new file mode 100644 index 0000000..d793bd2 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinWorldPackets1_13.java @@ -0,0 +1,17 @@ +package net.raphimc.viaproxy.injection.mixins; + +import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.packets.WorldPackets; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(value = WorldPackets.class, remap = false) +public abstract class MixinWorldPackets1_13 { + + @Inject(method = "toNewId", at = @At(value = "RETURN", ordinal = 2), cancellable = true) + private static void returnAirDefault(int oldId, CallbackInfoReturnable cir) { + cir.setReturnValue(0); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinWorldPackets1_17.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinWorldPackets1_17.java new file mode 100644 index 0000000..0327737 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinWorldPackets1_17.java @@ -0,0 +1,25 @@ +package net.raphimc.viaproxy.injection.mixins; + +import com.viaversion.viaversion.api.protocol.Protocol; +import com.viaversion.viaversion.api.protocol.packet.ClientboundPacketType; +import com.viaversion.viaversion.api.protocol.remapper.PacketRemapper; +import com.viaversion.viaversion.protocols.protocol1_16_2to1_16_1.ClientboundPackets1_16_2; +import com.viaversion.viaversion.protocols.protocol1_17to1_16_4.Protocol1_17To1_16_4; +import com.viaversion.viaversion.protocols.protocol1_17to1_16_4.packets.WorldPackets; +import net.raphimc.viaproxy.injection.ClassicWorldHeightInjection; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(value = WorldPackets.class, remap = false) +public abstract class MixinWorldPackets1_17 { + + @Redirect(method = "register", at = @At(value = "INVOKE", target = "Lcom/viaversion/viaversion/protocols/protocol1_17to1_16_4/Protocol1_17To1_16_4;registerClientbound(Lcom/viaversion/viaversion/api/protocol/packet/ClientboundPacketType;Lcom/viaversion/viaversion/api/protocol/remapper/PacketRemapper;)V")) + private static void handleClassicWorldHeight(Protocol1_17To1_16_4 instance, ClientboundPacketType packetType, PacketRemapper packetRemapper) { + if (packetType == ClientboundPackets1_16_2.CHUNK_DATA) packetRemapper = ClassicWorldHeightInjection.handleChunkData(instance, packetRemapper); + if (packetType == ClientboundPackets1_16_2.UPDATE_LIGHT) packetRemapper = ClassicWorldHeightInjection.handleUpdateLight(instance, packetRemapper); + + ((Protocol) instance).registerClientbound(packetType, packetRemapper); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinWorldPackets_2.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinWorldPackets_2.java new file mode 100644 index 0000000..9a8e243 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinWorldPackets_2.java @@ -0,0 +1,15 @@ +package net.raphimc.viaproxy.injection.mixins; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; + +@Mixin(targets = "com.viaversion.viaversion.protocols.protocol1_16_2to1_16_1.packets.WorldPackets$2", remap = false) +public abstract class MixinWorldPackets_2 { + + @ModifyConstant(method = "lambda$registerMap$0", constant = @Constant(intValue = 16)) + private static int modifySectionCountToSupportClassicWorldHeight() { + return 64; + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/transformer/ConnectionDataTransformer.java b/src/main/java/net/raphimc/viaproxy/injection/transformer/ConnectionDataTransformer.java new file mode 100644 index 0000000..6eb15af --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/transformer/ConnectionDataTransformer.java @@ -0,0 +1,52 @@ +package net.raphimc.viaproxy.injection.transformer; + +import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.blockconnections.ConnectionData; +import net.lenni0451.classtransform.annotations.CTransformer; +import net.lenni0451.classtransform.annotations.injection.CASM; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.*; + +@CTransformer(ConnectionData.class) +public abstract class ConnectionDataTransformer { + + @CASM("update") + public static void preventBlockChangeSpam1(MethodNode method) { + LabelNode continueLabel = new LabelNode(); + InsnList checkCode = new InsnList(); + checkCode.add(new VarInsnNode(Opcodes.ILOAD, 7)); + checkCode.add(new VarInsnNode(Opcodes.ILOAD, 9)); + checkCode.add(new JumpInsnNode(Opcodes.IF_ICMPEQ, continueLabel)); + + for (AbstractInsnNode insn : method.instructions.toArray()) { + if (checkCode != null && insn.getOpcode() == Opcodes.ISTORE) { + VarInsnNode varInsn = (VarInsnNode) insn; + if (varInsn.var == 9) { + method.instructions.insert(insn, checkCode); + checkCode = null; + } + } else if (continueLabel != null && insn.getOpcode() == Opcodes.IINC) { + method.instructions.insertBefore(insn, continueLabel); + continueLabel = null; + } + } + } + + @CASM("updateBlock") + public static void preventBlockChangeSpam2(MethodNode method) { + LabelNode addLabel = new LabelNode(); + InsnList checkCode = new InsnList(); + checkCode.add(new VarInsnNode(Opcodes.ILOAD, 3)); + checkCode.add(new VarInsnNode(Opcodes.ILOAD, 5)); + checkCode.add(new JumpInsnNode(Opcodes.IF_ICMPNE, addLabel)); + checkCode.add(new InsnNode(Opcodes.RETURN)); + checkCode.add(addLabel); + + for (AbstractInsnNode insn : method.instructions.toArray()) { + if (insn.getOpcode() == Opcodes.INVOKEINTERFACE) { + method.instructions.insertBefore(insn, checkCode); + break; + } + } + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/injection/transformer/MovementTrackerTransformer.java b/src/main/java/net/raphimc/viaproxy/injection/transformer/MovementTrackerTransformer.java new file mode 100644 index 0000000..2b119ae --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/injection/transformer/MovementTrackerTransformer.java @@ -0,0 +1,13 @@ +package net.raphimc.viaproxy.injection.transformer; + +import com.viaversion.viaversion.protocols.protocol1_9to1_8.storage.MovementTracker; +import net.lenni0451.classtransform.annotations.CShadow; +import net.lenni0451.classtransform.annotations.CTransformer; + +@CTransformer(MovementTracker.class) +public abstract class MovementTrackerTransformer { + + @CShadow + private boolean ground = false; + +} diff --git a/src/main/java/net/raphimc/viaproxy/plugins/PluginManager.java b/src/main/java/net/raphimc/viaproxy/plugins/PluginManager.java new file mode 100644 index 0000000..c4f9fc5 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/plugins/PluginManager.java @@ -0,0 +1,64 @@ +package net.raphimc.viaproxy.plugins; + +import net.lenni0451.lambdaevents.LambdaManager; +import net.lenni0451.lambdaevents.generator.LambdaMetaFactoryGenerator; +import org.yaml.snakeyaml.Yaml; + +import java.io.File; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.*; + +public class PluginManager { + + public static final LambdaManager EVENT_MANAGER = LambdaManager.threadSafe(new LambdaMetaFactoryGenerator()); + + private static final Yaml YAML = new Yaml(); + private static final File PLUGINS_DIR = new File("plugins"); + private static final List PLUGINS = new ArrayList<>(); + + public static void loadPlugins() { + if (!PLUGINS_DIR.exists() || !PLUGINS_DIR.isDirectory()) return; + + File[] files = PLUGINS_DIR.listFiles(); + if (files == null) return; + + for (File file : files) { + if (!file.getName().toLowerCase().endsWith(".jar")) continue; + try { + loadAndScanJar(file); + } catch (Throwable t) { + new Exception("Unable to load plugin '" + file.getName() + "'", t).printStackTrace(); + } + } + } + + private static void loadAndScanJar(final File file) throws Throwable { + URLClassLoader loader = new URLClassLoader(new URL[]{new URL("jar:file:" + file.getAbsolutePath() + "!/")}, PluginManager.class.getClassLoader()); + InputStream viaproxyYml = loader.getResourceAsStream("viaproxy.yml"); + if (viaproxyYml == null) + throw new IllegalStateException("Plugin '" + file.getName() + "' does not have a viaproxy.yml"); + Map yaml = YAML.load(viaproxyYml); + if (!yaml.containsKey("name")) + throw new IllegalStateException("Plugin '" + file.getName() + "' does not have a name attribute in the viaproxy.yml"); + if (!yaml.containsKey("author")) + throw new IllegalStateException("Plugin '" + file.getName() + "' does not have a author attribute in the viaproxy.yml"); + if (!yaml.containsKey("version")) + throw new IllegalStateException("Plugin '" + file.getName() + "' does not have a version attribute in the viaproxy.yml"); + if (!yaml.containsKey("main")) + throw new IllegalStateException("Plugin '" + file.getName() + "' does not have a main attribute in the viaproxy.yml"); + + String main = (String) yaml.get("main"); + + Class mainClass = loader.loadClass(main); + if (!ViaProxyPlugin.class.isAssignableFrom(mainClass)) + throw new IllegalStateException("Class '" + mainClass.getName() + "' from '" + file.getName() + "' does not extend ViaProxyPlugin"); + Object instance = mainClass.newInstance(); + ViaProxyPlugin plugin = (ViaProxyPlugin) instance; + PLUGINS.add(plugin); + + plugin.onEnable(); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/plugins/ViaProxyPlugin.java b/src/main/java/net/raphimc/viaproxy/plugins/ViaProxyPlugin.java new file mode 100644 index 0000000..7dbab1a --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/plugins/ViaProxyPlugin.java @@ -0,0 +1,7 @@ +package net.raphimc.viaproxy.plugins; + +public abstract class ViaProxyPlugin { + + public abstract void onEnable(); + +} diff --git a/src/main/java/net/raphimc/viaproxy/plugins/events/Client2ProxyChannelInitializeEvent.java b/src/main/java/net/raphimc/viaproxy/plugins/events/Client2ProxyChannelInitializeEvent.java new file mode 100644 index 0000000..893dc4f --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/plugins/events/Client2ProxyChannelInitializeEvent.java @@ -0,0 +1,39 @@ +package net.raphimc.viaproxy.plugins.events; + +import io.netty.channel.socket.SocketChannel; +import net.raphimc.viaproxy.plugins.events.types.ICancellable; +import net.raphimc.viaproxy.plugins.events.types.ITyped; + +public class Client2ProxyChannelInitializeEvent implements ICancellable, ITyped { + + private final Type type; + private final SocketChannel channel; + + private boolean cancelled; + + public Client2ProxyChannelInitializeEvent(final Type type, final SocketChannel channel) { + this.type = type; + this.channel = channel; + } + + public SocketChannel getChannel() { + return this.channel; + } + + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public Type getType() { + return this.type; + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/plugins/events/ConsoleCommandEvent.java b/src/main/java/net/raphimc/viaproxy/plugins/events/ConsoleCommandEvent.java new file mode 100644 index 0000000..415f75b --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/plugins/events/ConsoleCommandEvent.java @@ -0,0 +1,34 @@ +package net.raphimc.viaproxy.plugins.events; + +import net.raphimc.viaproxy.plugins.events.types.ICancellable; + +public class ConsoleCommandEvent implements ICancellable { + + private final String command; + private final String[] args; + private boolean cancelled; + + public ConsoleCommandEvent(final String command, final String[] args) { + this.command = command; + this.args = args; + } + + public String getCommand() { + return this.command; + } + + public String[] getArgs() { + return this.args; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/plugins/events/PreConnectEvent.java b/src/main/java/net/raphimc/viaproxy/plugins/events/PreConnectEvent.java new file mode 100644 index 0000000..776850f --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/plugins/events/PreConnectEvent.java @@ -0,0 +1,59 @@ +package net.raphimc.viaproxy.plugins.events; + +import io.netty.channel.Channel; +import net.raphimc.netminecraft.util.ServerAddress; +import net.raphimc.vialegacy.util.VersionEnum; +import net.raphimc.viaproxy.plugins.events.types.ICancellable; + +public class PreConnectEvent implements ICancellable { + + private final ServerAddress serverAddress; + private final VersionEnum serverVersion; + private final VersionEnum clientVersion; + private final Channel clientChannel; + + private boolean cancelled; + private String cancelMessage = "§cCould not connect to the backend server! (Server is blacklisted)"; + + public PreConnectEvent(final ServerAddress serverAddress, final VersionEnum serverVersion, final VersionEnum clientVersion, final Channel clientChannel) { + this.serverAddress = serverAddress; + this.serverVersion = serverVersion; + this.clientVersion = clientVersion; + this.clientChannel = clientChannel; + } + + public ServerAddress getServerAddress() { + return this.serverAddress; + } + + public VersionEnum getServerVersion() { + return this.serverVersion; + } + + public VersionEnum getClientVersion() { + return this.clientVersion; + } + + public Channel getClientChannel() { + return this.clientChannel; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + public String getCancelMessage() { + return this.cancelMessage; + } + + public void setCancelMessage(final String cancelMessage) { + this.cancelMessage = cancelMessage; + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/plugins/events/Proxy2ServerChannelInitializeEvent.java b/src/main/java/net/raphimc/viaproxy/plugins/events/Proxy2ServerChannelInitializeEvent.java new file mode 100644 index 0000000..ff61b49 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/plugins/events/Proxy2ServerChannelInitializeEvent.java @@ -0,0 +1,39 @@ +package net.raphimc.viaproxy.plugins.events; + +import io.netty.channel.socket.SocketChannel; +import net.raphimc.viaproxy.plugins.events.types.ICancellable; +import net.raphimc.viaproxy.plugins.events.types.ITyped; + +public class Proxy2ServerChannelInitializeEvent implements ICancellable, ITyped { + + private final Type type; + private final SocketChannel channel; + + private boolean cancelled; + + public Proxy2ServerChannelInitializeEvent(final Type type, final SocketChannel channel) { + this.type = type; + this.channel = channel; + } + + public SocketChannel getChannel() { + return this.channel; + } + + + @Override + public boolean isCancelled() { + return this.cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @Override + public Type getType() { + return this.type; + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/plugins/events/types/ICancellable.java b/src/main/java/net/raphimc/viaproxy/plugins/events/types/ICancellable.java new file mode 100644 index 0000000..5458525 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/plugins/events/types/ICancellable.java @@ -0,0 +1,9 @@ +package net.raphimc.viaproxy.plugins.events.types; + +public interface ICancellable { + + void setCancelled(final boolean cancelled); + + boolean isCancelled(); + +} diff --git a/src/main/java/net/raphimc/viaproxy/plugins/events/types/ITyped.java b/src/main/java/net/raphimc/viaproxy/plugins/events/types/ITyped.java new file mode 100644 index 0000000..41cfd9a --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/plugins/events/types/ITyped.java @@ -0,0 +1,12 @@ +package net.raphimc.viaproxy.plugins.events.types; + +public interface ITyped { + + Type getType(); + + + enum Type { + PRE, POST + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/protocolhack/ProtocolHack.java b/src/main/java/net/raphimc/viaproxy/protocolhack/ProtocolHack.java new file mode 100644 index 0000000..21fdf42 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/protocolhack/ProtocolHack.java @@ -0,0 +1,14 @@ +package net.raphimc.viaproxy.protocolhack; + +import net.raphimc.viaprotocolhack.ViaProtocolHack; +import net.raphimc.viaprotocolhack.impl.platform.*; +import net.raphimc.viaproxy.protocolhack.impl.ViaProxyVPLoader; +import net.raphimc.viaproxy.protocolhack.impl.ViaProxyViaVersionPlatformImpl; + +public class ProtocolHack { + + public static void init() { + ViaProtocolHack.init(new ViaProxyViaVersionPlatformImpl(), new ViaProxyVPLoader(), null, null, ViaBackwardsPlatformImpl::new, ViaRewindPlatformImpl::new, ViaLegacyPlatformImpl::new); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/protocolhack/impl/ViaProxyVPLoader.java b/src/main/java/net/raphimc/viaproxy/protocolhack/impl/ViaProxyVPLoader.java new file mode 100644 index 0000000..1113eed --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/protocolhack/impl/ViaProxyVPLoader.java @@ -0,0 +1,32 @@ +package net.raphimc.viaproxy.protocolhack.impl; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.protocol.version.VersionProvider; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.CompressionProvider; +import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.HandItemProvider; +import net.raphimc.vialegacy.protocols.classic.protocola1_0_15toc0_28_30.providers.*; +import net.raphimc.vialegacy.protocols.release.protocol1_3_1_2to1_2_4_5.providers.OldAuthProvider; +import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.providers.EncryptionProvider; +import net.raphimc.vialegacy.protocols.release.protocol1_8to1_7_6_10.providers.GameProfileFetcher; +import net.raphimc.viaprotocolhack.impl.viaversion.VPLoader; +import net.raphimc.viaproxy.protocolhack.providers.*; + +public class ViaProxyVPLoader extends VPLoader { + + @Override + public void load() { + super.load(); + + Via.getManager().getProviders().use(CompressionProvider.class, new ViaProxyCompressionProvider()); + Via.getManager().getProviders().use(HandItemProvider.class, new ViaProxyHandItemProvider()); + Via.getManager().getProviders().use(VersionProvider.class, new ViaProxyVersionProvider()); + + Via.getManager().getProviders().use(GameProfileFetcher.class, new ViaProxyGameProfileFetcher()); + Via.getManager().getProviders().use(EncryptionProvider.class, new ViaProxyEncryptionProvider()); + Via.getManager().getProviders().use(OldAuthProvider.class, new ViaProxyOldAuthProvider()); + Via.getManager().getProviders().use(ClassicWorldHeightProvider.class, new ViaProxyClassicWorldHeightProvider()); + Via.getManager().getProviders().use(ClassicCustomCommandProvider.class, new ViaProxyClassicCustomCommandProvider()); + Via.getManager().getProviders().use(ClassicMPPassProvider.class, new ViaProxyClassicMPPassProvider()); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/protocolhack/impl/ViaProxyViaDecodeHandler.java b/src/main/java/net/raphimc/viaproxy/protocolhack/impl/ViaProxyViaDecodeHandler.java new file mode 100644 index 0000000..b36c23e --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/protocolhack/impl/ViaProxyViaDecodeHandler.java @@ -0,0 +1,30 @@ +package net.raphimc.viaproxy.protocolhack.impl; + +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.exception.CancelCodecException; +import com.viaversion.viaversion.util.PipelineUtil; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import net.raphimc.viaprotocolhack.netty.ViaDecodeHandler; +import net.raphimc.viaproxy.util.logging.Logger; + +import java.util.List; + +public class ViaProxyViaDecodeHandler extends ViaDecodeHandler { + + public ViaProxyViaDecodeHandler(final UserConnection info) { + super(info); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf bytebuf, List out) throws Exception { + try { + super.decode(ctx, bytebuf, out); + } catch (Throwable e) { + if (PipelineUtil.containsCause(e, CancelCodecException.class)) throw e; + Logger.LOGGER.error("ProtocolHack Packet Error occurred", e); + Logger.u_err("ProtocolHack Error", this.user, "Caught unhandled exception: " + e.getClass().getSimpleName()); + } + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/protocolhack/impl/ViaProxyViaVersionPlatformImpl.java b/src/main/java/net/raphimc/viaproxy/protocolhack/impl/ViaProxyViaVersionPlatformImpl.java new file mode 100644 index 0000000..4b8e7f0 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/protocolhack/impl/ViaProxyViaVersionPlatformImpl.java @@ -0,0 +1,19 @@ +package net.raphimc.viaproxy.protocolhack.impl; + +import net.raphimc.viaprotocolhack.impl.platform.ViaVersionPlatformImpl; +import net.raphimc.viaproxy.cli.ConsoleFormatter; + +import java.util.UUID; + +public class ViaProxyViaVersionPlatformImpl extends ViaVersionPlatformImpl { + + public ViaProxyViaVersionPlatformImpl() { + super(null); + } + + @Override + public void sendMessage(UUID uuid, String msg) { + super.sendMessage(uuid, ConsoleFormatter.convert(msg)); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/protocolhack/providers/ViaProxyClassicCustomCommandProvider.java b/src/main/java/net/raphimc/viaproxy/protocolhack/providers/ViaProxyClassicCustomCommandProvider.java new file mode 100644 index 0000000..9342e71 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/protocolhack/providers/ViaProxyClassicCustomCommandProvider.java @@ -0,0 +1,41 @@ +package net.raphimc.viaproxy.protocolhack.providers; + +import com.viaversion.viaversion.api.connection.UserConnection; +import net.raphimc.vialegacy.ViaLegacy; +import net.raphimc.vialegacy.protocols.alpha.protocola1_0_17_1_0_17_4toa1_0_16_2.storage.TimeLockStorage; +import net.raphimc.vialegacy.protocols.classic.protocola1_0_15toc0_28_30.providers.ClassicCustomCommandProvider; + +import java.util.logging.Level; + +public class ViaProxyClassicCustomCommandProvider extends ClassicCustomCommandProvider { + + @Override + public boolean handleChatMessage(UserConnection user, String message) { + try { + if (message.startsWith("/")) { + message = message.substring(1); + final String[] args = message.split(" "); + if (args.length <= 0) return super.handleChatMessage(user, message); + if (args[0].equals("settime")) { + try { + if (args.length > 1) { + final long time = Long.parseLong(args[1]) % 24_000L; + user.get(TimeLockStorage.class).setTime(time); + this.sendFeedback(user, "§aTime has been set to §6" + time); + } else { + throw new RuntimeException("Invalid usage"); + } + } catch (Throwable ignored) { + this.sendFeedback(user, "§cUsage: /settime