WIP Custom Skin Support

This commit is contained in:
TheBrokenRail 2023-10-19 01:23:34 -04:00
parent c0cd9b8b1f
commit db03d964de
14 changed files with 341 additions and 10 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,13 @@
#pragma once
#include <GLES/gl.h>
#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

View File

@ -21,6 +21,7 @@ __attribute__((constructor)) static void init() {
init_textures();
init_atlas();
init_title_screen();
init_skin();
#endif
init_creative();
init_game_mode();

View File

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

2
mods/src/skin/README.md Normal file
View File

@ -0,0 +1,2 @@
# ``skin`` Mod
This mod adds custom skin loading.

162
mods/src/skin/loader.cpp Normal file
View File

@ -0,0 +1,162 @@
#include <pthread.h>
#include <vector>
#include <libreborn/libreborn.h>
#include <symbols/minecraft.h>
#include <png.h>
#include <GLES/gl.h>
#include <mods/misc/misc.h>
#include <mods/textures/textures.h>
#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<pending_skin> &get_pending_skins() {
static std::vector<pending_skin> 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);
}

View File

@ -0,0 +1,3 @@
#pragma once
__attribute__((visibility("internal"))) void _init_skin_loader();

94
mods/src/skin/skin.cpp Normal file
View File

@ -0,0 +1,94 @@
#include <libreborn/libreborn.h>
#include <symbols/minecraft.h>
#include <mods/init/init.h>
#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<char*>(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();
}

View File

@ -9,6 +9,7 @@
#include <mods/misc/misc.h>
#include <mods/feature/feature.h>
#include <mods/textures/textures.h>
#include <mods/init/init.h>
// 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, &current_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() {

View File

@ -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<Recipes_Type> const& ingredients);