diff --git a/Dockerfile.client b/Dockerfile.client index fd80ed2..9b23080 100644 --- a/Dockerfile.client +++ b/Dockerfile.client @@ -5,7 +5,7 @@ ENV DEBIAN_FRONTEND noninteractive RUN \ # Install Runtime Dependencies apt-get update && \ - apt-get install -y --no-install-recommends tini libgles1 libx11-6 libsdl1.2debian zlib1g libfreeimage3 libglfw3 xinput libxfixes3 gosu tk && \ + apt-get install -y --no-install-recommends tini libgles1 libx11-6 zlib1g libfreeimage3 libglfw3 xinput libxfixes3 gosu tk && \ rm -rf /var/lib/apt/lists/* # Compile Environment @@ -43,9 +43,7 @@ RUN \ mkdir -p /home && \ chmod -R a+rw /home -# Include Exported Libraries -COPY --from=build /app/export / -# Include Mods +# Copy Build COPY --from=build /app/minecraft-pi /app/minecraft-pi WORKDIR /app/minecraft-pi diff --git a/build/build-libpng12.sh b/build/build-libpng12.sh index 0e620a6..c885a9e 100755 --- a/build/build-libpng12.sh +++ b/build/build-libpng12.sh @@ -6,10 +6,12 @@ git clone --depth 1 https://git.code.sf.net/p/libpng/code libpng -b libpng12 cd libpng -./configure --prefix /usr +./configure make -j$(nproc) -make install DESTDIR=/app/export + +mkdir -p ../minecraft-pi/lib +cp -L .libs/libpng12.so.0 ../minecraft-pi/lib cd ../ diff --git a/debian/client/common/usr/lib/minecraft-pi/pre-launch.sh b/debian/client/common/usr/lib/minecraft-pi/pre-launch.sh index 5c8f877..a660ef8 100755 --- a/debian/client/common/usr/lib/minecraft-pi/pre-launch.sh +++ b/debian/client/common/usr/lib/minecraft-pi/pre-launch.sh @@ -21,7 +21,7 @@ RET=$? set -e # Kill Logging -kill ${TAIL_PID} +kill ${TAIL_PID} > /dev/null 2>&1 || : # Exit exit ${RET} \ No newline at end of file diff --git a/debian/client/native/usr/lib/minecraft-pi/run.sh b/debian/client/native/usr/lib/minecraft-pi/run.sh index 39dee33..b55bfb8 100755 --- a/debian/client/native/usr/lib/minecraft-pi/run.sh +++ b/debian/client/native/usr/lib/minecraft-pi/run.sh @@ -3,4 +3,4 @@ set -e # Launch Minecraft -docker-compose -f "${DOCKER_COMPOSE_YML}" run --rm minecraft-pi \ No newline at end of file +exec docker-compose -f "${DOCKER_COMPOSE_YML}" run --rm minecraft-pi \ No newline at end of file diff --git a/debian/client/virgl/usr/lib/minecraft-pi/run.sh b/debian/client/virgl/usr/lib/minecraft-pi/run.sh index 685a824..30b43b6 100755 --- a/debian/client/virgl/usr/lib/minecraft-pi/run.sh +++ b/debian/client/virgl/usr/lib/minecraft-pi/run.sh @@ -13,6 +13,7 @@ RET=$? set -e # Kill VirGL -kill ${VIRGL_PID} > /dev/null 2>&1 +kill ${VIRGL_PID} > /dev/null 2>&1 || : +# Exit exit ${RET} \ No newline at end of file diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index bc47c53..3f0dd29 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -4,8 +4,31 @@ project(launcher) add_compile_options(-Wall -Wextra -Werror) +## Launcher + add_executable(launcher src/launcher.c) # Install install(TARGETS launcher DESTINATION /) -install(PROGRAMS src/run.sh DESTINATION /) \ No newline at end of file +install(PROGRAMS src/run.sh DESTINATION /) + +## Stubs + +# Stub RPI-Specific Graphics +add_library(bcm_host SHARED src/stubs/bcm_host.c) + +# Stub EGL +add_library(EGL SHARED src/stubs/EGL.c) + +# Stub SDL +add_library(SDL-1.2 SHARED src/stubs/SDL.cpp) +target_link_libraries(SDL-1.2 pthread) +set_target_properties(SDL-1.2 PROPERTIES SOVERSION "0") + +# MCPI Links Against GLESv2 But Uses GLESv1_CM +add_library(GLESv2 SHARED src/stubs/GLESv2.c) +target_link_libraries(GLESv2 GLESv1_CM) +target_link_options(GLESv2 PRIVATE "-Wl,--no-as-needed") + +# Install Stubs +install(TARGETS bcm_host EGL SDL-1.2 GLESv2 DESTINATION /lib) \ No newline at end of file diff --git a/launcher/src/launcher.c b/launcher/src/launcher.c index 9fec557..297eeac 100644 --- a/launcher/src/launcher.c +++ b/launcher/src/launcher.c @@ -9,10 +9,10 @@ #include #include +// Check String Prefix/Suffix static int starts_with(const char *s, const char *t) { return strncmp(s, t, strlen(t)) == 0; } - static int ends_with(const char *s, const char *t) { size_t slen = strlen(s); size_t tlen = strlen(t); @@ -20,6 +20,7 @@ static int ends_with(const char *s, const char *t) { return strcmp(s + slen - tlen, t) == 0; } +// Set Environmental Variable static void trim(char *value) { // Remove Trailing Colon int length = strlen(value); @@ -27,26 +28,30 @@ static void trim(char *value) { value[length - 1] = '\0'; } } - static void set_and_print_env(char *name, char *value) { - // Set Variable With Not Trailing Colon + // Set Variable With No Trailing Colon trim(value); fprintf(stderr, "Set %s = %s\n", name, value); setenv(name, value, 1); } +// Get Environmental Variable static char *get_env_safe(const char *name) { // Get Variable Or Blank String If Not Set char *ret = getenv(name); return ret != NULL ? ret : ""; } -static void load(char **ld_path, char **ld_preload, char *folder) { +// Get All SO Files In Folder +static void load(char **ld_preload, char *folder) { int folder_name_length = strlen(folder); + // Retry Until Successful while (1) { + // Open Folder DIR *dp = opendir(folder); if (dp != NULL) { + // Loop Through Folder struct dirent *entry = NULL; errno = 0; while (1) { @@ -75,14 +80,14 @@ static void load(char **ld_path, char **ld_preload, char *folder) { fprintf(stderr, "Error Reading Directory: %s: %s\n", folder, strerror(errno)); exit(EXIT_FAILURE); } else { + // Done! break; } } + // Close Folder closedir(dp); - // Add To LD_LIBRARY_PATH - asprintf(ld_path, "%s:%s", *ld_path, folder); - + // Exit Function return; } else if (errno == ENOENT) { // Folder Doesn't Exists, Attempt Creation @@ -92,6 +97,7 @@ static void load(char **ld_path, char **ld_preload, char *folder) { fprintf(stderr, "Error Creating Directory: %s: %s\n", folder, strerror(errno)); exit(EXIT_FAILURE); } + // Continue Retrying } else { // Unable To Open Folder fprintf(stderr, "Error Opening Directory: %s: %s\n", folder, strerror(errno)); @@ -121,31 +127,25 @@ int main(__attribute__((unused)) int argc, char *argv[]) { } free(minecraft_folder); - char *ld_path = NULL; - - // Start Configuring LD_LIBRARY_PATH - asprintf(&ld_path, "/usr/arm-linux-gnueabihf/lib"); + // Configure LD_LIBRARY_PATH + char *ld_path; + asprintf(&ld_path, "./lib:/usr/arm-linux-gnueabihf/lib:%s", get_env_safe("LD_LIBRARY_PATH")); + set_and_print_env("LD_LIBRARY_PATH", ld_path); + free(ld_path); // Start Configuring LD_PRELOAD char *ld_preload = NULL; asprintf(&ld_preload, "%s", get_env_safe("LD_PRELOAD")); // Load Mods From ./mods - load(&ld_path, &ld_preload, "./mods/"); + load(&ld_preload, "./mods/"); // Loads Mods From ~/.minecraft-pi/mods char *home_mods = NULL; asprintf(&home_mods, "%s/.minecraft-pi/mods/", getenv("HOME")); - load(&ld_path, &ld_preload, home_mods); + load(&ld_preload, home_mods); free(home_mods); - // Add Existing LD_LIBRARY_PATH - asprintf(&ld_path, "%s:%s", ld_path, get_env_safe("LD_LIBRARY_PATH")); - - // Set LD_LIBRARY_PATH - set_and_print_env("LD_LIBRARY_PATH", ld_path); - free(ld_path); - // Set LD_PRELOAD set_and_print_env("LD_PRELOAD", ld_preload); free(ld_preload); diff --git a/launcher/src/run.sh b/launcher/src/run.sh index 32947b3..9bc3d43 100755 --- a/launcher/src/run.sh +++ b/launcher/src/run.sh @@ -32,4 +32,4 @@ if ! id user > /dev/null 2>&1; then fi # Start -exec gosu "${USER_UID}" ./launcher \ No newline at end of file +exec gosu user ./launcher \ No newline at end of file diff --git a/mods/src/stubs/EGL.c b/launcher/src/stubs/EGL.c similarity index 93% rename from mods/src/stubs/EGL.c rename to launcher/src/stubs/EGL.c index 3b2fba9..a27852d 100644 --- a/mods/src/stubs/EGL.c +++ b/launcher/src/stubs/EGL.c @@ -1,8 +1,6 @@ #include -#include "../compat/compat.h" - -// EGL/SDL Is Replaced With GLFW +// EGL Is Replaced With GLFW EGLDisplay eglGetDisplay(__attribute__((unused)) NativeDisplayType native_display) { return 0; @@ -34,9 +32,6 @@ EGLBoolean eglDestroyContext(__attribute__((unused)) EGLDisplay display, __attri EGLBoolean eglTerminate(__attribute__((unused)) EGLDisplay display) { return EGL_TRUE; } - -// Send Buffer Swap Request To GLFW EGLBoolean eglSwapBuffers(__attribute__((unused)) EGLDisplay display, __attribute__((unused)) EGLSurface surface) { - compat_eglSwapBuffers(); return EGL_TRUE; } \ No newline at end of file diff --git a/mods/src/stubs/GLESv2.c b/launcher/src/stubs/GLESv2.c similarity index 100% rename from mods/src/stubs/GLESv2.c rename to launcher/src/stubs/GLESv2.c diff --git a/launcher/src/stubs/SDL.cpp b/launcher/src/stubs/SDL.cpp new file mode 100644 index 0000000..29ee20c --- /dev/null +++ b/launcher/src/stubs/SDL.cpp @@ -0,0 +1,41 @@ +#include +#include +#include + +#include + +// SDL Is Replaced With GLFW + +int SDL_Init(__attribute__((unused)) uint32_t flags) { + return 0; +} + +void SDL_Quit() { + exit(EXIT_SUCCESS); +} + +// Event Queue + +static pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER; +static std::vector queue; + +int SDL_PollEvent(SDL_Event *event) { + pthread_mutex_lock(&queue_mutex); + int ret; + if (queue.size() > 0) { + *event = queue[0]; + queue.erase(queue.begin()); + ret = 1; + } else { + ret = 0; + } + pthread_mutex_unlock(&queue_mutex); + return ret; +} + +int SDL_PushEvent(SDL_Event *event) { + pthread_mutex_lock(&queue_mutex); + queue.push_back(*event); + pthread_mutex_unlock(&queue_mutex); + return 1; +} diff --git a/mods/src/stubs/bcm_host.c b/launcher/src/stubs/bcm_host.c similarity index 100% rename from mods/src/stubs/bcm_host.c rename to launcher/src/stubs/bcm_host.c diff --git a/libreborn/include/libreborn/libreborn.h b/libreborn/include/libreborn/libreborn.h index a220b30..4c76b84 100644 --- a/libreborn/include/libreborn/libreborn.h +++ b/libreborn/include/libreborn/libreborn.h @@ -18,6 +18,7 @@ extern "C" { // Set obj To NULL On asprintf() Failure #define asprintf(obj, ...) if (asprintf(obj, __VA_ARGS__) == -1) { *obj = NULL; } +// Hook Library Function #define HOOK(name, return_type, args) \ typedef return_type (*name##_t)args; \ static name##_t real_##name = NULL; \ @@ -34,6 +35,11 @@ extern "C" { \ __attribute__((__used__)) return_type name args +// Sanitize String +void sanitize_string(char **str, int max_length, unsigned int allow_newlines); + +// Patching Functions + void _overwrite_call(const char *file, int line, void *start, void *target); #define overwrite_call(start, target) _overwrite_call(__FILE__, __LINE__, start, target); diff --git a/libreborn/include/libreborn/minecraft.h b/libreborn/include/libreborn/minecraft.h index ca1bbb4..53de81a 100644 --- a/libreborn/include/libreborn/minecraft.h +++ b/libreborn/include/libreborn/minecraft.h @@ -124,6 +124,17 @@ static uint32_t Minecraft_gui_property_offset = 0x198; // Gui static uint32_t CommandServer_minecraft_property_offset = 0x18; // Minecraft * +// Packet + +typedef void (*Packet_read_t)(unsigned char *packet, unsigned char *bit_stream); + +// LoginPacket + +static Packet_read_t LoginPacket_read = (Packet_read_t) 0x6e5f8; +static void *LoginPacket_read_vtable_addr = (void *) 0x108dcc; + +static uint32_t LoginPacket_username_property_offset = 0xc; // RakString + // ChatPacket static uint32_t ChatPacket_message_property_offset = 0xc; // char * @@ -293,6 +304,17 @@ static uint32_t ItemInstance_auxilary_property_offset = 0x8; typedef int32_t (*FillingContainer_addItem_t)(unsigned char *filling_container, unsigned char *item_instance); static FillingContainer_addItem_t FillingContainer_addItem = (FillingContainer_addItem_t) 0x92aa0; +// RakNet::RakString + +typedef void (*RakNet_RakString_Assign_t)(unsigned char *rak_string, const char *str); +static RakNet_RakString_Assign_t RakNet_RakString_Assign = (RakNet_RakString_Assign_t) 0xe9e34; + +static uint32_t RakNet_RakString_sharedString_property_offset = 0x0; // RakNet::RakString::SharedString * + +// RakNet::RakString::SharedString + +static uint32_t RakNet_RakString_SharedString_c_str_property_offset = 0x10; // char * + // RakNetInstance typedef void (*RakNetInstance_send_t)(unsigned char *rak_net_instance, unsigned char *packet); @@ -301,7 +323,7 @@ static uint32_t RakNetInstance_send_vtable_offset = 0x38; typedef uint32_t (*RakNetInstance_isServer_t)(unsigned char *rak_net_instance); static uint32_t RakNetInstance_isServer_vtable_offset = 0x48; -static uint32_t RakNetInstance_peer_property_offset = 0x4; +static uint32_t RakNetInstance_peer_property_offset = 0x4; // RakNet::RakPeer * // RakNet::RakPeer diff --git a/libreborn/src/libreborn.c b/libreborn/src/libreborn.c index 82e196b..717e6da 100644 --- a/libreborn/src/libreborn.c +++ b/libreborn/src/libreborn.c @@ -210,3 +210,27 @@ void _patch_address(const char *file, int line, void *start, void *target) { unsigned char patch_data[4] = {addr & 0xff, (addr >> 8) & 0xff, (addr >> 16) & 0xff, (addr >> 24) & 0xff}; _patch(file, line, start, patch_data); } + +// Sanitize String +#define MINIMUM_SAFE_CHARACTER 32 +#define MAXIMUM_SAFE_CHARACTER 126 +#define MINIMUM_EXTENDED_SAFE_CHARACTER 128 +void sanitize_string(char **str, int max_length, unsigned int allow_newlines) { + // Store Message Length + int length = strlen(*str); + // Truncate Message + if (max_length != -1 && length > max_length) { + (*str)[max_length] = '\0'; + length = max_length; + } + // Loop Through Message + for (int i = 0; i < length; i++) { + if (allow_newlines && ((*str)[i] == '\n' || (*str)[i] == '\r')) { + continue; + } + if (((*str)[i] < MINIMUM_SAFE_CHARACTER || (*str)[i] > MAXIMUM_SAFE_CHARACTER) && (*str)[i] < MINIMUM_EXTENDED_SAFE_CHARACTER) { + // Replace Illegal Character + (*str)[i] = '?'; + } + } +} \ No newline at end of file diff --git a/mods/CMakeLists.txt b/mods/CMakeLists.txt index a284c08..97ac219 100644 --- a/mods/CMakeLists.txt +++ b/mods/CMakeLists.txt @@ -12,9 +12,6 @@ add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) # Add libreborn add_subdirectory(../libreborn libreborn) -# Include Libraries Exported To Runtime Environment -include_directories(/app/export/include) - # Find GLFW find_package(glfw3 3.3 REQUIRED) @@ -64,19 +61,5 @@ target_link_libraries(test reborn) add_library(init SHARED src/init/init.c) target_link_libraries(init compat server game_mode camera input misc options textures chat test) -## Stubs - -# Stub RPI-Specific Graphics -add_library(bcm_host SHARED src/stubs/bcm_host.c) - -# Stub EGL -add_library(EGL SHARED src/stubs/EGL.c) -target_link_libraries(EGL compat) - -# MCPI Links Against GLESv2 But Uses GLESv1_CM -add_library(GLESv2 SHARED src/stubs/GLESv2.c) -target_link_libraries(GLESv2 GLESv1_CM) -target_link_options(GLESv2 PRIVATE "-Wl,--no-as-needed") - -## Install -install(TARGETS init compat readdir feature screenshot override server game_mode camera input misc options textures chat test bcm_host EGL GLESv2 DESTINATION /mods) \ No newline at end of file +## Install Mods +install(TARGETS init compat readdir feature screenshot override server game_mode camera input misc options textures chat test DESTINATION /mods) \ No newline at end of file diff --git a/mods/src/chat/chat.cpp b/mods/src/chat/chat.cpp index b0ee998..fb5d400 100644 --- a/mods/src/chat/chat.cpp +++ b/mods/src/chat/chat.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -10,6 +11,9 @@ #include "chat.h" +// Message Limitations +#define MAX_CHAT_MESSAGE_LENGTH 512 + // Send API Command static void send_api_command(unsigned char *minecraft, char *str) { struct ConnectedClient client; @@ -35,6 +39,7 @@ static void send_message(unsigned char *server_side_network_handler, char *usern char *full_message = NULL; asprintf(&full_message, "<%s> %s", username, message); ALLOC_CHECK(full_message); + sanitize_string(&full_message, MAX_CHAT_MESSAGE_LENGTH, 0); (*ServerSideNetworkHandler_displayGameMessage)(server_side_network_handler, std::string(full_message)); free(full_message); } diff --git a/mods/src/compat/compat.c b/mods/src/compat/compat.c index 33110b4..d47d024 100644 --- a/mods/src/compat/compat.c +++ b/mods/src/compat/compat.c @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -22,8 +23,6 @@ #include "../chat/chat.h" #include "../init/init.h" -#include "compat.h" - static GLFWwindow *glfw_window; static int is_server = 0; @@ -212,11 +211,12 @@ HOOK(SDL_WM_SetCaption, void, (const char *title, __attribute__((unused)) const } } -void compat_eglSwapBuffers() { +HOOK(eglSwapBuffers, EGLBoolean, (__attribute__((unused)) EGLDisplay display, __attribute__((unused)) EGLSurface surface)) { if (!is_server) { // Don't Swap Buffers In A Context-Less Window glfwSwapBuffers(glfw_window); } + return EGL_TRUE; } static int is_fullscreen = 0; diff --git a/mods/src/compat/compat.h b/mods/src/compat/compat.h deleted file mode 100644 index 8311d8b..0000000 --- a/mods/src/compat/compat.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void compat_eglSwapBuffers(); \ No newline at end of file diff --git a/mods/src/misc/misc.c b/mods/src/misc/misc.c index 7765209..227c713 100644 --- a/mods/src/misc/misc.c +++ b/mods/src/misc/misc.c @@ -1,13 +1,18 @@ +#include + #include +#include #include "../feature/feature.h" #include "misc.h" #include "../init/init.h" -#include - +// Minecraft Pi User Data Root #define NEW_PATH "/.minecraft-pi/" +// Maximum Username Length +#define MAX_USERNAME_LENGTH 16 + // Render Selected Item Text static void Gui_renderChatMessages_injection(unsigned char *gui, int32_t param_1, uint32_t param_2, uint32_t param_3, unsigned char *font) { // Call Original Method @@ -40,6 +45,26 @@ static void Inventory_selectSlot_injection(unsigned char *inventory, int32_t slo reset_selected_item_text_timer = 1; } +// Sanitize Username +static void LoginPacket_read_injection(unsigned char *packet, unsigned char *bit_stream) { + // Call Original Method + (*LoginPacket_read)(packet, bit_stream); + + // Prepare + unsigned char *rak_string = packet + LoginPacket_username_property_offset; + // Get Original Username + unsigned char *shared_string = *(unsigned char **) (rak_string + RakNet_RakString_sharedString_property_offset); + char *c_str = *(char **) (shared_string + RakNet_RakString_SharedString_c_str_property_offset); + // Sanitize + char *new_username = strdup(c_str); + ALLOC_CHECK(new_username); + sanitize_string(&new_username, MAX_USERNAME_LENGTH, 0); + // Set New Username + (*RakNet_RakString_Assign)(rak_string, new_username); + // Free + free(new_username); +} + void init_misc() { // Store Data In ~/.minecraft-pi Instead Of ~/.minecraft patch_address((void *) default_path, (void *) NEW_PATH); @@ -55,6 +80,9 @@ void init_misc() { overwrite_calls((void *) Gui_tick, (void *) Gui_tick_injection); overwrite_calls((void *) Inventory_selectSlot, (void *) Inventory_selectSlot_injection); + // Sanitize Username + patch_address(LoginPacket_read_vtable_addr, (void *) LoginPacket_read_injection); + // Init C++ init_misc_cpp(); } \ No newline at end of file diff --git a/mods/src/misc/misc.cpp b/mods/src/misc/misc.cpp index 869e587..39a491b 100644 --- a/mods/src/misc/misc.cpp +++ b/mods/src/misc/misc.cpp @@ -71,22 +71,31 @@ static int32_t Inventory_setupDefault_FillingContainer_addItem_call_injection(un // Print Chat To Log static bool Gui_addMessage_recursing = false; static void Gui_addMessage_injection(unsigned char *gui, std::string const& text) { + // Sanitize Message + char *new_message = strdup(text.c_str()); + ALLOC_CHECK(new_message); + sanitize_string(&new_message, -1, 1); + + // Process Message if (!Gui_addMessage_recursing) { // Start Recursing Gui_addMessage_recursing = true; // Print Log Message - fprintf(stderr, "[CHAT]: %s\n", text.c_str()); + fprintf(stderr, "[CHAT]: %s\n", new_message); // Call Original Method - (*Gui_addMessage)(gui, text); + (*Gui_addMessage)(gui, std::string(new_message)); // End Recursing Gui_addMessage_recursing = false; } else { // Call Original Method - (*Gui_addMessage)(gui, text); + (*Gui_addMessage)(gui, std::string(new_message)); } + + // Free + free(new_message); } // Death Messages