More API Improvements

This commit is contained in:
TheBrokenRail 2025-03-06 00:01:54 -05:00
parent 54ef10e539
commit 7a237a9ee6
9 changed files with 303 additions and 138 deletions

View File

@ -74,7 +74,7 @@ By default, MCPI-Reborn runs in a "compatibility mode." This makes it completely
* Description: Move the camera to the given position. The XZ-coordinates are automatically offset by `0.5`.
* `events.clear()`
* Description: Clear all queued events.
* Note: On RaspberryJuice, this *does not* clear projectile events. This behavior is maintained only in compatibility mode.
* Note: On RaspberryJuice, this *does not* clear projectile events. This behavior is maintained only in the compatibility mode.
* `events.block.hits()`
* Description: Retrieve all queued block hit events.
* Output: List of `x,y,z,face,entity_id`

View File

@ -67,6 +67,7 @@
* `Fix Torch Placement` (Enabled By Default)
* `Fix Eggs Spawning Abnormally Healthy Chickens` (Enabled By Default)
* `Correctly Close API Sockets` (Enabled By Default)
* `Optimized API Sockets` (Enabled By Default)
* Existing Functionality (All Enabled By Default)
* `Fix Screen Rendering When Hiding HUD`
* `Sanitize Usernames`

View File

@ -141,6 +141,7 @@ CATEGORY Bug Fixes
TRUE Fix HUD When Spectating Other Players
TRUE Fix Crash When Spectated Entity Is Removed
TRUE Correctly Close API Sockets
TRUE Fix Moving Players With The API In Multiplayer
TRUE Fix Reloading Textures On Resize
TRUE Fix options.txt Loading/Saving
TRUE Fix Hanging When No Valid Spawn Point Exists
@ -150,10 +151,12 @@ CATEGORY Logging
TRUE Log Game Status
TRUE Log RakNet Startup Errors
CATEGORY Miscellaneous
CATEGORY API
TRUE Implement RaspberryJuice API
TRUE Optimized API Sockets
TRUE Fullscreen Support
TRUE Always Save Chest Tile Entities
TRUE Screenshot Support
TRUE Add Camera Functionality
TRUE Update Default Options
TRUE Remove Chest Placement Restrictions
TRUE Implement RaspberryJuice API
TRUE Remove Chest Placement Restrictions

View File

@ -103,6 +103,8 @@ add_library(mods SHARED
src/api/api.cpp
src/api/events.cpp
src/api/compat.cpp
src/api/misc.cpp
src/api/socket.cpp
# shading
src/shading/init.cpp
src/shading/tesselator.cpp

View File

@ -42,6 +42,8 @@ static std::string get_blocks(CommandServer *server, const Vec3 &start, const Ve
// Get
std::vector<std::string> ret;
const size_t expected_size = (end_x - start_x + 1) * (end_y - start_y + 1) * (end_z - start_z + 1);
ret.reserve(expected_size);
for (int x = start_x; x <= end_x; x++) {
for (int y = start_y; y <= end_y; y++) {
for (int z = start_z; z <= end_z; z++) {
@ -54,46 +56,13 @@ static std::string get_blocks(CommandServer *server, const Vec3 &start, const Ve
}
}
}
if (ret.size() != expected_size) {
IMPOSSIBLE();
}
// Return
return api_join_outputs(ret, api_compat_mode ? arg_separator : list_separator);
}
// Properly Teleport Players
static void update_player_position(const Entity *entity) {
if (entity->vtable == (Entity_vtable *) ServerPlayer_vtable::base) {
const ServerPlayer *player = (ServerPlayer *) entity;
MovePlayerPacket *packet = MovePlayerPacket::allocate();
((Packet *) packet)->constructor();
packet->vtable = MovePlayerPacket_vtable::base;
packet->x = player->x;
packet->y = player->y - player->height_offset;
packet->z = player->z;
packet->yaw = player->yaw;
packet->pitch = player->pitch;
packet->entity_id = player->id;
player->minecraft->rak_net_instance->send(*(Packet *) packet);
packet->destructor_deleting();
}
}
static void Entity_moveTo_injection(Entity *self, const float x, const float y, const float z, const float yaw, const float pitch) {
self->moveTo(x, y, z, yaw, pitch);
update_player_position(self);
}
static void ClientSideNetworkHandler_handle_MovePlayerPacket_injection(ClientSideNetworkHandler_handle_MovePlayerPacket_t original, ClientSideNetworkHandler *self, const RakNet_RakNetGUID &rak_net_guid, MovePlayerPacket *packet) {
if (self->level) {
Entity *entity = self->level->getEntity(packet->entity_id);
if (entity) {
if (entity == (Entity *) self->minecraft->player) {
// Just Teleport
entity->moveTo(packet->x, packet->y, packet->z, packet->yaw, packet->pitch);
} else {
// Call Original Method
original(self, rak_net_guid, packet);
}
}
}
}
// Set Entity Rotation From XYZ
static void set_dir(Entity *entity, const float x, const float y, const float z) {
// Check Rotation
@ -110,7 +79,7 @@ static void set_dir(Entity *entity, const float x, const float y, const float z)
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;
update_player_position(entity);
api_update_entity_position(entity);
}
// Convert Entity Rotation To XYZ
static Vec3 get_dir(const Entity *entity) {
@ -194,7 +163,7 @@ static bool is_entity_selected(Entity *entity, const int target_type) {
#define next_float(out) next_number(out, float, std::stof)
// Parse API Commands
#define package_str(name) (#name ".")
static bool _package(std::string &cmd, const std::string &package) {
static bool _package(std::string_view &cmd, const std::string &package) {
if (cmd.starts_with(package)) {
cmd = cmd.substr(package.size());
return true;
@ -214,33 +183,38 @@ static bool _package(std::string &cmd, const std::string &package) {
force_semicolon()
static std::string CommandServer_parse_injection(CommandServer_parse_t original, CommandServer *server, ConnectedClient &client, const std::string &command) {
// Parse Command
size_t arg_start = command.find('(');
std::string_view command_view = command;
size_t arg_start = command_view.find('(');
if (arg_start == std::string::npos) {
return CommandServer::Fail;
}
std::string cmd = command.substr(0, arg_start);
size_t cmd_end = command.rfind(')');
std::string_view cmd = command_view.substr(0, arg_start);
size_t cmd_end = command_view.rfind(')');
if (cmd_end == std::string::npos) {
return CommandServer::Fail;
}
std::string args_str = command.substr(arg_start + 1, cmd_end - arg_start - 1);
std::string_view args_str = command_view.substr(arg_start + 1, cmd_end - arg_start - 1);
// Redirect Player Package To Entity
std::string _new_cmd;
std::string _new_args_str;
package(player) {
// The One Exception
passthrough(setting);
// Redirect All Other Commands
LocalPlayer *player = server->minecraft->player;
if (player) {
cmd = package_str(entity) + cmd;
args_str = std::to_string(player->id) + arg_separator + args_str;
_new_cmd = package_str(entity) + std::string(cmd);
cmd = _new_cmd;
_new_args_str = std::to_string(player->id) + arg_separator + std::string(args_str);
args_str = _new_args_str;
} else {
return CommandServer::Fail;
}
}
// Read Arguments
std::stringstream args(args_str);
std::stringstream args(args_str.data());
// Manipulate The Level
package(world) {
@ -459,7 +433,7 @@ static std::string CommandServer_parse_injection(CommandServer_parse_t original,
}
// Get/Remove Nearby Entities
package(getEntities) {
command(getEntities) {
// Parse
_get_entity(); // Matching RJ Behavior, Even Though It is Dumb
next_int(dist);
@ -515,7 +489,7 @@ static std::string CommandServer_parse_injection(CommandServer_parse_t original,
next_float(yaw);
// Set
entity->yaw = yaw;
update_player_position(entity);
api_update_entity_position(entity);
return CommandServer::NullString;
}
command(getRotation) {
@ -530,7 +504,7 @@ static std::string CommandServer_parse_injection(CommandServer_parse_t original,
next_float(pitch);
// Set
entity->pitch = pitch;
update_player_position(entity);
api_update_entity_position(entity);
return CommandServer::NullString;
}
command(getPitch) {
@ -548,7 +522,8 @@ static std::string CommandServer_parse_injection(CommandServer_parse_t original,
next_float(y);
next_float(z);
// Set
Entity_moveTo_injection(entity, x, y, z, entity->yaw, entity->pitch);
entity->moveTo(x, y, z, entity->yaw, entity->pitch);
api_update_entity_position(entity);
return CommandServer::NullString;
}
command(getAbsPos) {
@ -630,74 +605,13 @@ static std::string CommandServer_parse_injection(CommandServer_parse_t original,
return CommandServer::Fail;
}
// Fix HUD Spectating Other Players
template <typename... Args>
static void ItemInHandRenderer_render_injection(const std::function<void(ItemInHandRenderer *, Args...)> &original, ItemInHandRenderer *self, Args... args) {
// "Fix" Current Player
LocalPlayer *&player = self->minecraft->player;
LocalPlayer *old_player = player;
Mob *camera = self->minecraft->camera;
if (camera && camera->isPlayer()) {
player = (LocalPlayer *) camera;
}
// Call Original Method
original(self, std::forward<Args>(args)...);
// Revert "Fix"
player = old_player;
}
// Fix Crash When Camera Entity Is Removed
static void LevelRenderer_entityRemoved_injection(LevelRenderer *self, Entity *entity) {
// Call Original Method
LevelListener_entityRemoved->get(false)((LevelListener *) self, entity);
// Fix Camera
Minecraft *minecraft = self->minecraft;
if ((Entity *) minecraft->camera == entity) {
minecraft->camera = (Mob *) minecraft->player;
}
}
// Close Sockets
static void CommandServer__close_injection(CommandServer__close_t original, CommandServer *self) {
// Close
for (const ConnectedClient &client : self->clients) {
close(client.sock);
}
self->clients.clear();
// Call Original Method
original(self);
}
static void Minecraft_leaveGame_injection(Minecraft_leaveGame_t original, Minecraft *self, const bool save_remote_level) {
// Destroy Server
CommandServer *&server = self->command_server;
if (server) {
server->destructor(0);
server = nullptr;
}
// Call Original Method
original(self, save_remote_level);
}
// Init
void init_api() {
if (feature_has("Implement RaspberryJuice API", server_enabled)) {
overwrite_calls(CommandServer_parse, CommandServer_parse_injection);
_init_api_events();
// Fix Teleporting Players
overwrite_calls(ClientSideNetworkHandler_handle_MovePlayerPacket, ClientSideNetworkHandler_handle_MovePlayerPacket_injection);
overwrite_call((void *) 0x6b6e8, Entity_moveTo, Entity_moveTo_injection);
}
// Bug Fixes
if (feature_has("Fix HUD When Spectating Other Players", server_enabled)) {
overwrite_calls(ItemInHandRenderer_render, ItemInHandRenderer_render_injection<float>);
overwrite_calls(ItemInHandRenderer_renderScreenEffect, ItemInHandRenderer_render_injection<float>);
overwrite_calls(ItemInHandRenderer_tick, ItemInHandRenderer_render_injection<>);
}
if (feature_has("Fix Crash When Spectated Entity Is Removed", server_enabled)) {
patch_vtable(LevelRenderer_entityRemoved, LevelRenderer_entityRemoved_injection);
}
if (feature_has("Correctly Close API Sockets", server_enabled)) {
overwrite_calls(CommandServer__close, CommandServer__close_injection);
overwrite_calls(Minecraft_leaveGame, Minecraft_leaveGame_injection);
}
// Miscellaneous Changes
_init_api_socket();
_init_api_misc();
}

View File

@ -1,4 +1,3 @@
#include <algorithm>
#include <ranges>
#include <libreborn/patch.h>
@ -101,22 +100,15 @@ static bool CommandServer__updateAccept_setSocketBlocking_injection(const int fd
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;
void api_free_event_data(const int sock) {
extra_client_data.erase(sock);
}
static void CommandServer__close_injection(CommandServer__close_t original, CommandServer *self) {
// Server Shutdown
void api_free_all_event_data() {
extra_client_data.clear();
original(self);
}
// Clear All Events
static void clear_events(const ConnectedClient &client) {
static void clear_all_events(const ConnectedClient &client) {
ExtraClientData &data = extra_client_data.at(client.sock);
if (!api_compat_mode) {
// Match RJ Bug
@ -129,9 +121,9 @@ static void clear_events(const ConnectedClient &client) {
// Clear Events Produced By Given Entity
template <typename T>
static void _clear_events(std::vector<T> &data, const int id) {
data.erase(std::remove_if(data.begin(), data.end(), [&id](const T &e) {
std::erase_if(data, [&id](const T &e) {
return id == e.owner_id;
}), data.end());
});
}
static void clear_events(const ConnectedClient &client, const int id) {
ExtraClientData &data = extra_client_data.at(client.sock);
@ -287,31 +279,29 @@ void _init_api_events() {
overwrite_calls(Gui_addMessage, Gui_addMessage_injection);
// Track Connected Clients
overwrite_call((void *) 0x6bd78, CommandServer_setSocketBlocking, CommandServer__updateAccept_setSocketBlocking_injection);
overwrite_calls(CommandServer__updateClient, CommandServer__updateClient_injection);
overwrite_calls(CommandServer__close, CommandServer__close_injection);
// Track Block Hits
overwrite_calls(GameMode_useItemOn, GameMode_useItemOn_injection);
overwrite_calls(CreatorMode_useItemOn, CreatorMode_useItemOn_injection);
}
// Handle Commands
std::string api_handle_event_command(CommandServer *server, const ConnectedClient &client, const std::string &cmd, std::optional<int> id) {
std::string api_handle_event_command(CommandServer *server, const ConnectedClient &client, const std::string_view &cmd, const std::optional<int> id) {
if (cmd == "clear") {
// Clear Events
if (id.has_value()) {
clear_events(client, id.value());
} else {
clear_events(client);
clear_all_events(client);
}
return CommandServer::NullString;
} else if (cmd == "chat.posts") {
// Chat Events
return get_chat_events(server, client, id);
} else if (cmd == "block.hits") {
// Chat Events
// Block Hit Events
return get_block_hit_events(server, client, id);
} else if (cmd == "projectile.hits") {
// Chat Events
// Projectile Events
return get_projectile_events(server, client, id);
} else {
// Invalid Command

View File

@ -19,8 +19,14 @@ __attribute__((visibility("internal"))) std::string api_join_outputs(const std::
__attribute__((visibility("internal"))) void api_convert_to_outside_entity_type(int &type);
__attribute__((visibility("internal"))) void api_convert_to_mcpi_entity_type(int &type);
__attribute__((visibility("internal"))) void _init_api_events();
__attribute__((visibility("internal"))) void api_update_entity_position(const Entity *entity);
__attribute__((visibility("internal"))) std::string api_handle_event_command(CommandServer *server, const ConnectedClient &client, const std::string &cmd, std::optional<int> id);
__attribute__((visibility("internal"))) void _init_api_events();
__attribute__((visibility("internal"))) void _init_api_misc();
__attribute__((visibility("internal"))) void _init_api_socket();
__attribute__((visibility("internal"))) std::string api_handle_event_command(CommandServer *server, const ConnectedClient &client, const std::string_view &cmd, std::optional<int> id);
__attribute__((visibility("internal"))) void api_free_event_data(int sock);
__attribute__((visibility("internal"))) void api_free_all_event_data();
__attribute__((visibility("internal"))) extern bool api_suppress_chat_events;

118
mods/src/api/misc.cpp Normal file
View File

@ -0,0 +1,118 @@
#include <libreborn/patch.h>
#include <mods/feature/feature.h>
#include "internal.h"
// Fix HUD Spectating Other Players
template <typename... Args>
static void ItemInHandRenderer_render_injection(const std::function<void(ItemInHandRenderer *, Args...)> &original, ItemInHandRenderer *self, Args... args) {
// "Fix" Current Player
LocalPlayer *&player = self->minecraft->player;
LocalPlayer *old_player = player;
Mob *camera = self->minecraft->camera;
if (camera && camera->isPlayer()) {
player = (LocalPlayer *) camera;
}
// Call Original Method
original(self, std::forward<Args>(args)...);
// Revert "Fix"
player = old_player;
}
// Fix Crash When Camera Entity Is Removed
static void LevelRenderer_entityRemoved_injection(LevelRenderer *self, Entity *entity) {
// Call Original Method
LevelListener_entityRemoved->get(false)((LevelListener *) self, entity);
// Fix Camera
Minecraft *minecraft = self->minecraft;
if ((Entity *) minecraft->camera == entity) {
minecraft->camera = (Mob *) minecraft->player;
}
}
// Close Sockets
static void CommandServer__close_injection_1(CommandServer__close_t original, CommandServer *self) {
// Close
for (const ConnectedClient &client : self->clients) {
close(client.sock);
}
self->clients.clear();
// Call Original Method
original(self);
}
static void Minecraft_leaveGame_injection(Minecraft_leaveGame_t original, Minecraft *self, const bool save_remote_level) {
// Destroy Server
CommandServer *&server = self->command_server;
if (server) {
server->destructor(0);
::operator delete(server);
server = nullptr;
}
// Call Original Method
original(self, save_remote_level);
}
static bool CommandServer__updateClient_injection_1(CommandServer__updateClient_t original, CommandServer *self, ConnectedClient &client) {
// Call Original Method
const bool ret = original(self, client);
// Close Socket If Needed
if (!ret) {
close(client.sock);
}
return ret;
}
// Properly Teleport Players
void api_update_entity_position(const Entity *entity) {
MovePlayerPacket *packet = MovePlayerPacket::allocate(); // Despite The Name, This Supports All Entities
((Packet *) packet)->constructor();
packet->vtable = MovePlayerPacket_vtable::base;
packet->x = entity->x;
packet->y = entity->y - entity->height_offset;
packet->z = entity->z;
packet->yaw = entity->yaw;
packet->pitch = entity->pitch;
packet->entity_id = entity->id;
entity->level->rak_net_instance->send(*(Packet *) packet);
packet->destructor_deleting();
}
static void CommandServer_parse_Entity_moveTo_injection(Entity *self, const float x, const float y, const float z, const float yaw, const float pitch) {
self->moveTo(x, y, z, yaw, pitch);
api_update_entity_position(self);
}
static void ClientSideNetworkHandler_handle_MovePlayerPacket_injection(ClientSideNetworkHandler_handle_MovePlayerPacket_t original, ClientSideNetworkHandler *self, const RakNet_RakNetGUID &rak_net_guid, MovePlayerPacket *packet) {
if (self->level) {
Entity *entity = self->level->getEntity(packet->entity_id);
if (entity) {
if (entity == (Entity *) self->minecraft->player) {
// Just Teleport
entity->moveTo(packet->x, packet->y, packet->z, packet->yaw, packet->pitch);
} else {
// Call Original Method
original(self, rak_net_guid, packet);
}
}
}
}
// Init
void _init_api_misc() {
// Bug Fixes
if (feature_has("Fix HUD When Spectating Other Players", server_enabled)) {
overwrite_calls(ItemInHandRenderer_render, ItemInHandRenderer_render_injection<float>);
overwrite_calls(ItemInHandRenderer_renderScreenEffect, ItemInHandRenderer_render_injection<float>);
overwrite_calls(ItemInHandRenderer_tick, ItemInHandRenderer_render_injection<>);
}
if (feature_has("Fix Crash When Spectated Entity Is Removed", server_enabled)) {
patch_vtable(LevelRenderer_entityRemoved, LevelRenderer_entityRemoved_injection);
}
if (feature_has("Correctly Close API Sockets", server_enabled)) {
overwrite_calls(CommandServer__close, CommandServer__close_injection_1);
overwrite_calls(Minecraft_leaveGame, Minecraft_leaveGame_injection);
overwrite_calls(CommandServer__updateClient, CommandServer__updateClient_injection_1);
}
if (feature_has("Fix Moving Players With The API In Multiplayer", server_enabled)) {
overwrite_calls(ClientSideNetworkHandler_handle_MovePlayerPacket, ClientSideNetworkHandler_handle_MovePlayerPacket_injection);
overwrite_call((void *) 0x6b6e8, Entity_moveTo, CommandServer_parse_Entity_moveTo_injection);
}
}

131
mods/src/api/socket.cpp Normal file
View File

@ -0,0 +1,131 @@
#include <sys/socket.h>
#include <unordered_map>
#include <libreborn/patch.h>
#include <libreborn/log.h>
#include <mods/feature/feature.h>
#include "internal.h"
// Queue Data For Writing
static std::unordered_map<int, std::string> to_write;
static void write_queued_data(const int sock) {
// Check
if (!to_write.contains(sock)) {
return;
}
// Write Data
std::string &data = to_write.at(sock);
size_t pos = 0;
while (pos < data.size()) {
const size_t remaining = data.size() - pos;
ssize_t bytes_sent = send(sock, data.data() + pos, remaining, 0);
if (bytes_sent == -1) {
bytes_sent = 0;
if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
// Unable To Send Right Now, Try Again Later
break;
} else {
// Error, Give Up
pos = 0;
data.clear();
}
}
// Advance
pos += bytes_sent;
}
// Erase Sent Data
data.erase(0, pos);
// Remove Finished Sockets
std::erase_if(to_write, [](const std::pair<const int, std::string> &pair) {
return pair.second.empty();
});
}
static void handle_line(CommandServer *self, ConnectedClient &client, const std::string &line) {
// Run
const std::string ret = self->parse(client, line);
// Return
if (ret != CommandServer::NullString) {
if (ret.back() != '\n') {
IMPOSSIBLE();
}
// Queue For Sending
to_write[client.sock] += ret;
}
}
// Remove Speed Limit
static constexpr int max_read_per_tick_per_client = 16384; // 16 KiB
static bool CommandServer__updateClient_injection_2(__attribute__((unused)) CommandServer__updateClient_t original, CommandServer *self, ConnectedClient &client) {
// Read Lines
size_t total_read = 0;
constexpr size_t buffer_size = 2048;
char buffer[buffer_size];
while (total_read < max_read_per_tick_per_client) {
const ssize_t bytes_received = recv(client.sock, buffer, buffer_size, 0);
if (bytes_received == -1) {
if (errno == EINTR) {
// Signal Detected, Try Again
continue;
} else if (errno == EWOULDBLOCK || errno == EAGAIN) {
// End Of Available Data (For Now)
break;
} else {
// Error
return false;
}
} else if (bytes_received == 0) {
// Connection Closed
return false;
}
total_read += bytes_received;
// Append Received Data To Client Buffer
client.str.append(buffer, bytes_received);
}
// Process Lines
size_t start = 0;
size_t end;
while ((end = client.str.find('\n', start)) != std::string::npos) {
end++; // Include Newline
const std::string line = client.str.substr(start, end - start);
handle_line(self, client, line);
start = end;
}
client.str.erase(0, start);
// Write Queued Data
write_queued_data(client.sock);
// Success
return true;
}
// Clear Extra Data On Socket Close
static bool CommandServer__updateClient_injection_3(CommandServer__updateClient_t original, CommandServer *self, ConnectedClient &client) {
const bool ret = original(self, client);
if (!ret) {
// Client Disconnected
api_free_event_data(client.sock);
to_write.erase(client.sock);
}
return ret;
}
static void CommandServer__close_injection_2(CommandServer__close_t original, CommandServer *self) {
// Clear All Extra Data
api_free_all_event_data();
to_write.clear();
// Call Original Method
original(self);
}
// Init
void _init_api_socket() {
// Optimization
if (feature_has("Optimized API Sockets", server_enabled)) {
overwrite_calls(CommandServer__updateClient, CommandServer__updateClient_injection_2);
}
// Clear Extra Data On Socket Close
overwrite_calls(CommandServer__updateClient, CommandServer__updateClient_injection_3);
overwrite_calls(CommandServer__close, CommandServer__close_injection_2);
}