From a2b2d6e7c42c2850f5be246187ef0a5a1d4a3686 Mon Sep 17 00:00:00 2001 From: TheBrokenRail Date: Sat, 3 Oct 2020 16:18:53 -0400 Subject: [PATCH] Improve Modding --- MODDING.md | 82 ++++++++++++++++++++++++ README.md | 3 + core/CMakeLists.txt | 5 -- core/src/launcher.c | 44 ++++++++++--- mods/CMakeLists.txt | 6 +- {core => mods}/include/libcore/libcore.h | 0 {core => mods}/src/core.c | 0 mods/src/extra.c | 1 + scripts/package.sh | 13 +++- 9 files changed, 137 insertions(+), 17 deletions(-) create mode 100644 MODDING.md rename {core => mods}/include/libcore/libcore.h (100%) rename {core => mods}/src/core.c (100%) diff --git a/MODDING.md b/MODDING.md new file mode 100644 index 000000000..40c765b12 --- /dev/null +++ b/MODDING.md @@ -0,0 +1,82 @@ +# Modding +Modding Minecraft: Pi Edition is possible by patching the binary at runtime. To make this easier ``minecraft-pi-dcoker`` includes a libary called ``libcore.so`` which provides several functions to help you patch the game. + +## Hex Addresses +Minecraft: Pi Edition has no symbols so you must patch the hex address of an instruction instead of using a function name. Hex addresses can be found using tools like [Ghidra](https://ghidra-sre.org) or [RetDec](https://retdec.com). To find out what a function does, you can find its equivalent in Minecraft: Pocket Edition 0.6.1 and use its name for reference because Minecraft: Pocket Edition 0.6.1 includes symbols. + +## Loading Directories +``minecraft-pi-docker`` loads mods from two locations, ``/app/minecraft-pi/mods``, and ``~/.minecraft/mods``. The first location only exists in the Docker container and is immutable, so you should place your mods in ``~/.minecraft/mods`` which is mounted on the host as ``~/.minecraft-pi/mods``. + +## ``libcore.so`` API +Header files and the shared library can be download from [Jenkins](https://jenkins.thebrokenrail.com/job/minecraft-pi-docker/job/master/lastSuccessfulBuild/artifact/out/lib). + +### ``void *overwrite(void *start, void *target)`` +This method replaces a function with another function. + +#### Parameters +- **start:** The function you are replacing. +- **target:** The function you are replacing it with. + +#### Return Value +The original contents of the function. + +#### Warning +This should never be used on functions that are only 1 byte long because it overwrites 2 bytes. + +#### Example +```c +static int func_injection(int a, int b) { + return a + 4; +} + +__attribute__((constructor)) static void init() { + overwrite((void *) 0xabcde, func_injection); +} +``` + +### ``void revert_overwrite(void *start, void *original)`` +This allows you to revert ``overwrite()``. This can be used to call the original version of a function. + +#### Parameters +- **start:** The function that was overwritten. +- **original:** The return value of ``overwrite()``. + +#### Return Value +None + +#### Example +```c +typedef int (*func_t)(int a, int b); +static func_t func = (func_t) 0xabcde; +static void *func_original = NULL; + +static int func_injection(int a, int b) { + revert_overwrite((void *) func, func_original); + (*func)(a, b); + revert_overwrite((void *) func, func_original); + + return a + 4; +} + +__attribute__((constructor)) static void init() { + func_original = overwrite((void *) func, func_injection); +} +``` + +### ``void patch(void *start, unsigned char patch[])`` +This allows you to replace a specific instruction. + +#### Parameters +- **start:** The target instruction. +- **patch:** The new instruction (array length must be 4). + +#### Return Value +None + +#### Example +```c +__attribute__((constructor)) static void init() { + unsigned char patch_data[4] = {0x00, 0x00, 0x00, 0x00}; + patch((void *) 0xabcde, patch_data); +} +``` diff --git a/README.md b/README.md index 846615761..00b74f851 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,6 @@ This is a project allowing Minecraft: Pi Edition to be run without a Raspberry P ## Setup [View Binaries](https://jenkins.thebrokenrail.com/job/minecraft-pi-docker/job/master/lastSuccessfulBuild/artifact/out/) + +## Modding +[View Modding](MODDING.md) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 9775adb1e..a9a6593fc 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -4,11 +4,6 @@ project(core) add_compile_options(-Wall -Wextra -Werror) -include_directories(include) - -add_library(core SHARED src/core.c) -target_link_libraries(core dl) - add_library(bcm_host SHARED src/bcm_host.c) add_executable(launcher src/launcher.c) diff --git a/core/src/launcher.c b/core/src/launcher.c index 58d662647..15d3c690d 100644 --- a/core/src/launcher.c +++ b/core/src/launcher.c @@ -19,22 +19,29 @@ static int ends_with(const char *s, const char *t) { return strcmp(s + slen - tlen, t) == 0; } -static void set_and_print_env(char *name, char *value) { +static void trim(char *value) { + // Remove Trailing Colon int length = strlen(value); if (value[length - 1] == ':') { value[length - 1] = '\0'; } +} + +static void set_and_print_env(char *name, char *value) { + // Set Variable With Not Trailing Colon + trim(value); fprintf(stderr, "Set %s = %s\n", name, value); setenv(name, value, 1); } 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_preload, char *folder) { +static void load(char **ld_path, char **ld_preload, char *folder) { int folder_name_length = strlen(folder); while (1) { DIR *dp = opendir(folder); @@ -44,7 +51,8 @@ static void load(char **ld_preload, char *folder) { while (1) { entry = readdir(dp); if (entry != NULL) { - if (starts_with(entry->d_name, "lib") && ends_with(entry->d_name, ".so")) { + // Check If File Is A Shared Library + if (entry->d_type == DT_REG && starts_with(entry->d_name, "lib") && ends_with(entry->d_name, ".so")) { int name_length = strlen(entry->d_name); int total_length = folder_name_length + name_length; char name[total_length + 1]; @@ -58,9 +66,11 @@ static void load(char **ld_preload, char *folder) { name[total_length] = '\0'; + // Add To LD_PRELOAD asprintf(ld_preload, "%s:%s", name, *ld_preload); } } else if (errno != 0) { + // Error Reading Contents Of Folder fprintf(stderr, "Error Reading Directory: %s\n", strerror(errno)); exit(1); } else { @@ -68,8 +78,13 @@ static void load(char **ld_preload, char *folder) { } } closedir(dp); + + // Add To LD_LIBRARY_PATH + asprintf(ld_path, "%s:%s", *ld_path, folder); + return; } else if (errno == ENOENT) { + // Folder Doesn't Exists, Attempt Creation char *cmd = NULL; asprintf(&cmd, "mkdir -p %s", folder); int ret = system(cmd); @@ -77,6 +92,7 @@ static void load(char **ld_preload, char *folder) { exit(ret); } } else { + // Unable To Open Folder fprintf(stderr, "Error Opening Directory: %s\n", strerror(errno)); exit(1); } @@ -88,26 +104,36 @@ int main(__attribute__((unused)) int argc, char *argv[]) { char *ld_path = NULL; + // Start Configuring LD_LIBRARY_PATH char *cwd = getcwd(NULL, 0); - asprintf(&ld_path, "%s:/usr/arm-linux-gnueabihf/lib:%s", cwd, get_env_safe("LD_LIBRARY_PATH")); + asprintf(&ld_path, "%s:/usr/arm-linux-gnueabihf/lib", cwd); free(cwd); - 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(&ld_preload, "./mods/"); + // Load Mods From ./mods + load(&ld_path, &ld_preload, "./mods/"); + // Loads Mods From ~/.minecraft/mods char *home_mods = NULL; asprintf(&home_mods, "%s/.minecraft/mods/", getenv("HOME")); - load(&ld_preload, home_mods); + load(&ld_path, &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); + // Start Game fprintf(stderr, "Starting Game...\n"); return execve("./minecraft-pi", argv, environ); } diff --git a/mods/CMakeLists.txt b/mods/CMakeLists.txt index 69fe7eb38..9f6dfca7a 100644 --- a/mods/CMakeLists.txt +++ b/mods/CMakeLists.txt @@ -5,7 +5,11 @@ project(mods) add_compile_options(-Wall -Wextra -Werror) add_subdirectory(../core core) -include_directories(../core/include) + +include_directories(include) + +add_library(core SHARED src/core.c) +target_link_libraries(core dl) add_library(compat SHARED src/compat.c) target_link_libraries(compat core SDL EGL GLESv1_CM GLESv2 X11 dl) diff --git a/core/include/libcore/libcore.h b/mods/include/libcore/libcore.h similarity index 100% rename from core/include/libcore/libcore.h rename to mods/include/libcore/libcore.h diff --git a/core/src/core.c b/mods/src/core.c similarity index 100% rename from core/src/core.c rename to mods/src/core.c diff --git a/mods/src/extra.c b/mods/src/extra.c index 23fbc15ed..4686562a0 100644 --- a/mods/src/extra.c +++ b/mods/src/extra.c @@ -195,6 +195,7 @@ __attribute__((constructor)) static void init() { // Change Username const char *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/scripts/package.sh b/scripts/package.sh index 23a67967d..0090dbaa3 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -7,7 +7,16 @@ chmod -R g-s debian # Clean out Directory rm -rf out -mkdir out +mkdir -p out/deb # Generate DEB -dpkg -b debian out +dpkg -b debian out/deb + +# Export Libraries +mkdir -p out/lib + +# Copy Headers +cp -r mods/include out/lib/include + +# Copy Shared Library +docker run -v "$(pwd)/out/lib:/out" --entrypoint sh thebrokenrail/minecraft-pi -c 'cp ./mods/lib*.so /out'