minecraft-pi-reborn/mods/src/textures/textures.cpp

239 lines
7.8 KiB
C++

#include <vector>
#include <assert.h>
#include <cstdint>
#include <GLES/gl.h>
#include <libreborn/libreborn.h>
#include <symbols/minecraft.h>
#include <mods/misc/misc.h>
#include <mods/feature/feature.h>
#include <mods/textures/textures.h>
#include <mods/init/init.h>
#include "textures-internal.h"
#include "stb_image.h"
// Animated Water
static void Minecraft_tick_injection(Minecraft *minecraft) {
// Tick Dynamic Textures
Textures *textures = minecraft->textures;
if (textures != nullptr) {
Textures_tick(textures, true);
}
}
// Store Texture Sizes
struct texture_data {
GLint id;
GLsizei width;
GLsizei height;
};
static std::vector<texture_data> &get_texture_data() {
static std::vector<texture_data> data;
return data;
}
HOOK(glTexImage2D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels)) {
// Store
texture_data data;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &data.id);
data.width = width;
data.height = height;
get_texture_data().push_back(data);
// Call Original Method
ensure_glTexImage2D();
real_glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
}
HOOK(glDeleteTextures, void, (GLsizei n, const GLuint *textures)) {
// Remove Old Data
for (int i = 0; i < n; i++) {
GLint id = textures[i];
std::vector<texture_data>::iterator it = get_texture_data().begin();
while (it != get_texture_data().end()) {
texture_data data = *it;
if (data.id == id) {
it = get_texture_data().erase(it);
} else {
++it;
}
}
}
// Call Original Method
ensure_glDeleteTextures();
real_glDeleteTextures(n, textures);
}
static void get_texture_size(GLint id, GLsizei *width, GLsizei *height) {
// Iterate
std::vector<texture_data>::iterator it = get_texture_data().begin();
while (it != get_texture_data().end()) {
texture_data data = *it;
if (data.id == id) {
// Found
*width = data.width;
*height = data.height;
return;
}
++it;
}
// Not Found
*width = 0;
*height = 0;
}
// Scale Texture (Remember To Free)
#define PIXEL_SIZE 4
static int get_line_size(int width) {
int line_size = width * PIXEL_SIZE;
{
// Handle Alignment
int alignment;
glGetIntegerv(GL_UNPACK_ALIGNMENT, &alignment);
// Round
int diff = line_size % alignment;
if (diff > 0) {
line_size = line_size + (alignment - diff);
}
}
return line_size;
}
static void *scale_texture(const unsigned char *src, GLsizei old_width, GLsizei old_height, GLsizei new_width, GLsizei new_height) {
int old_line_size = get_line_size(old_width);
int new_line_size = get_line_size(new_width);
// Allocate
unsigned char *dst = (unsigned char *) malloc(new_height * new_line_size);
ALLOC_CHECK(dst);
// Scale
for (int new_x = 0; new_x < new_width; new_x++) {
int old_x = (int) (((float) new_x / (float) new_width) * (float) old_width);
for (int new_y = 0; new_y < new_height; new_y++) {
int old_y = (int) (((float) new_y / (float) new_height) * (float) old_height);
// Find Position
int new_position = (new_y * new_line_size) + (new_x * PIXEL_SIZE);
int old_position = (old_y * old_line_size) + (old_x * PIXEL_SIZE);
// Copy
static_assert(sizeof (int32_t) == PIXEL_SIZE, "Pixel Size Doesn't Match 32-Bit Integer Size");
*(int32_t *) &dst[new_position] = *(int32_t *) &src[old_position];
}
}
// Return
return dst;
}
// Scale Animated Textures
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);
GLsizei texture_width;
GLsizei texture_height;
get_texture_size(current_texture, &texture_width, &texture_height);
// Calculate Factor
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) {
// No Scaling
glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
} else {
// Check
if (format != GL_RGBA || type != GL_UNSIGNED_BYTE) {
// Pixels Must Be 4 Bytes
ERR("Unsupported Texture Format For Scaling");
}
// Scale
GLsizei new_width = width * width_factor;
GLsizei new_height = height * height_factor;
void *new_pixels = scale_texture((const unsigned char *) pixels, width, height, new_width, new_height);
// Call Original Method
GLint new_xoffset = xoffset * width_factor;
GLint new_yoffset = yoffset * height_factor;
glTexSubImage2D(target, level, new_xoffset, new_yoffset, new_width, new_height, format, type, new_pixels);
// Free
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);
}
// Load Textures
static Texture AppPlatform_linux_loadTexture_injection(__attribute__((unused)) AppPlatform_linux *app_platform, std::string *path, bool b) {
Texture out;
std::string real_path = *path;
if (b) {
real_path = "data/images/" + real_path;
}
// Empty Texture
out.width = 0;
out.height = 0;
out.data = nullptr;
out.field3_0xc = 0;
out.field4_0x10 = true;
out.field5_0x11 = false;
out.field6_0x14 = 0;
out.field7_0x18 = -1;
// Read Image
int width = 0, height = 0, channels = 0;
stbi_uc *img = stbi_load(real_path.c_str(), &width, &height, &channels, STBI_rgb_alpha);
if (!img)
{
// Failed To Parse Image
WARN("Unable To Load Texture: %s", real_path.c_str());
return out;
}
// Copy Image
unsigned char *img2 = new unsigned char[width * height * channels];
memcpy(img2, img, width * height * channels);
stbi_image_free(img);
// Create Texture
out.width = width;
out.height = height;
out.data = img2;
// Return
return out;
}
// Init
void init_textures() {
// Tick Dynamic Textures (Animated Water)
bool animated_water = feature_has("Animated Water", server_disabled);
bool animated_lava = feature_has("Animated Lava", server_disabled);
bool animated_fire = feature_has("Animated Fire", server_disabled);
if (animated_water || animated_lava || animated_fire) {
// Tick Dynamic Textures
misc_run_on_tick(Minecraft_tick_injection);
// Disable Animated Water If Set
if (!animated_water) {
unsigned char disable_water_patch[4] = {0x00, 0xf0, 0x20, 0xe3}; // "nop"
patch((void *) 0x17094, disable_water_patch);
patch((void *) 0x170b4, disable_water_patch);
}
// Animated Lava
_init_textures_lava(animated_water, animated_lava, animated_fire);
}
// Scale Animated Textures
overwrite_call((void *) 0x53274, (void *) Textures_tick_glTexSubImage2D_injection);
// Load Textures
overwrite(*AppPlatform_linux_loadTexture_vtable_addr, AppPlatform_linux_loadTexture_injection);
}