commit 9380770492015453a895eb79d743c84b9d228c9f Author: TheMode Date: Sat Aug 3 15:25:24 2019 +0200 Initial commit diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..39c249ed7 --- /dev/null +++ b/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'java' +} + +group 'fr.themode.minestom' +version '1.0' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() + maven { url 'https://jitpack.io' } +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' + implementation 'com.github.Adamaq01:ozao-net:2.3.1' +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..94336fcae Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..3ecf9801d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Aug 01 13:49:10 CEST 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 000000000..cccdd3d51 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$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="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..f9553162f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@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 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= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +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 init + +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 + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +: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 %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="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! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..ad69ae53c --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'Minestom' + diff --git a/src/main/java/fr/themode/minestom/Main.java b/src/main/java/fr/themode/minestom/Main.java new file mode 100644 index 000000000..632f43132 --- /dev/null +++ b/src/main/java/fr/themode/minestom/Main.java @@ -0,0 +1,61 @@ +package fr.themode.minestom; + +import fr.adamaq01.ozao.net.packet.Packet; +import fr.adamaq01.ozao.net.server.Connection; +import fr.adamaq01.ozao.net.server.Server; +import fr.adamaq01.ozao.net.server.ServerHandler; +import fr.adamaq01.ozao.net.server.backend.tcp.TCPServer; +import fr.themode.minestom.net.ConnectionManager; +import fr.themode.minestom.net.PacketProcessor; +import fr.themode.minestom.net.protocol.MinecraftProtocol; + +import java.lang.reflect.InvocationTargetException; + +public class Main { + + private static ConnectionManager connectionManager; + private static PacketProcessor packetProcessor; + + public static void main(String[] args) { + + connectionManager = new ConnectionManager(); + packetProcessor = new PacketProcessor(connectionManager); + + Server server = new TCPServer(new MinecraftProtocol()).addHandler(new ServerHandler() { + @Override + public void onConnect(Server server, Connection connection) { + System.out.println("A connection"); + } + + @Override + public void onDisconnect(Server server, Connection connection) { + System.out.println("A DISCONNECTION"); + } + + @Override + public void onPacketReceive(Server server, Connection connection, Packet packet) { + try { + packetProcessor.process(connection, packet); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } + } + + @Override + public void onException(Server server, Connection connection, Throwable cause) { + cause.printStackTrace(); + } + }); + + server.bind(25565); + System.out.println("Server started"); + + } + +} diff --git a/src/main/java/fr/themode/minestom/entity/GameMode.java b/src/main/java/fr/themode/minestom/entity/GameMode.java new file mode 100644 index 000000000..0665db213 --- /dev/null +++ b/src/main/java/fr/themode/minestom/entity/GameMode.java @@ -0,0 +1,25 @@ +package fr.themode.minestom.entity; + +public enum GameMode { + + SURVIVAL((byte) 0), CREATIVE((byte) 1), ADVENTURE((byte) 2), SPECTATOR((byte) 3); + + private byte id; + private boolean hardcore; + + GameMode(byte id) { + this.id = id; + } + + public void setHardcore(boolean hardcore) { + this.hardcore = hardcore; + } + + public byte getId() { + return id; + } + + public boolean isHardcore() { + return hardcore; + } +} diff --git a/src/main/java/fr/themode/minestom/entity/Player.java b/src/main/java/fr/themode/minestom/entity/Player.java new file mode 100644 index 000000000..e490d1b21 --- /dev/null +++ b/src/main/java/fr/themode/minestom/entity/Player.java @@ -0,0 +1,12 @@ +package fr.themode.minestom.entity; + +import fr.themode.minestom.net.player.PlayerConnection; + +public class Player { + + private PlayerConnection playerConnection; + + public PlayerConnection getPlayerConnection() { + return playerConnection; + } +} diff --git a/src/main/java/fr/themode/minestom/net/ConnectionManager.java b/src/main/java/fr/themode/minestom/net/ConnectionManager.java new file mode 100644 index 000000000..3e547aa4f --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/ConnectionManager.java @@ -0,0 +1,24 @@ +package fr.themode.minestom.net; + +import fr.themode.minestom.entity.Player; +import fr.themode.minestom.net.player.PlayerConnection; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class ConnectionManager { + + private Set connections = new HashSet<>(); + private Map connectionPlayerMap = new HashMap<>(); + + public Player getPlayer(PlayerConnection connection) { + return connectionPlayerMap.get(connection); + } + + // Is only used at LoginStartPacket#process + public void createPlayer(PlayerConnection connection) { + this.connectionPlayerMap.put(connection, new Player()); + } +} diff --git a/src/main/java/fr/themode/minestom/net/ConnectionState.java b/src/main/java/fr/themode/minestom/net/ConnectionState.java new file mode 100644 index 000000000..0de9dce6c --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/ConnectionState.java @@ -0,0 +1,7 @@ +package fr.themode.minestom.net; + +public enum ConnectionState { + + UNKNOWN, STATUS, LOGIN, PLAY; + +} diff --git a/src/main/java/fr/themode/minestom/net/PacketProcessor.java b/src/main/java/fr/themode/minestom/net/PacketProcessor.java new file mode 100644 index 000000000..6a87230de --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/PacketProcessor.java @@ -0,0 +1,80 @@ +package fr.themode.minestom.net; + +import fr.adamaq01.ozao.net.Buffer; +import fr.adamaq01.ozao.net.packet.Packet; +import fr.adamaq01.ozao.net.server.Connection; +import fr.themode.minestom.entity.Player; +import fr.themode.minestom.net.packet.client.ClientPlayPacket; +import fr.themode.minestom.net.packet.client.ClientPreplayPacket; +import fr.themode.minestom.net.packet.client.handler.ClientLoginPacketsHandler; +import fr.themode.minestom.net.packet.client.handler.ClientPlayPacketsHandler; +import fr.themode.minestom.net.packet.client.handler.ClientStatusPacketsHandler; +import fr.themode.minestom.net.packet.client.handshake.HandshakePacket; +import fr.themode.minestom.net.player.PlayerConnection; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; + +import static fr.themode.minestom.net.protocol.MinecraftProtocol.PACKET_ID_IDENTIFIER; + +public class PacketProcessor { + + private Map connectionPlayerConnectionMap = new HashMap<>(); + + private ConnectionManager connectionManager; + + // Protocols + private ClientStatusPacketsHandler statusPacketsHandler; + private ClientLoginPacketsHandler loginPacketsHandler; + private ClientPlayPacketsHandler playPacketsHandler; + + public PacketProcessor(ConnectionManager connectionManager) { + this.connectionManager = connectionManager; + + this.statusPacketsHandler = new ClientStatusPacketsHandler(); + this.loginPacketsHandler = new ClientLoginPacketsHandler(); + this.playPacketsHandler = new ClientPlayPacketsHandler(); + } + + public void process(Connection connection, Packet packet) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + int id = packet.get(PACKET_ID_IDENTIFIER); + System.out.println("RECEIVED ID: " + id); + Buffer buffer = packet.getPayload(); + connectionPlayerConnectionMap.get(connection); + PlayerConnection playerConnection = connectionPlayerConnectionMap.computeIfAbsent(connection, c -> new PlayerConnection(c)); + ConnectionState connectionState = playerConnection.getConnectionState(); + + if (connectionState == ConnectionState.UNKNOWN) { + // Should be handshake packet + if (id == 0) { + HandshakePacket handshakePacket = new HandshakePacket(); + handshakePacket.read(buffer); + handshakePacket.process(playerConnection, connectionManager); + } + return; + } + + switch (connectionState) { + case PLAY: + Player player = connectionManager.getPlayer(playerConnection); + ClientPlayPacket playPacket = (ClientPlayPacket) playPacketsHandler.getPacketClass(id).getDeclaredConstructor().newInstance(); + playPacket.read(buffer); + playPacket.process(player); + break; + case LOGIN: + ClientPreplayPacket loginPacket = (ClientPreplayPacket) loginPacketsHandler.getPacketClass(id).getDeclaredConstructor().newInstance(); + loginPacket.read(buffer); + loginPacket.process(playerConnection, connectionManager); + break; + case STATUS: + ClientPreplayPacket statusPacket = (ClientPreplayPacket) statusPacketsHandler.getPacketClass(id).getDeclaredConstructor().newInstance(); + statusPacket.read(buffer); + statusPacket.process(playerConnection, connectionManager); + break; + case UNKNOWN: + // Ignore packet (unexpected) + break; + } + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/client/ClientPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/ClientPacket.java new file mode 100644 index 000000000..a63780d92 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/ClientPacket.java @@ -0,0 +1,9 @@ +package fr.themode.minestom.net.packet.client; + +import fr.adamaq01.ozao.net.Buffer; + +public interface ClientPacket { + + void read(Buffer buffer); + +} diff --git a/src/main/java/fr/themode/minestom/net/packet/client/ClientPlayPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/ClientPlayPacket.java new file mode 100644 index 000000000..ed0ef3485 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/ClientPlayPacket.java @@ -0,0 +1,10 @@ +package fr.themode.minestom.net.packet.client; + +import fr.themode.minestom.entity.Player; +import fr.themode.minestom.net.packet.client.ClientPacket; + +public interface ClientPlayPacket extends ClientPacket { + + void process(Player player); + +} diff --git a/src/main/java/fr/themode/minestom/net/packet/client/ClientPreplayPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/ClientPreplayPacket.java new file mode 100644 index 000000000..4d77055a9 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/ClientPreplayPacket.java @@ -0,0 +1,9 @@ +package fr.themode.minestom.net.packet.client; + +import fr.themode.minestom.net.ConnectionManager; +import fr.themode.minestom.net.player.PlayerConnection; + +public interface ClientPreplayPacket extends ClientPacket { + + void process(PlayerConnection connection, ConnectionManager connectionManager); +} diff --git a/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientLoginPacketsHandler.java b/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientLoginPacketsHandler.java new file mode 100644 index 000000000..f222da5c4 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientLoginPacketsHandler.java @@ -0,0 +1,11 @@ +package fr.themode.minestom.net.packet.client.handler; + +import fr.themode.minestom.net.packet.client.login.LoginStartPacket; + +public class ClientLoginPacketsHandler extends ClientPacketsHandler { + + public ClientLoginPacketsHandler() { + register(0, LoginStartPacket.class); + } + +} diff --git a/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPacketsHandler.java b/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPacketsHandler.java new file mode 100644 index 000000000..ba150ce7a --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPacketsHandler.java @@ -0,0 +1,20 @@ +package fr.themode.minestom.net.packet.client.handler; + +import fr.themode.minestom.net.packet.client.ClientPacket; + +import java.util.HashMap; +import java.util.Map; + +public class ClientPacketsHandler { + + private Map> idPacketMap = new HashMap<>(); + + public void register(int id, Class packet) { + this.idPacketMap.put(id, packet); + } + + public Class getPacketClass(int id) { + return idPacketMap.get(id); + } + +} diff --git a/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPlayPacketsHandler.java b/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPlayPacketsHandler.java new file mode 100644 index 000000000..0238a5f83 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPlayPacketsHandler.java @@ -0,0 +1,13 @@ +package fr.themode.minestom.net.packet.client.handler; + +import fr.themode.minestom.net.packet.client.play.ClientPluginMessagePacket; +import fr.themode.minestom.net.packet.client.play.ClientSettingsPacket; + +public class ClientPlayPacketsHandler extends ClientPacketsHandler { + + public ClientPlayPacketsHandler() { + register(0x05, ClientSettingsPacket.class); + register(0x0B, ClientPluginMessagePacket.class); + } + +} diff --git a/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientStatusPacketsHandler.java b/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientStatusPacketsHandler.java new file mode 100644 index 000000000..241a9afcb --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientStatusPacketsHandler.java @@ -0,0 +1,13 @@ +package fr.themode.minestom.net.packet.client.handler; + +import fr.themode.minestom.net.packet.client.status.PingPacket; +import fr.themode.minestom.net.packet.client.status.StatusRequestPacket; + +public class ClientStatusPacketsHandler extends ClientPacketsHandler { + + public ClientStatusPacketsHandler() { + register(0x00, StatusRequestPacket.class); + register(0x01, PingPacket.class); + } + +} diff --git a/src/main/java/fr/themode/minestom/net/packet/client/handshake/HandshakePacket.java b/src/main/java/fr/themode/minestom/net/packet/client/handshake/HandshakePacket.java new file mode 100644 index 000000000..41a396948 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/handshake/HandshakePacket.java @@ -0,0 +1,38 @@ +package fr.themode.minestom.net.packet.client.handshake; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.net.ConnectionManager; +import fr.themode.minestom.net.ConnectionState; +import fr.themode.minestom.net.packet.client.ClientPreplayPacket; +import fr.themode.minestom.net.player.PlayerConnection; + +import static fr.themode.minestom.utils.Utils.readString; +import static fr.themode.minestom.utils.Utils.readVarInt; + +public class HandshakePacket implements ClientPreplayPacket { + + private int nextState; + + @Override + public void read(Buffer buffer) { + int protocolVersion = readVarInt(buffer); + String serverAddress = readString(buffer); + short serverPort = buffer.getShort(); + this.nextState = readVarInt(buffer); + } + + @Override + public void process(PlayerConnection connection, ConnectionManager connectionManager) { + switch (nextState) { + case 1: + connection.setConnectionState(ConnectionState.STATUS); + break; + case 2: + connection.setConnectionState(ConnectionState.LOGIN); + break; + default: + // Unexpected error + break; + } + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/client/login/LoginStartPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/login/LoginStartPacket.java new file mode 100644 index 000000000..573f0b855 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/login/LoginStartPacket.java @@ -0,0 +1,58 @@ +package fr.themode.minestom.net.packet.client.login; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.entity.GameMode; +import fr.themode.minestom.net.ConnectionManager; +import fr.themode.minestom.net.ConnectionState; +import fr.themode.minestom.net.packet.client.ClientPreplayPacket; +import fr.themode.minestom.net.packet.server.login.JoinGamePacket; +import fr.themode.minestom.net.packet.server.login.LoginSuccessPacket; +import fr.themode.minestom.net.packet.server.play.SpawnPositionPacket; +import fr.themode.minestom.net.player.PlayerConnection; +import fr.themode.minestom.utils.Utils; +import fr.themode.minestom.world.Dimension; + +public class LoginStartPacket implements ClientPreplayPacket { + + private String username; + + @Override + public void process(PlayerConnection connection, ConnectionManager connectionManager) { + // TODO send encryption request OR directly login success + LoginSuccessPacket successPacket = new LoginSuccessPacket(username); + connection.sendPacket(successPacket); + + connection.setConnectionState(ConnectionState.PLAY); + connectionManager.createPlayer(connection); + + // TODO complete login sequence with optionals packets + JoinGamePacket joinGamePacket = new JoinGamePacket(); + joinGamePacket.entityId = 32; + joinGamePacket.gameMode = GameMode.SURVIVAL; + joinGamePacket.dimension = Dimension.OVERWORLD; + joinGamePacket.maxPlayers = 0; + joinGamePacket.levelType = "default"; + joinGamePacket.reducedDebugInfo = false; + + connection.sendPacket(joinGamePacket); + + // TODO minecraft:brand plugin message + + // TODO send server difficulty + + // TODO player abilities + + SpawnPositionPacket spawnPositionPacket = new SpawnPositionPacket(); + spawnPositionPacket.x = 50; + spawnPositionPacket.y = 50; + spawnPositionPacket.z = 50; + + // connection.sendPacket(spawnPositionPacket); + + } + + @Override + public void read(Buffer buffer) { + this.username = Utils.readString(buffer); + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPluginMessagePacket.java b/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPluginMessagePacket.java new file mode 100644 index 000000000..840526d2c --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPluginMessagePacket.java @@ -0,0 +1,23 @@ +package fr.themode.minestom.net.packet.client.play; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.entity.Player; +import fr.themode.minestom.net.packet.client.ClientPlayPacket; +import fr.themode.minestom.utils.Utils; + +public class ClientPluginMessagePacket implements ClientPlayPacket { + + private String identifier; + private byte[] data; + + @Override + public void process(Player player) { + + } + + @Override + public void read(Buffer buffer) { + this.identifier = Utils.readString(buffer); + this.data = buffer.getAllBytes(); + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/client/play/ClientSettingsPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/play/ClientSettingsPacket.java new file mode 100644 index 000000000..7a65fb46e --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/play/ClientSettingsPacket.java @@ -0,0 +1,31 @@ +package fr.themode.minestom.net.packet.client.play; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.entity.Player; +import fr.themode.minestom.net.packet.client.ClientPlayPacket; +import fr.themode.minestom.utils.Utils; + +public class ClientSettingsPacket implements ClientPlayPacket { + + private String locale; + private byte viewDistance; + // TODO chat mode + private boolean chatColors; + private byte displayedSkinParts; + // TODO main hand + + @Override + public void process(Player player) { + + } + + @Override + public void read(Buffer buffer) { + this.locale = Utils.readString(buffer); + this.viewDistance = buffer.getByte(); + Utils.readVarInt(buffer); // chat mode + this.chatColors = buffer.getBoolean(); + this.displayedSkinParts = buffer.getByte(); + Utils.readVarInt(buffer); // main hand + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/client/play/TestPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/play/TestPacket.java new file mode 100644 index 000000000..c4ab90351 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/play/TestPacket.java @@ -0,0 +1,18 @@ +package fr.themode.minestom.net.packet.client.play; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.entity.Player; +import fr.themode.minestom.net.packet.client.ClientPlayPacket; + +public class TestPacket implements ClientPlayPacket { + + @Override + public void process(Player player) { + + } + + @Override + public void read(Buffer buffer) { + System.out.println("Hey c'est moi"); + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/client/status/PingPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/status/PingPacket.java new file mode 100644 index 000000000..04764bdc3 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/status/PingPacket.java @@ -0,0 +1,24 @@ +package fr.themode.minestom.net.packet.client.status; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.net.ConnectionManager; +import fr.themode.minestom.net.packet.client.ClientPreplayPacket; +import fr.themode.minestom.net.packet.server.status.PongPacket; +import fr.themode.minestom.net.player.PlayerConnection; + +public class PingPacket implements ClientPreplayPacket { + + private long number; + + @Override + public void process(PlayerConnection connection, ConnectionManager connectionManager) { + PongPacket pongPacket = new PongPacket(number); + connection.sendPacket(pongPacket); + connection.getConnection().close(); + } + + @Override + public void read(Buffer buffer) { + this.number = buffer.getLong(); + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/client/status/StatusRequestPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/status/StatusRequestPacket.java new file mode 100644 index 000000000..656dd857a --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/status/StatusRequestPacket.java @@ -0,0 +1,21 @@ +package fr.themode.minestom.net.packet.client.status; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.net.ConnectionManager; +import fr.themode.minestom.net.packet.client.ClientPreplayPacket; +import fr.themode.minestom.net.packet.server.handshake.ResponsePacket; +import fr.themode.minestom.net.player.PlayerConnection; + +public class StatusRequestPacket implements ClientPreplayPacket { + + @Override + public void process(PlayerConnection connection, ConnectionManager connectionManager) { + ResponsePacket responsePacket = new ResponsePacket(); + connection.sendPacket(responsePacket); + } + + @Override + public void read(Buffer buffer) { + // Empty + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/ServerPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/ServerPacket.java new file mode 100644 index 000000000..a2c9c9d93 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/ServerPacket.java @@ -0,0 +1,11 @@ +package fr.themode.minestom.net.packet.server; + +import fr.adamaq01.ozao.net.Buffer; + +public interface ServerPacket { + + void write(Buffer buffer); + + int getId(); + +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/handshake/ResponsePacket.java b/src/main/java/fr/themode/minestom/net/packet/server/handshake/ResponsePacket.java new file mode 100644 index 000000000..9fbcae91d --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/handshake/ResponsePacket.java @@ -0,0 +1,39 @@ +package fr.themode.minestom.net.packet.server.handshake; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.net.packet.server.ServerPacket; +import fr.themode.minestom.utils.Utils; + +public class ResponsePacket implements ServerPacket { + + private static final String JSON_EXAMPLE = "{\n" + + " \"version\": {\n" + + " \"name\": \"1.14.4\",\n" + + " \"protocol\": 498\n" + + " },\n" + + " \"players\": {\n" + + " \"max\": 100,\n" + + " \"online\": 1,\n" + + " \"sample\": [\n" + + " {\n" + + " \"name\": \"TheMode\",\n" + + " \"id\": \"4566e69f-c907-48ee-8d71-d7ba5aa00d20\"\n" + + " }\n" + + " ]\n" + + " },\t\n" + + " \"description\": {\n" + + " \"text\": \"Wallah les cubes\"\n" + + " },\n" + + " \"favicon\": \"data:image/png;base64,\"\n" + + "}"; + + @Override + public void write(Buffer buffer) { + Utils.writeString(buffer, JSON_EXAMPLE); + } + + @Override + public int getId() { + return 0x00; + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/login/JoinGamePacket.java b/src/main/java/fr/themode/minestom/net/packet/server/login/JoinGamePacket.java new file mode 100644 index 000000000..506bfdb0a --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/login/JoinGamePacket.java @@ -0,0 +1,37 @@ +package fr.themode.minestom.net.packet.server.login; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.entity.GameMode; +import fr.themode.minestom.net.packet.server.ServerPacket; +import fr.themode.minestom.utils.Utils; +import fr.themode.minestom.world.Dimension; + +public class JoinGamePacket implements ServerPacket { + + public int entityId; + public GameMode gameMode = GameMode.SURVIVAL; + public Dimension dimension = Dimension.OVERWORLD; + public byte maxPlayers = 0; // Unused + public String levelType = "default"; + public boolean reducedDebugInfo = false; + + @Override + public void write(Buffer buffer) { + int gameModeId = gameMode.getId(); + if (gameMode.isHardcore()) + gameModeId |= 8; + + buffer.putInt(entityId); + buffer.putByte((byte) gameModeId); + buffer.putInt(dimension.getId()); + buffer.putByte(maxPlayers); + Utils.writeString(buffer, levelType); + Utils.writeVarInt(buffer, 8); + buffer.putBoolean(reducedDebugInfo); + } + + @Override + public int getId() { + return 0x25; + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/login/LoginSuccessPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/login/LoginSuccessPacket.java new file mode 100644 index 000000000..96ea98d18 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/login/LoginSuccessPacket.java @@ -0,0 +1,27 @@ +package fr.themode.minestom.net.packet.server.login; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.net.packet.server.ServerPacket; +import fr.themode.minestom.utils.Utils; + +import java.util.UUID; + +public class LoginSuccessPacket implements ServerPacket { + + public String username; + + public LoginSuccessPacket(String username) { + this.username = username; + } + + @Override + public void write(Buffer buffer) { + Utils.writeString(buffer, UUID.randomUUID().toString()); // TODO mojang auth + Utils.writeString(buffer, username); + } + + @Override + public int getId() { + return 0x02; + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnPositionPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnPositionPacket.java new file mode 100644 index 000000000..4fa53a05c --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/SpawnPositionPacket.java @@ -0,0 +1,20 @@ +package fr.themode.minestom.net.packet.server.play; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.net.packet.server.ServerPacket; +import fr.themode.minestom.utils.Utils; + +public class SpawnPositionPacket implements ServerPacket { + + public int x, y, z; + + @Override + public void write(Buffer buffer) { + Utils.writePosition(buffer, x, y, z); + } + + @Override + public int getId() { + return 0x49; + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/WindowItemsPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/WindowItemsPacket.java new file mode 100644 index 000000000..4b255af34 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/WindowItemsPacket.java @@ -0,0 +1,25 @@ +package fr.themode.minestom.net.packet.server.play; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.net.packet.server.ServerPacket; + +public class WindowItemsPacket implements ServerPacket { + + public byte windowId; + public short count; + + // TODO slot data (Array of Slot) + + @Override + public void write(Buffer buffer) { + buffer.putByte(windowId); + buffer.putShort(count); + // TODO replace with actual array of slot + buffer.putBoolean(false); // Not present + } + + @Override + public int getId() { + return 0x15; + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/status/PongPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/status/PongPacket.java new file mode 100644 index 000000000..dc38e489e --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/server/status/PongPacket.java @@ -0,0 +1,23 @@ +package fr.themode.minestom.net.packet.server.status; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.net.packet.server.ServerPacket; + +public class PongPacket implements ServerPacket { + + public long number; + + public PongPacket(long number) { + this.number = number; + } + + @Override + public void write(Buffer buffer) { + buffer.putLong(number); + } + + @Override + public int getId() { + return 0x01; + } +} diff --git a/src/main/java/fr/themode/minestom/net/player/PlayerConnection.java b/src/main/java/fr/themode/minestom/net/player/PlayerConnection.java new file mode 100644 index 000000000..a442a06b3 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/player/PlayerConnection.java @@ -0,0 +1,41 @@ +package fr.themode.minestom.net.player; + +import fr.adamaq01.ozao.net.Buffer; +import fr.adamaq01.ozao.net.packet.Packet; +import fr.adamaq01.ozao.net.server.Connection; +import fr.themode.minestom.net.ConnectionState; +import fr.themode.minestom.net.packet.server.ServerPacket; + +import static fr.themode.minestom.net.protocol.MinecraftProtocol.PACKET_ID_IDENTIFIER; + +public class PlayerConnection { + + private Connection connection; + private ConnectionState connectionState; + + public PlayerConnection(Connection connection) { + this.connection = connection; + this.connectionState = ConnectionState.UNKNOWN; + } + + public void sendPacket(ServerPacket serverPacket) { + Packet packet = Packet.create(); + Buffer buffer = packet.getPayload(); + serverPacket.write(buffer); + packet.put(PACKET_ID_IDENTIFIER, serverPacket.getId()); + + this.connection.sendPacket(packet); + } + + public Connection getConnection() { + return connection; + } + + public void setConnectionState(ConnectionState connectionState) { + this.connectionState = connectionState; + } + + public ConnectionState getConnectionState() { + return connectionState; + } +} diff --git a/src/main/java/fr/themode/minestom/net/protocol/MinecraftProtocol.java b/src/main/java/fr/themode/minestom/net/protocol/MinecraftProtocol.java new file mode 100644 index 000000000..b6b4224e9 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/protocol/MinecraftProtocol.java @@ -0,0 +1,67 @@ +package fr.themode.minestom.net.protocol; + +import fr.adamaq01.ozao.net.Buffer; +import fr.adamaq01.ozao.net.packet.Packet; +import fr.adamaq01.ozao.net.protocol.Protocol; + +import java.util.ArrayList; +import java.util.Collection; + +import static fr.themode.minestom.utils.Utils.readVarInt; +import static fr.themode.minestom.utils.Utils.writeVarInt; + +public class MinecraftProtocol extends Protocol { + + public static final String PACKET_ID_IDENTIFIER = "id"; + + public MinecraftProtocol() { + super("minecraft"); + } + + @Override + public boolean verify(Buffer buffer) { + int length = readVarInt(buffer); + int realLength = buffer.slice(buffer.readerIndex()).length(); + int id = readVarInt(buffer); + buffer.readerIndex(0); + return length == realLength && id >= 0; + } + + @Override + public boolean verify(Packet packet) { + return packet.get("id") != null; + } + + @Override + public Collection cut(Buffer buffer) { + ArrayList buffers = new ArrayList<>(); + int read = 0; + while (read < buffer.length()) { + int lengthLength = buffer.readerIndex(read).readerIndex(); + int length = readVarInt(buffer); + lengthLength = buffer.readerIndex() - lengthLength; + buffers.add(buffer.sliceCopy(read, length + lengthLength)); + read += length + lengthLength; + } + return buffers; + } + + @Override + public Packet decode(Buffer buffer) { + int length = readVarInt(buffer); + int id = readVarInt(buffer); + Buffer packetPayload = buffer.sliceCopy(buffer.readerIndex()); + return Packet.create(packetPayload).put(PACKET_ID_IDENTIFIER, id); + } + + @Override + public Buffer encode(Packet packet) { + Buffer buffer = Buffer.create(); + Buffer idAndPayload = Buffer.create(); + writeVarInt(idAndPayload, packet.get(PACKET_ID_IDENTIFIER)); + idAndPayload.putBuffer(packet.getPayload()); + writeVarInt(buffer, idAndPayload.length()); + buffer.putBuffer(idAndPayload); + return buffer; + } +} diff --git a/src/main/java/fr/themode/minestom/utils/Utils.java b/src/main/java/fr/themode/minestom/utils/Utils.java new file mode 100644 index 000000000..99860987c --- /dev/null +++ b/src/main/java/fr/themode/minestom/utils/Utils.java @@ -0,0 +1,111 @@ +package fr.themode.minestom.utils; + +import fr.adamaq01.ozao.net.Buffer; + +import java.io.UnsupportedEncodingException; + +public class Utils { + + public static void writeString(Buffer buffer, String value) { + byte[] bytes = new byte[0]; + try { + bytes = value.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + if (bytes.length > 32767) { + System.out.println("String too big (was " + value.length() + " bytes encoded, max " + 32767 + ")"); + } else { + writeVarInt(buffer, bytes.length); + buffer.putBytes(bytes); + } + } + + public static String readString(Buffer buffer) { + int length = readVarInt(buffer); + byte bytes[] = buffer.getBytes(length); + try { + return new String(bytes, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return null; + } + + public static void writeVarInt(Buffer buffer, int value) { + do { + byte temp = (byte) (value & 0b01111111); + value >>>= 7; + if (value != 0) { + temp |= 0b10000000; + } + buffer.putByte(temp); + } while (value != 0); + } + + public static int readVarInt(Buffer buffer) { + int numRead = 0; + int result = 0; + byte read; + do { + read = buffer.getByte(); + int value = (read & 0b01111111); + result |= (value << (7 * numRead)); + + numRead++; + if (numRead > 5) { + throw new RuntimeException("VarInt is too big"); + } + } while ((read & 0b10000000) != 0); + + return result; + } + + // ?? + public static int lengthVarInt(int value) { + int i = 0; + do { + i++; + byte temp = (byte) (value & 0b01111111); + value >>>= 7; + if (value != 0) { + temp |= 0b10000000; + } + } while (value != 0); + return i; + } + + public static void writeVarLong(Buffer buffer, long value) { + do { + byte temp = (byte) (value & 0b01111111); + value >>>= 7; + if (value != 0) { + temp |= 0b10000000; + } + buffer.putByte(temp); + } while (value != 0); + } + + public static long readVarLong(Buffer buffer) { + int numRead = 0; + long result = 0; + byte read; + do { + read = buffer.getByte(); + int value = (read & 0b01111111); + result |= (value << (7 * numRead)); + + numRead++; + if (numRead > 10) { + throw new RuntimeException("VarLong is too big"); + } + } while ((read & 0b10000000) != 0); + + return result; + } + + public static void writePosition(Buffer buffer, int x, int y, int z) { + buffer.putLong(((x & 0x3FFFFFF) << 38) | ((y & 0xFFF) << 26) | (z & 0x3FFFFFF)); + } + +} diff --git a/src/main/java/fr/themode/minestom/world/Difficulty.java b/src/main/java/fr/themode/minestom/world/Difficulty.java new file mode 100644 index 000000000..13aefba81 --- /dev/null +++ b/src/main/java/fr/themode/minestom/world/Difficulty.java @@ -0,0 +1,16 @@ +package fr.themode.minestom.world; + +public enum Difficulty { + + PEACEFUL((byte) 0), EASY((byte) 1), NORMAL((byte) 2), HARD((byte) 3); + + private byte id; + + Difficulty(byte id) { + this.id = id; + } + + public byte getId() { + return id; + } +} diff --git a/src/main/java/fr/themode/minestom/world/Dimension.java b/src/main/java/fr/themode/minestom/world/Dimension.java new file mode 100644 index 000000000..80dc05a0d --- /dev/null +++ b/src/main/java/fr/themode/minestom/world/Dimension.java @@ -0,0 +1,16 @@ +package fr.themode.minestom.world; + +public enum Dimension { + + NETHER(-1), OVERWORLD(0), END(1); + + private int id; + + Dimension(int id) { + this.id = id; + } + + public int getId() { + return id; + } +}