From 84bfd9506c88cca8bbe94a0d9b44bf84b7a868a3 Mon Sep 17 00:00:00 2001 From: TheBrokenRail Date: Sat, 10 Oct 2020 19:02:13 -0400 Subject: [PATCH] Add Dedicated Server --- Dockerfile => Dockerfile.client | 2 +- Dockerfile.server | 7 + debian/{ => client}/DEBIAN/control | 0 debian/client/DEBIAN/postinst | 7 + debian/{ => client}/usr/bin/minecraft-pi | 7 +- .../share/applications/minecraft-pi.desktop | 0 .../minecraft-pi/client}/docker-compose.yml | 2 +- .../usr/share/pixmaps/minecraft-pi.png | Bin debian/server/DEBIAN/control | 7 + debian/{ => server}/DEBIAN/postinst | 2 +- debian/server/usr/bin/minecraft-pi-server | 10 + .../minecraft-pi/server/docker-compose.yml | 7 + mods/CMakeLists.txt | 8 +- mods/src/compat.c | 86 +++++-- mods/src/cxx11_util.cpp | 18 ++ mods/src/cxx11_util.h | 21 ++ mods/src/extra.c | 83 ++++-- mods/src/extra.cpp | 9 +- mods/src/extra.h | 1 + mods/src/server/server.cpp | 237 ++++++++++++++++++ mods/src/server/server.h | 19 ++ mods/src/server/server_properties.cpp | 37 +++ mods/src/server/server_properties.h | 22 ++ scripts/build.sh | 3 +- scripts/package.sh | 5 +- 25 files changed, 537 insertions(+), 63 deletions(-) rename Dockerfile => Dockerfile.client (95%) create mode 100644 Dockerfile.server rename debian/{ => client}/DEBIAN/control (100%) create mode 100755 debian/client/DEBIAN/postinst rename debian/{ => client}/usr/bin/minecraft-pi (77%) rename debian/{ => client}/usr/share/applications/minecraft-pi.desktop (100%) rename debian/{usr/share/minecraft-pi => client/usr/share/minecraft-pi/client}/docker-compose.yml (88%) rename debian/{ => client}/usr/share/pixmaps/minecraft-pi.png (100%) create mode 100644 debian/server/DEBIAN/control rename debian/{ => server}/DEBIAN/postinst (51%) create mode 100755 debian/server/usr/bin/minecraft-pi-server create mode 100644 debian/server/usr/share/minecraft-pi/server/docker-compose.yml create mode 100644 mods/src/cxx11_util.cpp create mode 100644 mods/src/cxx11_util.h create mode 100644 mods/src/server/server.cpp create mode 100644 mods/src/server/server.h create mode 100644 mods/src/server/server_properties.cpp create mode 100644 mods/src/server/server_properties.h diff --git a/Dockerfile b/Dockerfile.client similarity index 95% rename from Dockerfile rename to Dockerfile.client index fecf5312f..ab90ef452 100644 --- a/Dockerfile +++ b/Dockerfile.client @@ -1,4 +1,4 @@ -FROM arm64v8/debian:bullseye +FROM debian:bullseye RUN dpkg --add-architecture armhf diff --git a/Dockerfile.server b/Dockerfile.server new file mode 100644 index 000000000..79f6a0c5b --- /dev/null +++ b/Dockerfile.server @@ -0,0 +1,7 @@ +FROM thebrokenrail/minecraft-pi:client + +ENV MCPI_SERVER=1 + +RUN apt-get install -y xvfb + +ENTRYPOINT xvfb-run ./launcher diff --git a/debian/DEBIAN/control b/debian/client/DEBIAN/control similarity index 100% rename from debian/DEBIAN/control rename to debian/client/DEBIAN/control diff --git a/debian/client/DEBIAN/postinst b/debian/client/DEBIAN/postinst new file mode 100755 index 000000000..acd43e52a --- /dev/null +++ b/debian/client/DEBIAN/postinst @@ -0,0 +1,7 @@ +#!/bin/sh + +set -e + +docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + +MCPI_FEATURES='' MCPI_USERNAME='' docker-compose -f /usr/share/minecraft-pi/client/docker-compose.yml pull diff --git a/debian/usr/bin/minecraft-pi b/debian/client/usr/bin/minecraft-pi similarity index 77% rename from debian/usr/bin/minecraft-pi rename to debian/client/usr/bin/minecraft-pi index 8c0f0e2d9..adf4c5b45 100755 --- a/debian/usr/bin/minecraft-pi +++ b/debian/client/usr/bin/minecraft-pi @@ -26,10 +26,9 @@ fi xhost local:root # Launch Minecraft -DOCKER_COMPOSE="docker-compose -f /usr/share/minecraft-pi/docker-compose.yml" -${DOCKER_COMPOSE} pull | zenity --class 'Minecraft - Pi edition' --progress --pulsate --no-cancel --auto-close --text 'Updating Minecraft...' -${DOCKER_COMPOSE} up -${DOCKER_COMPOSE} down +DOCKER_COMPOSE="docker-compose -f /usr/share/minecraft-pi/client/docker-compose.yml" +(${DOCKER_COMPOSE} pull || :) | zenity --class 'Minecraft - Pi edition' --progress --pulsate --no-cancel --auto-close --text 'Updating Minecraft...' +${DOCKER_COMPOSE} run --rm minecraft-pi || : # Kill VirGL kill "${VIRGL_PID}" diff --git a/debian/usr/share/applications/minecraft-pi.desktop b/debian/client/usr/share/applications/minecraft-pi.desktop similarity index 100% rename from debian/usr/share/applications/minecraft-pi.desktop rename to debian/client/usr/share/applications/minecraft-pi.desktop diff --git a/debian/usr/share/minecraft-pi/docker-compose.yml b/debian/client/usr/share/minecraft-pi/client/docker-compose.yml similarity index 88% rename from debian/usr/share/minecraft-pi/docker-compose.yml rename to debian/client/usr/share/minecraft-pi/client/docker-compose.yml index 4732da76a..a4fb39085 100644 --- a/debian/usr/share/minecraft-pi/docker-compose.yml +++ b/debian/client/usr/share/minecraft-pi/client/docker-compose.yml @@ -1,7 +1,7 @@ version: '3.7' services: minecraft-pi: - image: thebrokenrail/minecraft-pi + image: 'thebrokenrail/minecraft-pi:client' network_mode: 'host' volumes: - '/tmp/.X11-unix:/tmp/.X11-unix' diff --git a/debian/usr/share/pixmaps/minecraft-pi.png b/debian/client/usr/share/pixmaps/minecraft-pi.png similarity index 100% rename from debian/usr/share/pixmaps/minecraft-pi.png rename to debian/client/usr/share/pixmaps/minecraft-pi.png diff --git a/debian/server/DEBIAN/control b/debian/server/DEBIAN/control new file mode 100644 index 000000000..5adfb8a4e --- /dev/null +++ b/debian/server/DEBIAN/control @@ -0,0 +1,7 @@ +Package: minecraft-pi-server +Version: 1.0.0 +Maintainer: TheBrokenRail +Description: Fun with Blocks +Homepage: https://www.minecraft.net/en-us/edition/pi +Architecture: amd64 +Depends: docker.io, docker-compose diff --git a/debian/DEBIAN/postinst b/debian/server/DEBIAN/postinst similarity index 51% rename from debian/DEBIAN/postinst rename to debian/server/DEBIAN/postinst index d468ad389..6015d64fa 100755 --- a/debian/DEBIAN/postinst +++ b/debian/server/DEBIAN/postinst @@ -4,4 +4,4 @@ set -e docker run --rm --privileged multiarch/qemu-user-static --reset -p yes -MCPI_FEATURES='' docker-compose -f /usr/share/minecraft-pi/docker-compose.yml pull +MCPI_ROOT='' docker-compose -f /usr/share/minecraft-pi/server/docker-compose.yml pull diff --git a/debian/server/usr/bin/minecraft-pi-server b/debian/server/usr/bin/minecraft-pi-server new file mode 100755 index 000000000..77f258432 --- /dev/null +++ b/debian/server/usr/bin/minecraft-pi-server @@ -0,0 +1,10 @@ +#!/bin/sh + +set -e + +export MCPI_ROOT="${PWD}" + +# Launch Minecraft +DOCKER_COMPOSE="docker-compose -f /usr/share/minecraft-pi/server/docker-compose.yml" +${DOCKER_COMPOSE} pull || : +${DOCKER_COMPOSE} run --rm minecraft-pi diff --git a/debian/server/usr/share/minecraft-pi/server/docker-compose.yml b/debian/server/usr/share/minecraft-pi/server/docker-compose.yml new file mode 100644 index 000000000..9875c17f3 --- /dev/null +++ b/debian/server/usr/share/minecraft-pi/server/docker-compose.yml @@ -0,0 +1,7 @@ +version: '3.7' +services: + minecraft-pi: + image: 'thebrokenrail/minecraft-pi:server' + network_mode: 'host' + volumes: + - '${MCPI_ROOT}:/root/.minecraft' diff --git a/mods/CMakeLists.txt b/mods/CMakeLists.txt index 3b531b832..8f99dafb2 100644 --- a/mods/CMakeLists.txt +++ b/mods/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.1.0) project(mods) add_compile_options(-Wall -Wextra -Werror) +add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) add_subdirectory(../core core) @@ -11,8 +12,11 @@ include_directories(include) add_library(core SHARED src/core.c) target_link_libraries(core dl) -add_library(extra SHARED src/extra.c src/extra.cpp) -target_link_libraries(extra core dl) +add_library(server SHARED src/server/server.cpp src/server/server_properties.cpp) +target_link_libraries(server core dl SDL) + +add_library(extra SHARED src/extra.c src/extra.cpp src/cxx11_util.cpp) +target_link_libraries(extra core dl server) find_package(glfw3 3.3 REQUIRED) diff --git a/mods/src/compat.c b/mods/src/compat.c index 77dc6237b..5961c9a04 100644 --- a/mods/src/compat.c +++ b/mods/src/compat.c @@ -28,6 +28,8 @@ static Window x11_window; static Window x11_root_window; static int window_loaded = 0; +static int is_server = 0; + // Get Reference To X Window static void store_x11_window() { x11_display = glfwGetX11Display(); @@ -184,10 +186,17 @@ HOOK(SDL_WM_SetCaption, void, (const char *title, __attribute__((unused)) const exit(1); } - glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + if (is_server) { + // Don't Show Window In Server Mode + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + } else { + // Create OpenGL ES 1.1 Context + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + } glfw_window = glfwCreateWindow(840, 480, title, NULL, NULL); if (!glfw_window) { @@ -195,19 +204,27 @@ HOOK(SDL_WM_SetCaption, void, (const char *title, __attribute__((unused)) const exit(1); } - glfwSetKeyCallback(glfw_window, glfw_key); - glfwSetCharCallback(glfw_window, glfw_char); - glfwSetCursorPosCallback(glfw_window, glfw_motion); - glfwSetMouseButtonCallback(glfw_window, glfw_click); - glfwSetScrollCallback(glfw_window, glfw_scroll); + if (!is_server) { + // Don't Process Events In Server Mode + glfwSetKeyCallback(glfw_window, glfw_key); + glfwSetCharCallback(glfw_window, glfw_char); + glfwSetCursorPosCallback(glfw_window, glfw_motion); + glfwSetMouseButtonCallback(glfw_window, glfw_click); + glfwSetScrollCallback(glfw_window, glfw_scroll); + } store_x11_window(); - glfwMakeContextCurrent(glfw_window); + if (!is_server) { + glfwMakeContextCurrent(glfw_window); + } } HOOK(eglSwapBuffers, EGLBoolean, (__attribute__((unused)) EGLDisplay display, __attribute__((unused)) EGLSurface surface)) { - glfwSwapBuffers(glfw_window); + if (!is_server) { + // Don't Swap Buffers In A Context-Less Window + glfwSwapBuffers(glfw_window); + } return EGL_TRUE; } @@ -345,28 +362,42 @@ HOOK(SDL_Quit, void, ()) { glfwTerminate(); } +static SDL_GrabMode fake_grab_mode = SDL_GRAB_OFF; + // Fix SDL Cursor Visibility/Grabbing HOOK(SDL_WM_GrabInput, SDL_GrabMode, (SDL_GrabMode mode)) { - if (mode != SDL_GRAB_QUERY && mode != SDL_WM_GrabInput(SDL_GRAB_QUERY)) { - glfwSetInputMode(glfw_window, GLFW_CURSOR, mode == SDL_GRAB_OFF ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED); - glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, mode == SDL_GRAB_OFF ? GLFW_FALSE : GLFW_TRUE); - - // GLFW Cursor Hiding is Broken - if (window_loaded) { - if (mode == SDL_GRAB_OFF) { - XFixesShowCursor(x11_display, x11_window); - } else { - XFixesHideCursor(x11_display, x11_window); - } - XFlush(x11_display); + if (is_server) { + // Don't Grab Input In Server/Headless Mode + if (mode != SDL_GRAB_QUERY) { + fake_grab_mode = mode; } + return fake_grab_mode; + } else { + if (mode != SDL_GRAB_QUERY && mode != SDL_WM_GrabInput(SDL_GRAB_QUERY)) { + glfwSetInputMode(glfw_window, GLFW_CURSOR, mode == SDL_GRAB_OFF ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED); + glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, mode == SDL_GRAB_OFF ? GLFW_FALSE : GLFW_TRUE); + + // GLFW Cursor Hiding is Broken + if (window_loaded) { + if (mode == SDL_GRAB_OFF) { + XFixesShowCursor(x11_display, x11_window); + } else { + XFixesHideCursor(x11_display, x11_window); + } + XFlush(x11_display); + } + } + return mode == SDL_GRAB_QUERY ? (glfwGetInputMode(glfw_window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL ? SDL_GRAB_OFF : SDL_GRAB_ON) : mode; } - return mode == SDL_GRAB_QUERY ? (glfwGetInputMode(glfw_window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL ? SDL_GRAB_OFF : SDL_GRAB_ON) : mode; } // Stub SDL Cursor Visibility HOOK(SDL_ShowCursor, int, (int toggle)) { - return toggle == SDL_QUERY ? (glfwGetInputMode(glfw_window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL ? SDL_ENABLE : SDL_DISABLE) : toggle; + if (is_server) { + return toggle == SDL_QUERY ? (fake_grab_mode == SDL_GRAB_OFF ? SDL_ENABLE : SDL_DISABLE) : toggle; + } else { + return toggle == SDL_QUERY ? (glfwGetInputMode(glfw_window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL ? SDL_ENABLE : SDL_DISABLE) : toggle; + } } // SDL Stub @@ -430,6 +461,9 @@ HOOK(eglTerminate, EGLBoolean, (__attribute__((unused)) EGLDisplay display)) { // Use VirGL __attribute__((constructor)) static void init() { + is_server = get_is_server(); setenv("LIBGL_ALWAYS_SOFTWARE", "1", 1); - setenv("GALLIUM_DRIVER", "virpipe", 1); + if (!is_server) { + setenv("GALLIUM_DRIVER", "virpipe", 1); + } } diff --git a/mods/src/cxx11_util.cpp b/mods/src/cxx11_util.cpp new file mode 100644 index 000000000..180cc1b36 --- /dev/null +++ b/mods/src/cxx11_util.cpp @@ -0,0 +1,18 @@ +// Use C++11 ABI +#undef _GLIBCXX_USE_CXX11_ABI +#define _GLIBCXX_USE_CXX11_ABI 1 + +#include + +#include "cxx11_util.h" + +// Convert A C-String into A C++11 String That Can be Acessed In C++03 Code +cxx11_string create_cxx11_string(const char *str) { + std::string *new_str = new std::string(str); + int32_t new_size = sizeof (cxx11_string); + int32_t old_size = sizeof *new_str; + if (new_size != old_size) { + fprintf(stderr, "Mismatched String Size: Expected: %i Real: %i\n", new_size, old_size); + } + return *reinterpret_cast(new_str); +} diff --git a/mods/src/cxx11_util.h b/mods/src/cxx11_util.h new file mode 100644 index 000000000..9823f8773 --- /dev/null +++ b/mods/src/cxx11_util.h @@ -0,0 +1,21 @@ +#ifndef CXX_11_UTIL_H + +#define CXX_11_UTIL + +#ifdef __cplusplus +extern "C" { +#endif + +#define CXX11_STRING_SIZE 24 + +struct cxx11_string { + unsigned char data[CXX11_STRING_SIZE]; +}; + +cxx11_string create_cxx11_string(const char *str); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/mods/src/extra.c b/mods/src/extra.c index 7852a39d8..4d407ff61 100644 --- a/mods/src/extra.c +++ b/mods/src/extra.c @@ -5,6 +5,7 @@ #include #include "extra.h" +#include "server/server.h" static uint32_t getSpawnMobs_injection(__attribute__((unused)) int32_t obj) { return 1; @@ -66,23 +67,6 @@ static void handleClick_injection(unsigned char *this, unsigned char *param_2, u } } -int has_feature(const char *name) { - char *env = getenv("MCPI_FEATURES"); - char *features = strdup(env != NULL ? env : ""); - char *tok = strtok(features, "|"); - int ret = 0; - while (tok != NULL) { - if (strcmp(tok, name) == 0) { - ret = 1; - break; - } - tok = strtok(NULL, "|"); - } - free(features); - fprintf(stderr, "Feature: %s: %s\n", name, ret ? "Enabled" : "Disabled"); - return ret; -} - // Patch Game Mode static void set_is_survival(int new_is_survival) { if (is_survival != new_is_survival) { @@ -137,19 +121,62 @@ static void minecraft_init_injection(unsigned char *this) { *(this + 83) = 1; } +// Is Dedicated Server +static int is_server = 0; + +// Check For Feature +int has_feature(const char *name) { + if (is_server) { + // Enable All Features In Server + return 1; + } else { + char *env = getenv("MCPI_FEATURES"); + char *features = strdup(env != NULL ? env : ""); + char *tok = strtok(features, "|"); + int ret = 0; + while (tok != NULL) { + if (strcmp(tok, name) == 0) { + ret = 1; + break; + } + tok = strtok(NULL, "|"); + } + free(features); + fprintf(stderr, "Feature: %s: %s\n", name, ret ? "Enabled" : "Disabled"); + return ret; + } +} + +int get_is_server() { + return getenv("MCPI_SERVER") != NULL; +} + __attribute__((constructor)) static void init() { + is_server = get_is_server(); + if (is_server) { + server_init(); + } + if (has_feature("Touch GUI")) { // Use Touch UI unsigned char touch_gui_patch[4] = {0x01, 0x00, 0x50, 0xe3}; patch((void *) 0x292fc, touch_gui_patch); } + // Get Default Game Mode + int default_game_mode; + if (is_server) { + default_game_mode = server_get_default_game_mode(); + } else { + default_game_mode = !has_feature("Survival Mode"); + } + // Dyanmic Game Mode Switching - set_is_survival(0); + set_is_survival(!default_game_mode); setIsCreativeMode_original = overwrite((void *) setIsCreativeMode, setIsCreativeMode_injection); // Set Default Game Mode - unsigned char default_game_mode_patch[4] = {has_feature("Survival Mode") ? 0x00 : 0x01, 0x30, 0xa0, 0xe3}; + unsigned char default_game_mode_patch[4] = {default_game_mode ? 0x01 : 0x00, 0x30, 0xa0, 0xe3}; patch((void *) 0xba744, default_game_mode_patch); // Disable Item Dropping When Cursor Is Hidden @@ -170,7 +197,13 @@ __attribute__((constructor)) static void init() { patch((void *) 0x15b0c, instamine_patch); } - if (has_feature("Mob Spawning")) { + int mob_spawning; + if (is_server) { + mob_spawning = server_get_mob_spawning(); + } else { + mob_spawning = has_feature("Mob Spawning"); + } + if (mob_spawning) { // Enable Mob Spawning overwrite((void *) 0xbabec, getSpawnMobs_injection); } @@ -193,8 +226,14 @@ __attribute__((constructor)) static void init() { patch((void *) 0x6dc70, patch_data_9); // Change Username - const char *username = get_username(); - fprintf(stderr, "Setting Username: %s\n", username); + const char *username; + if (is_server) { + // MOTD is Username + username = server_get_motd(); + } else { + username = get_username(); + fprintf(stderr, "Setting Username: %s\n", username); + } patch_address((void *) 0x18fd4, (void *) username); if (has_feature("Disable Autojump By Default")) { diff --git a/mods/src/extra.cpp b/mods/src/extra.cpp index 2eea159b1..364c8c723 100644 --- a/mods/src/extra.cpp +++ b/mods/src/extra.cpp @@ -8,14 +8,17 @@ #include #include "extra.h" +#include "cxx11_util.h" + +#include extern "C" { - static std::string readAssetFile(__attribute__((unused)) unsigned char *obj, const std::string& path) { + static cxx11_string readAssetFile(__attribute__((unused)) unsigned char *obj, std::string const& path) { std::string full_path("./data/"); - full_path.append(path.c_str()); + full_path.append(path); std::ifstream stream(full_path); std::string str((std::istreambuf_iterator(stream)), std::istreambuf_iterator()); - return str; + return create_cxx11_string(str.c_str()); } typedef unsigned char *(*TextEditScreen_t)(unsigned char *, unsigned char *); diff --git a/mods/src/extra.h b/mods/src/extra.h index be0085d5b..a8e4baa4c 100644 --- a/mods/src/extra.h +++ b/mods/src/extra.h @@ -7,6 +7,7 @@ extern "C" { #endif int has_feature(const char *name); +int get_is_server(); void key_press(char key); void clear_input(); diff --git a/mods/src/server/server.cpp b/mods/src/server/server.cpp new file mode 100644 index 000000000..0eb5856da --- /dev/null +++ b/mods/src/server/server.cpp @@ -0,0 +1,237 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include "server.h" +#include "server_properties.h" + +typedef void (*Minecraft_update_t)(unsigned char *minecraft); +static Minecraft_update_t Minecraft_update = (Minecraft_update_t) 0x16b74; +static void *Minecraft_update_original = NULL; + +struct LevelSettings { + unsigned long seed; + int32_t game_type; +}; + +typedef void (*Minecraft_selectLevel_t)(unsigned char *minecraft, std::string const& level_dir, std::string const& level_name, LevelSettings const& vsettings); +static Minecraft_selectLevel_t Minecraft_selectLevel = (Minecraft_selectLevel_t) 0x16f38; + +typedef void (*Minecraft_hostMultiplayer_t)(unsigned char *minecraft, int32_t port); +static Minecraft_hostMultiplayer_t Minecraft_hostMultiplayer = (Minecraft_hostMultiplayer_t) 0x16664; + +typedef void *(*ProgressScreen_t)(unsigned char *obj); +static ProgressScreen_t ProgressScreen = (ProgressScreen_t) 0x37044; + +typedef void (*Minecraft_setScreen_t)(unsigned char *minecraft, unsigned char *screen); +static Minecraft_setScreen_t Minecraft_setScreen = (Minecraft_setScreen_t) 0x15d6c; + +#define INFO(msg, ...) printf("[INFO]: " msg "\n", __VA_ARGS__); + +// Store Minecraft For Exit +static unsigned char *stored_minecraft = NULL; + +// Server Properties +static ServerProperties &get_server_properties() { + static ServerProperties properties; + return properties; +} + +// Default Server Properties +#define DEFAULT_MOTD "Minecraft Server" +#define DEFAULT_GAME_MODE "0" +#define DEFAULT_PORT "19132" +#define DEFAULT_SEED "" +#define DEFAULT_MOB_SPAWNING "true" +#define DEFAULT_WORLD_NAME "world" + +typedef const char *(*Minecraft_getProgressMessage_t)(unsigned char *minecraft); +static Minecraft_getProgressMessage_t Minecraft_getProgressMessage = (Minecraft_getProgressMessage_t) 0x16e58; + +typedef int32_t (*Minecraft_isLevelGenerated_t)(unsigned char *minecraft); +static Minecraft_isLevelGenerated_t Minecraft_isLevelGenerated = (Minecraft_isLevelGenerated_t) 0x16e6c; + +#define SIGNIFICANT_PROGRESS 5 + +// Check If Two Percentages Are Different Enough To Be Logged +static bool is_progress_difference_significant(int32_t new_val, int32_t old_val) { + if (new_val != old_val) { + if (new_val == -1 || old_val == -1) { + return true; + } else if (new_val == 0 || new_val == 100) { + return true; + } else { + return new_val - old_val >= SIGNIFICANT_PROGRESS; + } + } else { + return false; + } +} + +// Runs Every Tick +static int last_progress = -1; +static const char *last_message = NULL; +static bool loaded = false; +static void Minecraft_update_injection(unsigned char *minecraft) { + // Create/Start World + if (!loaded) { + INFO("%s", "Starting Minecraft: Pi Edition Dedicated Server"); + + LevelSettings settings; + settings.game_type = 0; // Patched By MCPI-Docker + std::string seed_str = get_server_properties().get_string("seed", DEFAULT_SEED); + int32_t seed = seed_str.length() > 0 ? std::stoi(seed_str) : time(NULL); + settings.seed = seed; + + std::string world_name = get_server_properties().get_string("world-name", DEFAULT_WORLD_NAME); + (*Minecraft_selectLevel)(minecraft, world_name, world_name, settings); + + int port = get_server_properties().get_int("port", DEFAULT_PORT); + (*Minecraft_hostMultiplayer)(minecraft, port); + INFO("Listening On: %i", port); + + void *screen = ::operator new(0x4c); + screen = (*ProgressScreen)((unsigned char *) screen); + (*Minecraft_setScreen)(minecraft, (unsigned char *) screen); + + stored_minecraft = minecraft; + + loaded = true; + } + + // Print Progress Message + const char *message = (*Minecraft_getProgressMessage)(minecraft); + int32_t progress = *(int32_t *) (minecraft + 0xc60); + if ((*Minecraft_isLevelGenerated)(minecraft)) { + message = "Ready"; + progress = -1; + } + if (message != NULL) { + bool message_different = message != last_message; + bool progress_significant = is_progress_difference_significant(progress, last_progress); + if (message_different || progress_significant) { + if (progress != -1) { + INFO("Status: %s: %i%%", message, progress); + } else { + INFO("Status: %s", message); + } + if (message_different) { + last_message = message; + } + if (progress_significant) { + last_progress = progress; + } + } + } + + // Call Original Method + revert_overwrite((void *) Minecraft_update, Minecraft_update_original); + (*Minecraft_update)(minecraft); + revert_overwrite((void *) Minecraft_update, Minecraft_update_original); +} + +typedef void (*Level_saveLevelData_t)(unsigned char *level); +static Level_saveLevelData_t Level_saveLevelData = (Level_saveLevelData_t) 0xa2e94; +static void *Level_saveLevelData_original = NULL; + +static void Level_saveLevelData_injection(unsigned char *level) { + // Print Log Message + INFO("%s", "Saving Game"); + + // Call Original Method + revert_overwrite((void *) Level_saveLevelData, Level_saveLevelData_original); + (*Level_saveLevelData)(level); + revert_overwrite((void *) Level_saveLevelData, Level_saveLevelData_original); +} + +typedef void (*Gui_addMessage_t)(unsigned char *gui, std::string const& text); +static Gui_addMessage_t Gui_addMessage = (Gui_addMessage_t) 0x27820; +static void *Gui_addMessage_original = NULL; + +static void Gui_addMessage_injection(unsigned char *gui, std::string const& text) { + // Print Log Message + printf("[CHAT]: %s\n", text.c_str()); + + // Call Original Method + revert_overwrite((void *) Gui_addMessage, Gui_addMessage_original); + (*Gui_addMessage)(gui, text); + revert_overwrite((void *) Gui_addMessage, Gui_addMessage_original); +} + +static void exit_handler(__attribute__((unused)) int data) { + INFO("%s", "Stopping Server"); + if (stored_minecraft != NULL) { + unsigned char *level = *(unsigned char **) (stored_minecraft + 0x188); + if (level != NULL) { + // Save Game + (*Level_saveLevelData)(level); + } + } + // Stop Game + SDL_Event event; + event.type = SDL_QUIT; + SDL_PushEvent(&event); +} + +const char *server_get_motd() { + return get_server_properties().get_string("motd", DEFAULT_MOTD).c_str(); +} +int server_get_default_game_mode() { + return get_server_properties().get_int("game-mode", DEFAULT_GAME_MODE); +} +int server_get_mob_spawning() { + return get_server_properties().get_bool("spawn-mobs", DEFAULT_MOB_SPAWNING); +} + +void server_init() { + // Open Properties File + std::string file(getenv("HOME")); + file.append("/.minecraft/server.properties"); + + std::ifstream properties_file(file); + + if (!properties_file || !properties_file.is_open()) { + // Write Defaults + std::ofstream properties_file_output(file); + properties_file_output << "motd=" DEFAULT_MOTD "\n"; + properties_file_output << "game-mode=" DEFAULT_GAME_MODE "\n"; + properties_file_output << "port=" DEFAULT_PORT "\n"; + properties_file_output << "seed=" DEFAULT_SEED "\n"; + properties_file_output << "spawn-mobs=" DEFAULT_MOB_SPAWNING "\n"; + properties_file_output << "world-name=" DEFAULT_WORLD_NAME "\n"; + properties_file_output.close(); + // Re-Open File + properties_file = std::ifstream(file); + } + + if (!properties_file.is_open()) { + printf("[ERR]: Unable To Open server.properties\n"); + exit(1); + } + + // Load Properties + get_server_properties().load(properties_file); + + properties_file.close(); + + // Prevent Main Player From Loading + unsigned char player_patch[4] = {0x00, 0x20, 0xa0, 0xe3}; + patch((void *) 0x1685c, player_patch); + // Start World On Launch + Minecraft_update_original = overwrite((void *) Minecraft_update, (void *) Minecraft_update_injection); + // Print Log On Game Save + Level_saveLevelData_original = overwrite((void *) Level_saveLevelData, (void *) Level_saveLevelData_injection); + // Exit handler + signal(SIGINT, exit_handler); + // Print Chat To Log + Gui_addMessage_original = overwrite((void *) Gui_addMessage, (void *) Gui_addMessage_injection); +} diff --git a/mods/src/server/server.h b/mods/src/server/server.h new file mode 100644 index 000000000..d92de7406 --- /dev/null +++ b/mods/src/server/server.h @@ -0,0 +1,19 @@ +#ifndef SERVER_H + +#define SERVER_H + +#ifdef __cplusplus +extern "C" { +#endif + +void server_init(); + +const char *server_get_motd(); +int server_get_default_game_mode(); +int server_get_mob_spawning(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/mods/src/server/server_properties.cpp b/mods/src/server/server_properties.cpp new file mode 100644 index 000000000..5496f6955 --- /dev/null +++ b/mods/src/server/server_properties.cpp @@ -0,0 +1,37 @@ +#include "server_properties.h" + +static bool is_true(std::string const& val) { + return (val == "true" || val == "yes" || val == "1"); +} + +void ServerProperties::load(std::istream& stream) { + std::string line; + while (std::getline(stream, line)) { + if (line.length() > 0) { + if (line[0] == '#') { + continue; + } + size_t i = line.find('='); + if (i == std::string::npos) { + continue; + } + properties.insert(std::pair(line.substr(0, i), line.substr(i + 1))); + } + } +} + +std::string ServerProperties::get_string(std::string const& name, std::string const& def) { + return properties.count(name) > 0 ? properties.at(name) : def; +} + +int ServerProperties::get_int(std::string const& name, std::string const& def) { + return properties.count(name) > 0 ? std::stoi(properties.at(name)) : std::stoi(def); +} + +bool ServerProperties::get_bool(std::string const& name, std::string const& def) { + if (properties.count(name) > 0) { + std::string const& val = properties.at(name); + return is_true(val); + } + return is_true(def); +} diff --git a/mods/src/server/server_properties.h b/mods/src/server/server_properties.h new file mode 100644 index 000000000..e3ffdd1bc --- /dev/null +++ b/mods/src/server/server_properties.h @@ -0,0 +1,22 @@ +#ifndef SERVER_PROPERTIES_H + +#define SERVER_PROPERTIES_H + +#include +#include +#include + +class ServerProperties { + +private: + std::map properties; + +public: + void load(std::istream& fstream); + + std::string get_string(std::string const& name, std::string const& def); + int get_int(std::string const& name, std::string const& def); + bool get_bool(std::string const& name, std::string const& def); +}; + +#endif diff --git a/scripts/build.sh b/scripts/build.sh index 4d05a8908..ea8b76c13 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -2,4 +2,5 @@ set -e -docker build ${DOCKER_BUILD_OPTIONS} --tag thebrokenrail/minecraft-pi:latest . +docker build ${DOCKER_BUILD_OPTIONS} --tag thebrokenrail/minecraft-pi:client -f Dockerfile.client . +docker build ${DOCKER_BUILD_OPTIONS} --tag thebrokenrail/minecraft-pi:server -f Dockerfile.server . diff --git a/scripts/package.sh b/scripts/package.sh index 256be33d9..198fb2a9a 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -10,7 +10,8 @@ rm -rf out mkdir -p out/deb # Generate DEB -dpkg -b debian out/deb +dpkg -b debian/client out/deb +dpkg -b debian/server out/deb # Export Libraries mkdir -p out/lib @@ -19,6 +20,6 @@ mkdir -p out/lib cp -r mods/include out/lib/include # Copy Shared Library -IMG_ID="$(docker create thebrokenrail/minecraft-pi)" +IMG_ID="$(docker create thebrokenrail/minecraft-pi:client)" docker cp "${IMG_ID}":/app/minecraft-pi/mods/. ./out/lib/. docker rm -v "${IMG_ID}"