#include #include #include #include #include #include #include #include #include #include #ifndef MCPI_HEADLESS_MODE #include #endif #include #include #include #include #include #include #include "misc-internal.h" #include // Classic HUD #define DEFAULT_HUD_PADDING 2 #define NEW_HUD_PADDING 1 #define HUD_ELEMENT_WIDTH 82 #define HUD_ELEMENT_HEIGHT 9 #define TOOLBAR_HEIGHT 22 #define SLOT_WIDTH 20 #define DEFAULT_BUBBLES_PADDING 1 #define NUMBER_OF_SLOTS 9 static int use_classic_hud = 0; static void Gui_renderHearts_GuiComponent_blit_hearts_injection(GuiComponent *component, int32_t x_dest, int32_t y_dest, int32_t x_src, int32_t y_src, int32_t width_dest, int32_t height_dest, int32_t width_src, int32_t height_src) { Minecraft *minecraft = ((Gui *) component)->minecraft; x_dest -= DEFAULT_HUD_PADDING; float width = ((float) minecraft->screen_width) * Gui::InvGuiScale; float height = ((float) minecraft->screen_height) * Gui::InvGuiScale; x_dest += (width - (NUMBER_OF_SLOTS * SLOT_WIDTH)) / 2; y_dest -= DEFAULT_HUD_PADDING; y_dest += height - HUD_ELEMENT_HEIGHT - TOOLBAR_HEIGHT - NEW_HUD_PADDING; // Call Original Method component->blit(x_dest, y_dest, x_src, y_src, width_dest, height_dest, width_src, height_src); } static void Gui_renderHearts_GuiComponent_blit_armor_injection(Gui *component, int32_t x_dest, int32_t y_dest, int32_t x_src, int32_t y_src, int32_t width_dest, int32_t height_dest, int32_t width_src, int32_t height_src) { Minecraft *minecraft = component->minecraft; x_dest -= DEFAULT_HUD_PADDING + HUD_ELEMENT_WIDTH; float width = ((float) minecraft->screen_width) * Gui::InvGuiScale; float height = ((float) minecraft->screen_height) * Gui::InvGuiScale; x_dest += width - ((width - (NUMBER_OF_SLOTS * SLOT_WIDTH)) / 2) - HUD_ELEMENT_WIDTH; y_dest -= DEFAULT_HUD_PADDING; y_dest += height - HUD_ELEMENT_HEIGHT - TOOLBAR_HEIGHT - NEW_HUD_PADDING; // Call Original Method component->blit(x_dest, y_dest, x_src, y_src, width_dest, height_dest, width_src, height_src); } static void Gui_renderBubbles_GuiComponent_blit_injection(Gui *component, int32_t x_dest, int32_t y_dest, int32_t x_src, int32_t y_src, int32_t width_dest, int32_t height_dest, int32_t width_src, int32_t height_src) { Minecraft *minecraft = component->minecraft; x_dest -= DEFAULT_HUD_PADDING; float width = ((float) minecraft->screen_width) * Gui::InvGuiScale; float height = ((float) minecraft->screen_height) * Gui::InvGuiScale; x_dest += (width - (NUMBER_OF_SLOTS * SLOT_WIDTH)) / 2; y_dest -= DEFAULT_HUD_PADDING + DEFAULT_BUBBLES_PADDING + HUD_ELEMENT_HEIGHT; y_dest += height - HUD_ELEMENT_HEIGHT - TOOLBAR_HEIGHT - HUD_ELEMENT_HEIGHT - NEW_HUD_PADDING; // Call Original Method component->blit(x_dest, y_dest, x_src, y_src, width_dest, height_dest, width_src, height_src); } // Heart Food Overlay static int heal_amount = 0, heal_amount_drawing = 0; static void Gui_renderHearts_injection(Gui_renderHearts_t original, Gui *gui) { // Get heal_amount heal_amount = heal_amount_drawing = 0; Inventory *inventory = gui->minecraft->player->inventory; ItemInstance *held_ii = inventory->getSelected(); if (held_ii) { Item *held = Item::items[held_ii->id]; if (held->isFood() && held_ii->id) { int nutrition = ((FoodItem *) held)->nutrition; int cur_health = gui->minecraft->player->health; int heal_num = fmin(cur_health + nutrition, 20) - cur_health; heal_amount = heal_amount_drawing = heal_num; } } // Call original original(gui); } static GuiComponent_blit_t get_blit_with_classic_hud_offset() { return use_classic_hud ? Gui_renderHearts_GuiComponent_blit_hearts_injection : GuiComponent_blit; } #define PINK_HEART_FULL 70 #define PINK_HEART_HALF 79 static void Gui_renderHearts_GuiComponent_blit_overlay_empty_injection(Gui *gui, int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t w1, int32_t h1, int32_t w2, int32_t h2) { // Call Original Method get_blit_with_classic_hud_offset()((GuiComponent *) gui, x1, y1, x2, y2, w1, h1, w2, h2); // Render The Overlay if (heal_amount_drawing == 1) { // Half Heart get_blit_with_classic_hud_offset()((GuiComponent *) gui, x1, y1, PINK_HEART_HALF, 0, w1, h1, w2, h2); heal_amount_drawing = 0; } else if (heal_amount_drawing > 0) { // Full Heart get_blit_with_classic_hud_offset()((GuiComponent *) gui, x1, y1, PINK_HEART_FULL, 0, w1, h1, w2, h2); heal_amount_drawing -= 2; } } static void Gui_renderHearts_GuiComponent_blit_overlay_hearts_injection(Gui *gui, int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t w1, int32_t h1, int32_t w2, int32_t h2) { // Offset the overlay if (x2 == 52) { heal_amount_drawing += 2; } else if (x2 == 61 && heal_amount) { // Half heart, flipped get_blit_with_classic_hud_offset()((GuiComponent *) gui, x1, y1, PINK_HEART_FULL, 0, w1, h1, w2, h2); heal_amount_drawing += 1; } // Call Original Method get_blit_with_classic_hud_offset()((GuiComponent *) gui, x1, y1, x2, y2, w1, h1, w2, h2); heal_amount_drawing = fmin(heal_amount_drawing, heal_amount); } // Additional GUI Rendering static int hide_chat_messages = 0; bool is_in_chat = false; static int render_selected_item_text = 0; static void Gui_renderChatMessages_injection(Gui_renderChatMessages_t original, Gui *gui, int32_t y_offset, uint32_t max_messages, bool disable_fading, Font *font) { // Handle Classic HUD if (use_classic_hud) { Minecraft *minecraft = gui->minecraft; if (!Minecraft_isCreativeMode(minecraft)) { y_offset -= (HUD_ELEMENT_HEIGHT * 2) + NEW_HUD_PADDING; } } // Call Original Method if (!hide_chat_messages && !is_in_chat) { original(gui, y_offset, max_messages, disable_fading, font); } // Render Selected Item Text if (render_selected_item_text) { // Fix GL Mode #ifndef MCPI_HEADLESS_MODE glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); #endif // Calculate Selected Item Text Scale Minecraft *minecraft = gui->minecraft; int32_t screen_width = minecraft->screen_width; float scale = ((float) screen_width) * Gui::InvGuiScale; // Render Selected Item Text gui->renderOnSelectItemNameText((int32_t) scale, font, y_offset - 0x13); } } // Reset Selected Item Text Timer On Slot Select static uint32_t reset_selected_item_text_timer = 0; static void Gui_tick_injection(Gui_tick_t original, Gui *gui) { // Call Original Method original(gui); // Handle Reset if (render_selected_item_text) { float *selected_item_text_timer = &gui->selected_item_text_timer; if (reset_selected_item_text_timer) { // Reset *selected_item_text_timer = 0; reset_selected_item_text_timer = 0; } } } // Trigger Reset Selected Item Text Timer On Slot Select static void Inventory_selectSlot_injection(Inventory_selectSlot_t original, Inventory *inventory, int32_t slot) { // Call Original Method original(inventory, slot); // Trigger Reset Selected Item Text Timer if (render_selected_item_text) { reset_selected_item_text_timer = 1; } } // Translucent Toolbar static void Gui_renderToolBar_injection(Gui_renderToolBar_t original, Gui *gui, float param_1, int32_t param_2, int32_t param_3) { // Call Original Method #ifndef MCPI_HEADLESS_MODE int was_blend_enabled = glIsEnabled(GL_BLEND); if (!was_blend_enabled) { glEnable(GL_BLEND); } glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); #endif original(gui, param_1, param_2, param_3); #ifndef MCPI_HEADLESS_MODE if (!was_blend_enabled) { glDisable(GL_BLEND); } #endif } static void Gui_renderToolBar_glColor4f_injection(GLfloat red, GLfloat green, GLfloat blue, __attribute__((unused)) GLfloat alpha) { // Fix Alpha #ifndef MCPI_HEADLESS_MODE glColor4f(red, green, blue, 1.0f); #else (void) red; (void) green; (void) blue; #endif } // Fix Screen Rendering When GUI is Hidden static void Screen_render_injection(Screen_render_t original, Screen *screen, int32_t param_1, int32_t param_2, float param_3) { // Fix #ifndef MCPI_HEADLESS_MODE glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); #endif // Call Original Method original(screen, param_1, param_2, param_3); } // Sanitize Username #define MAX_USERNAME_LENGTH 16 static void LoginPacket_read_injection(LoginPacket_read_t original, LoginPacket *packet, RakNet_BitStream *bit_stream) { // Call Original Method original(packet, bit_stream); // Prepare RakNet_RakString *rak_string = &packet->username; // Get Original Username RakNet_RakString_SharedString *shared_string = rak_string->sharedString; char *c_str = shared_string->c_str; // Sanitize char *new_username = strdup(c_str); ALLOC_CHECK(new_username); sanitize_string(&new_username, MAX_USERNAME_LENGTH, 0); // Set New Username rak_string->Assign(new_username); // Free free(new_username); } // Fix RakNet::RakString Security Bug // // RakNet::RakString's format constructor is often given unsanitized user input and is never used for formatting, // this is a massive security risk, allowing clients to run arbitrary format specifiers, this disables the // formatting functionality. static RakNet_RakString *RakNet_RakString_injection(RakNet_RakString *rak_string, const char *format, ...) { // Call Original Method return RakNet_RakString_constructor(rak_string, "%s", format); } // Print Error Message If RakNet Startup Fails static const char *RAKNET_ERROR_NAMES[] = { "Success", "Already Started", "Invalid Socket Descriptors", "Invalid Max Connections", "Socket Family Not Supported", "Part Already In Use", "Failed To Bind Port", "Failed Test Send", "Port Cannot Be 0", "Failed To Create Network Thread", "Couldn't Generate GUID", "Unknown" }; #ifdef MCPI_SERVER_MODE #define PRINT_RAKNET_STARTUP_FAILURE ERR #else #define PRINT_RAKNET_STARTUP_FAILURE WARN #endif static RakNet_StartupResult RakNetInstance_host_RakNet_RakPeer_Startup_injection(RakNet_RakPeer *rak_peer, unsigned short maxConnections, unsigned char *socketDescriptors, uint32_t socketDescriptorCount, int32_t threadPriority) { // Call Original Method RakNet_StartupResult result = rak_peer->Startup(maxConnections, socketDescriptors, socketDescriptorCount, threadPriority); // Print Error if (result != RAKNET_STARTED) { PRINT_RAKNET_STARTUP_FAILURE("Failed To Start RakNet: %s", RAKNET_ERROR_NAMES[result]); } // Return return result; } // Fix Bug Where RakNetInstance Starts Pinging Potential Servers Before The "Join Game" Screen Is Opened static RakNetInstance *RakNetInstance_injection(RakNetInstance_constructor_t original, RakNetInstance *rak_net_instance) { // Call Original Method RakNetInstance *result = original(rak_net_instance); // Fix rak_net_instance->pinging_for_hosts = 0; // Return return result; } // Close Current Screen On Death To Prevent Bugs static void LocalPlayer_die_injection(LocalPlayer_die_t original, LocalPlayer *entity, Entity *cause) { // Close Screen Minecraft *minecraft = entity->minecraft; minecraft->setScreen(nullptr); // Call Original Method original(entity, cause); } // Fix Furnace Not Checking Item Auxiliary When Inserting New Item static int32_t FurnaceScreen_handleAddItem_injection(FurnaceScreen_handleAddItem_t original, FurnaceScreen *furnace_screen, int32_t slot, ItemInstance *item) { // Get Existing Item FurnaceTileEntity *tile_entity = furnace_screen->tile_entity; ItemInstance *existing_item = tile_entity->getItem(slot); // Check Item int valid; if (item->id == existing_item->id && item->auxiliary == existing_item->auxiliary) { // Item Matches, Is Valid valid = 1; } else { // Item Doesn't Match, Check If Existing Item Is Empty if ((existing_item->id | existing_item->count | existing_item->auxiliary) == 0) { // Existing Item Is Empty, Is Valid valid = 1; } else { // Existing Item Isn't Empty, Isn't Valid valid = 0; } } // Call Original Method if (valid) { // Valid return original(furnace_screen, slot, item); } else { // Invalid return 0; } } // Custom Cursor Rendering // // The default behavior for Touch GUI is to only render the cursor when the mouse is clicking, this fixes that. // This also makes the cursor always render if the mouse is unlocked, instead of just when there is a Screen showing. #ifndef MCPI_HEADLESS_MODE static void GameRenderer_render_injection(GameRenderer_render_t original, GameRenderer *game_renderer, float param_1) { // Call Original Method original(game_renderer, param_1); // Check If Cursor Should Render if (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_OFF) { // Fix GL Mode glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Get X And Y float x = Mouse::getX() * Gui::InvGuiScale; float y = Mouse::getY() * Gui::InvGuiScale; // Render Cursor Minecraft *minecraft = game_renderer->minecraft; Common::renderCursor(x, y, minecraft); } } #endif // Get Real Selected Slot int32_t misc_get_real_selected_slot(Player *player) { // Get Selected Slot Inventory *inventory = player->inventory; int32_t selected_slot = inventory->selectedSlot; // Linked Slots int32_t linked_slots_length = inventory->linked_slots_length; if (selected_slot < linked_slots_length) { int32_t *linked_slots = inventory->linked_slots; selected_slot = linked_slots[selected_slot]; } // Return return selected_slot; } #ifndef MCPI_HEADLESS_MODE // Properly Generate Buffers static void anGenBuffers_injection(int32_t count, uint32_t *buffers) { glGenBuffers(count, buffers); } #endif // Fix Graphics Bug When Switching To First-Person While Sneaking static void PlayerRenderer_render_injection(PlayerRenderer *model_renderer, Entity *entity, float param_2, float param_3, float param_4, float param_5, float param_6) { (*HumanoidMobRenderer_render_vtable_addr)((HumanoidMobRenderer *) model_renderer, entity, param_2, param_3, param_4, param_5, param_6); HumanoidModel *model = model_renderer->model; model->is_sneaking = false; } // Custom API Port HOOK(bind, int, (int sockfd, const struct sockaddr *addr, socklen_t addrlen)) { const sockaddr *new_addr = addr; sockaddr_in in_addr = {}; if (addr->sa_family == AF_INET) { in_addr = *(const struct sockaddr_in *) new_addr; if (in_addr.sin_port == ntohs(4711)) { const char *new_port_str = getenv("MCPI_API_PORT"); long int new_port; if (new_port_str != nullptr && (new_port = strtol(new_port_str, nullptr, 0)) != 0L) { in_addr.sin_port = htons(new_port); } } new_addr = (const struct sockaddr *) &in_addr; } ensure_bind(); return real_bind(sockfd, new_addr, addrlen); } // Change Grass Color static int32_t get_color(LevelSource *level_source, int32_t x, int32_t z) { Biome *biome = level_source->getBiome(x, z); if (biome == nullptr) { return 0; } return biome->color; } #define BIOME_BLEND_SIZE 7 static int32_t GrassTile_getColor_injection(__attribute__((unused)) GrassTile *tile, LevelSource *level_source, int32_t x, __attribute__((unused)) int32_t y, int32_t z) { int r_sum = 0; int g_sum = 0; int b_sum = 0; int color_sum = 0; int x_start = x - (BIOME_BLEND_SIZE / 2); int z_start = z - (BIOME_BLEND_SIZE / 2); for (int x_offset = 0; x_offset < BIOME_BLEND_SIZE; x_offset++) { for (int z_offset = 0; z_offset < BIOME_BLEND_SIZE; z_offset++) { int32_t color = get_color(level_source, x_start + x_offset, z_start + z_offset); r_sum += (color >> 16) & 0xff; g_sum += (color >> 8) & 0xff; b_sum += color & 0xff; color_sum++; } } int r_avg = r_sum / color_sum; int g_avg = g_sum / color_sum; int b_avg = b_sum / color_sum; return (r_avg << 16) | (g_avg << 8) | b_avg; } static int32_t TallGrass_getColor_injection(TallGrass_getColor_t original, TallGrass *tile, LevelSource *level_source, int32_t x, int32_t y, int32_t z) { int32_t original_color = original(tile, level_source, x, y, z); if (original_color == 0x339933) { return GrassTile_getColor_injection(nullptr, level_source, x, y, z); } else { return original_color; } } // Generate Caves static void RandomLevelSource_buildSurface_injection(RandomLevelSource_buildSurface_t original, RandomLevelSource *random_level_source, int32_t chunk_x, int32_t chunk_y, unsigned char *chunk_data, Biome **biomes) { // Call Original Method original(random_level_source, chunk_x, chunk_y, chunk_data, biomes); // Get Level Level *level = random_level_source->level; // Get Cave Feature LargeCaveFeature *cave_feature = &random_level_source->cave_feature; // Generate cave_feature->apply((ChunkSource *) random_level_source, level, chunk_x, chunk_y, chunk_data, 0); } // No Block Tinting template static int32_t Tile_getColor_injection(__attribute__((unused)) T original, __attribute__((unused)) S *self, __attribute__((unused)) LevelSource *level_source, __attribute__((unused)) int x, __attribute__((unused)) int y, __attribute__((unused)) int z) { return 0xffffff; } // Disable Hostile AI In Creative Mode static Entity *PathfinderMob_findAttackTarget_injection(PathfinderMob *mob) { // Call Original Method Entity *target = mob->findAttackTarget(); // Only modify the AI of monsters if (mob->getCreatureBaseType() != 1) { return target; } // Check If Creative Mode if (target != nullptr && target->isPlayer()) { Player *player = (Player *) target; Inventory *inventory = player->inventory; bool is_creative = inventory->is_creative; if (is_creative) { target = nullptr; } } // Return return target; } // 3D Chests static int32_t Tile_getRenderShape_injection(Tile *tile) { if (tile == Tile::chest) { // Don't Render "Simple" Chest Model return -1; } else { // Call Original Method return tile->getRenderShape(); } } static ChestTileEntity *ChestTileEntity_injection(ChestTileEntity_constructor_t original, ChestTileEntity *tile_entity) { // Call Original Method original(tile_entity); // Enable Renderer tile_entity->renderer_id = 1; // Return return tile_entity; } static bool is_rendering_chest = false; static void ModelPart_render_injection(ModelPart *model_part, float scale) { // Start is_rendering_chest = true; // Call Original Method model_part->render(scale); // Stop is_rendering_chest = false; } static void Tesselator_vertexUV_injection(Tesselator_vertexUV_t original, Tesselator *tesselator, float x, float y, float z, float u, float v) { // Fix Chest Texture if (is_rendering_chest) { v /= 2; } // Call Original Method original(tesselator, x, y, z, u, v); } static bool ChestTileEntity_shouldSave_injection(__attribute__((unused)) unsigned char *tile_entity) { return true; } // Animated 3D Chest static ContainerMenu *ContainerMenu_injection(ContainerMenu_constructor_t original, ContainerMenu *container_menu, Container *container, int32_t param_1) { // Call Original Method original(container_menu, container, param_1); // Play Animation ChestTileEntity *tile_entity = (ChestTileEntity *) (((unsigned char *) container) - offsetof(ChestTileEntity, container)); bool is_client = tile_entity->is_client; if (!is_client) { container->startOpen(); } // Return return container_menu; } static ContainerMenu *ContainerMenu_destructor_injection(ContainerMenu_destructor_complete_t original, ContainerMenu *container_menu) { // Play Animation Container *container = container_menu->container; ChestTileEntity *tile_entity = (ChestTileEntity *) (((unsigned char *) container) - offsetof(ChestTileEntity, container)); bool is_client = tile_entity->is_client; if (!is_client) { container->stopOpen(); } // Call Original Method return original(container_menu); } #ifndef MCPI_HEADLESS_MODE // Custom Outline Color static void glColor4f_injection(__attribute__((unused)) GLfloat red, __attribute__((unused)) GLfloat green, __attribute__((unused)) GLfloat blue, __attribute__((unused)) GLfloat alpha) { // Set Color glColor4f(0, 0, 0, 0.4); // Find Line Width char *custom_line_width = getenv("MCPI_BLOCK_OUTLINE_WIDTH"); float line_width; if (custom_line_width != nullptr) { // Custom line_width = strtof(custom_line_width, nullptr); } else { // Guess line_width = 1.5f / Gui::InvGuiScale; } // Clamp Line Width float range[2]; glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, range); if (range[1] < line_width) { line_width = range[1]; } else if (range[0] > line_width) { line_width = range[0]; } // Set Line Width glLineWidth(line_width); } #endif // Fix Furnace Visual Bug static int FurnaceTileEntity_getLitProgress_injection(FurnaceTileEntity_getLitProgress_t original, FurnaceTileEntity *furnace, int max) { // Call Original Method int ret = original(furnace, max); // Fix Bug if (ret > max) { ret = max; } // Return return ret; } // Fix used items transferring durability static int selected_slot = -1; static void Player_startUsingItem_injection(Player_startUsingItem_t original, Player *self, ItemInstance *item_instance, int time) { selected_slot = self->inventory->selectedSlot; original(self, item_instance, time); } static void Player_stopUsingItem_injection(Player_stopUsingItem_t original, Player *self) { if (selected_slot != self->inventory->selectedSlot) { self->itemBeingUsed.id = 0; } original(self); } // Java Light Ramp static void Dimension_updateLightRamp_injection(__attribute__((unused)) Dimension_updateLightRamp_t original, Dimension *self) { // https://github.com/ReMinecraftPE/mcpe/blob/d7a8b6baecf8b3b050538abdbc976f690312aa2d/source/world/level/Dimension.cpp#L92-L105 for (int i = 0; i <= 15; i++) { float f1 = 1.0f - (((float) i) / 15.0f); self->light_ramp[i] = ((1.0f - f1) / (f1 * 3.0f + 1.0f)) * (1.0f - 0.1f) + 0.1f; // Default Light Ramp: // float fVar4 = 1.0 - ((float) i * 0.0625); // self->light_ramp[i] = ((1.0 - fVar4) / (fVar4 * 3.0 + 1.0)) * 0.95 + 0.15; } } // Read Asset File static AppPlatform_readAssetFile_return_value AppPlatform_readAssetFile_injection(__attribute__((unused)) AppPlatform_readAssetFile_t original, __attribute__((unused)) AppPlatform *app_platform, std::string *path) { // Open File std::ifstream stream("data/" + *path, std::ios_base::binary | std::ios_base::ate); if (!stream) { // Does Not Exist AppPlatform_readAssetFile_return_value ret; ret.length = -1; ret.data = nullptr; return ret; } // Read File std::streamoff len = stream.tellg(); char *buf = new char[len]; ALLOC_CHECK(buf); stream.seekg(0, std::ifstream::beg); stream.read(buf, len); stream.close(); // Return String AppPlatform_readAssetFile_return_value ret; ret.length = len; ret.data = strdup(buf); return ret; } // Add Missing Buttons To Pause Menu static void PauseScreen_init_injection(PauseScreen_init_t original, PauseScreen *screen) { // Call Original Method original(screen); // Check If Server Minecraft *minecraft = screen->minecraft; RakNetInstance *rak_net_instance = minecraft->rak_net_instance; if (rak_net_instance != nullptr) { if (rak_net_instance->isServer()) { // Add Button std::vector