WIP Custom Skin Support
This commit is contained in:
parent
c0cd9b8b1f
commit
db03d964de
@ -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")
|
||||
|
@ -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@"
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
13
mods/include/mods/textures/textures.h
Normal file
13
mods/include/mods/textures/textures.h
Normal 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
|
@ -21,6 +21,7 @@ __attribute__((constructor)) static void init() {
|
||||
init_textures();
|
||||
init_atlas();
|
||||
init_title_screen();
|
||||
init_skin();
|
||||
#endif
|
||||
init_creative();
|
||||
init_game_mode();
|
||||
|
@ -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
2
mods/src/skin/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
# ``skin`` Mod
|
||||
This mod adds custom skin loading.
|
162
mods/src/skin/loader.cpp
Normal file
162
mods/src/skin/loader.cpp
Normal 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);
|
||||
}
|
3
mods/src/skin/skin-internal.h
Normal file
3
mods/src/skin/skin-internal.h
Normal file
@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
__attribute__((visibility("internal"))) void _init_skin_loader();
|
94
mods/src/skin/skin.cpp
Normal file
94
mods/src/skin/skin.cpp
Normal 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();
|
||||
}
|
@ -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, ¤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() {
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user