diff --git a/dependencies/symbol-processor/src b/dependencies/symbol-processor/src index 01a64f32..3db1515e 160000 --- a/dependencies/symbol-processor/src +++ b/dependencies/symbol-processor/src @@ -1 +1 @@ -Subproject commit 01a64f321d5f98a59d1432f659b8a9e11d3d60c6 +Subproject commit 3db1515ecf3a5ab8f9b3b642dc564ca84ab3182c diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 710c0a4c..ae8c79c7 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -61,6 +61,7 @@ * `Batch Font Rendering` (Enabled By Default) * `Fix Furnace Screen Visual Bug` (Enabled By Default) * `Fix Held Item Poking Through Screen Overlay` (Enabled By Default) + * `Implement RaspberryJuice API` (Enabled By Default) * Existing Functionality (All Enabled By Default) * `Fix Screen Rendering When Hiding HUD` * `Sanitize Usernames` diff --git a/docs/CREDITS.md b/docs/CREDITS.md index 433b5d21..5c3e693c 100644 --- a/docs/CREDITS.md +++ b/docs/CREDITS.md @@ -1,11 +1,13 @@ # Credits | Project | Reason | | --- | --- | -| [mhsjlw/mcpilauncher](https://github.com/mhsjlw/mcpilauncher/blob/master/trampoline/trampoline.c) | Information On Getting Minecraft: Pi Eiditon To Run On Desktop Linux | +| [mhsjlw/mcpilauncher](https://web.archive.org/web/20220727030722/https://github.com/mhsjlw/mcpilauncher/blob/master/trampoline/trampoline.c) | Information On Getting Minecraft: Pi Edition To Run On Desktop Linux | | [Phirel's Survival Patch](https://www.minecraftforum.net/forums/minecraft-editions/minecraft-pi-edition/1960005-survival-mode-patch) | Information On Survival Mode Support | | [zhuowei/MinecraftPEModWiki](https://github.com/zhuowei/MinecraftPEModWiki/wiki/How-some-unlocks-are-made) | Information On Smooth Lighting Support | +| [zhuowei/RaspberryJuice](https://github.com/zhuowei/RaspberryJuice) | Design Of RaspberryJuice Extended API | | [Ghidra](https://ghidra-sre.org) | Used For Decompiling Minecraft: Pi Edition | | [RetDec](https://retdec.com) | Used For Decompiling Minecraft: Pi Edition | | [minecraft-linux/mcpelauncher-core](https://github.com/minecraft-linux/mcpelauncher-core/blob/6b5e17b5685a612143297ae4595bdd12327284f3/src/patch_utils.cpp#L42) | Original Function Overwrite Code | | [Hooking C Functions at Runtime - Thomas Finch](http://thomasfinch.me/blog/2015/07/24/Hooking-C-Functions-At-Runtime.html) | Original Patching Code | -| [ReMinecraftPE](https://github.com/ReMinecraftPE/mcpe) | A Lot Of Decompiled Code | \ No newline at end of file +| [ReMinecraftPE](https://github.com/ReMinecraftPE/mcpe) | A Lot Of Decompiled Code | +| [Bigjango](https://github.com/Bigjango13) | A Ton Of Programming Contributions | \ No newline at end of file diff --git a/launcher/src/options/option-list.h b/launcher/src/options/option-list.h index 7e05a583..676f0ece 100644 --- a/launcher/src/options/option-list.h +++ b/launcher/src/options/option-list.h @@ -1,7 +1,7 @@ OPTION(debug, "debug", 'd', "Enable Debug Logging") OPTION(copy_sdk, "copy-sdk", -2, "Extract Modding SDK And Exit") OPTION(disable_logger, "disable-logger", -1, "Disable Logger (And Crash Report Dialog)") -OPTION(use_default, "default", -3, "Skip Client-Mode Configuration Dialogs") +OPTION(use_default, "default", -3, "Skip Client-Mode Configuration Dialog") OPTION(no_cache, "no-cache", -4, "Disable Client-Mode Configuration Cache") OPTION(wipe_cache, "wipe-cache", -5, "Wipe Cached Client-Mode Configuration And Exit") OPTION(print_available_feature_flags, "print-available-feature-flags", -6, "Print Available Client-Mode Feature Flags") diff --git a/libreborn/src/util/env/flags/available-feature-flags b/libreborn/src/util/env/flags/available-feature-flags index 8c973df2..1f0e3ab7 100644 --- a/libreborn/src/util/env/flags/available-feature-flags +++ b/libreborn/src/util/env/flags/available-feature-flags @@ -149,4 +149,5 @@ CATEGORY Miscellaneous TRUE Screenshot Support TRUE Add Camera Functionality TRUE Update Default Options - TRUE Remove Chest Placement Restrictions \ No newline at end of file + TRUE Remove Chest Placement Restrictions + TRUE Implement RaspberryJuice API \ No newline at end of file diff --git a/mods/CMakeLists.txt b/mods/CMakeLists.txt index 07689ff9..dc2ea139 100644 --- a/mods/CMakeLists.txt +++ b/mods/CMakeLists.txt @@ -34,6 +34,7 @@ add_library(mods SHARED src/misc/ui.cpp src/misc/tinting.cpp src/misc/home.cpp + src/misc/base64.cpp # extend src/extend/Screen.cpp src/extend/DynamicTexture.cpp @@ -98,6 +99,8 @@ add_library(mods SHARED src/multidraw/storage.cpp # classic-ui src/classic-ui/classic-ui.cpp + # api + src/api/api.cpp # shading src/shading/init.cpp src/shading/tesselator.cpp diff --git a/mods/include/mods/api/api.h b/mods/include/mods/api/api.h new file mode 100644 index 00000000..0c04ae91 --- /dev/null +++ b/mods/include/mods/api/api.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +extern "C" { +void api_add_chat_event(const Player *sender, const std::string &message); +} \ No newline at end of file diff --git a/mods/include/mods/chat/chat.h b/mods/include/mods/chat/chat.h index bda3dc52..d6c6f2dd 100644 --- a/mods/include/mods/chat/chat.h +++ b/mods/include/mods/chat/chat.h @@ -1,7 +1,6 @@ #pragma once #include - #include extern "C" { @@ -9,6 +8,7 @@ extern "C" { std::string chat_send_api_command(const Minecraft *minecraft, const std::string &str); // Override using the HOOK() macro to provide customized chat behavior. -void chat_send_message_to_clients(ServerSideNetworkHandler *server_side_network_handler, const char *username, const char *message); +void chat_send_message_to_clients(ServerSideNetworkHandler *server_side_network_handler, const Player *sender, const char *message); void chat_handle_packet_send(const Minecraft *minecraft, ChatPacket *packet); +bool chat_is_sending(); } diff --git a/mods/include/mods/init/init.h b/mods/include/mods/init/init.h index 2fbb415f..f0ae61e2 100644 --- a/mods/include/mods/init/init.h +++ b/mods/include/mods/init/init.h @@ -28,5 +28,6 @@ void init_screenshot(); void init_f3(); void init_multidraw(); void init_classic_ui(); +void init_api(); void init_shading(); } diff --git a/mods/include/mods/misc/misc.h b/mods/include/mods/misc/misc.h index ff57a6f5..ae5bf22e 100644 --- a/mods/include/mods/misc/misc.h +++ b/mods/include/mods/misc/misc.h @@ -28,4 +28,38 @@ void misc_run_on_key_press(const std::function &func); void misc_run_on_creative_inventory_setup(const std::function &function); void misc_run_on_swap_buffers(const std::function &function); +std::string misc_get_player_username_utf(const Player *player); + +enum class EntityType { + UNKNOWN = 0, + // Animals + CHICKEN = 10, + COW = 11, + PIG = 12, + SHEEP = 13, + // Hostiles + ZOMBIE = 32, + CREEPER = 33, + SKELETON = 34, + SPIDER = 35, + ZOMBIE_PIGMAN = 36, + // Special #1, Miscellaneous + DROPPED_ITEM = 64, + PRIMED_TNT = 65, + FALLING_SAND = 66, + PAINTING = 83, + // Special #2, Projectiles + ARROW = 80, + THROWN_SNOWBALL = 81, + THROWN_EGG = 82 +}; +std::map> &misc_get_entity_type_names(); +std::pair misc_get_entity_type_name(Entity *entity); +std::string misc_get_entity_name(Entity *entity); + +Entity *misc_make_entity_from_id(Level *level, int id); + +std::string misc_base64_encode(const std::string &data); +std::string misc_base64_decode(const std::string &input); + static constexpr int line_height = 8; \ No newline at end of file diff --git a/mods/src/api/api.cpp b/mods/src/api/api.cpp new file mode 100644 index 00000000..2179e25c --- /dev/null +++ b/mods/src/api/api.cpp @@ -0,0 +1,823 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +// Compatibility Mode +static bool compat_mode = true; + +// Argument Separator +static constexpr char arg_separator = ','; +static constexpr char list_separator = '|'; + +// Read String Input +static std::string get_input(std::string message) { + // Decode + if (!compat_mode) { + message = misc_base64_decode(message); + } + // Convert To CP-437 + return to_cp437(message); +} +// Output String +static std::string get_output(std::string message, const bool replace_comma = false) { + if (compat_mode) { + // Output In Plaintext For RJ Compatibility + std::ranges::replace(message, list_separator, '\\'); + if (replace_comma) { + std::ranges::replace(message, arg_separator, '.'); + } + } else { + message = misc_base64_encode(message); + } + // Convert To Unicode + return from_cp437(message); +} + +// Join Strings Into Output +static std::string join_outputs(const std::vector &pieces, const char separator = arg_separator) { + // Join + std::string out; + for (std::string piece : pieces) { + // Check + if (piece.find(separator) != std::string::npos) { + // This Should Be Escapes + IMPOSSIBLE(); + } + // Remove Trailing Newline + if (!piece.empty() && piece.back() == '\n') { + piece.pop_back(); + } + // Add + out += piece + separator; + } + // Remove Hanging Comma + if (!out.empty()) { + out.pop_back(); + } + // Return + return out + '\n'; +} + +// Get Blocks In Region +static std::string get_blocks(CommandServer *server, const Vec3 &start, const Vec3 &end) { + // Start Coordinate + int startx = int(start.x); + int starty = int(start.y); + int startz = int(start.z); + // End Coordinate + int endx = int(end.x); + int endy = int(end.y); + int endz = int(end.z); + + // Apply Offset + server->pos_translator.from(startx, starty, startz); + server->pos_translator.from(endx, endy, endz); + + // Swap If Needed + if (endx < startx) { + std::swap(startx, endx); + } + if (endy < starty) { + std::swap(starty, endy); + } + if (endz < startz) { + std::swap(startz, endz); + } + + // Get + std::vector ret; + for (int x = startx; x <= endx; x++) { + for (int y = starty; y <= endy; y++) { + for (int z = startz; z <= endz; z++) { + ret.push_back(std::to_string(server->minecraft->level->getTile(x, y, z))); + } + } + } + // Return + return join_outputs(ret); +} + +// Set Entity Rotation From XYZ +static void set_dir(Entity *entity, const float x, const float y, const float z) { + // Check Rotation + if (entity == nullptr) { + return; + } + // Calculate + if (x == 0 && z == 0) { + entity->pitch = y > 0 ? -90 : 90; + return; + } + constexpr float _2PI = 2 * M_PI; + constexpr float factor = float(180.0f / M_PI); + entity->yaw = std::fmod(std::atan2(-x, z), _2PI) * factor; + const float xz = std::sqrt(x * x + z * z); + entity->pitch = std::atan(-y / xz) * factor; +} +// Convert Entity Rotation To XYZ +static Vec3 get_dir(const Entity *entity) { + constexpr float factor = float(M_PI / 180); + const float y = -std::sin(entity->pitch * factor); + const float xz = std::cos(entity->pitch * factor); + const float x = -xz * std::sin(entity->yaw * factor); + const float z = xz * std::cos(entity->yaw * factor); + return Vec3{x, y, z}; +} + +// Entity Types +std::unordered_map modern_entity_id_mapping = { + {93, EntityType::CHICKEN}, + {92, EntityType::COW}, + {90, EntityType::PIG}, + {91, EntityType::SHEEP}, + {54, EntityType::ZOMBIE}, + {50, EntityType::CREEPER}, + {51, EntityType::SKELETON}, + {52, EntityType::SPIDER}, + {57, EntityType::ZOMBIE_PIGMAN}, + {1, EntityType::DROPPED_ITEM}, + {20, EntityType::PRIMED_TNT}, + {21, EntityType::FALLING_SAND}, + {10, EntityType::ARROW}, + {11, EntityType::THROWN_SNOWBALL}, + {7, EntityType::THROWN_EGG}, + {9, EntityType::PAINTING} +}; +static void convert_to_rj_entity_type(int &type) { + for (const std::pair &pair : modern_entity_id_mapping) { + if (static_cast(pair.second) == type) { + type = pair.first; + } + } +} +static void convert_to_mcpi_entity_type(int &type) { + if (modern_entity_id_mapping.contains(type)) { + type = static_cast(modern_entity_id_mapping[type]); + } +} + +// Convert Entity To String +static std::string get_entity_message(CommandServer *server, Entity *entity) { + // Offset Position + float x = entity->x; + float y = entity->y - entity->height_offset; + float z = entity->z; + server->pos_translator.to(x, y, z); + // Fix Type ID + int type = entity->getEntityTypeId(); + if (compat_mode) { + convert_to_rj_entity_type(type); + } + // Return + return join_outputs({ + // ID + std::to_string(entity->id), + // Type + std::to_string(type), + // Name + get_output(misc_get_entity_type_name(entity).second, true), + // X + std::to_string(x), + // Y + std::to_string(y), + // X + std::to_string(z) + }); +} + +// Calculate Distance Between Entities +static float distance_between(const Entity *e1, const Entity *e2) { + if (e1 == nullptr || e2 == nullptr) { + return 0; + } + const float dx = e2->x - e1->x; + const float dy = e2->y - e1->y; + const float dz = e2->z - e1->z; + return std::sqrt(dx * dx + dy * dy + dz * dz); +} + +// Projectile Event +struct ProjectileHitEvent { + int x; + int y; + int z; + std::string owner = ""; + int owner_id = 0; + int target_id = 0; +}; +static std::string event_to_string(CommandServer *server, const ProjectileHitEvent &e) { + // Offset Position + float nx = float(e.x); + float ny = float(e.y); + float nz = float(e.z); + server->pos_translator.to(nx, ny, nz); + // Get Outputs + std::vector pieces = { + // Position + std::to_string(int(nx)), + std::to_string(int(ny)), + std::to_string(int(nz)) + }; + // Needed For Compatibility + if (compat_mode) { + pieces.push_back("1"); + } + // Owner + if (compat_mode) { + pieces.push_back(get_output(e.owner, true)); + } else { + pieces.push_back(std::to_string(e.owner_id)); + pieces.push_back(std::to_string(e.target_id)); + } + // Return + return join_outputs(pieces); +} + +// Chat Message Event +struct ChatEvent { + std::string message = ""; + int owner_id = 0; +}; +static std::string event_to_string(__attribute__((unused)) CommandServer *server, const ChatEvent &e) { + return join_outputs({ + std::to_string(e.owner_id), + get_output(e.message, true), + }); +} + +// Block Hit Event +static std::string event_to_string(CommandServer *server, const TileEvent &e) { + // Offset Coordinates + float x = float(e.x); + float y = float(e.y); + float z = float(e.z); + server->pos_translator.to(x, y, z); + // Output + return join_outputs({ + // Position + std::to_string(int(x)), + std::to_string(int(y)), + std::to_string(int(z)), + // Face + std::to_string(e.face), + // Entity ID + std::to_string(e.owner_id) + }); +} + +// Track Event Queues Per Client +struct ExtraClientData { + std::vector projectile_events; + std::vector chat_events; + std::vector block_hit_events; +}; +static std::unordered_map extra_client_data; +static bool CommandServer__updateAccept_setSocketBlocking_injection(const int fd, const bool param_1) { + // Client Was Created + extra_client_data[fd] = ExtraClientData(); + return CommandServer::setSocketBlocking(fd, param_1); +} +static bool CommandServer__updateClient_injection(CommandServer__updateClient_t original, CommandServer *self, ConnectedClient &client) { + const bool ret = original(self, client); + if (!ret) { + // Client Disconnected + extra_client_data.erase(client.sock); + } + return ret; +} + +// Clear All Events +static void clear_events(const ConnectedClient &client) { + ExtraClientData &data = extra_client_data[client.sock]; + if (!compat_mode) { + // Match RJ Bug + data.projectile_events.clear(); + } + data.chat_events.clear(); + data.block_hit_events.clear(); +} + +// Clear Events Produced By Given Entity +template +static void clear_events(std::vector &data, const int id) { + std::ranges::remove_if(data, [&id](const T &e) { + return id == e.owner_id; + }); +} +static void clear_events(const ConnectedClient &client, const int id) { + ExtraClientData &data = extra_client_data[client.sock]; + clear_events(data.block_hit_events, id); + clear_events(data.chat_events, id); + clear_events(data.projectile_events, id); +} + +// Get Events In Queue +template +static std::string get_events(CommandServer *server, std::vector &queue, const std::optional id) { + std::vector out; + typename std::vector::iterator it = queue.begin(); + while (it != queue.end()) { + const T &e = *it; + if (id.has_value() && id.value() != e.owner_id) { + // Skip Event + ++it; + continue; + } + // Output + out.push_back(event_to_string(server, e)); + // Erase + it = queue.erase(it); + } + return join_outputs(out, list_separator); +} + +// Map RJ Entity IDs To MCPI IDs +static constexpr int no_entity_id = -1; +static const std::string player_namespace = "player."; +#define API_WARN(format, ...) WARN("API: %s: " format, cmd.c_str(), ##__VA_ARGS__) +#define ENTITY_NOT_FOUND API_WARN("Entity Not Found: %i", id) +std::string CommandServer_parse_injection(CommandServer_parse_t old, CommandServer *server, ConnectedClient &client, const std::string &command) { + size_t arg_start = command.find("("); + if (arg_start == std::string::npos) { + return CommandServer::Fail; + } + std::string cmd = command.substr(0, arg_start); + // rfind() So ) In Input Does Not Break + size_t cmd_end = command.rfind(")"); + if (cmd_end == std::string::npos) { + return CommandServer::Fail; + } + std::string args = command.substr(arg_start + 1, cmd_end - arg_start - 1); + + // Redirect Player Namespace To The Entity One + if (server->minecraft->player != nullptr) { + if (cmd.starts_with(player_namespace) && cmd != "player.setting") { + cmd = "entity." + cmd.substr(player_namespace.size()); + args = std::to_string(server->minecraft->player->id) + arg_separator + args; + } + } + + // And Now The Big If-Else Chain + if (cmd == "world.getBlocks") { + int x0, y0, z0, x1, y1, z1; + if (sscanf(args.c_str(), "%d,%d,%d,%d,%d,%d", &x0, &y0, &z0, &x1, &y1, &z1) != 6) { + return CommandServer::Fail; + } + // Get The Blocks + return get_blocks(server, Vec3{(float) x0, (float) y0, (float) z0}, Vec3{(float) x1, (float) y1, (float) z1}); + } else if (cmd == "world.getPlayerId") { + std::string username = get_input(args); + for (Player *player : server->minecraft->level->players) { + if (misc_get_player_username_utf(player) == username) { + return std::to_string(player->id) + "\n"; + } + } + API_WARN("Player Not Found: %s", args.c_str()); + return CommandServer::Fail; + } else if (cmd == "entity.getName") { + int id; + if (sscanf(args.c_str(), "%d", &id) != 1) { + return CommandServer::Fail; + } + Entity *entity = server->minecraft->level->getEntity(id); + if (entity == nullptr) { + ENTITY_NOT_FOUND; + return CommandServer::NullString; + } else { + return get_output(misc_get_entity_name(entity)) + '\n'; + } + } else if (cmd == "world.getEntities") { + int type; + if (sscanf(args.c_str(), "%d", &type) != 1) { + return CommandServer::Fail; + } + if (compat_mode) { + convert_to_mcpi_entity_type(type); + } + std::vector result; + for (Entity *entity : server->minecraft->level->entities) { + int i = entity->getEntityTypeId(); + if (i > 0 && (type == no_entity_id || i == type)) { + result.push_back(get_entity_message(server, entity)); + } + } + return join_outputs(result, list_separator); + } else if (cmd == "world.removeEntity") { + int id; + if (sscanf(args.c_str(), "%d", &id) != 1) { + return CommandServer::Fail; + } + Entity *entity = server->minecraft->level->getEntity(id); + int result = 0; + if (entity != nullptr && !entity->isPlayer()) { + entity->remove(); + result++; + } + return std::to_string(result) + '\n'; + } else if (cmd == "world.removeEntities") { + int type; + if (sscanf(args.c_str(), "%d", &type) != 1) { + return CommandServer::Fail; + } + if (compat_mode) { + convert_to_mcpi_entity_type(type); + } + int removed = 0; + for (Entity *entity : server->minecraft->level->entities) { + int i = entity->getEntityTypeId(); + if (i > 0) { + if (type == no_entity_id || i == type) { + entity->remove(); + removed++; + } + } + } + return std::to_string(removed) + '\n'; + } else if (cmd == "events.chat.posts") { + return get_events(server, extra_client_data[client.sock].chat_events, std::nullopt); + } else if (cmd == "entity.events.chat.posts") { + int id; + if (sscanf(args.c_str(), "%d", &id) != 1) { + return CommandServer::Fail; + } + return get_events(server, extra_client_data[client.sock].chat_events, id); + } else if (cmd == "events.block.hits") { + return get_events(server, extra_client_data[client.sock].block_hit_events, std::nullopt); + } else if (cmd == "entity.events.block.hits") { + int id; + if (sscanf(args.c_str(), "%d", &id) != 1) { + return CommandServer::Fail; + } + return get_events(server, extra_client_data[client.sock].block_hit_events, id); + } else if (cmd == "events.projectile.hits") { + return get_events(server, extra_client_data[client.sock].projectile_events, std::nullopt); + } else if (cmd == "entity.events.projectile.hits") { + int id; + if (sscanf(args.c_str(), "%d", &id) != 1) { + return CommandServer::Fail; + } + return get_events(server, extra_client_data[client.sock].projectile_events, id); + } else if (cmd == "entity.setDirection") { + int id; + float x, y, z; + if (sscanf(args.c_str(), "%d,%f,%f,%f", &id, &x, &y, &z) != 4) { + return CommandServer::Fail; + } + Entity *entity = server->minecraft->level->getEntity(id); + if (entity == nullptr) { + ENTITY_NOT_FOUND; + } else { + set_dir(entity, x, y, z); + } + return CommandServer::NullString; + } else if (cmd == "entity.getDirection") { + int id; + if (sscanf(args.c_str(), "%d", &id) != 1) { + return CommandServer::Fail; + } + Entity *entity = server->minecraft->level->getEntity(id); + if (entity == nullptr) { + ENTITY_NOT_FOUND; + return CommandServer::Fail; + } else { + Vec3 vec = get_dir(entity); + return join_outputs({std::to_string(vec.x), std::to_string(vec.y), std::to_string(vec.z)}); + } + } else if (cmd == "entity.setRotation") { + int id; + float yaw; + if (sscanf(args.c_str(), "%d,%f", &id, &yaw) != 2) { + return CommandServer::Fail; + } + Entity *entity = server->minecraft->level->getEntity(id); + if (entity == nullptr) { + ENTITY_NOT_FOUND; + } else { + entity->yaw = yaw; + } + return CommandServer::NullString; + } else if (cmd == "entity.setPitch") { + int id; + float pitch; + if (sscanf(args.c_str(), "%d,%f", &id, &pitch) != 2) { + return CommandServer::Fail; + } + Entity *entity = server->minecraft->level->getEntity(id); + if (entity == nullptr) { + ENTITY_NOT_FOUND; + } else { + entity->pitch = pitch; + } + return CommandServer::NullString; + } else if (cmd == "entity.getRotation") { + int id; + if (sscanf(args.c_str(), "%d", &id) != 1) { + return CommandServer::Fail; + } + Entity *entity = server->minecraft->level->getEntity(id); + if (entity == nullptr) { + ENTITY_NOT_FOUND; + return CommandServer::Fail; + } else { + return std::to_string(entity->yaw) + '\n'; + } + } else if (cmd == "entity.getPitch") { + int id; + if (sscanf(args.c_str(), "%d", &id) != 1) { + return CommandServer::Fail; + } + Entity *entity = server->minecraft->level->getEntity(id); + if (entity == nullptr) { + ENTITY_NOT_FOUND; + return CommandServer::Fail; + } else { + return std::to_string(entity->pitch) + '\n'; + } + } else if (cmd == "entity.getEntities") { + // Parse + int dist, type; + int id = 0; + if (sscanf(args.c_str(), "%d,%d,%d", &id, &dist, &type) != 3) { + return CommandServer::Fail; + } + Entity *src = server->minecraft->level->getEntity(id); + if (src == nullptr) { + ENTITY_NOT_FOUND; + return CommandServer::Fail; + } + // Run + std::vector result; + for (Entity *entity : server->minecraft->level->entities) { + int i = entity->getEntityTypeId(); + if (i > 0 && (type == no_entity_id || i == type) && distance_between(src, entity) < dist) { + result.push_back(get_entity_message(server, entity)); + } + } + return join_outputs(result, list_separator); + } else if (cmd == "entity.removeEntities") { + // Parse + int dist, type; + int id = 0; + if (sscanf(args.c_str(), "%d,%d,%d", &id, &dist, &type) != 3) { + return CommandServer::Fail; + } + Entity *src = server->minecraft->level->getEntity(id); + if (src == nullptr) { + ENTITY_NOT_FOUND; + return CommandServer::Fail; + } + // Run + int removed = 0; + for (Entity *entity : server->minecraft->level->entities) { + int i = entity->getEntityTypeId(); + if (i > 0 && (type == no_entity_id || i == type) && distance_between(src, entity) < dist) { + entity->remove(); + removed++; + } + } + return std::to_string(removed) + '\n'; + } else if (cmd == "world.setSign") { + // Parse + int x, y, z, id, data; + char l1[100], l2[100], l3[100], l4[100]; + int ret = sscanf(args.c_str(), "%d,%d,%d,%d,%d,%99[^,],%99[^,],%99[^,],%99s", &x, &y, &z, &id, &data, l1, l2, l3, l4); + constexpr int minimal_arg_count = 5; + constexpr int line_count = 4; + if (ret < minimal_arg_count || ret > (minimal_arg_count + line_count)) { + return CommandServer::Fail; + } + // Translate + server->pos_translator.from(x, y, z); + // Set Block + server->minecraft->level->setTileAndData(x, y, z, id, data); + // Set Sign Data + if (ret == minimal_arg_count) { + return CommandServer::NullString; + } + SignTileEntity *sign = (SignTileEntity *) server->minecraft->level->getTileEntity(x, y, z); + if (sign == nullptr || sign->type != 4) { + return CommandServer::NullString; + } + char *lines[line_count] = {l1, l2, l3, l4}; + for (int i = 0; i < line_count; i++) { + if (ret > (i + minimal_arg_count)) { + sign->lines[i] = get_input(lines[i]); + } + } + // Send Update Packet + sign->setChanged(); + Packet *packet = sign->getUpdatePacket(); + server->minecraft->rak_net_instance->send(*packet); + return CommandServer::NullString; + } else if (cmd == "world.spawnEntity") { + // Parse + float x, y, z; + int id; + if (sscanf(args.c_str(), "%f,%f,%f,%d", &x, &y, &z, &id) != 4) { + return CommandServer::Fail; + } + // Translate + x -= server->pos_translator.x; + y -= server->pos_translator.y; + z -= server->pos_translator.z; + if (compat_mode) { + convert_to_mcpi_entity_type(id); + } + // Spawn + Entity *entity = misc_make_entity_from_id(server->minecraft->level, id); + if (entity == nullptr) { + return CommandServer::Fail; + } + entity->moveTo(x, y, z, 0, 0); + server->minecraft->level->addEntity(entity); + return std::to_string(entity->id) + '\n'; + } else if (cmd == "world.getEntityTypes") { + std::vector result; + for (const std::pair> &i: misc_get_entity_type_names()) { + int id = static_cast(i.first); + if (compat_mode) { + convert_to_rj_entity_type(id); + } + result.push_back(join_outputs({std::to_string(id), i.second.second})); + } + return join_outputs(result, list_separator); + } else if (cmd == "entity.setAbsPos") { + int id; + float x, y, z; + if (sscanf(args.c_str(), "%d,%f,%f,%f", &id, &x, &y, &z) != 4) { + return CommandServer::Fail; + } + Entity *entity = server->minecraft->level->getEntity(id); + if (entity == nullptr) { + ENTITY_NOT_FOUND; + return CommandServer::Fail; + } + entity->moveTo(x, y, z, entity->yaw, entity->pitch); + return CommandServer::NullString; + } else if (cmd == "entity.getAbsPos") { + int id; + if (sscanf(args.c_str(), "%d", &id) != 1) { + return CommandServer::Fail; + } + Entity *entity = server->minecraft->level->getEntity(id); + if (entity == nullptr) { + ENTITY_NOT_FOUND; + return CommandServer::Fail; + } + return join_outputs({std::to_string(entity->x), std::to_string(entity->y), std::to_string(entity->z)}); + } else if (cmd == "entity.events.clear") { + int id; + if (sscanf(args.c_str(), "%d", &id) != 1) { + return CommandServer::Fail; + } + clear_events(client, id); + return CommandServer::NullString; + } else if (cmd == "events.clear") { + clear_events(client); + return CommandServer::NullString; + } else if (cmd == "reborn.disableCompatMode") { + compat_mode = false; + return std::string(reborn_get_version()) + '\n'; + } else if (cmd == "reborn.enableCompatMode") { + compat_mode = true; + return CommandServer::NullString; + } else { + // Call Original Method + return old(server, client, command); + } +} + +// Push Event To Queue +template +static void push_event(std::vector ExtraClientData::*ptr, const T e) { + for (ExtraClientData &data : extra_client_data | std::views::values) { + (data.*ptr).push_back(e); + } +} + +// Arrow Entity Hit +static Entity *shooter = nullptr; +static HitResult *Arrow_tick_HitResult_constructor_injection(HitResult *self, Entity *target) { + // Original + self->constructor(target); + // Add event + if (shooter && shooter->isPlayer()) { + push_event(&ExtraClientData::projectile_events, { + .x = (int) target->x, + .y = (int) target->y, + .z = (int) target->z, + .owner = ((Player *) shooter)->username, + .owner_id = shooter->id, + .target_id = target->id + }); + } + return self; +} + +// Arrow block hit +static void Arrow_tick_injection(Arrow_tick_t old, Arrow *self) { + const int oldFlightTime = self->flight_time; + shooter = self->level->getEntity(self->getAuxData()); + old(self); + if (!self->pending_removal && self->grounded && oldFlightTime != self->flight_time) { + if (shooter && shooter->isPlayer()) { + // Hit! Get the data + push_event(&ExtraClientData::projectile_events, { + .x = self->hit_x, + .y = self->hit_y, + .z = self->hit_z, + .owner = ((Player *) shooter)->username, + .owner_id = shooter->id, + .target_id = 0 + }); + } + } +} + +// Throwable Hits +static void Throwable_tick_Throwable_onHit_injection(Throwable *self, HitResult *res) { + if (res == nullptr || res->type == 2) { + return self->onHit(res); + } + Entity *thrower = self->level->getEntity(self->getAuxData()); + if (thrower == nullptr || !thrower->isPlayer()) { + return self->onHit(res); + } + ProjectileHitEvent event; + if (res->type == 1 && res->entity) { + // Entity + event = { + .x = (int) res->exact.x, + .y = (int) res->exact.y, + .z = (int) res->exact.z, + .owner = ((Player *) thrower)->username, + .owner_id = thrower->id, + .target_id = res->entity->id + }; + } else { + // Tile + event = { + .x = res->x, + .y = res->y, + .z = res->z, + .owner = ((Player *) thrower)->username, + .owner_id = thrower->id, + .target_id = 0 + }; + } + push_event(&ExtraClientData::projectile_events, event); + self->onHit(res); +} + +// Chat Events +static void Gui_addMessage_injection(Gui_addMessage_t original, Gui *gui, const std::string &text) { + static bool recursing = false; + if (recursing) { + original(gui, text); + } else { + if (!chat_is_sending()) { + api_add_chat_event(nullptr, text); + } + recursing = true; + original(gui, text); + recursing = false; + } +} +static bool enabled = false; +void api_add_chat_event(const Player *sender, const std::string &message) { + if (!enabled || (!sender && compat_mode)) { + return; + } + push_event(&ExtraClientData::chat_events, ChatEvent{message, sender ? sender->id : no_entity_id}); +} + +// Init +void init_api() { + if (feature_has("Implement RaspberryJuice API", server_enabled)) { + enabled = true; + overwrite_calls(CommandServer_parse, CommandServer_parse_injection); + overwrite_calls(Arrow_tick, Arrow_tick_injection); + overwrite_call((void *) 0x8b1e8, HitResult_constructor, Arrow_tick_HitResult_constructor_injection); + overwrite_call((void *) 0x8c5a4, Throwable_onHit, Throwable_tick_Throwable_onHit_injection); + overwrite_calls(Gui_addMessage, Gui_addMessage_injection); + overwrite_call((void *) 0x6bd78, CommandServer_setSocketBlocking, CommandServer__updateAccept_setSocketBlocking_injection); + overwrite_calls(CommandServer__updateClient, CommandServer__updateClient_injection); + } +} diff --git a/mods/src/chat/chat.cpp b/mods/src/chat/chat.cpp index 3b0168c4..85e6727f 100644 --- a/mods/src/chat/chat.cpp +++ b/mods/src/chat/chat.cpp @@ -7,6 +7,7 @@ #include #include "chat-internal.h" #include +#include // Send UTF-8 API Command std::string chat_send_api_command(const Minecraft *minecraft, const std::string &str) { @@ -33,10 +34,18 @@ static void send_api_chat_command(const Minecraft *minecraft, const char *str) { std::string _chat_get_prefix(const char *username) { return std::string("<") + username + "> "; } -void chat_send_message_to_clients(ServerSideNetworkHandler *server_side_network_handler, const char *username, const char *message) { +static bool is_sending = false; +bool chat_is_sending() { + return is_sending; +} +void chat_send_message_to_clients(ServerSideNetworkHandler *server_side_network_handler, const Player *sender, const char *message) { + is_sending = true; + api_add_chat_event(sender, message); + const char *username = ((Player *) sender)->username.c_str(); std::string full_message = _chat_get_prefix(username) + message; sanitize_string(full_message, MAX_CHAT_MESSAGE_LENGTH, false); server_side_network_handler->displayGameMessage(full_message); + is_sending = false; } // Handle Chat packet Send void chat_handle_packet_send(const Minecraft *minecraft, ChatPacket *packet) { @@ -48,7 +57,7 @@ void chat_handle_packet_send(const Minecraft *minecraft, ChatPacket *packet) { // Hosting Multiplayer const char *message = packet->message.c_str(); ServerSideNetworkHandler *server_side_network_handler = (ServerSideNetworkHandler *) minecraft->network_handler; - chat_send_message_to_clients(server_side_network_handler, Strings::default_username, (char *) message); + chat_send_message_to_clients(server_side_network_handler, (Player *) minecraft->player, message); } else { // Client rak_net_instance->send(*(Packet *) packet); @@ -67,9 +76,8 @@ static void CommandServer_parse_CommandServer_dispatchPacket_injection(CommandSe static void ServerSideNetworkHandler_handle_ChatPacket_injection(ServerSideNetworkHandler *server_side_network_handler, const RakNet_RakNetGUID &rak_net_guid, ChatPacket *chat_packet) { const Player *player = server_side_network_handler->getPlayer(rak_net_guid); if (player != nullptr) { - const char *username = player->username.c_str(); const char *message = chat_packet->message.c_str(); - chat_send_message_to_clients(server_side_network_handler, username, message); + chat_send_message_to_clients(server_side_network_handler, player, message); } } diff --git a/mods/src/death/death.cpp b/mods/src/death/death.cpp index 64c00e07..5147fc09 100644 --- a/mods/src/death/death.cpp +++ b/mods/src/death/death.cpp @@ -6,38 +6,38 @@ #include #include +#include // Death Messages -static const char *monster_names[] = {"Zombie", "Creeper", "Skeleton", "Spider", "Zombie Pigman"}; std::string get_death_message(Player *player, Entity *cause, const bool was_shot = false) { // Prepare Death Message std::string message = player->username; if (cause) { - // Entity cause + // Entity Cause const int type_id = cause->getEntityTypeId(); - int aux = cause->getAuxData(); + const int aux = cause->getAuxData(); const bool is_player = cause->isPlayer(); if (cause->getCreatureBaseType() != 0 || is_player) { - // Killed by a creature + // Killed By A Creature if (was_shot) { message += " was shot by "; } else { message += " was killed by "; } if (is_player) { - // Killed by a player + // Killed By A Player message += ((Player *) cause)->username; - } else if (32 <= type_id && type_id <= 36) { - // Normal monster - message += "a "; - message += monster_names[type_id - 32]; } else { - // Unknown creature - message += "a mysterious beast"; + // Normal Creature + std::string type = misc_get_entity_type_name(cause).first; + if (type.empty()) { + type = "mysterious beast"; + } + message += "a " + type; } return message; } else if (aux) { - // Killed by a throwable with owner + // Killed By A Throwable With Owner Level *level = player->level; Entity *shooter = level->getEntity(aux); return get_death_message(player, shooter, true); @@ -49,19 +49,20 @@ std::string get_death_message(Player *player, Entity *cause, const bool was_shot return message + " admired too much art"; } } - + // Miscellaneous Causes if (was_shot) { - // Throwable with invalid owner + // Throwable With Invalid Owner return message + " was shot under mysterious circumstances"; } else if (cause) { - // Unknown entity + // Unknown Entity return message + " was killed"; } else { - // Anything else + // Anything Else return message + " has died"; } } +// Track If A Mob Is Being Hurt static bool is_hurt = false; static bool Mob_hurt_injection(Mob_hurt_t original, Mob *mob, Entity *source, const int dmg) { is_hurt = true; @@ -91,7 +92,7 @@ static void Player_die_injection(const std::function static void Player_actuallyHurt_injection(const std::function &original, Self *player, int32_t damage) { // Store Old Health - int32_t old_health = player->health; + const int32_t old_health = player->health; // Call Original Method original((OriginalSelf *) player, damage); diff --git a/mods/src/f3/f3.cpp b/mods/src/f3/f3.cpp index 4b96463a..c92db6e8 100644 --- a/mods/src/f3/f3.cpp +++ b/mods/src/f3/f3.cpp @@ -134,7 +134,7 @@ static std::vector get_debug_info_right(const Minecraft *minecraft) z = entity->z; type = "Entity"; const int type_id = entity->getEntityTypeId(); - type_info.push_back(format_type(type_id, "")); // TODO: Specify name when RJ PR is merged + type_info.push_back(format_type(type_id, misc_get_entity_type_name(entity).first)); type_info.push_back("ID: " + std::to_string(entity->id)); if (entity->isMob()) { Mob *mob = (Mob *) entity; diff --git a/mods/src/init/init.cpp b/mods/src/init/init.cpp index e903c32b..6637afa8 100644 --- a/mods/src/init/init.cpp +++ b/mods/src/init/init.cpp @@ -38,6 +38,7 @@ __attribute__((constructor)) static void init() { init_bucket(); init_cake(); init_override(); + init_api(); if (!reborn_is_server()) { init_benchmark(); } diff --git a/mods/src/misc/api.cpp b/mods/src/misc/api.cpp index 501596cb..2cfc3226 100644 --- a/mods/src/misc/api.cpp +++ b/mods/src/misc/api.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -128,10 +129,10 @@ void misc_render_background(int color, const Minecraft *minecraft, const int x, Tesselator *t = &Tesselator::instance; t->begin(GL_QUADS); t->color(color, color, color, 255); - float x1 = x; - float x2 = x + width; - float y1 = y; - float y2 = y + height; + float x1 = float(x); + float x2 = x1 + float(width); + float y1 = float(y); + float y2 = y1 + float(height); t->vertexUV(x1, y2, 0.0f, x1 / 32.0f, y2 / 32.0f); t->vertexUV(x2, y2, 0.0f, x2 / 32.0f, y2 / 32.0f); t->vertexUV(x2, y1, 0.0f, x2 / 32.0f, y1 / 32.0f); @@ -139,6 +140,84 @@ void misc_render_background(int color, const Minecraft *minecraft, const int x, t->draw(); } +// Entity Names +static std::pair format_entity_name(const std::string &friendly_name) { + std::string api_name = friendly_name; + for (char &c : api_name) { + c = c == ' ' ? '_' : std::toupper(c); + } + return {friendly_name, api_name}; +} +std::pair misc_get_entity_type_name(Entity *entity) { + if (entity) { + if (entity->isPlayer()) { + // Player + return format_entity_name("Player"); + } else { + const EntityType type = static_cast(entity->getEntityTypeId()); + if (misc_get_entity_type_names().contains(type)) { + // Normal Entity + return misc_get_entity_type_names()[type]; + } else if (type == EntityType::UNKNOWN) { + // Special Entity + static std::unordered_map vtable_to_name = { + {(void *) Particle_vtable::base, "Particle"}, + {(void *) TripodCamera_vtable::base, "Tripod Camera"}, + {(void *) CameraEntity_vtable::base, "API Camera"} + }; + void *vtable = entity->vtable; + if (vtable_to_name.contains(vtable)) { + return format_entity_name(vtable_to_name[vtable]); + } + } + } + } + // Invalid + return format_entity_name("Unknown"); +} +std::string misc_get_entity_name(Entity *entity) { + if (entity && entity->isPlayer()) { + return ((Player *) entity)->username; + } else { + return misc_get_entity_type_name(entity).first; + } +} +std::map> &misc_get_entity_type_names() { + static std::map> names = { + {EntityType::CHICKEN, format_entity_name("Chicken")}, + {EntityType::COW, format_entity_name("Cow")}, + {EntityType::PIG, format_entity_name("Pig")}, + {EntityType::SHEEP, format_entity_name("Sheep")}, + {EntityType::ZOMBIE, format_entity_name("Zombie")}, + {EntityType::CREEPER, format_entity_name("Creeper")}, + {EntityType::SKELETON, format_entity_name("Skeleton")}, + {EntityType::SPIDER, format_entity_name("Spider")}, + {EntityType::ZOMBIE_PIGMAN, {"Zombie Pigman", "PIG_ZOMBIE"}}, + {EntityType::DROPPED_ITEM, format_entity_name("Dropped Item")}, + {EntityType::PRIMED_TNT, format_entity_name("Primed TNT")}, + {EntityType::FALLING_SAND, format_entity_name("Falling Block")}, + {EntityType::PAINTING, format_entity_name("Painting")}, + {EntityType::ARROW, format_entity_name("Arrow")}, + {EntityType::THROWN_SNOWBALL, format_entity_name("Snowball")}, + {EntityType::THROWN_EGG, format_entity_name("Egg")} + }; + return names; +} + +// Spawn Entities +Entity *misc_make_entity_from_id(Level *level, const int id) { + if (id < 0x40) { + return (Entity *) MobFactory::CreateMob(id, level); + } else { + return EntityFactory::CreateEntity(id, level); + } +} + +// Username In Unicode +std::string misc_get_player_username_utf(const Player *player) { + return from_cp437(player->username); +} + // Init void _init_misc_api() { // Handle Custom Creative Inventory Setup Behavior diff --git a/mods/src/misc/base64.cpp b/mods/src/misc/base64.cpp new file mode 100644 index 00000000..434b6ac1 --- /dev/null +++ b/mods/src/misc/base64.cpp @@ -0,0 +1,96 @@ +#include +#include + +// Base64 Encoder (https://gist.github.com/tomykaira/f0fd86b6c73063283afe550bc5d77594) +std::string misc_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', '+', '/' + }; + + const size_t in_len = data.size(); + const size_t out_len = 4 * ((in_len + 2) / 3); + std::string ret(out_len, '\0'); + size_t i; + char *p = const_cast(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; +} + +// Base64 Decoder +std::string misc_base64_decode(const std::string &input) { + static constexpr unsigned char decoding_table[] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64 + }; + + const size_t in_len = input.size(); + if (in_len % 4 != 0) { + return ""; + } + + std::string out = ""; + size_t out_len = in_len / 4 * 3; + if (in_len >= 1 && input[in_len - 1] == '=') out_len--; + if (in_len >= 2 && input[in_len - 2] == '=') out_len--; + out.resize(out_len); + + for (size_t i = 0, j = 0; i < in_len;) { + const uint32_t a = input[i] == '=' ? 0 & i++ : decoding_table[static_cast(input[i++])]; + const uint32_t b = input[i] == '=' ? 0 & i++ : decoding_table[static_cast(input[i++])]; + const uint32_t c = input[i] == '=' ? 0 & i++ : decoding_table[static_cast(input[i++])]; + const uint32_t d = input[i] == '=' ? 0 & i++ : decoding_table[static_cast(input[i++])]; + + const uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6); + + if (j < out_len) { + out[j++] = (triple >> 2 * 8) & 0xFF; + } + if (j < out_len) { + out[j++] = (triple >> 1 * 8) & 0xFF; + } + if (j < out_len) { + out[j++] = (triple >> 0 * 8) & 0xFF; + } + } + + return out; +} \ No newline at end of file diff --git a/mods/src/server/server.cpp b/mods/src/server/server.cpp index 11401083..1f678b0a 100644 --- a/mods/src/server/server.cpp +++ b/mods/src/server/server.cpp @@ -116,12 +116,6 @@ static std::string get_blacklist_file() { static std::vector get_players_in_level(Level *level) { return level->players; } -// Get Player's Username -static std::string get_player_username(const Player *player) { - const std::string *username = &player->username; - std::string safe_username = from_cp437(*username); - return safe_username; -} // Get Level From Minecraft static Level *get_level(const Minecraft *minecraft) { return minecraft->level; @@ -135,7 +129,7 @@ static void find_players(Minecraft *minecraft, const std::string &target_usernam bool found_player = false; for (Player *player : players) { // Iterate Players - std::string username = get_player_username(player); + std::string username = misc_get_player_username_utf(player); if (all_players || username == target_username) { // Run Callback callback(minecraft, username, player); diff --git a/mods/src/skin/skin.cpp b/mods/src/skin/skin.cpp index 0d3213d7..ff2e0fd4 100644 --- a/mods/src/skin/skin.cpp +++ b/mods/src/skin/skin.cpp @@ -5,50 +5,13 @@ #include #include +#include #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', '+', '/' - }; - - const size_t in_len = data.size(); - const size_t out_len = 4 * ((in_len + 2) / 3); - std::string ret(out_len, '\0'); - size_t i; - char *p = const_cast(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 std::string get_skin_texture_path(const std::string &username) { + return '$' + misc_base64_encode(username); +} static void Player_username_assign_injection(std::string *target, const std::string &username) { // Call Original Method *target = username; @@ -59,7 +22,7 @@ static void Player_username_assign_injection(std::string *target, const std::str std::string *texture = &player->texture; // Set Texture - *texture = '$' + base64_encode(username); + *texture = get_skin_texture_path(username); } static void Player_username_assign_injection_2(std::string *target, const char *username) { const std::string username_str = username; @@ -67,12 +30,11 @@ static void Player_username_assign_injection_2(std::string *target, const char * } // Change Texture For HUD -static uint32_t Textures_loadAndBindTexture_injection(Textures *textures, __attribute__((unused)) std::string const& name) { +static uint32_t ItemInHandRenderer_render_Textures_loadAndBindTexture_injection(Textures *textures, __attribute__((unused)) std::string const& name) { // Change Texture static std::string new_texture; if (new_texture.length() == 0) { - const std::string username = base64_encode(Strings::default_username); - new_texture = '$' + username; + new_texture = get_skin_texture_path(Strings::default_username); } // Call Original Method @@ -91,7 +53,7 @@ void init_skin() { overwrite_call_manual((void *) 0x7639c, (void *) Player_username_assign_injection_2); // HUD - overwrite_call((void *) 0x4c6d0, Textures_loadAndBindTexture, Textures_loadAndBindTexture_injection); + overwrite_call((void *) 0x4c6d0, Textures_loadAndBindTexture, ItemInHandRenderer_render_Textures_loadAndBindTexture_injection); // Loader _init_skin_loader(); diff --git a/mods/src/title-screen/splashes.txt b/mods/src/title-screen/splashes.txt index 00fb2813..a4fa653e 100644 --- a/mods/src/title-screen/splashes.txt +++ b/mods/src/title-screen/splashes.txt @@ -62,4 +62,5 @@ Python API! Raspberry Pi! It's alive! Now with cake! -The bug attractor! \ No newline at end of file +The bug attractor! +I promise, this is the last time! \ No newline at end of file diff --git a/symbols/src/api/CommandServer.def b/symbols/src/api/CommandServer.def index 6c36b25c..87a1c4a0 100644 --- a/symbols/src/api/CommandServer.def +++ b/symbols/src/api/CommandServer.def @@ -1,5 +1,11 @@ method std::string parse(ConnectedClient &client, const std::string &command) = 0x6aa8c; method void dispatchPacket(Packet &packet) = 0x6a548; +static-method bool setSocketBlocking(int fd, bool param_1) = 0x6a2d4; +method bool _updateClient(ConnectedClient &client) = 0x6ba6c; property Minecraft *minecraft = 0x18; property OffsetPosTranslator pos_translator = 0x1c; + +static-property std::string Fail = 0x137dbc; +static-property std::string Ok = 0x138a08; +static-property std::string NullString = 0x137db8; \ No newline at end of file diff --git a/symbols/src/game/mode/creator/TileEvent.def b/symbols/src/game/mode/creator/TileEvent.def index 8ff37b87..1d719c34 100644 --- a/symbols/src/game/mode/creator/TileEvent.def +++ b/symbols/src/game/mode/creator/TileEvent.def @@ -1,6 +1,6 @@ size 0x14; -property int entityId = 0x0; +property int owner_id = 0x0; property int x = 0x4; property int y = 0x8; property int z = 0xc; diff --git a/symbols/src/tile/entity/TileEntity.def b/symbols/src/tile/entity/TileEntity.def index 30eb1f3f..8e15e3d1 100644 --- a/symbols/src/tile/entity/TileEntity.def +++ b/symbols/src/tile/entity/TileEntity.def @@ -10,6 +10,9 @@ virtual-method bool shouldSave() = 0x8; virtual-method void load(CompoundTag *tag) = 0xc; virtual-method bool save(CompoundTag *tag) = 0x10; virtual-method void tick() = 0x14; +virtual-method Packet *getUpdatePacket() = 0x20; + +method void setChanged() = 0xd23a4; property Level *level = 0x4; property int x = 0x8;