Initial commit

This commit is contained in:
RaphiMC 2023-01-04 20:58:09 +01:00
parent d1acd4fb31
commit 886fbd9c5e
72 changed files with 3804 additions and 0 deletions

9
.gitattributes vendored Normal file
View File

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

6
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "daily"

16
.gitignore vendored Normal file
View File

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

25
README.md Normal file
View File

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

110
build.gradle Normal file
View File

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

9
gradle.properties Normal file
View File

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

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

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

240
gradlew vendored Normal file
View File

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

91
gradlew.bat vendored Normal file
View File

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

8
settings.gradle Normal file
View File

@ -0,0 +1,8 @@
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
rootProject.name = "ViaProxy"

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Void> help = parser.acceptsAll(asList("help", "h", "?"), "Get a list of all arguments").forHelp();
final OptionSpec<String> 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<Integer> bindPort = parser.acceptsAll(asList("bind_port", "bp"), "The port the proxy should bind to").withRequiredArg().ofType(Integer.class).defaultsTo(BIND_PORT);
final OptionSpec<Void> srvMode = parser.acceptsAll(asList("srv_mode", "srv", "s"), "Enable srv mode");
final OptionSpec<Void> iSrvMode = parser.acceptsAll(asList("internal_srv_mode", "isrv"), "Enable internal srv mode").availableUnless(srvMode);
final OptionSpec<Void> onlineMode = parser.acceptsAll(asList("online_mode", "om", "o"), "Enable online mode");
final OptionSpec<Integer> nettyThreads = parser.acceptsAll(asList("netty_threads", "t"), "The amount of netty threads to use").withRequiredArg().ofType(Integer.class).defaultsTo(NETTY_THREADS);
final OptionSpec<Integer> compressionThreshold = parser.acceptsAll(asList("compression_threshold", "ct", "c"), "The threshold for packet compression").withRequiredArg().ofType(Integer.class).defaultsTo(COMPRESSION_THRESHOLD);
final OptionSpec<String> connectAddress = parser.acceptsAll(asList("connect_address", "target_ip", "ca", "a"), "The address of the target server").withRequiredArg().ofType(String.class).required();
final OptionSpec<Integer> 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<VersionEnum> version = parser.acceptsAll(asList("version", "v"), "The version of the target server").withRequiredArg().withValuesConvertedBy(new VersionEnumConverter()).required();
final OptionSpec<Void> openAuthModAuth = parser.acceptsAll(asList("openauthmod_auth", "oam_auth"), "Enable OpenAuthMod authentication");
final OptionSpec<Void> localSocketAuth = parser.accepts("local_socket_auth", "Enable authentication over a local socket");
final OptionSpec<Void> 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);
}
}

View File

@ -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<VersionEnum> {
@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<VersionEnum> 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 + "]";
}
}

View File

@ -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).<CompoundTag>get("minecraft:dimension_type").<ListTag>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<byte[]> 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));
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = {"<init>", "setHealth"}, at = @At(value = "INVOKE", target = "Lcom/google/common/base/Preconditions;checkArgument(ZLjava/lang/Object;)V"))
private void removeBoundChecks(boolean expression, Object errorMessage) {
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Metadata> metadatas, UserConnection connection, CallbackInfo ci) {
if (connection.getEntityTracker(Protocol1_9To1_8.class).clientEntityId() == entityId) {
ci.cancel();
}
}
}

View File

@ -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<String> skips;
@Unique
private static Map<String, Pair<String, VersionRange>> remaps;
@Inject(method = "<clinit>", 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 = "<clinit>", 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<String, VersionRange> 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 = "<clinit>", 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<String, VersionRange> remapEntry = remaps.get(name);
if (remapEntry != null) {
if (remapEntry.key() != null) name = remapEntry.key();
}
return ProtocolVersion.register(version, snapshotVersion, name);
}
@Redirect(method = "<clinit>", 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<String, VersionRange> 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);
}
}

View File

@ -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<Integer> cir) {
cir.setReturnValue(0);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
package net.raphimc.viaproxy.plugins;
public abstract class ViaProxyPlugin {
public abstract void onEnable();
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,9 @@
package net.raphimc.viaproxy.plugins.events.types;
public interface ICancellable {
void setCancelled(final boolean cancelled);
boolean isCancelled();
}

View File

@ -0,0 +1,12 @@
package net.raphimc.viaproxy.plugins.events.types;
public interface ITyped {
Type getType();
enum Type {
PRE, POST
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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 <Time (Long)>");
}
return true;
}
}
} catch (Throwable e) {
ViaLegacy.getPlatform().getLogger().log(Level.WARNING, "Error handling custom classic command", e);
}
return super.handleChatMessage(user, message);
}
}

View File

@ -0,0 +1,46 @@
package net.raphimc.viaproxy.protocolhack.providers;
import com.google.common.hash.Hashing;
import com.google.common.io.Resources;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.connection.UserConnection;
import net.raphimc.vialegacy.protocols.classic.protocola1_0_15toc0_28_30.providers.ClassicMPPassProvider;
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.storage.HandshakeStorage;
import net.raphimc.viaproxy.cli.options.Options;
import net.raphimc.viaproxy.proxy.ProxyConnection;
import java.net.InetAddress;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
public class ViaProxyClassicMPPassProvider extends ClassicMPPassProvider {
@Override
public String getMpPass(UserConnection user) {
final String mppass = ProxyConnection.fromUserConnection(user).getClassicMpPass();
if (mppass != null && !mppass.isEmpty() && !mppass.equals("0")) {
return mppass;
} else if (Options.BETACRAFT_AUTH) {
final HandshakeStorage handshakeStorage = user.get(HandshakeStorage.class);
return getBetacraftMpPass(user, user.getProtocolInfo().getUsername(), handshakeStorage.getHostname(), handshakeStorage.getPort());
} else {
return super.getMpPass(user);
}
}
private static String getBetacraftMpPass(final UserConnection user, final String username, final String serverIp, final int port) {
try {
final String server = InetAddress.getByName(serverIp).getHostAddress() + ":" + port;
Via.getManager().getProviders().get(OldAuthProvider.class).sendAuthRequest(user, Hashing.sha1().hashBytes(server.getBytes()).toString());
final String mppass = Resources.toString(new URL("http://api.betacraft.uk/getmppass.jsp?user=" + username + "&server=" + server), StandardCharsets.UTF_8);
if (mppass.contains("FAILED") || mppass.contains("SERVER NOT FOUND")) return "0";
return mppass;
} catch (Throwable e) {
Via.getPlatform().getLogger().log(Level.WARNING, "An unknown error occurred while authenticating with BetaCraft", e);
}
return "0";
}
}

View File

@ -0,0 +1,18 @@
package net.raphimc.viaproxy.protocolhack.providers;
import com.viaversion.viaversion.api.connection.UserConnection;
import net.raphimc.vialegacy.protocols.classic.protocola1_0_15toc0_28_30.providers.ClassicWorldHeightProvider;
import net.raphimc.vialegacy.util.VersionEnum;
import net.raphimc.viaproxy.proxy.ProxyConnection;
public class ViaProxyClassicWorldHeightProvider extends ClassicWorldHeightProvider {
@Override
public short getMaxChunkSectionCount(UserConnection user) {
if (ProxyConnection.fromUserConnection(user).getClientVersion().isNewerThanOrEqualTo(VersionEnum.r1_17)) {
return 64;
}
return 16;
}
}

View File

@ -0,0 +1,17 @@
package net.raphimc.viaproxy.protocolhack.providers;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.CompressionProvider;
import net.raphimc.netminecraft.constants.MCPipeline;
public class ViaProxyCompressionProvider extends CompressionProvider {
@Override
public void handlePlayCompression(UserConnection user, int threshold) {
if (!user.isClientSide()) {
throw new IllegalStateException("PLAY state Compression packet is unsupported");
}
user.getChannel().attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).set(threshold);
}
}

View File

@ -0,0 +1,18 @@
package net.raphimc.viaproxy.protocolhack.providers;
import com.viaversion.viaversion.api.connection.UserConnection;
import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.providers.EncryptionProvider;
import net.raphimc.viaproxy.proxy.ProxyConnection;
public class ViaProxyEncryptionProvider extends EncryptionProvider {
@Override
public void enableDecryption(UserConnection user) {
try {
ProxyConnection.fromUserConnection(user).enablePreNettyEncryption();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,51 @@
package net.raphimc.viaproxy.protocolhack.providers;
import com.github.steveice10.mc.auth.exception.profile.ProfileException;
import com.github.steveice10.mc.auth.exception.profile.ProfileNotFoundException;
import com.github.steveice10.mc.auth.service.ProfileService;
import com.github.steveice10.mc.auth.service.SessionService;
import net.raphimc.vialegacy.protocols.release.protocol1_8to1_7_6_10.model.GameProfile;
import net.raphimc.vialegacy.protocols.release.protocol1_8to1_7_6_10.providers.GameProfileFetcher;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class ViaProxyGameProfileFetcher extends GameProfileFetcher {
public static SessionService sessionService = new SessionService();
public static ProfileService profileService = new ProfileService();
@Override
public UUID loadMojangUUID(String playerName) throws ExecutionException, InterruptedException {
final CompletableFuture<com.github.steveice10.mc.auth.data.GameProfile> future = new CompletableFuture<>();
profileService.findProfilesByName(new String[]{playerName}, new ProfileService.ProfileLookupCallback() {
@Override
public void onProfileLookupSucceeded(com.github.steveice10.mc.auth.data.GameProfile profile) {
future.complete(profile);
}
@Override
public void onProfileLookupFailed(com.github.steveice10.mc.auth.data.GameProfile profile, Exception e) {
future.completeExceptionally(e);
}
});
if (!future.isDone()) {
future.completeExceptionally(new ProfileNotFoundException());
}
return future.get().getId();
}
@Override
public GameProfile loadGameProfile(UUID uuid) throws ProfileException {
final com.github.steveice10.mc.auth.data.GameProfile inProfile = new com.github.steveice10.mc.auth.data.GameProfile(uuid, null);
final com.github.steveice10.mc.auth.data.GameProfile mojangProfile = sessionService.fillProfileProperties(inProfile);
final GameProfile gameProfile = new GameProfile(mojangProfile.getName(), mojangProfile.getId());
for (com.github.steveice10.mc.auth.data.GameProfile.Property prop : mojangProfile.getProperties()) {
gameProfile.addProperty(new GameProfile.Property(prop.getName(), prop.getValue(), prop.getSignature()));
}
return gameProfile;
}
}

View File

@ -0,0 +1,14 @@
package net.raphimc.viaproxy.protocolhack.providers;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.item.Item;
import com.viaversion.viaversion.protocols.protocol1_9to1_8.providers.HandItemProvider;
public class ViaProxyHandItemProvider extends HandItemProvider {
@Override
public Item getHandItem(final UserConnection info) {
return null;
}
}

View File

@ -0,0 +1,15 @@
package net.raphimc.viaproxy.protocolhack.providers;
import com.viaversion.viaversion.api.connection.UserConnection;
import net.raphimc.vialegacy.protocols.release.protocol1_3_1_2to1_2_4_5.providers.OldAuthProvider;
import net.raphimc.viaproxy.proxy.CustomPayloadInterface;
import net.raphimc.viaproxy.proxy.ProxyConnection;
public class ViaProxyOldAuthProvider extends OldAuthProvider {
@Override
public void sendAuthRequest(final UserConnection user, final String serverId) throws Throwable {
CustomPayloadInterface.joinServer(serverId, ProxyConnection.fromUserConnection(user));
}
}

View File

@ -0,0 +1,15 @@
package net.raphimc.viaproxy.protocolhack.providers;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.protocols.base.BaseVersionProvider;
import net.raphimc.viaproxy.proxy.ProxyConnection;
public class ViaProxyVersionProvider extends BaseVersionProvider {
@Override
public int getClosestServerProtocol(UserConnection connection) throws Exception {
if (connection.isClientSide()) return ProxyConnection.fromUserConnection(connection).getServerVersion().getVersion();
return super.getClosestServerProtocol(connection);
}
}

View File

@ -0,0 +1,66 @@
package net.raphimc.viaproxy.proxy;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.raphimc.netminecraft.packet.PacketTypes;
import net.raphimc.netminecraft.packet.impl.login.C2SLoginKeyPacket1_19;
import net.raphimc.viaproxy.cli.options.Options;
import net.raphimc.viaproxy.util.LocalSocketClient;
import net.raphimc.viaproxy.util.logging.Logger;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.concurrent.*;
public class CustomPayloadInterface {
public static final String OPENAUTHMOD_BASE_CHANNEL = "oam:";
public static final byte[] OPENAUTHMOD_LEGACY_MAGIC_BYTES = new byte[]{2, 20, 12, 3}; // 1.8 - 1.12.2
public static final String OPENAUTHMOD_LEGACY_MAGIC_STRING = new String(OPENAUTHMOD_LEGACY_MAGIC_BYTES, StandardCharsets.UTF_8); // 1.8 - 1.12.2
public static final int OPENAUTHMOD_LEGACY_MAGIC_INT = new BigInteger(OPENAUTHMOD_LEGACY_MAGIC_BYTES).intValueExact(); // 1.8 - 1.12.2
// Request
public static final String OPENAUTHMOD_JOIN_CHANNEL = OPENAUTHMOD_BASE_CHANNEL + "join"; // 1.8 - latest
public static final String OPENAUTHMOD_SIGN_NONCE_CHANNEL = OPENAUTHMOD_BASE_CHANNEL + "sign_nonce"; // 1.19 - latest
// Response
public static final String OPENAUTHMOD_DATA_CHANNEL = OPENAUTHMOD_BASE_CHANNEL + "data"; // 1.8 - latest
public static void joinServer(final String serverIdHash, final ProxyConnection proxyConnection) throws InterruptedException, ExecutionException {
Logger.u_info("auth", proxyConnection.getC2P().remoteAddress(), proxyConnection.getGameProfile(), "Trying to join online mode server");
if (Options.OPENAUTHMOD_AUTH) {
try {
final ByteBuf response = proxyConnection.sendCustomPayload(OPENAUTHMOD_JOIN_CHANNEL, PacketTypes.writeString(Unpooled.buffer(), serverIdHash)).get(6, TimeUnit.SECONDS);
if (response == null) throw new TimeoutException();
if (response.isReadable() && !response.readBoolean()) throw new TimeoutException();
} catch (TimeoutException e) {
proxyConnection.kickClient("§cAuthentication cancelled! You need to install OpenAuthMod in order to join this server.");
}
} else if (Options.LOCAL_SOCKET_AUTH) {
new LocalSocketClient(48941).request("authenticate", serverIdHash);
}
}
public static void signNonce(final byte[] nonce, final C2SLoginKeyPacket1_19 packet, final ProxyConnection proxyConnection) throws InterruptedException, ExecutionException {
Logger.u_info("auth", proxyConnection.getC2P().remoteAddress(), proxyConnection.getGameProfile(), "Requesting nonce signature");
if (Options.OPENAUTHMOD_AUTH) {
try {
final ByteBuf response = proxyConnection.sendCustomPayload(OPENAUTHMOD_SIGN_NONCE_CHANNEL, PacketTypes.writeByteArray(Unpooled.buffer(), nonce)).get(4, TimeUnit.SECONDS);
if (response == null) throw new TimeoutException();
if (!response.readBoolean()) throw new TimeoutException();
packet.salt = response.readLong();
packet.signature = PacketTypes.readByteArray(response);
} catch (TimeoutException e) {
proxyConnection.kickClient("§cAuthentication cancelled! You need to install OpenAuthMod in order to join this server.");
}
} else if (Options.LOCAL_SOCKET_AUTH) {
final String[] response = new LocalSocketClient(48941).request("sign_nonce", Base64.getEncoder().encodeToString(nonce));
if (response != null && response[0].equals("success")) {
packet.salt = Long.valueOf(response[1]);
packet.signature = Base64.getDecoder().decode(response[2]);
}
}
}
}

View File

@ -0,0 +1,9 @@
package net.raphimc.viaproxy.proxy;
public enum LoginState {
FIRST_PACKET,
SENT_HELLO,
SENT_KEY
}

View File

@ -0,0 +1,248 @@
package net.raphimc.viaproxy.proxy;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.libs.gson.JsonObject;
import com.viaversion.viaversion.libs.gson.JsonPrimitive;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.*;
import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import io.netty.util.AttributeKey;
import net.raphimc.netminecraft.constants.*;
import net.raphimc.netminecraft.netty.connection.NetClient;
import net.raphimc.netminecraft.netty.crypto.AESEncryption;
import net.raphimc.netminecraft.packet.PacketTypes;
import net.raphimc.netminecraft.packet.impl.login.*;
import net.raphimc.netminecraft.packet.impl.status.S2CStatusResponsePacket;
import net.raphimc.netminecraft.packet.registry.PacketRegistryUtil;
import net.raphimc.netminecraft.util.ServerAddress;
import net.raphimc.vialegacy.util.VersionEnum;
import net.raphimc.viaproxy.proxy.util.CloseAndReturn;
import net.raphimc.viaproxy.util.logging.Logger;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.util.Base64;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Supplier;
public class ProxyConnection extends NetClient {
public static final AttributeKey<ProxyConnection> PROXY_CONNECTION_ATTRIBUTE_KEY = AttributeKey.valueOf("proxy_connection");
private final SocketChannel c2p;
private final AtomicInteger customPayloadId = new AtomicInteger(0);
private final Map<Integer, CompletableFuture<ByteBuf>> customPayloadListener = new ConcurrentHashMap<>();
private ServerAddress serverAddress;
private VersionEnum serverVersion;
private VersionEnum clientVersion;
private GameProfile gameProfile;
private C2SLoginHelloPacket1_7 loginHelloPacket;
private UserConnection userConnection;
private ConnectionState connectionState = ConnectionState.HANDSHAKING;
private Key storedSecretKey;
private String classicMpPass;
public ProxyConnection(final Supplier<ChannelHandler> handlerSupplier, final Function<Supplier<ChannelHandler>, ChannelInitializer<SocketChannel>> channelInitializerSupplier, final SocketChannel c2p) {
super(handlerSupplier, channelInitializerSupplier);
this.c2p = c2p;
}
public static ProxyConnection fromChannel(final Channel channel) {
return channel.attr(PROXY_CONNECTION_ATTRIBUTE_KEY).get();
}
public static ProxyConnection fromUserConnection(final UserConnection userConnection) {
return fromChannel(userConnection.getChannel());
}
@Override
@Deprecated
public void connect(final ServerAddress serverAddress) {
throw new UnsupportedOperationException();
}
@Override
public void initialize(final Bootstrap bootstrap) {
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 4_000);
bootstrap.attr(ProxyConnection.PROXY_CONNECTION_ATTRIBUTE_KEY, this);
super.initialize(bootstrap);
}
public void connectToServer(final ServerAddress serverAddress, final VersionEnum targetVersion) {
this.serverAddress = serverAddress;
this.serverVersion = targetVersion;
super.connect(serverAddress);
}
public SocketChannel getC2P() {
return this.c2p;
}
public ServerAddress getServerAddress() {
return this.serverAddress;
}
public VersionEnum getServerVersion() {
return this.serverVersion;
}
public void setKeyForPreNettyEncryption(Key key) {
this.storedSecretKey = key;
}
public void enablePreNettyEncryption() throws GeneralSecurityException {
this.getChannel().attr(MCPipeline.ENCRYPTION_ATTRIBUTE_KEY).set(new AESEncryption(this.storedSecretKey));
}
public void setClientVersion(VersionEnum clientVersion) {
this.clientVersion = clientVersion;
}
public VersionEnum getClientVersion() {
return this.clientVersion;
}
public void setGameProfile(GameProfile gameProfile) {
this.gameProfile = gameProfile;
}
public GameProfile getGameProfile() {
return this.gameProfile;
}
public void setLoginHelloPacket(final C2SLoginHelloPacket1_7 loginHelloPacket) {
this.loginHelloPacket = loginHelloPacket;
}
public C2SLoginHelloPacket1_7 getLoginHelloPacket() {
return this.loginHelloPacket;
}
public void setUserConnection(UserConnection userConnection) {
this.userConnection = userConnection;
}
public UserConnection getUserConnection() {
return this.userConnection;
}
public void setConnectionState(ConnectionState connectionState) {
this.connectionState = connectionState;
switch (this.connectionState) {
case HANDSHAKING:
if (this.getChannel() != null)
this.getChannel().attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getHandshakeRegistry(true));
this.c2p.attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getHandshakeRegistry(false));
break;
case STATUS:
if (this.getChannel() != null)
this.getChannel().attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getStatusRegistry(true));
this.c2p.attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getStatusRegistry(false));
break;
case LOGIN:
if (this.getChannel() != null)
this.getChannel().attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getLoginRegistry(true, this.clientVersion.getVersion()));
this.c2p.attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getLoginRegistry(false, this.clientVersion.getVersion()));
break;
case PLAY:
if (this.getChannel() != null)
this.getChannel().attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getPlayRegistry(true, this.clientVersion.getVersion()));
this.c2p.attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getPlayRegistry(false, this.clientVersion.getVersion()));
break;
}
}
public ConnectionState getConnectionState() {
return this.connectionState;
}
public CompletableFuture<ByteBuf> sendCustomPayload(final String channel, final ByteBuf data) {
if (channel.length() > 20) throw new IllegalStateException("Channel name can't be longer than 20 characters");
final CompletableFuture<ByteBuf> future = new CompletableFuture<>();
final int id = this.customPayloadId.getAndIncrement();
switch (this.connectionState) {
case LOGIN:
if (this.clientVersion.isNewerThanOrEqualTo(VersionEnum.r1_13)) {
this.c2p.writeAndFlush(new S2CLoginCustomPayloadPacket(id, channel, PacketTypes.readReadableBytes(data))).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
} else {
final ByteBuf disconnectPacketData = Unpooled.buffer();
PacketTypes.writeString(disconnectPacketData, channel);
PacketTypes.writeVarInt(disconnectPacketData, id);
disconnectPacketData.writeBytes(data);
this.c2p.writeAndFlush(new S2CLoginDisconnectPacket(messageToJson("§cYou need to install OpenAuthMod in order to join this server.§k\n" + Base64.getEncoder().encodeToString(ByteBufUtil.getBytes(disconnectPacketData)) + "\n" + CustomPayloadInterface.OPENAUTHMOD_LEGACY_MAGIC_STRING))).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
}
break;
case PLAY:
final ByteBuf customPayloadPacket = Unpooled.buffer();
PacketTypes.writeVarInt(customPayloadPacket, MCPackets.S2C_PLUGIN_MESSAGE.getId(this.clientVersion.getVersion()));
PacketTypes.writeString(customPayloadPacket, channel); // channel
PacketTypes.writeVarInt(customPayloadPacket, id);
customPayloadPacket.writeBytes(data);
this.c2p.writeAndFlush(customPayloadPacket).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
break;
default:
throw new IllegalStateException("Can't send a custom payload packet during " + this.connectionState);
}
this.customPayloadListener.put(id, future);
return future;
}
public boolean handleCustomPayload(final int id, final ByteBuf data) {
if (this.customPayloadListener.containsKey(id)) {
this.customPayloadListener.remove(id).complete(data);
return true;
}
return false;
}
public void setClassicMpPass(final String classicMpPass) {
this.classicMpPass = classicMpPass;
}
public String getClassicMpPass() {
return this.classicMpPass;
}
public void kickClient(final String message) throws InterruptedException, CloseAndReturn {
Logger.u_err("kick", this.c2p.remoteAddress(), this.getGameProfile(), message.replaceAll("§.", ""));
final ChannelFuture future;
if (this.connectionState == ConnectionState.LOGIN) {
future = this.c2p.writeAndFlush(new S2CLoginDisconnectPacket(messageToJson(message)));
} else if (this.connectionState == ConnectionState.PLAY) {
final ByteBuf disconnectPacket = Unpooled.buffer();
PacketTypes.writeVarInt(disconnectPacket, MCPackets.S2C_DISCONNECT.getId(this.clientVersion.getVersion()));
PacketTypes.writeString(disconnectPacket, messageToJson(message));
future = this.c2p.writeAndFlush(disconnectPacket);
} else if (this.connectionState == ConnectionState.STATUS) {
future = this.c2p.writeAndFlush(new S2CStatusResponsePacket("{\"players\":{\"max\":0,\"online\":0},\"description\":" + new JsonPrimitive(message) + ",\"version\":{\"protocol\":-1,\"name\":\"ViaProxy\"}}"));
} else {
future = this.c2p.newSucceededFuture();
}
future.await().channel().close();
throw CloseAndReturn.INSTANCE;
}
public boolean isClosed() {
return !this.c2p.isOpen() || (this.getChannel() != null && !this.getChannel().isOpen());
}
private static String messageToJson(final String message) {
final JsonObject obj = new JsonObject();
obj.addProperty("text", message);
return obj.toString();
}
}

View File

@ -0,0 +1,35 @@
package net.raphimc.viaproxy.proxy.client2proxy;
import io.netty.channel.ChannelHandler;
import io.netty.channel.socket.SocketChannel;
import net.raphimc.netminecraft.constants.MCPipeline;
import net.raphimc.netminecraft.netty.connection.MinecraftChannelInitializer;
import net.raphimc.netminecraft.packet.registry.PacketRegistryUtil;
import net.raphimc.viaproxy.plugins.PluginManager;
import net.raphimc.viaproxy.plugins.events.Client2ProxyChannelInitializeEvent;
import net.raphimc.viaproxy.plugins.events.types.ITyped;
import java.util.function.Supplier;
public class Client2ProxyChannelInitializer extends MinecraftChannelInitializer {
public Client2ProxyChannelInitializer(final Supplier<ChannelHandler> handlerSupplier) {
super(handlerSupplier);
}
@Override
protected void initChannel(SocketChannel socketChannel) {
if (PluginManager.EVENT_MANAGER.call(new Client2ProxyChannelInitializeEvent(ITyped.Type.PRE, socketChannel)).isCancelled()) {
socketChannel.close();
return;
}
super.initChannel(socketChannel);
socketChannel.attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getHandshakeRegistry(false));
if (PluginManager.EVENT_MANAGER.call(new Client2ProxyChannelInitializeEvent(ITyped.Type.POST, socketChannel)).isCancelled()) {
socketChannel.close();
}
}
}

View File

@ -0,0 +1,292 @@
package net.raphimc.viaproxy.proxy.client2proxy;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.mc.auth.util.UUIDSerializer;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import net.raphimc.netminecraft.constants.*;
import net.raphimc.netminecraft.netty.crypto.AESEncryption;
import net.raphimc.netminecraft.netty.crypto.CryptUtil;
import net.raphimc.netminecraft.packet.*;
import net.raphimc.netminecraft.packet.impl.handshake.C2SHandshakePacket;
import net.raphimc.netminecraft.packet.impl.login.*;
import net.raphimc.netminecraft.util.ServerAddress;
import net.raphimc.vialegacy.util.VersionEnum;
import net.raphimc.viaproxy.ViaProxy;
import net.raphimc.viaproxy.cli.options.Options;
import net.raphimc.viaproxy.plugins.PluginManager;
import net.raphimc.viaproxy.plugins.events.PreConnectEvent;
import net.raphimc.viaproxy.protocolhack.providers.ViaProxyGameProfileFetcher;
import net.raphimc.viaproxy.proxy.*;
import net.raphimc.viaproxy.proxy.proxy2server.Proxy2ServerChannelInitializer;
import net.raphimc.viaproxy.proxy.proxy2server.Proxy2ServerHandler;
import net.raphimc.viaproxy.proxy.util.CloseAndReturn;
import net.raphimc.viaproxy.proxy.util.ExceptionUtil;
import net.raphimc.viaproxy.util.ArrayHelper;
import net.raphimc.viaproxy.util.LocalSocketClient;
import net.raphimc.viaproxy.util.logging.Logger;
import javax.crypto.SecretKey;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.time.Instant;
import java.util.*;
import java.util.regex.Pattern;
public class Client2ProxyHandler extends SimpleChannelInboundHandler<IPacket> {
private static final KeyPair KEY_PAIR;
private static final Random RANDOM = new Random();
static {
if (Options.ONLINE_MODE) {
KEY_PAIR = CryptUtil.generateKeyPair();
} else {
KEY_PAIR = null;
}
}
private ProxyConnection proxyConnection;
private LoginState loginState = LoginState.FIRST_PACKET;
private final byte[] verifyToken = new byte[4];
private int customPayloadPacketId = -1;
private int chatSessionUpdatePacketId = -1;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
if (Options.ONLINE_MODE) RANDOM.nextBytes(this.verifyToken);
this.proxyConnection = new ProxyConnection(Proxy2ServerHandler::new, Proxy2ServerChannelInitializer::new, (SocketChannel) ctx.channel());
ctx.channel().attr(ProxyConnection.PROXY_CONNECTION_ATTRIBUTE_KEY).set(this.proxyConnection);
ViaProxy.c2pChannels.add(ctx.channel());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
try {
this.proxyConnection.getChannel().close();
} catch (Throwable ignored) {
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, IPacket packet) throws Exception {
if (this.proxyConnection.isClosed()) return;
switch (this.proxyConnection.getConnectionState()) {
case HANDSHAKING:
if (packet instanceof C2SHandshakePacket) this.handleHandshake((C2SHandshakePacket) packet);
else break;
return;
case LOGIN:
if (packet instanceof C2SLoginHelloPacket1_7) this.handleLoginHello((C2SLoginHelloPacket1_7) packet);
else if (packet instanceof C2SLoginKeyPacket1_7) this.handleLoginKey((C2SLoginKeyPacket1_7) packet);
else if (packet instanceof C2SLoginCustomPayloadPacket) this.handleLoginCustomPayload((C2SLoginCustomPayloadPacket) packet);
else break;
return;
case PLAY:
final UnknownPacket unknownPacket = (UnknownPacket) packet;
if (unknownPacket.packetId == this.customPayloadPacketId) {
if (this.handlePlayCustomPayload(Unpooled.wrappedBuffer(unknownPacket.data))) return;
} else if (unknownPacket.packetId == this.chatSessionUpdatePacketId && this.proxyConnection.getChannel().attr(MCPipeline.ENCRYPTION_ATTRIBUTE_KEY).get() == null) {
return;
}
break;
}
this.proxyConnection.getChannel().writeAndFlush(packet).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ExceptionUtil.handleNettyException(ctx, cause, this.proxyConnection);
}
private void handleHandshake(final C2SHandshakePacket packet) throws InterruptedException {
String address = packet.address.split("\0")[0];
final VersionEnum clientVersion = VersionEnum.fromProtocolVersion(ProtocolVersion.getProtocol(packet.protocolVersion));
if (ConnectionState.LOGIN.equals(packet.intendedState)) {
if (clientVersion == VersionEnum.UNKNOWN) throw CloseAndReturn.INSTANCE;
} else if (!ConnectionState.STATUS.equals(packet.intendedState)) {
throw CloseAndReturn.INSTANCE;
}
this.proxyConnection.setClientVersion(clientVersion);
this.proxyConnection.setConnectionState(packet.intendedState);
this.customPayloadPacketId = MCPackets.C2S_PLUGIN_MESSAGE.getId(clientVersion.getVersion());
this.chatSessionUpdatePacketId = MCPackets.C2S_CHAT_SESSION_UPDATE.getId(clientVersion.getVersion());
String connectIP = Options.CONNECT_ADDRESS;
int connectPort = Options.CONNECT_PORT;
VersionEnum serverVersion = Options.PROTOCOL_VERSION;
if (Options.INTERNAL_SRV_MODE) {
final ArrayHelper arrayHelper = ArrayHelper.instanceOf(address.split("\7"));
connectIP = arrayHelper.get(0);
connectPort = arrayHelper.getInteger(1);
final String versionString = arrayHelper.get(2);
if (arrayHelper.isIndexValid(3)) {
this.proxyConnection.setClassicMpPass(arrayHelper.getString(3));
}
for (VersionEnum v : VersionEnum.getAllVersions()) {
if (v.getName().equalsIgnoreCase(versionString)) {
serverVersion = v;
break;
}
}
if (serverVersion == null) throw CloseAndReturn.INSTANCE;
} else if (Options.SRV_MODE) {
try {
if (address.toLowerCase().contains(".viaproxy.")) {
address = address.substring(0, address.toLowerCase().lastIndexOf(".viaproxy."));
} else {
throw CloseAndReturn.INSTANCE;
}
final ArrayHelper arrayHelper = ArrayHelper.instanceOf(address.split(Pattern.quote("_")));
if (arrayHelper.getLength() < 3) {
throw CloseAndReturn.INSTANCE;
}
connectIP = arrayHelper.getAsString(0, arrayHelper.getLength() - 3, "_");
connectPort = arrayHelper.getInteger(arrayHelper.getLength() - 2);
final String versionString = arrayHelper.get(arrayHelper.getLength() - 1);
for (VersionEnum v : VersionEnum.getAllVersions()) {
if (v.getName().replace(" ", "-").equalsIgnoreCase(versionString)) {
serverVersion = v;
break;
}
}
if (serverVersion == null) throw CloseAndReturn.INSTANCE;
} catch (CloseAndReturn e) {
this.proxyConnection.kickClient("§cWrong SRV syntax! §6Please use:\n§7ip_port_version.viaproxy.hostname");
}
}
final ServerAddress serverAddress;
if (serverVersion.isOlderThan(VersionEnum.r1_3_1tor1_3_2)) {
serverAddress = new ServerAddress(connectIP, connectPort);
} else {
serverAddress = ServerAddress.fromSRV(connectIP + ":" + connectPort);
}
final PreConnectEvent preConnectEvent = new PreConnectEvent(serverAddress, serverVersion, clientVersion, this.proxyConnection.getC2P());
if (PluginManager.EVENT_MANAGER.call(preConnectEvent).isCancelled()) {
this.proxyConnection.kickClient(preConnectEvent.getCancelMessage());
}
Logger.u_info("connect", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "[" + clientVersion.getName() + " <-> " + serverVersion.getName() + "] Connecting to " + serverAddress.getAddress() + ":" + serverAddress.getPort());
try {
this.proxyConnection.connectToServer(serverAddress, serverVersion);
this.proxyConnection.getChannel().writeAndFlush(new C2SHandshakePacket(clientVersion.getOriginalVersion(), serverAddress.getAddress(), serverAddress.getPort(), packet.intendedState)).await().addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
this.proxyConnection.setConnectionState(packet.intendedState);
} catch (Throwable e) {
this.proxyConnection.kickClient("§cCould not connect to the backend server!\n§cTry again in a few seconds.");
}
}
private void handleLoginHello(C2SLoginHelloPacket1_7 packet) throws NoSuchAlgorithmException, InvalidKeySpecException {
if (this.loginState != LoginState.FIRST_PACKET) throw CloseAndReturn.INSTANCE;
this.loginState = LoginState.SENT_HELLO;
if (packet instanceof C2SLoginHelloPacket1_19) {
final C2SLoginHelloPacket1_19 packet1_19 = (C2SLoginHelloPacket1_19) packet;
if (packet1_19.expiresAt != null && packet1_19.expiresAt.isBefore(Instant.now())) {
throw new IllegalStateException("Expired public key");
}
}
this.proxyConnection.setLoginHelloPacket(packet);
this.proxyConnection.setGameProfile(new GameProfile((UUID) null, packet.name));
if (Options.ONLINE_MODE) {
this.proxyConnection.getC2P().writeAndFlush(new S2CLoginKeyPacket1_8("", KEY_PAIR.getPublic().getEncoded(), this.verifyToken)).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
} else {
if (Options.LOCAL_SOCKET_AUTH) {
String[] response = new LocalSocketClient(48941).request("getusername");
if (response != null && response[0].equals("success")) {
this.proxyConnection.setGameProfile(new GameProfile((UUID) null, response[1]));
}
response = new LocalSocketClient(48941).request("get_public_key_data");
if (response != null && response[0].equals("success")) {
final UUID uuid = UUIDSerializer.fromString(response[1].replaceFirst("(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5"));
final PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(response[2])));
packet = new C2SLoginHelloPacket1_19_3(packet.name, Instant.ofEpochMilli(Long.parseLong(response[4])), publicKey, Base64.getDecoder().decode(response[3]), uuid);
}
}
this.proxyConnection.getChannel().writeAndFlush(packet).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
}
}
private void handleLoginKey(final C2SLoginKeyPacket1_7 packet) throws GeneralSecurityException, InterruptedException {
if (this.proxyConnection.getClientVersion().isOlderThanOrEqualTo(VersionEnum.r1_12_2) && new String(packet.encryptedNonce, StandardCharsets.UTF_8).equals(CustomPayloadInterface.OPENAUTHMOD_DATA_CHANNEL)) { // 1.8-1.12.2 OpenAuthMod response handling
final ByteBuf byteBuf = Unpooled.wrappedBuffer(packet.encryptedSecretKey);
this.proxyConnection.handleCustomPayload(PacketTypes.readVarInt(byteBuf), byteBuf);
return;
}
if (this.loginState != LoginState.SENT_HELLO) throw CloseAndReturn.INSTANCE;
this.loginState = LoginState.SENT_KEY;
if (packet.encryptedNonce != null) {
if (!Arrays.equals(this.verifyToken, CryptUtil.decryptData(KEY_PAIR.getPrivate(), packet.encryptedNonce))) {
Logger.u_err("auth", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Invalid verify token");
this.proxyConnection.kickClient("§cInvalid verify token!");
}
} else {
final C2SLoginKeyPacket1_19 keyPacket = (C2SLoginKeyPacket1_19) packet;
final C2SLoginHelloPacket1_19 helloPacket = (C2SLoginHelloPacket1_19) this.proxyConnection.getLoginHelloPacket();
if (helloPacket.key == null || !CryptUtil.verifySignedNonce(helloPacket.key, this.verifyToken, keyPacket.salt, keyPacket.signature)) {
Logger.u_err("auth", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Invalid verify token");
this.proxyConnection.kickClient("§cInvalid verify token!");
}
}
final SecretKey secretKey = CryptUtil.decryptSecretKey(KEY_PAIR.getPrivate(), packet.encryptedSecretKey);
this.proxyConnection.getC2P().attr(MCPipeline.ENCRYPTION_ATTRIBUTE_KEY).set(new AESEncryption(secretKey));
final String userName = this.proxyConnection.getGameProfile().getName();
try {
final String serverHash = new BigInteger(CryptUtil.computeServerIdHash("", KEY_PAIR.getPublic(), secretKey)).toString(16);
this.proxyConnection.setGameProfile(ViaProxyGameProfileFetcher.sessionService.getProfileByServer(userName, serverHash));
if (this.proxyConnection.getGameProfile() == null) {
Logger.u_err("auth", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Invalid session");
this.proxyConnection.kickClient("§cInvalid session! Please restart minecraft (and the launcher) and try again.");
}
Logger.u_info("auth", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Authenticated as " + this.proxyConnection.getGameProfile().getIdAsString());
} catch (Throwable e) {
throw new RuntimeException("Failed to make session request for user '" + userName + "'!", e);
}
this.proxyConnection.getChannel().writeAndFlush(this.proxyConnection.getLoginHelloPacket()).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
}
private void handleLoginCustomPayload(final C2SLoginCustomPayloadPacket packet) {
if (packet.response == null || !this.proxyConnection.handleCustomPayload(packet.queryId, Unpooled.wrappedBuffer(packet.response))) {
this.proxyConnection.getChannel().writeAndFlush(packet).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
}
}
private boolean handlePlayCustomPayload(final ByteBuf packet) {
final String channel = PacketTypes.readString(packet, Short.MAX_VALUE); // channel
if (channel.equals(CustomPayloadInterface.OPENAUTHMOD_DATA_CHANNEL)) {
return this.proxyConnection.handleCustomPayload(PacketTypes.readVarInt(packet), packet);
}
return false;
}
}

View File

@ -0,0 +1,58 @@
package net.raphimc.viaproxy.proxy.proxy2server;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.connection.UserConnectionImpl;
import com.viaversion.viaversion.protocol.ProtocolPipelineImpl;
import io.netty.channel.ChannelHandler;
import io.netty.channel.socket.SocketChannel;
import net.raphimc.netminecraft.constants.MCPipeline;
import net.raphimc.netminecraft.netty.connection.MinecraftChannelInitializer;
import net.raphimc.netminecraft.packet.registry.PacketRegistryUtil;
import net.raphimc.vialegacy.netty.PreNettyDecoder;
import net.raphimc.vialegacy.netty.PreNettyEncoder;
import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.baseprotocols.PreNettyBaseProtocol;
import net.raphimc.vialegacy.util.VersionEnum;
import net.raphimc.viaprotocolhack.netty.ViaEncodeHandler;
import net.raphimc.viaprotocolhack.netty.ViaPipeline;
import net.raphimc.viaproxy.plugins.PluginManager;
import net.raphimc.viaproxy.plugins.events.Proxy2ServerChannelInitializeEvent;
import net.raphimc.viaproxy.plugins.events.types.ITyped;
import net.raphimc.viaproxy.protocolhack.impl.ViaProxyViaDecodeHandler;
import net.raphimc.viaproxy.proxy.ProxyConnection;
import java.util.function.Supplier;
public class Proxy2ServerChannelInitializer extends MinecraftChannelInitializer {
public Proxy2ServerChannelInitializer(final Supplier<ChannelHandler> handlerSupplier) {
super(handlerSupplier);
}
@Override
protected void initChannel(SocketChannel socketChannel) {
if (PluginManager.EVENT_MANAGER.call(new Proxy2ServerChannelInitializeEvent(ITyped.Type.PRE, socketChannel)).isCancelled()) {
socketChannel.close();
return;
}
final UserConnection user = new UserConnectionImpl(socketChannel, true);
new ProtocolPipelineImpl(user);
ProxyConnection.fromChannel(socketChannel).setUserConnection(user);
super.initChannel(socketChannel);
socketChannel.attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getHandshakeRegistry(true));
socketChannel.pipeline().addBefore(MCPipeline.PACKET_CODEC_HANDLER_NAME, ViaPipeline.HANDLER_ENCODER_NAME, new ViaEncodeHandler(user));
socketChannel.pipeline().addBefore(MCPipeline.PACKET_CODEC_HANDLER_NAME, ViaPipeline.HANDLER_DECODER_NAME, new ViaProxyViaDecodeHandler(user));
if (ProxyConnection.fromChannel(socketChannel).getServerVersion().isOlderThanOrEqualTo(VersionEnum.r1_6_4)) {
user.getProtocolInfo().getPipeline().add(PreNettyBaseProtocol.INSTANCE);
socketChannel.pipeline().addBefore(MCPipeline.SIZER_HANDLER_NAME, ViaPipeline.HANDLER_PRE_NETTY_ENCODER_NAME, new PreNettyEncoder(user));
socketChannel.pipeline().addBefore(MCPipeline.SIZER_HANDLER_NAME, ViaPipeline.HANDLER_PRE_NETTY_DECODER_NAME, new PreNettyDecoder(user));
}
if (PluginManager.EVENT_MANAGER.call(new Proxy2ServerChannelInitializeEvent(ITyped.Type.POST, socketChannel)).isCancelled()) {
socketChannel.close();
}
}
}

View File

@ -0,0 +1,117 @@
package net.raphimc.viaproxy.proxy.proxy2server;
import com.github.steveice10.mc.auth.data.GameProfile;
import io.netty.channel.*;
import net.raphimc.netminecraft.constants.ConnectionState;
import net.raphimc.netminecraft.constants.MCPipeline;
import net.raphimc.netminecraft.netty.crypto.AESEncryption;
import net.raphimc.netminecraft.netty.crypto.CryptUtil;
import net.raphimc.netminecraft.packet.IPacket;
import net.raphimc.netminecraft.packet.impl.login.*;
import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.storage.ProtocolMetadataStorage;
import net.raphimc.vialegacy.util.VersionEnum;
import net.raphimc.viaproxy.cli.options.Options;
import net.raphimc.viaproxy.proxy.CustomPayloadInterface;
import net.raphimc.viaproxy.proxy.ProxyConnection;
import net.raphimc.viaproxy.proxy.util.ExceptionUtil;
import net.raphimc.viaproxy.util.logging.Logger;
import javax.crypto.SecretKey;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.util.concurrent.ExecutionException;
public class Proxy2ServerHandler extends SimpleChannelInboundHandler<IPacket> {
private ProxyConnection proxyConnection;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
this.proxyConnection = ProxyConnection.fromChannel(ctx.channel());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
Logger.u_info("disconnect", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Connection closed");
try {
this.proxyConnection.getC2P().close();
} catch (Throwable ignored) {
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, IPacket packet) throws Exception {
if (this.proxyConnection.isClosed()) return;
switch (this.proxyConnection.getConnectionState()) {
case LOGIN:
if (packet instanceof S2CLoginKeyPacket1_7) this.handleLoginKey((S2CLoginKeyPacket1_7) packet);
else if (packet instanceof S2CLoginSuccessPacket1_7) this.handleLoginSuccess((S2CLoginSuccessPacket1_7) packet);
else if (packet instanceof S2CLoginCompressionPacket) this.handleLoginCompression((S2CLoginCompressionPacket) packet);
else break;
return;
}
this.proxyConnection.getC2P().writeAndFlush(packet).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
ExceptionUtil.handleNettyException(ctx, cause, this.proxyConnection);
}
private void handleLoginKey(final S2CLoginKeyPacket1_7 packet) throws InterruptedException, GeneralSecurityException, ExecutionException {
final PublicKey publicKey = CryptUtil.decodeRsaPublicKey(packet.publicKey);
final SecretKey secretKey = CryptUtil.generateSecretKey();
final String serverHash = new BigInteger(CryptUtil.computeServerIdHash(packet.serverId, publicKey, secretKey)).toString(16);
boolean auth = true;
if (this.proxyConnection.getServerVersion().isOlderThanOrEqualTo(VersionEnum.r1_6_4)) {
auth = this.proxyConnection.getUserConnection().get(ProtocolMetadataStorage.class).authenticate;
}
if (auth) {
CustomPayloadInterface.joinServer(serverHash, this.proxyConnection);
}
final byte[] encryptedSecretKey = CryptUtil.encryptData(publicKey, secretKey.getEncoded());
final byte[] encryptedNonce = CryptUtil.encryptData(publicKey, packet.nonce);
final C2SLoginKeyPacket1_19_3 loginKey = new C2SLoginKeyPacket1_19_3(encryptedSecretKey, encryptedNonce);
if (this.proxyConnection.getLoginHelloPacket() instanceof C2SLoginHelloPacket1_19 && ((C2SLoginHelloPacket1_19) this.proxyConnection.getLoginHelloPacket()).key != null) {
CustomPayloadInterface.signNonce(packet.nonce, loginKey, this.proxyConnection);
}
this.proxyConnection.getChannel().writeAndFlush(loginKey).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
if (this.proxyConnection.getServerVersion().isNewerThanOrEqualTo(VersionEnum.r1_7_2tor1_7_5)) {
this.proxyConnection.getChannel().attr(MCPipeline.ENCRYPTION_ATTRIBUTE_KEY).set(new AESEncryption(secretKey));
} else {
this.proxyConnection.setKeyForPreNettyEncryption(secretKey);
}
}
private void handleLoginSuccess(final S2CLoginSuccessPacket1_7 packet) throws Exception {
if (this.proxyConnection.getClientVersion().isNewerThanOrEqualTo(VersionEnum.r1_8)) {
if (Options.COMPRESSION_THRESHOLD > -1) {
this.proxyConnection.getC2P().writeAndFlush(new S2CLoginCompressionPacket(Options.COMPRESSION_THRESHOLD)).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE).await();
this.proxyConnection.getC2P().attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).set(Options.COMPRESSION_THRESHOLD);
}
}
this.proxyConnection.setGameProfile(new GameProfile(packet.uuid, packet.name));
Logger.u_info("connect", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "Connected successfully! Switching to PLAY state");
this.proxyConnection.getC2P().writeAndFlush(packet).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE).await();
this.proxyConnection.setConnectionState(ConnectionState.PLAY);
}
private void handleLoginCompression(final S2CLoginCompressionPacket packet) {
this.proxyConnection.getChannel().attr(MCPipeline.COMPRESSION_THRESHOLD_ATTRIBUTE_KEY).set(packet.compressionThreshold);
}
}

View File

@ -0,0 +1,20 @@
package net.raphimc.viaproxy.proxy.util;
public class CloseAndReturn extends RuntimeException {
public static final CloseAndReturn INSTANCE = new CloseAndReturn();
CloseAndReturn() {
}
@Override
public String toString() {
return "";
}
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}

View File

@ -0,0 +1,83 @@
package net.raphimc.viaproxy.proxy.util;
import com.viaversion.viaversion.exception.InformativeException;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.EncoderException;
import net.raphimc.viaproxy.proxy.ProxyConnection;
import net.raphimc.viaproxy.util.logging.Logger;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.channels.ClosedChannelException;
import java.util.Iterator;
import java.util.Map;
public class ExceptionUtil {
private static Field infoField;
static {
try {
infoField = InformativeException.class.getDeclaredField("info");
infoField.setAccessible(true);
} catch (Throwable e) {
e.printStackTrace();
}
}
public static void handleNettyException(ChannelHandlerContext ctx, Throwable cause, ProxyConnection proxyConnection) {
if (!ctx.channel().isOpen() || !ctx.channel().isActive()) return;
if (cause instanceof ClosedChannelException) return;
if (cause instanceof IOException) return;
if (cause instanceof CloseAndReturn) {
ctx.channel().close();
return;
}
Logger.LOGGER.error("Caught unhandled netty exception", cause);
try {
proxyConnection.kickClient("§cAn unhandled error occurred in your connection and it has been closed.\n§aError details for report:§f" + ExceptionUtil.prettyPrint(cause));
} catch (Throwable ignored) {
}
ctx.channel().close();
}
public static String prettyPrint(Throwable t) {
StringBuilder msg = new StringBuilder();
if (t instanceof EncoderException) t = t.getCause();
while (t != null) {
String exceptionMessage = t.getMessage();
if (t instanceof InformativeException) {
exceptionMessage = getMessageFor((InformativeException) t);
}
msg.append("\n");
msg.append("§c").append(t.getClass().getSimpleName()).append("§7: §f").append(exceptionMessage);
t = t.getCause();
if (t != null) {
msg.append(" §9Caused by");
}
}
return msg.toString();
}
private static String getMessageFor(final InformativeException e) {
Map<String, Object> info = null;
try {
info = (Map<String, Object>) infoField.get(e);
} catch (Throwable ignored) {
}
if (info != null) {
final StringBuilder builder = new StringBuilder();
boolean first = true;
for (Iterator<Map.Entry<String, Object>> var3 = info.entrySet().iterator(); var3.hasNext(); first = false) {
Map.Entry<String, Object> entry = var3.next();
if (!first) {
builder.append(", ");
}
builder.append(entry.getKey()).append(": ").append(entry.getValue());
}
return builder.toString();
}
return "Unable to get source info";
}
}

View File

@ -0,0 +1,238 @@
package net.raphimc.viaproxy.ui;
import com.formdev.flatlaf.FlatDarkLaf;
import com.google.common.net.HostAndPort;
import net.raphimc.vialegacy.util.VersionEnum;
import net.raphimc.viaproxy.ViaProxy;
import net.raphimc.viaproxy.cli.options.Options;
import net.raphimc.viaproxy.util.logging.Logger;
import javax.swing.*;
import java.awt.*;
public class ViaProxyUI extends JFrame {
private final JPanel contentPane = new JPanel();
private ImageIcon icon;
private JTextField serverAddress;
private JComboBox<VersionEnum> serverVersion;
private JSpinner bindPort;
private JComboBox<String> authMethod;
private JCheckBox betaCraftAuth;
private JLabel stateLabel;
private JButton stateButton;
public ViaProxyUI() {
this.applyDarkFlatLafTheme();
this.loadIcons();
this.initWindow();
this.initElements();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
Logger.LOGGER.error("Caught exception in thread " + t.getName(), e);
final StringBuilder builder = new StringBuilder("An error occurred:\n");
builder.append("[").append(e.getClass().getSimpleName()).append("] ").append(e.getMessage()).append("\n");
for (StackTraceElement element : e.getStackTrace()) {
builder.append("\tat ").append(element.toString()).append("\n");
}
this.showError(builder.toString());
});
SwingUtilities.updateComponentTreeUI(this);
this.setVisible(true);
}
private void applyDarkFlatLafTheme() {
try {
UIManager.setLookAndFeel(new FlatDarkLaf());
} catch (Throwable t) {
t.printStackTrace();
}
}
private void loadIcons() {
this.icon = new ImageIcon(this.getClass().getClassLoader().getResource("assets/icons/icon.png"));
}
private void initWindow() {
this.setTitle("ViaProxy");
this.setIconImage(this.icon.getImage());
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(500, 370);
this.setResizable(false);
this.setLocationRelativeTo(null);
this.setContentPane(this.contentPane);
}
private void initElements() {
this.contentPane.setLayout(null);
{
JLabel titleLabel = new JLabel("ViaProxy");
titleLabel.setBounds(0, 0, 500, 50);
titleLabel.setHorizontalAlignment(SwingConstants.CENTER);
titleLabel.setFont(titleLabel.getFont().deriveFont(30F));
this.contentPane.add(titleLabel);
}
{
JLabel addressLabel = new JLabel("Server Address:");
addressLabel.setBounds(10, 50, 100, 20);
this.contentPane.add(addressLabel);
this.serverAddress = new JTextField();
this.serverAddress.setBounds(10, 70, 465, 20);
this.contentPane.add(this.serverAddress);
}
{
JLabel serverVersionLabel = new JLabel("Server Version:");
serverVersionLabel.setBounds(10, 100, 100, 20);
this.contentPane.add(serverVersionLabel);
this.serverVersion = new JComboBox<>(VersionEnum.RENDER_VERSIONS.toArray(new VersionEnum[0]));
this.serverVersion.setBounds(10, 120, 465, 20);
this.serverVersion.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
if (value instanceof VersionEnum) {
VersionEnum version = (VersionEnum) value;
value = version.getName();
}
return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
}
});
this.contentPane.add(this.serverVersion);
}
{
JLabel bindPortLabel = new JLabel("Bind Port:");
bindPortLabel.setBounds(10, 150, 100, 20);
this.contentPane.add(bindPortLabel);
this.bindPort = new JSpinner(new SpinnerNumberModel(25568, 1, 65535, 1));
this.bindPort.setBounds(10, 170, 465, 20);
this.bindPort.setEditor(new JSpinner.NumberEditor(this.bindPort, "#"));
((JSpinner.DefaultEditor) this.bindPort.getEditor()).getTextField().setHorizontalAlignment(SwingConstants.LEFT);
this.contentPane.add(this.bindPort);
}
{
JLabel authMethodLabel = new JLabel("Auth Method:");
authMethodLabel.setBounds(10, 200, 100, 20);
this.contentPane.add(authMethodLabel);
this.authMethod = new JComboBox<>(new String[]{"OpenAuthMod"});
this.authMethod.setBounds(10, 220, 465, 20);
this.contentPane.add(this.authMethod);
}
{
this.betaCraftAuth = new JCheckBox("BetaCraft Auth (Classic)");
this.betaCraftAuth.setBounds(10, 250, 465, 20);
this.contentPane.add(this.betaCraftAuth);
}
{
this.stateLabel = new JLabel();
this.stateLabel.setBounds(14, 280, 465, 20);
this.stateLabel.setVisible(false);
this.contentPane.add(this.stateLabel);
}
{
this.stateButton = new JButton("Loading ViaProxy...");
this.stateButton.setBounds(10, 300, 465, 20);
this.stateButton.addActionListener(e -> {
if (this.stateButton.getText().equalsIgnoreCase("Start")) this.start();
else if (this.stateButton.getText().equalsIgnoreCase("Stop")) this.stop();
});
this.stateButton.setEnabled(false);
this.contentPane.add(this.stateButton);
}
}
private void setComponentsEnabled(final boolean state) {
this.serverAddress.setEnabled(state);
this.serverVersion.setEnabled(state);
this.bindPort.setEnabled(state);
this.authMethod.setEnabled(state);
this.betaCraftAuth.setEnabled(state);
}
private void updateStateLabel() {
this.stateLabel.setText("ViaProxy is running! Connect with Minecraft 1.7+ to 127.0.0.1:" + this.bindPort.getValue());
this.stateLabel.setVisible(true);
}
private void start() {
this.setComponentsEnabled(false);
this.stateButton.setEnabled(false);
this.stateButton.setText("Starting...");
new Thread(() -> {
final String serverAddress = this.serverAddress.getText();
final VersionEnum serverVersion = (VersionEnum) this.serverVersion.getSelectedItem();
final int bindPort = (int) this.bindPort.getValue();
final String authMethod = (String) this.authMethod.getSelectedItem();
final boolean betaCraftAuth = this.betaCraftAuth.isSelected();
try {
final HostAndPort hostAndPort = HostAndPort.fromString(serverAddress);
Options.BIND_ADDRESS = "127.0.0.1";
Options.BIND_PORT = bindPort;
Options.CONNECT_ADDRESS = hostAndPort.getHost();
Options.CONNECT_PORT = hostAndPort.getPortOrDefault(25565);
Options.PROTOCOL_VERSION = serverVersion;
Options.OPENAUTHMOD_AUTH = true;
Options.BETACRAFT_AUTH = betaCraftAuth;
ViaProxy.startProxy();
SwingUtilities.invokeLater(() -> {
this.updateStateLabel();
this.stateButton.setEnabled(true);
this.stateButton.setText("Stop");
});
} catch (Throwable e) {
SwingUtilities.invokeLater(() -> {
this.showError("Invalid server address!");
this.setComponentsEnabled(true);
this.stateButton.setEnabled(true);
this.stateButton.setText("Start");
this.stateLabel.setVisible(false);
});
}
}).start();
}
private void stop() {
ViaProxy.stopProxy();
this.stateLabel.setVisible(false);
this.stateButton.setText("Start");
this.setComponentsEnabled(true);
}
public void setReady() {
SwingUtilities.invokeLater(() -> {
this.stateButton.setText("Start");
this.stateButton.setEnabled(true);
});
}
public void showInfo(final String message) {
this.showNotification(message, JOptionPane.INFORMATION_MESSAGE);
}
public void showWarning(final String message) {
this.showNotification(message, JOptionPane.WARNING_MESSAGE);
}
public void showError(final String message) {
this.showNotification(message, JOptionPane.ERROR_MESSAGE);
}
public void showNotification(final String message, final int type) {
JOptionPane.showMessageDialog(this, message, "ViaProxy", type);
}
}

View File

@ -0,0 +1,353 @@
package net.raphimc.viaproxy.util;
import java.lang.reflect.Array;
public class ArrayHelper {
public static ArrayHelper instanceOf(final String... array) {
return new ArrayHelper(array);
}
private String[] array;
public ArrayHelper(final String[] array) {
this.array = array;
}
public int getLength() {
return this.array.length;
}
public boolean isLength(final int length) {
return this.getLength() == length;
}
public boolean isSmaller(final int length) {
return this.getLength() < length;
}
public boolean isSmallerOrEqual(final int length) {
return this.getLength() <= length;
}
public boolean isLarger(final int length) {
return this.getLength() > length;
}
public boolean isLargerOrEqual(final int length) {
return this.getLength() >= length;
}
public boolean isEmpty() {
return this.getLength() == 0;
}
public boolean isIndexValid(final int index) {
return index >= 0 && index < this.getLength();
}
public String get(final int index) {
if (!this.isIndexValid(index)) {
return null;
}
return this.array[index];
}
public boolean isString(final int index) {
return this.isIndexValid(index);
}
public boolean isBoolean(final int index) {
if (!this.isIndexValid(index)) {
return false;
}
try {
Boolean.valueOf(this.getString(index));
return true;
} catch (Exception ignored) {
}
return false;
}
public boolean isChar(final int index) {
if (!this.isIndexValid(index) || !this.isString(index)) {
return false;
}
return this.getString(index).length() == 1;
}
public boolean isShort(final int index) {
if (!this.isIndexValid(index)) {
return false;
}
try {
Short.valueOf(this.get(index));
return true;
} catch (Exception ignored) {
}
return false;
}
public boolean isInteger(final int index) {
if (!this.isIndexValid(index)) {
return false;
}
try {
Integer.valueOf(this.get(index));
return true;
} catch (Exception ignored) {
}
return false;
}
public boolean isLong(final int index) {
if (!this.isIndexValid(index)) {
return false;
}
try {
Long.valueOf(this.get(index));
return true;
} catch (Exception ignored) {
}
return false;
}
public boolean isFloat(final int index) {
if (!this.isIndexValid(index)) {
return false;
}
try {
Float.valueOf(this.get(index));
return true;
} catch (Exception ignored) {
}
return false;
}
public boolean isDouble(final int index) {
if (!this.isIndexValid(index)) {
return false;
}
try {
Double.valueOf(this.get(index));
return true;
} catch (Exception ignored) {
}
return false;
}
public String getString(final int index, final String standart) {
if (!this.isIndexValid(index) || !this.isString(index)) {
return standart;
}
return this.get(index);
}
public boolean getBoolean(final int index, final boolean standart) {
if (!this.isIndexValid(index) || !this.isBoolean(index)) {
return standart;
}
return Boolean.parseBoolean(this.getString(index));
}
public char getChar(final int index, final char standart) {
if (!this.isIndexValid(index) || !this.isChar(index)) {
return standart;
}
return this.getString(index, String.valueOf(standart)).charAt(0);
}
public short getShort(final int index, final short standart) {
if (!this.isIndexValid(index) || !this.isShort(index)) {
return standart;
}
return Short.parseShort(this.get(index));
}
public int getInteger(final int index, final int standart) {
if (!this.isIndexValid(index) || !this.isInteger(index)) {
return standart;
}
return Integer.parseInt(this.get(index));
}
public long getLong(final int index, final long standart) {
if (!this.isIndexValid(index) || !this.isLong(index)) {
return standart;
}
return Long.parseLong(this.get(index));
}
public float getFloat(final int index, final float standart) {
if (!this.isIndexValid(index) || !this.isFloat(index)) {
return standart;
}
return Float.parseFloat(this.get(index));
}
public double getDouble(final int index, final double standart) {
if (!this.isIndexValid(index) || !this.isDouble(index)) {
return standart;
}
return Double.parseDouble(this.get(index));
}
public String getString(final int index) {
return this.getString(index, "");
}
public boolean getBoolean(final int index) {
return this.getBoolean(index, false);
}
public char getChar(final int index) {
return this.getChar(index, "A".toCharArray()[0]);
}
public short getShort(final int index) {
return this.getShort(index, (short) 0);
}
public int getInteger(final int index) {
return this.getInteger(index, 0);
}
public long getLong(final int index) {
return this.getLong(index, 0);
}
public float getFloat(final int index) {
return this.getFloat(index, 0);
}
public double getDouble(final int index) {
return this.getDouble(index, 0);
}
public ArrayHelper add(final String object, final String... objects) {
this.array = this.advance(object, objects);
return this;
}
public String[] advance(final String obToAdd, final String... obs) {
String[] newArray = new String[this.getLength() + 1 + obs.length];
int i = 0;
for (String ob : this.array) {
Array.set(newArray, i, ob);
i++;
}
Array.set(newArray, i, obToAdd);
i++;
for (String ob : obs) {
Array.set(newArray, i, ob);
i++;
}
return newArray;
}
public String[] advanceToStrings(final String strToAdd, final String... strs) {
String[] newArray = new String[this.getLength() + 1 + strs.length];
int i = 0;
for (Object ob : this.array) {
newArray[i] = ob.toString();
i++;
}
newArray[i] = strToAdd;
i++;
for (String str : strs) {
newArray[i] = str;
i++;
}
return newArray;
}
public String[] getAsArray() {
return this.array;
}
public String getAsString() {
return this.getAsString(0, " ");
}
public String getAsString(final String combiner) {
return this.getAsString(0, combiner);
}
public String getAsString(final int start) {
return this.getAsString(start, " ");
}
public String getAsString(final int start, final String combiner) {
return this.getAsString(start, this.getLength() - 1, combiner);
}
public String getAsString(final int start, final int end) {
return this.getAsString(start, end, " ");
}
public String getAsString(int start, int end, final String combiner) {
if (start < 0) {
start = 0;
}
if (end > this.getLength() - 1) {
end = this.getLength() - 1;
}
if (end < start) {
return "";
}
String out = "";
for (int i = start; i <= end; i++) {
if (out.isEmpty()) {
out = this.getString(i);
} else {
out += combiner + this.getString(i);
}
}
return out;
}
@Override
public String toString() {
String complete = "";
for (String t : this.array) {
complete += (complete.isEmpty() ? "" : ", ") + t;
}
return "[" + complete + "]";
}
}

View File

@ -0,0 +1,37 @@
package net.raphimc.viaproxy.util;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
public class LocalSocketClient {
private final int port;
public LocalSocketClient(final int port) {
this.port = port;
}
public String[] request(final String command, final String... args) {
try {
final Socket socket = new Socket();
socket.setSoTimeout(500);
socket.connect(new InetSocketAddress("127.0.0.1", this.port), 500);
final DataOutputStream out = new DataOutputStream(socket.getOutputStream());
final DataInputStream in = new DataInputStream(socket.getInputStream());
out.writeUTF(command);
out.writeInt(args.length);
for (String s : args) out.writeUTF(s);
final String[] response = new String[in.readInt()];
for (int i = 0; i < response.length; i++) response[i] = in.readUTF();
socket.close();
return response;
} catch (Throwable e) {
return null;
}
}
}

View File

@ -0,0 +1,46 @@
package net.raphimc.viaproxy.util.logging;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.viaversion.viaversion.api.connection.UserConnection;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Locale;
public class Logger {
public static final org.apache.logging.log4j.Logger LOGGER = LogManager.getLogger("ViaProxy");
public static final PrintStream SYSOUT = System.out;
public static final PrintStream SYSERR = System.err;
public static void setup() {
System.setErr(new LoggerPrintStream("STDERR", SYSERR));
System.setOut(new LoggerPrintStream("STDOUT", SYSOUT));
}
public static void u_info(final String title, final SocketAddress address, final GameProfile gameProfile, final String msg) {
u_log(Level.INFO, title, address, gameProfile, msg);
}
public static void u_err(final String title, final SocketAddress address, final GameProfile gameProfile, final String msg) {
u_log(Level.ERROR, title, address, gameProfile, msg);
}
public static void u_err(final String title, final UserConnection user, final String msg) {
GameProfile gameProfile = null;
if (user.getProtocolInfo().getUsername() != null) {
gameProfile = new GameProfile(user.getProtocolInfo().getUuid(), user.getProtocolInfo().getUsername());
}
u_log(Level.ERROR, title, user.getChannel().remoteAddress(), gameProfile, msg);
}
public static void u_log(final Level level, final String title, final SocketAddress address, final GameProfile gameProfile, final String msg) {
final InetSocketAddress socketAddress = (InetSocketAddress) address;
LOGGER.log(level, "[" + title.toUpperCase(Locale.ROOT) + "] (" + socketAddress.getAddress().getHostAddress() + " | " + (gameProfile != null ? gameProfile.getName() : "null") + ") " + msg);
}
}

View File

@ -0,0 +1,32 @@
package net.raphimc.viaproxy.util.logging;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.OutputStream;
import java.io.PrintStream;
public class LoggerPrintStream extends PrintStream {
protected static final Logger LOGGER = LogManager.getLogger();
protected final String name;
public LoggerPrintStream(final String name, final OutputStream out) {
super(out);
this.name = name;
}
public void println(final String message) {
this.log(message);
}
public void println(final Object object) {
this.log(String.valueOf(object));
}
protected void log(final String message) {
LOGGER.info("[{}]: {}", this.name, message);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<!-- System out -->
<Console name="SysOut" target="SYSTEM_OUT">
<PatternLayout pattern="%style{[%d{HH:mm:ss}]}{blue} %highlight{[%t/%level]}{FATAL=red, ERROR=red, WARN=yellow, INFO=green, DEBUG=green, TRACE=blue} %style{(%logger{1})}{cyan} %highlight{%msg%n}{FATAL=red, ERROR=red, WARN=normal, INFO=normal, DEBUG=normal, TRACE=normal}" disableAnsi="false"/>
</Console>
<!-- latest.log same as vanilla -->
<RollingRandomAccessFile name="LatestFile" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level] (%logger{1}) %msg{nolookups}%n"/>
<Policies>
<TimeBasedTriggeringPolicy />
<OnStartupTriggeringPolicy />
</Policies>
</RollingRandomAccessFile>
<!-- Debug log file -->
<RollingRandomAccessFile name="DebugFile" fileName="logs/debug.log" filePattern="logs/debug-%i.log.gz">
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level] (%logger) %msg{nolookups}%n"/>
<!-- Keep 5 files max -->
<DefaultRolloverStrategy max="5" fileIndex="min"/>
<Policies>
<SizeBasedTriggeringPolicy size="200MB"/>
<OnStartupTriggeringPolicy />
</Policies>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="all">
<AppenderRef ref="DebugFile" level="debug"/>
<AppenderRef ref="SysOut" level="info"/>
<AppenderRef ref="LatestFile" level="info"/>
</Root>
</Loggers>
</Configuration>