589 lines
22 KiB
C++

#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cmath>
#include <string>
#include <fstream>
#include <streambuf>
#include <GLES/gl.h>
#include <SDL/SDL.h>
#include <media-layer/core.h>
#include <libreborn/libreborn.h>
#include <symbols/minecraft.h>
#include <mods/init/init.h>
#include <mods/feature/feature.h>
#include <mods/input/input.h>
#include <mods/misc/misc.h>
#include "misc-internal.h"
// 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
const RakNet_RakString_SharedString *shared_string = rak_string->sharedString;
const 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.
RakNet_RakString_constructor_t RakNet_RakString_constructor = (RakNet_RakString_constructor_t) 0xea5cc;
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",
"Unknown"
};
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
const RakNet_StartupResult result = rak_peer->Startup(maxConnections, socketDescriptors, socketDescriptorCount, threadPriority);
// Print Error
if (result != RAKNET_STARTED) {
CONDITIONAL_ERR(reborn_is_server(), "Failed To Start RakNet: %s", RAKNET_ERROR_NAMES[result]);
}
// Return
return result;
}
// 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, const ItemInstance *item) {
// Get Existing Item
FurnaceTileEntity *tile_entity = furnace_screen->tile_entity;
const 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;
}
}
// Get Real Selected Slot
int32_t misc_get_real_selected_slot(const Player *player) {
// Get Selected Slot
const Inventory *inventory = player->inventory;
int32_t selected_slot = inventory->selectedSlot;
// Linked Slots
const int32_t linked_slots_length = inventory->linked_slots_length;
if (selected_slot < linked_slots_length) {
const int32_t *linked_slots = inventory->linked_slots;
selected_slot = linked_slots[selected_slot];
}
// Return
return selected_slot;
}
// 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 sockaddr_in *) new_addr;
if (in_addr.sin_port == ntohs(4711)) {
const char *new_port_str = getenv(MCPI_API_PORT_ENV);
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 sockaddr *) &in_addr;
}
ensure_bind();
return real_bind(sockfd, new_addr, addrlen);
}
// 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);
}
// 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()) {
const Player *player = (Player *) target;
const Inventory *inventory = player->inventory;
const bool is_creative = inventory->is_creative;
if (is_creative) {
target = nullptr;
}
}
// Return
return target;
}
// Fix used items transferring durability
static int selected_slot = -1;
static void Player_startUsingItem_injection(Player_startUsingItem_t original, Player *self, ItemInstance *item_instance, const 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);
}
// Read Asset File
static AppPlatform_readAssetFile_return_value AppPlatform_readAssetFile_injection(__attribute__((unused)) AppPlatform_readAssetFile_t original, __attribute__((unused)) AppPlatform *app_platform, const 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 = int(len);
ret.data = strdup(buf);
return ret;
}
// Implement crafting remainders
static void PaneCraftingScreen_craftSelectedItem_PaneCraftingScreen_recheckRecipes_injection(PaneCraftingScreen *self) {
// Check for crafting remainders
const CItem *item = self->item;
for (size_t i = 0; i < item->ingredients.size(); i++) {
ItemInstance requested_item_instance = item->ingredients[i].requested_item;
Item *requested_item = Item::items[requested_item_instance.id];
ItemInstance *craftingRemainingItem = requested_item->getCraftingRemainingItem(&requested_item_instance);
if (craftingRemainingItem != nullptr) {
// Add or drop remainder
LocalPlayer *player = self->minecraft->player;
if (!player->inventory->add(craftingRemainingItem)) {
// Drop
player->drop(craftingRemainingItem, false);
}
}
}
// Call Original Method
self->recheckRecipes();
}
static ItemInstance *Item_getCraftingRemainingItem_injection(__attribute__((unused)) Item_getCraftingRemainingItem_t original, const Item *self, const ItemInstance *item_instance) {
if (self->craftingRemainingItem != nullptr) {
ItemInstance *ret = new ItemInstance;
ret->id = self->craftingRemainingItem->id;
ret->count = 1;
ret->auxiliary = 0;
return ret;
}
return nullptr;
}
// Display Date In Select World Screen
static std::string AppPlatform_linux_getDateString_injection(__attribute__((unused)) AppPlatform_linux *app_platform, const int time) {
// From https://github.com/ReMinecraftPE/mcpe/blob/56e51027b1c2e67fe5a0e8a091cefe51d4d11926/platforms/sdl/base/AppPlatform_sdl_base.cpp#L68-L84
const time_t tt = time;
tm t = {};
gmtime_r(&tt, &t);
char buf[2048];
strftime(buf, sizeof buf, "%b %d %Y %H:%M:%S", &t);
return std::string(buf);
}
// Missing Strings
static void add_missing_string(const std::string &key, const std::string &value) {
if (!I18n::_strings.contains(key)) {
I18n::_strings[key] = value;
}
}
static void Language_injection() {
// Fix Language Strings
add_missing_string("tile.waterStill.name", "Still Water");
add_missing_string("tile.lavaStill.name", "Still Lava");
add_missing_string("tile.grassCarried.name", "Carried Grass");
add_missing_string("tile.leavesCarried.name", "Carried Leaves");
add_missing_string("tile.invBedrock.name", "Invisible Bedrock");
// Missing Language Strings
add_missing_string("item.camera.name", "Camera");
add_missing_string("item.seedsMelon.name", "Melon Seeds");
add_missing_string("tile.pumpkinStem.name", "Pumpkin Stem");
add_missing_string("tile.stoneSlab.name", "Double Stone Slab");
}
// Invisible Bedrock
static Tile *Tile_initTiles_Tile_init_invBedrock_injection(Tile *t) {
Tile *ret = t->init();
t->setDescriptionId("invBedrock");
return ret;
}
// Append "Still" Suffix To Liquid Description Keys
static std::string *Tile_initTiles_std_string_constructor(std::string *self, const char *from, const std::string::allocator_type &alloc) {
new (self) std::string(from, alloc);
self->append("Still");
return self;
}
// Fix Pigmen Burning In The Sun
static bool fix_pigmen_burning = false;
static float Zombie_aiStep_getBrightness_injection(Entity *self, float param_1) {
if (fix_pigmen_burning && self->getEntityTypeId() == 36) {
return 0;
} else {
return self->getBrightness(param_1);
}
}
// Fix Door Item Dropping
static void DoorTile_neighborChanged_Tile_spawnResources_injection(DoorTile *self, Level *level, int x, int y, int z, int data2, __attribute__((unused)) float chance) {
self->spawnResources(level, x, y, z, data2, 1);
}
// Fix Cobweb Lighting
static Tile *Tile_initTiles_WebTile_setLightBlock_injection(Tile *self, __attribute__((unused)) int strength) {
return self;
}
// Fix Fire Immunity
static void Mob_baseTick_injection_fire_immunity(Mob_baseTick_t original, Mob *self) {
// Fix Fire Timer
if (self->fire_immune) {
self->fire_timer = 0;
}
// Call Original Method
original(self);
}
// Fix Fire Syncing
#define FLAG_ONFIRE 0
static void Mob_baseTick_injection_fire_syncing(Mob_baseTick_t original, Mob *self) {
// Fix Fire Timer
if (self->level->is_client_side) {
self->fire_timer = 0;
}
// Call Original Method
original(self);
// Sync Data
if (!self->level->is_client_side) {
self->setSharedFlag(FLAG_ONFIRE, self->fire_timer > 0);
}
}
static bool Entity_isOnFire_injection(Entity_isOnFire_t original, Entity *self) {
// Call Original Method
bool ret = original(self);
// Check Shared Data
bool shared_data = false;
if (self->isMob()) {
shared_data = ((Mob *) self)->getSharedFlag(FLAG_ONFIRE);
}
if (shared_data) {
ret = true;
}
// Return
return ret;
}
// Fix Sneaking Syncing
#define FLAG_SNEAKING 1
#define PLAYER_ACTION_STOP_SNEAKING 100
#define PLAYER_ACTION_START_SNEAKING 101
static void LocalPlayer_tick_injection(LocalPlayer_tick_t original, LocalPlayer *self) {
// Call Original Method
original(self);
// Sync Data
if (!self->level->is_client_side) {
self->setSharedFlag(FLAG_SNEAKING, self->isSneaking());
} else {
const bool real = self->isSneaking();
const bool synced = self->getSharedFlag(FLAG_SNEAKING);
if (real != synced) {
// Send To Server
PlayerActionPacket *packet = PlayerActionPacket::allocate();
Packet_constructor->get(false)((Packet *) packet);
packet->vtable = PlayerActionPacket_vtable_base;
packet->entity_id = self->id;
packet->action = real ? PLAYER_ACTION_START_SNEAKING : PLAYER_ACTION_STOP_SNEAKING;
self->minecraft->rak_net_instance->send(*(Packet *) packet);
}
}
}
static void ServerSideNetworkHandler_handle_PlayerActionPacket_injection(ServerSideNetworkHandler_handle_PlayerActionPacket_t original, ServerSideNetworkHandler *self, const RakNet_RakNetGUID &rak_net_guid, PlayerActionPacket *packet) {
// Call Original Method
original(self, rak_net_guid, packet);
// Handle Sneaking
const bool is_sneaking = packet->action == PLAYER_ACTION_START_SNEAKING;
if (self->level != nullptr && (is_sneaking || packet->action == PLAYER_ACTION_STOP_SNEAKING)) {
Entity *entity = self->level->getEntity(packet->entity_id);
if (entity != nullptr && entity->isPlayer()) {
((Player *) entity)->setSharedFlag(FLAG_SNEAKING, is_sneaking);
}
}
}
// Make Mobs Actually Catch On Fire
static void set_on_fire(Mob *mob, const int seconds) {
const int value = seconds * 20;
if (value > mob->fire_timer) {
mob->fire_timer = value;
}
}
template <typename Self>
static void Monster_aiStep_injection(__attribute__((unused)) std::function<void(Self *)> original, Self *self) {
// Fire!
Level *level = self->level;
if (level->isDay() && !level->is_client_side) {
const float brightness = Zombie_aiStep_getBrightness_injection((Entity *) self, 1);
if (brightness > 0.5f) {
Random *random = &self->random;
if (level->canSeeSky(Mth::floor(self->x), Mth::floor(self->y), Mth::floor(self->z)) && random->nextFloat() * 3.5f < (brightness - 0.4f)) {
set_on_fire((Mob *) self, 8);
}
}
}
// Call Parent Method
Monster_aiStep->get(false)((Monster *) self);
}
// Clear Fire For Creative Players
static void Player_tick_injection(Player_tick_t original, Player *self) {
// Fix Value
if (self->inventory->is_creative && !self->level->is_client_side && self->isOnFire()) {
self->fire_timer = 0;
}
// Call Original Method
original(self);
}
// Init
void init_misc() {
// Sanitize Username
if (feature_has("Sanitize Usernames", server_enabled)) {
overwrite_calls(LoginPacket_read, LoginPacket_read_injection);
}
// Fix RakNet::RakString Security Bug
if (feature_has("Patch RakNet Security Bug", server_enabled)) {
overwrite_calls_manual((void *) RakNet_RakString_constructor, (void *) RakNet_RakString_injection);
}
// Print Error Message If RakNet Startup Fails
if (feature_has("Log RakNet Startup Errors", server_enabled)) {
overwrite_call((void *) 0x73778, (void *) RakNetInstance_host_RakNet_RakPeer_Startup_injection);
}
// Fix Furnace Not Checking Item Auxiliary When Inserting New Item
if (feature_has("Fix Furnace Not Checking Item Auxiliary", server_disabled)) {
overwrite_calls(FurnaceScreen_handleAddItem, FurnaceScreen_handleAddItem_injection);
}
// Disable Speed Bridging
if (feature_has("Disable Speed Bridging", server_disabled)) {
unsigned char disable_speed_bridging_patch[4] = {0x03, 0x00, 0x53, 0xe1}; // "cmp r3, r3"
patch((void *) 0x494b4, disable_speed_bridging_patch);
}
// Disable Creative Mode Mining Delay
if (feature_has("Disable Creative Mode Mining Delay", server_disabled)) {
unsigned char nop_patch[4] = {0x00, 0xf0, 0x20, 0xe3}; // "nop"
patch((void *) 0x19fa0, nop_patch);
}
// Generate Caves
if (feature_has("Generate Caves", server_auto)) {
overwrite_calls(RandomLevelSource_buildSurface, RandomLevelSource_buildSurface_injection);
}
// Disable Hostile AI In Creative Mode
if (feature_has("Disable Hostile AI In Creative Mode", server_enabled)) {
overwrite_call((void *) 0x83b8c, (void *) PathfinderMob_findAttackTarget_injection);
}
// Send The Full Level, Not Only Changed Chunks
if (feature_has("Send Full Level When Hosting Game", server_enabled)) {
unsigned char nop_patch[4] = {0x00, 0xf0, 0x20, 0xe3}; // "nop"
patch((void *) 0x717c4, nop_patch);
unsigned char mov_r3_ff[4] = {0xff, 0x30, 0xa0, 0xe3}; // "mov r3, #0xff"
patch((void *) 0x7178c, mov_r3_ff);
}
// Fix Used Items Transferring Durability
if (feature_has("Fix Transferring Durability When Using Items", server_disabled)) {
overwrite_calls(Player_startUsingItem, Player_startUsingItem_injection);
overwrite_calls(Player_stopUsingItem, Player_stopUsingItem_injection);
}
// Implement AppPlatform::readAssetFile So Translations Work
if (feature_has("Load Language Files", server_enabled)) {
overwrite_calls(AppPlatform_readAssetFile, AppPlatform_readAssetFile_injection);
}
// Implement Crafting Remainders
if (feature_has("Implement Crafting Remainders", server_enabled)) {
overwrite_call((void *) 0x2e230, (void *) PaneCraftingScreen_craftSelectedItem_PaneCraftingScreen_recheckRecipes_injection);
overwrite_calls(Item_getCraftingRemainingItem, Item_getCraftingRemainingItem_injection);
}
// Display Date In Select World Screen
if (feature_has("Display Date In Select World Screen", server_disabled)) {
patch_vtable(AppPlatform_linux_getDateString, AppPlatform_linux_getDateString_injection);
}
// Fullscreen
if (feature_has("Fullscreen Support", server_disabled)) {
misc_run_on_key_press([](__attribute__((unused)) Minecraft *mc, int key) {
if (key == MC_KEY_F11) {
media_toggle_fullscreen();
return true;
} else {
return false;
}
});
}
// Fix/Update Language Strings
if (feature_has("Add Missing Language Strings", server_disabled)) {
misc_run_on_language_setup(Language_injection);
// Water/Lava Language Strings
overwrite_call((void *) 0xc3b54, (void *) Tile_initTiles_std_string_constructor);
overwrite_call((void *) 0xc3c7c, (void *) Tile_initTiles_std_string_constructor);
// Carried Tile Language Strings
patch_address((void *) 0xc6674, (void *) "grassCarried");
patch_address((void *) 0xc6684, (void *) "leavesCarried");
// Invisible Bedrock Language String
overwrite_call((void *) 0xc5f04, (void *) Tile_initTiles_Tile_init_invBedrock_injection);
}
// Prevent Pigmen From Burning In The Sun
if (feature_has("Fix Pigmen Burning In The Sun", server_enabled)) {
fix_pigmen_burning = true;
overwrite_call((void *) 0x89a1c, (void *) Zombie_aiStep_getBrightness_injection);
}
// Fix Door Duplication
if (feature_has("Fix Door Duplication", server_enabled)) {
unsigned char nop_patch[4] = {0x00, 0xf0, 0x20, 0xe3}; // "nop"
patch((void *) 0xbe230, nop_patch);
overwrite_call((void *) 0xbe110, (void *) DoorTile_neighborChanged_Tile_spawnResources_injection);
}
// Fix Cobweb Lighting
if (feature_has("Fix Cobweb Lighting", server_enabled)) {
overwrite_call((void *) 0xc444c, (void *) Tile_initTiles_WebTile_setLightBlock_injection);
}
// Fix Fire Immunity
if (feature_has("Fix Fire Immunity", server_enabled)) {
overwrite_calls(Mob_baseTick, Mob_baseTick_injection_fire_immunity);
}
// Fix Fire Syncing
if (feature_has("Fix Fire Syncing", server_enabled)) {
overwrite_calls(Mob_baseTick, Mob_baseTick_injection_fire_syncing);
overwrite_calls(Entity_isOnFire, Entity_isOnFire_injection);
}
// Fix Sneaking Syncing
if (feature_has("Fix Sneaking Syncing", server_enabled)) {
overwrite_calls(LocalPlayer_tick, LocalPlayer_tick_injection);
overwrite_calls(ServerSideNetworkHandler_handle_PlayerActionPacket, ServerSideNetworkHandler_handle_PlayerActionPacket_injection);
}
// Make Skeletons/Zombies Actually Catch On Fire
if (feature_has("Fix Sunlight Not Properly Setting Mobs On Fire", server_enabled)) {
overwrite_calls(Zombie_aiStep, Monster_aiStep_injection<Zombie>);
overwrite_calls(Skeleton_aiStep, Monster_aiStep_injection<Skeleton>);
}
// Clear Fire For Creative Players
if (feature_has("Stop Creative Players From Burning", server_enabled)) {
overwrite_calls(Player_tick, Player_tick_injection);
}
// Init Other Components
_init_misc_tinting();
_init_misc_ui();
_init_misc_logging();
_init_misc_api();
_init_misc_graphics();
}