Improve Modding

This commit is contained in:
TheBrokenRail 2020-10-03 16:18:53 -04:00
parent a2d3941bd5
commit a2b2d6e7c4
9 changed files with 137 additions and 17 deletions

82
MODDING.md Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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")) {

View File

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