From db03d964defd8cbd8b58a3ee773c4482d5dc9054 Mon Sep 17 00:00:00 2001 From: TheBrokenRail Date: Thu, 19 Oct 2023 01:23:34 -0400 Subject: [PATCH] WIP Custom Skin Support --- CMakeLists.txt | 3 + libreborn/include/libreborn/config.h.in | 1 + libreborn/src/util/exec.c | 42 +++++- mods/CMakeLists.txt | 7 +- mods/include/mods/init/init.h | 1 + mods/include/mods/textures/textures.h | 13 ++ mods/src/init/init.c | 1 + mods/src/override/override.c | 6 + mods/src/skin/README.md | 2 + mods/src/skin/loader.cpp | 162 ++++++++++++++++++++++++ mods/src/skin/skin-internal.h | 3 + mods/src/skin/skin.cpp | 94 ++++++++++++++ mods/src/textures/textures.cpp | 10 +- symbols/include/symbols/minecraft.h | 6 +- 14 files changed, 341 insertions(+), 10 deletions(-) create mode 100644 mods/include/mods/textures/textures.h create mode 100644 mods/src/skin/README.md create mode 100644 mods/src/skin/loader.cpp create mode 100644 mods/src/skin/skin-internal.h create mode 100644 mods/src/skin/skin.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c4c018cd..278601f5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,9 @@ else() string(APPEND MCPI_VARIANT_NAME "-client") endif() +# Skin Server +set(MCPI_SKIN_SERVER "http://localhost:8000" CACHE STRING "Skin Server") + # Specify Installation Paths set(MCPI_INSTALL_DIR "lib/${MCPI_VARIANT_NAME}") set(MCPI_BIN_DIR "${MCPI_INSTALL_DIR}/bin") diff --git a/libreborn/include/libreborn/config.h.in b/libreborn/include/libreborn/config.h.in index a9f82ec70..a31eb1734 100644 --- a/libreborn/include/libreborn/config.h.in +++ b/libreborn/include/libreborn/config.h.in @@ -11,3 +11,4 @@ #cmakedefine MCPI_VERSION "@MCPI_VERSION@" #cmakedefine MCPI_VARIANT_NAME "@MCPI_VARIANT_NAME@" #cmakedefine MCPI_SDK_DIR "@MCPI_SDK_DIR@" +#cmakedefine MCPI_SKIN_SERVER "@MCPI_SKIN_SERVER@" diff --git a/libreborn/src/util/exec.c b/libreborn/src/util/exec.c index e692b6e23..2fd1ef153 100644 --- a/libreborn/src/util/exec.c +++ b/libreborn/src/util/exec.c @@ -107,16 +107,50 @@ char *run_command(const char *const command[], int *exit_status) { // Read stdout close(output_pipe[1]); - char *output = NULL; #define BUFFER_SIZE 1024 + size_t size = BUFFER_SIZE; + char *output = malloc(size); char buf[BUFFER_SIZE]; + size_t position = 0; ssize_t bytes_read = 0; - while ((bytes_read = read(output_pipe[0], (void *) buf, BUFFER_SIZE - 1 /* Account For NULL-Terminator */)) > 0) { - buf[bytes_read] = '\0'; - string_append(&output, "%s", buf); + while ((bytes_read = read(output_pipe[0], (void *) buf, BUFFER_SIZE)) > 0) { + // Grow Output If Needed + size_t needed_size = position + bytes_read; + if (needed_size > size) { + // More Memeory Needed + size_t new_size = size; + while (new_size < needed_size) { + new_size += BUFFER_SIZE; + } + char *new_output = realloc(output, new_size); + if (new_output == NULL) { + ERR("Unable To Grow Output Buffer"); + } else { + output = new_output; + size = new_size; + } + } + + // Copy Data To Output + memcpy(output + position, buf, bytes_read); + position += bytes_read; } close(output_pipe[0]); + // Add NULL-Terminator To Output + size_t needed_size = position + 1; + if (needed_size > size) { + // More Memeory Needed + size_t new_size = size + 1; + char *new_output = realloc(output, new_size); + if (new_output == NULL) { + ERR("Unable To Grow Output Buffer (For NULL-Terminator)"); + } else { + output = new_output; + } + } + output[position] = '\0'; + // Get Return Code int status; waitpid(ret, &status, 0); diff --git a/mods/CMakeLists.txt b/mods/CMakeLists.txt index ab2ac3b8e..783d44815 100644 --- a/mods/CMakeLists.txt +++ b/mods/CMakeLists.txt @@ -80,6 +80,9 @@ else() add_library(title-screen SHARED src/title-screen/title-screen.cpp) target_link_libraries(title-screen mods-headers reborn-patch symbols feature compat) + add_library(skin SHARED src/skin/skin.cpp src/skin/loader.cpp) + target_link_libraries(skin mods-headers reborn-patch symbols feature misc textures media-layer-core png12) + add_library(benchmark SHARED src/benchmark/benchmark.cpp) target_link_libraries(benchmark mods-headers reborn-patch symbols compat misc media-layer-core) endif() @@ -115,7 +118,7 @@ target_link_libraries(init mods-headers reborn-util compat game-mode misc death if(MCPI_SERVER_MODE) target_link_libraries(init server) else() - target_link_libraries(init multiplayer sound camera input sign touch textures atlas title-screen benchmark) + target_link_libraries(init multiplayer sound camera input sign touch textures atlas title-screen skin benchmark) endif() ## Install Mods @@ -123,7 +126,7 @@ set(MODS_TO_INSTALL init compat readdir feature game-mode misc override death op if(MCPI_SERVER_MODE) list(APPEND MODS_TO_INSTALL server) else() - list(APPEND MODS_TO_INSTALL multiplayer sound camera input sign touch textures atlas title-screen benchmark) + list(APPEND MODS_TO_INSTALL multiplayer sound camera input sign touch textures atlas title-screen skin benchmark) endif() if(NOT MCPI_HEADLESS_MODE) list(APPEND MODS_TO_INSTALL screenshot) diff --git a/mods/include/mods/init/init.h b/mods/include/mods/init/init.h index 7e54f3301..2d12f4c7e 100644 --- a/mods/include/mods/init/init.h +++ b/mods/include/mods/init/init.h @@ -21,6 +21,7 @@ void init_touch(); void init_textures(); void init_atlas(); void init_title_screen(); +void init_skin(); #endif void init_creative(); void init_game_mode(); diff --git a/mods/include/mods/textures/textures.h b/mods/include/mods/textures/textures.h new file mode 100644 index 000000000..3b6e71444 --- /dev/null +++ b/mods/include/mods/textures/textures.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void glTexSubImage2D_with_scaling(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLsizei normal_texture_width, GLsizei normal_texture_height, GLenum format, GLenum type, const void *pixels); + +#ifdef __cplusplus +} +#endif diff --git a/mods/src/init/init.c b/mods/src/init/init.c index 1812100b6..cb70f2344 100644 --- a/mods/src/init/init.c +++ b/mods/src/init/init.c @@ -21,6 +21,7 @@ __attribute__((constructor)) static void init() { init_textures(); init_atlas(); init_title_screen(); + init_skin(); #endif init_creative(); init_game_mode(); diff --git a/mods/src/override/override.c b/mods/src/override/override.c index 5d88228ea..d82dffde3 100644 --- a/mods/src/override/override.c +++ b/mods/src/override/override.c @@ -27,6 +27,12 @@ HOOK(access, int, (const char *pathname, int mode)) { // Get Override Path For File (If It Exists) char *override_get_path(const char *filename) { + // Custom Skin + if (starts_with(filename, "data/images/$")) { + // Fallback Texture + filename = "data/images/mob/char.png"; + } + // Get MCPI Home Path char *home_path = home_get(); // Get Asset Override Path diff --git a/mods/src/skin/README.md b/mods/src/skin/README.md new file mode 100644 index 000000000..61ef2935e --- /dev/null +++ b/mods/src/skin/README.md @@ -0,0 +1,2 @@ +# ``skin`` Mod +This mod adds custom skin loading. diff --git a/mods/src/skin/loader.cpp b/mods/src/skin/loader.cpp new file mode 100644 index 000000000..6303727c9 --- /dev/null +++ b/mods/src/skin/loader.cpp @@ -0,0 +1,162 @@ +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include "skin-internal.h" + +// Constants +#define SKIN_WIDTH 64 +#define SKIN_HEIGHT 32 + +// Loading Pending Skins +struct pending_skin { + int32_t texture_id; + char *data; +}; +static std::vector &get_pending_skins() { + static std::vector pending_skins; + return pending_skins; +} +static pthread_mutex_t pending_skins_lock = PTHREAD_MUTEX_INITIALIZER; +static void read_from_png(png_structp pngPtr, png_bytep data, png_size_t length) { + char **src = (char **) png_get_io_ptr(pngPtr); + memcpy(data, *src, length); + *src += length; +} +static void load_pending_skins(__attribute__((unused)) unsigned char *minecraft) { + // Lock + pthread_mutex_lock(&pending_skins_lock); + + // Loop + for (pending_skin &skin : get_pending_skins()) { + // Init LibPNG + png_structp pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!pngPtr) { + continue; + } + png_infop infoPtr = png_create_info_struct(pngPtr); + if (!infoPtr) { + png_destroy_read_struct(&pngPtr, NULL, NULL); + continue; + } + + // Read PNG Info + char *cursor = skin.data; + png_set_read_fn(pngPtr, (png_voidp) &cursor, read_from_png); + png_read_info(pngPtr, infoPtr); + int width = png_get_image_width(pngPtr, infoPtr); + int height = png_get_image_height(pngPtr, infoPtr); + if (png_get_color_type(pngPtr, infoPtr) != PNG_COLOR_TYPE_RGBA) { + continue; + } + if (width != SKIN_WIDTH || height != SKIN_HEIGHT) { + continue; + } + int pixelSize = 4; + + // Read Image + png_bytep *rowPtrs = new png_bytep[height]; + unsigned char *data = new unsigned char[pixelSize * width * height]; + int rowStrideBytes = pixelSize * width; + for (int i = 0; i < height; i++) { + rowPtrs[i] = (png_bytep) &data[i * rowStrideBytes]; + } + png_read_image(pngPtr, rowPtrs); + + // Load Texture + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glBindTexture(GL_TEXTURE_2D, skin.texture_id); + glTexSubImage2D_with_scaling(GL_TEXTURE_2D, 0, 0, 0, width, height, SKIN_WIDTH, SKIN_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, data); + glBindTexture(GL_TEXTURE_2D, last_texture); + + // Free + delete[] data; + png_destroy_read_struct(&pngPtr, &infoPtr, (png_infopp) 0); + delete[] rowPtrs; + } + + // Free + for (pending_skin &skin : get_pending_skins()) { + free(skin.data); + } + + // Clear + get_pending_skins().clear(); + + // Unlock + pthread_mutex_unlock(&pending_skins_lock); +} + +// Skin Loader +struct loader_data { + int32_t texture_id; + std::string name; +}; +static void *loader_thread(void *user_data) { + // Loader Data + loader_data *data = (loader_data *) user_data; + + // Download + std::string url = std::string(MCPI_SKIN_SERVER) + '/' + data->name + ".png"; + int return_code; + const char *command[] = {"wget", "-O", "-", url.c_str(), NULL}; + char *output = run_command(command, &return_code); + + // Check Success + if (output != NULL && is_exit_status_success(return_code)) { + // Success + DEBUG("Downloaded Skin: %s", data->name.c_str()); + + // Add To Pending Skins + pending_skin skin; + skin.texture_id = data->texture_id; + skin.data = output; + pthread_mutex_lock(&pending_skins_lock); + get_pending_skins().push_back(skin); + pthread_mutex_unlock(&pending_skins_lock); + } else { + // Failure + WARN("Failed To Download Skin: %s", data->name.c_str()); + free(output); + } + + // Free + delete data; + return NULL; +} + +// Intercept Texture Creation +static int32_t Textures_assignTexture_injection(unsigned char *textures, std::string const& name, unsigned char *data) { + // Call Original Method + int32_t id = (*Textures_assignTexture)(textures, name, data); + + // Load Skin + if (starts_with(name.c_str(), "$")) { + loader_data *user_data = new loader_data; + user_data->name = name.substr(1); + DEBUG("Loading Skin: %s", user_data->name.c_str()); + user_data->texture_id = id; + // Start Thread + pthread_t thread; + pthread_create(&thread, NULL, loader_thread, (void *) user_data); + } + + // Return + return id; +} + +// Init +void _init_skin_loader() { + // Intercept Texture Creation + overwrite_calls((void *) Textures_assignTexture, (void *) Textures_assignTexture_injection); + // Pending Skins + misc_run_on_tick(load_pending_skins); +} diff --git a/mods/src/skin/skin-internal.h b/mods/src/skin/skin-internal.h new file mode 100644 index 000000000..587a13725 --- /dev/null +++ b/mods/src/skin/skin-internal.h @@ -0,0 +1,3 @@ +#pragma once + +__attribute__((visibility("internal"))) void _init_skin_loader(); diff --git a/mods/src/skin/skin.cpp b/mods/src/skin/skin.cpp new file mode 100644 index 000000000..b97026387 --- /dev/null +++ b/mods/src/skin/skin.cpp @@ -0,0 +1,94 @@ +#include +#include + +#include + +#include "skin-internal.h" + +// Base64 Encode (https://gist.github.com/tomykaira/f0fd86b6c73063283afe550bc5d77594) +static std::string base64_encode(const std::string data) { + static constexpr char encoding_table[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' + }; + + size_t in_len = data.size(); + size_t out_len = 4 * ((in_len + 2) / 3); + std::string ret(out_len, '\0'); + size_t i; + char *p = const_cast(ret.c_str()); + + for (i = 0; i < in_len - 2; i += 3) { + *p++ = encoding_table[(data[i] >> 2) & 0x3f]; + *p++ = encoding_table[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xf0) >> 4)]; + *p++ = encoding_table[((data[i + 1] & 0xf) << 2) | ((int) (data[i + 2] & 0xc0) >> 6)]; + *p++ = encoding_table[data[i + 2] & 0x3f]; + } + if (i < in_len) { + *p++ = encoding_table[(data[i] >> 2) & 0x3f]; + if (i == (in_len - 1)) { + *p++ = encoding_table[((data[i] & 0x3) << 4)]; + *p++ = '='; + } + else { + *p++ = encoding_table[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xf0) >> 4)]; + *p++ = encoding_table[((data[i + 1] & 0xf) << 2)]; + } + *p++ = '='; + } + + return ret; +} + +// Change Texture For Player Entities +static void Player_username_assign_injection(std::string *target, std::string *username) { + // Call Original Method + *target = *username; + + // Get Player + unsigned char *player = ((unsigned char *) target) - Player_username_property_offset; + // Get Texture + std::string *texture = (std::string *) (player + Mob_texture_property_offset); + + // Set Texture + *texture = '$' + base64_encode(*username); +} +static void Player_username_assign_injection_2(std::string *target, const char *username) { + std::string username_str = username; + Player_username_assign_injection(target, &username_str); +} + +// Change Texture For HUD +static int32_t Textures_loadAndBindTexture_injection(unsigned char *textures, __attribute__((unused)) std::string const& name) { + // Change Texture + static std::string new_texture; + if (new_texture.length() == 0) { + std::string username = base64_encode(*default_username); + new_texture = '$' + username; + } + + // Call Original Method + return (*Textures_loadAndBindTexture)(textures, new_texture); +} + +// Init +void init_skin() { + // LocalPlayer + overwrite_call((void *) 0x44c28, (void *) Player_username_assign_injection); + // RemotePlayer + overwrite_call((void *) 0x6ce58, (void *) Player_username_assign_injection_2); + // ServerPlayer + overwrite_call((void *) 0x7639c, (void *) Player_username_assign_injection_2); + + // HUD + overwrite_call((void *) 0x4c6d0, (void *) Textures_loadAndBindTexture_injection); + + // Loader + _init_skin_loader(); +} diff --git a/mods/src/textures/textures.cpp b/mods/src/textures/textures.cpp index bbf840ccb..c87da6c96 100644 --- a/mods/src/textures/textures.cpp +++ b/mods/src/textures/textures.cpp @@ -9,6 +9,7 @@ #include #include +#include #include // Animated Water @@ -124,7 +125,7 @@ static void *scale_texture(const unsigned char *src, GLsizei old_width, GLsizei } // Scale Animated Textures -static void Textures_tick_glTexSubImage2D_injection(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels) { +void glTexSubImage2D_with_scaling(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLsizei normal_texture_width, GLsizei normal_texture_height, GLenum format, GLenum type, const void *pixels) { // Get Current Texture Size GLint current_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture); @@ -133,8 +134,8 @@ static void Textures_tick_glTexSubImage2D_injection(GLenum target, GLint level, get_texture_size(current_texture, &texture_width, &texture_height); // Calculate Factor - float width_factor = ((float) texture_width) / 256.0f; - float height_factor = ((float) texture_height) / 256.0f; + float width_factor = ((float) texture_width) / ((float) normal_texture_width); + float height_factor = ((float) texture_height) / ((float) normal_texture_height); // Only Scale If Needed if (width_factor == 1.0f && height_factor == 1.0f) { @@ -161,6 +162,9 @@ static void Textures_tick_glTexSubImage2D_injection(GLenum target, GLint level, free(new_pixels); } } +static void Textures_tick_glTexSubImage2D_injection(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels) { + glTexSubImage2D_with_scaling(target, level, xoffset, yoffset, width, height, 256, 256, format, type, pixels); +} // Init void init_textures() { diff --git a/symbols/include/symbols/minecraft.h b/symbols/include/symbols/minecraft.h index 8d5da8e20..997504aec 100644 --- a/symbols/include/symbols/minecraft.h +++ b/symbols/include/symbols/minecraft.h @@ -443,6 +443,7 @@ typedef void (*Mob_die_t)(unsigned char *entity, unsigned char *cause); static uint32_t Mob_die_vtable_offset = 0x130; static uint32_t Mob_health_property_offset = 0xec; // int32_t +static uint32_t Mob_texture_property_offset = 0xb54; // std::string // PathfinderMob @@ -1051,9 +1052,12 @@ static OptionsPane_unknown_toggle_creating_function_t OptionsPane_unknown_toggle // Textures -typedef void (*Textures_loadAndBindTexture_t)(unsigned char *textures, std::string const& name); +typedef int32_t (*Textures_loadAndBindTexture_t)(unsigned char *textures, std::string const& name); static Textures_loadAndBindTexture_t Textures_loadAndBindTexture = (Textures_loadAndBindTexture_t) 0x539cc; +typedef int32_t (*Textures_assignTexture_t)(unsigned char *textures, std::string const& name, unsigned char *data); +static Textures_assignTexture_t Textures_assignTexture = (Textures_assignTexture_t) 0x5354c; + // Recipes typedef void (*Recipes_addShapelessRecipe_t)(unsigned char *recipes, ItemInstance const& result, std::vector const& ingredients);