WIP RJ API

This commit is contained in:
Bigjango13 2025-02-25 07:13:46 -05:00 committed by TheBrokenRail
parent 240d07e1ec
commit 49f654916d
23 changed files with 1112 additions and 88 deletions

@ -1 +1 @@
Subproject commit 01a64f321d5f98a59d1432f659b8a9e11d3d60c6 Subproject commit 3db1515ecf3a5ab8f9b3b642dc564ca84ab3182c

View File

@ -61,6 +61,7 @@
* `Batch Font Rendering` (Enabled By Default) * `Batch Font Rendering` (Enabled By Default)
* `Fix Furnace Screen Visual Bug` (Enabled By Default) * `Fix Furnace Screen Visual Bug` (Enabled By Default)
* `Fix Held Item Poking Through Screen Overlay` (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) * Existing Functionality (All Enabled By Default)
* `Fix Screen Rendering When Hiding HUD` * `Fix Screen Rendering When Hiding HUD`
* `Sanitize Usernames` * `Sanitize Usernames`

View File

@ -1,11 +1,13 @@
# Credits # Credits
| Project | Reason | | 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 | | [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/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 | | [Ghidra](https://ghidra-sre.org) | Used For Decompiling Minecraft: Pi Edition |
| [RetDec](https://retdec.com) | 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 | | [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 | | [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 | | [ReMinecraftPE](https://github.com/ReMinecraftPE/mcpe) | A Lot Of Decompiled Code |
| [Bigjango](https://github.com/Bigjango13) | A Ton Of Programming Contributions |

View File

@ -1,7 +1,7 @@
OPTION(debug, "debug", 'd', "Enable Debug Logging") OPTION(debug, "debug", 'd', "Enable Debug Logging")
OPTION(copy_sdk, "copy-sdk", -2, "Extract Modding SDK And Exit") OPTION(copy_sdk, "copy-sdk", -2, "Extract Modding SDK And Exit")
OPTION(disable_logger, "disable-logger", -1, "Disable Logger (And Crash Report Dialog)") 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(no_cache, "no-cache", -4, "Disable Client-Mode Configuration Cache")
OPTION(wipe_cache, "wipe-cache", -5, "Wipe Cached Client-Mode Configuration And Exit") 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") OPTION(print_available_feature_flags, "print-available-feature-flags", -6, "Print Available Client-Mode Feature Flags")

View File

@ -149,4 +149,5 @@ CATEGORY Miscellaneous
TRUE Screenshot Support TRUE Screenshot Support
TRUE Add Camera Functionality TRUE Add Camera Functionality
TRUE Update Default Options TRUE Update Default Options
TRUE Remove Chest Placement Restrictions TRUE Remove Chest Placement Restrictions
TRUE Implement RaspberryJuice API

View File

@ -34,6 +34,7 @@ add_library(mods SHARED
src/misc/ui.cpp src/misc/ui.cpp
src/misc/tinting.cpp src/misc/tinting.cpp
src/misc/home.cpp src/misc/home.cpp
src/misc/base64.cpp
# extend # extend
src/extend/Screen.cpp src/extend/Screen.cpp
src/extend/DynamicTexture.cpp src/extend/DynamicTexture.cpp
@ -98,6 +99,8 @@ add_library(mods SHARED
src/multidraw/storage.cpp src/multidraw/storage.cpp
# classic-ui # classic-ui
src/classic-ui/classic-ui.cpp src/classic-ui/classic-ui.cpp
# api
src/api/api.cpp
# shading # shading
src/shading/init.cpp src/shading/init.cpp
src/shading/tesselator.cpp src/shading/tesselator.cpp

View File

@ -0,0 +1,8 @@
#pragma once
#include <string>
#include <symbols/minecraft.h>
extern "C" {
void api_add_chat_event(const Player *sender, const std::string &message);
}

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include <string> #include <string>
#include <symbols/minecraft.h> #include <symbols/minecraft.h>
extern "C" { extern "C" {
@ -9,6 +8,7 @@ extern "C" {
std::string chat_send_api_command(const Minecraft *minecraft, const std::string &str); std::string chat_send_api_command(const Minecraft *minecraft, const std::string &str);
// Override using the HOOK() macro to provide customized chat behavior. // 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); void chat_handle_packet_send(const Minecraft *minecraft, ChatPacket *packet);
bool chat_is_sending();
} }

View File

@ -28,5 +28,6 @@ void init_screenshot();
void init_f3(); void init_f3();
void init_multidraw(); void init_multidraw();
void init_classic_ui(); void init_classic_ui();
void init_api();
void init_shading(); void init_shading();
} }

View File

@ -28,4 +28,38 @@ void misc_run_on_key_press(const std::function<bool(Minecraft *, int)> &func);
void misc_run_on_creative_inventory_setup(const std::function<void(FillingContainer *)> &function); void misc_run_on_creative_inventory_setup(const std::function<void(FillingContainer *)> &function);
void misc_run_on_swap_buffers(const std::function<void()> &function); void misc_run_on_swap_buffers(const std::function<void()> &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<EntityType, std::pair<std::string, std::string>> &misc_get_entity_type_names();
std::pair<std::string, std::string> 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; static constexpr int line_height = 8;

823
mods/src/api/api.cpp Normal file
View File

@ -0,0 +1,823 @@
#include <cmath>
#include <string>
#include <fstream>
#include <algorithm>
#include <vector>
#include <unordered_map>
#include <optional>
#include <ranges>
#include <libreborn/log.h>
#include <libreborn/util/string.h>
#include <libreborn/patch.h>
#include <libreborn/config.h>
#include <symbols/minecraft.h>
#include <mods/api/api.h>
#include <mods/init/init.h>
#include <mods/misc/misc.h>
#include <mods/chat/chat.h>
#include <mods/feature/feature.h>
// 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<std::string> &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<std::string> 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<int, EntityType> 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<const int, EntityType> &pair : modern_entity_id_mapping) {
if (static_cast<int>(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<int>(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<ProjectileHitEvent> projectile_events;
std::vector<ChatEvent> chat_events;
std::vector<TileEvent> block_hit_events;
};
static std::unordered_map<int, ExtraClientData> 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 <typename T>
static void clear_events(std::vector<T> &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 <typename T>
static std::string get_events(CommandServer *server, std::vector<T> &queue, const std::optional<int> id) {
std::vector<std::string> out;
typename std::vector<T>::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<std::string> 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<std::string> 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<std::string> result;
for (const std::pair<const EntityType, std::pair<std::string, std::string>> &i: misc_get_entity_type_names()) {
int id = static_cast<int>(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 <typename T>
static void push_event(std::vector<T> 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);
}
}

View File

@ -7,6 +7,7 @@
#include <mods/feature/feature.h> #include <mods/feature/feature.h>
#include "chat-internal.h" #include "chat-internal.h"
#include <mods/chat/chat.h> #include <mods/chat/chat.h>
#include <mods/api/api.h>
// Send UTF-8 API Command // Send UTF-8 API Command
std::string chat_send_api_command(const Minecraft *minecraft, const std::string &str) { 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) { std::string _chat_get_prefix(const char *username) {
return std::string("<") + 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; std::string full_message = _chat_get_prefix(username) + message;
sanitize_string(full_message, MAX_CHAT_MESSAGE_LENGTH, false); sanitize_string(full_message, MAX_CHAT_MESSAGE_LENGTH, false);
server_side_network_handler->displayGameMessage(full_message); server_side_network_handler->displayGameMessage(full_message);
is_sending = false;
} }
// Handle Chat packet Send // Handle Chat packet Send
void chat_handle_packet_send(const Minecraft *minecraft, ChatPacket *packet) { 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 // Hosting Multiplayer
const char *message = packet->message.c_str(); const char *message = packet->message.c_str();
ServerSideNetworkHandler *server_side_network_handler = (ServerSideNetworkHandler *) minecraft->network_handler; 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 { } else {
// Client // Client
rak_net_instance->send(*(Packet *) packet); 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) { 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); const Player *player = server_side_network_handler->getPlayer(rak_net_guid);
if (player != nullptr) { if (player != nullptr) {
const char *username = player->username.c_str();
const char *message = chat_packet->message.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);
} }
} }

View File

@ -6,38 +6,38 @@
#include <mods/init/init.h> #include <mods/init/init.h>
#include <mods/feature/feature.h> #include <mods/feature/feature.h>
#include <mods/misc/misc.h>
// Death Messages // 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) { std::string get_death_message(Player *player, Entity *cause, const bool was_shot = false) {
// Prepare Death Message // Prepare Death Message
std::string message = player->username; std::string message = player->username;
if (cause) { if (cause) {
// Entity cause // Entity Cause
const int type_id = cause->getEntityTypeId(); const int type_id = cause->getEntityTypeId();
int aux = cause->getAuxData(); const int aux = cause->getAuxData();
const bool is_player = cause->isPlayer(); const bool is_player = cause->isPlayer();
if (cause->getCreatureBaseType() != 0 || is_player) { if (cause->getCreatureBaseType() != 0 || is_player) {
// Killed by a creature // Killed By A Creature
if (was_shot) { if (was_shot) {
message += " was shot by "; message += " was shot by ";
} else { } else {
message += " was killed by "; message += " was killed by ";
} }
if (is_player) { if (is_player) {
// Killed by a player // Killed By A Player
message += ((Player *) cause)->username; message += ((Player *) cause)->username;
} else if (32 <= type_id && type_id <= 36) {
// Normal monster
message += "a ";
message += monster_names[type_id - 32];
} else { } else {
// Unknown creature // Normal Creature
message += "a mysterious beast"; std::string type = misc_get_entity_type_name(cause).first;
if (type.empty()) {
type = "mysterious beast";
}
message += "a " + type;
} }
return message; return message;
} else if (aux) { } else if (aux) {
// Killed by a throwable with owner // Killed By A Throwable With Owner
Level *level = player->level; Level *level = player->level;
Entity *shooter = level->getEntity(aux); Entity *shooter = level->getEntity(aux);
return get_death_message(player, shooter, true); 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"; return message + " admired too much art";
} }
} }
// Miscellaneous Causes
if (was_shot) { if (was_shot) {
// Throwable with invalid owner // Throwable With Invalid Owner
return message + " was shot under mysterious circumstances"; return message + " was shot under mysterious circumstances";
} else if (cause) { } else if (cause) {
// Unknown entity // Unknown Entity
return message + " was killed"; return message + " was killed";
} else { } else {
// Anything else // Anything Else
return message + " has died"; return message + " has died";
} }
} }
// Track If A Mob Is Being Hurt
static bool is_hurt = false; static bool is_hurt = false;
static bool Mob_hurt_injection(Mob_hurt_t original, Mob *mob, Entity *source, const int dmg) { static bool Mob_hurt_injection(Mob_hurt_t original, Mob *mob, Entity *source, const int dmg) {
is_hurt = true; is_hurt = true;
@ -91,7 +92,7 @@ static void Player_die_injection(const std::function<void(ParentSelf *, Entity *
template <typename OriginalSelf, typename Self> template <typename OriginalSelf, typename Self>
static void Player_actuallyHurt_injection(const std::function<void(OriginalSelf *, int)> &original, Self *player, int32_t damage) { static void Player_actuallyHurt_injection(const std::function<void(OriginalSelf *, int)> &original, Self *player, int32_t damage) {
// Store Old Health // Store Old Health
int32_t old_health = player->health; const int32_t old_health = player->health;
// Call Original Method // Call Original Method
original((OriginalSelf *) player, damage); original((OriginalSelf *) player, damage);

View File

@ -134,7 +134,7 @@ static std::vector<std::string> get_debug_info_right(const Minecraft *minecraft)
z = entity->z; z = entity->z;
type = "Entity"; type = "Entity";
const int type_id = entity->getEntityTypeId(); 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)); type_info.push_back("ID: " + std::to_string(entity->id));
if (entity->isMob()) { if (entity->isMob()) {
Mob *mob = (Mob *) entity; Mob *mob = (Mob *) entity;

View File

@ -38,6 +38,7 @@ __attribute__((constructor)) static void init() {
init_bucket(); init_bucket();
init_cake(); init_cake();
init_override(); init_override();
init_api();
if (!reborn_is_server()) { if (!reborn_is_server()) {
init_benchmark(); init_benchmark();
} }

View File

@ -2,6 +2,7 @@
#include <libreborn/patch.h> #include <libreborn/patch.h>
#include <libreborn/util/util.h> #include <libreborn/util/util.h>
#include <libreborn/util/string.h>
#include <symbols/minecraft.h> #include <symbols/minecraft.h>
#include <GLES/gl.h> #include <GLES/gl.h>
@ -128,10 +129,10 @@ void misc_render_background(int color, const Minecraft *minecraft, const int x,
Tesselator *t = &Tesselator::instance; Tesselator *t = &Tesselator::instance;
t->begin(GL_QUADS); t->begin(GL_QUADS);
t->color(color, color, color, 255); t->color(color, color, color, 255);
float x1 = x; float x1 = float(x);
float x2 = x + width; float x2 = x1 + float(width);
float y1 = y; float y1 = float(y);
float y2 = y + height; float y2 = y1 + float(height);
t->vertexUV(x1, y2, 0.0f, x1 / 32.0f, y2 / 32.0f); 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, y2, 0.0f, x2 / 32.0f, y2 / 32.0f);
t->vertexUV(x2, y1, 0.0f, x2 / 32.0f, y1 / 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(); t->draw();
} }
// Entity Names
static std::pair<std::string, std::string> 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<std::string, std::string> misc_get_entity_type_name(Entity *entity) {
if (entity) {
if (entity->isPlayer()) {
// Player
return format_entity_name("Player");
} else {
const EntityType type = static_cast<EntityType>(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<void *, std::string> 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<EntityType, std::pair<std::string, std::string>> &misc_get_entity_type_names() {
static std::map<EntityType, std::pair<std::string, std::string>> 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 // Init
void _init_misc_api() { void _init_misc_api() {
// Handle Custom Creative Inventory Setup Behavior // Handle Custom Creative Inventory Setup Behavior

96
mods/src/misc/base64.cpp Normal file
View File

@ -0,0 +1,96 @@
#include <cstdint>
#include <string>
// 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<char *>(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<int>(input[i++])];
const uint32_t b = input[i] == '=' ? 0 & i++ : decoding_table[static_cast<int>(input[i++])];
const uint32_t c = input[i] == '=' ? 0 & i++ : decoding_table[static_cast<int>(input[i++])];
const uint32_t d = input[i] == '=' ? 0 & i++ : decoding_table[static_cast<int>(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;
}

View File

@ -116,12 +116,6 @@ static std::string get_blacklist_file() {
static std::vector<Player *> get_players_in_level(Level *level) { static std::vector<Player *> get_players_in_level(Level *level) {
return level->players; 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 // Get Level From Minecraft
static Level *get_level(const Minecraft *minecraft) { static Level *get_level(const Minecraft *minecraft) {
return minecraft->level; return minecraft->level;
@ -135,7 +129,7 @@ static void find_players(Minecraft *minecraft, const std::string &target_usernam
bool found_player = false; bool found_player = false;
for (Player *player : players) { for (Player *player : players) {
// Iterate 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) { if (all_players || username == target_username) {
// Run Callback // Run Callback
callback(minecraft, username, player); callback(minecraft, username, player);

View File

@ -5,50 +5,13 @@
#include <mods/init/init.h> #include <mods/init/init.h>
#include <mods/feature/feature.h> #include <mods/feature/feature.h>
#include <mods/misc/misc.h>
#include "skin-internal.h" #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<char *>(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 // 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) { static void Player_username_assign_injection(std::string *target, const std::string &username) {
// Call Original Method // Call Original Method
*target = username; *target = username;
@ -59,7 +22,7 @@ static void Player_username_assign_injection(std::string *target, const std::str
std::string *texture = &player->texture; std::string *texture = &player->texture;
// Set 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) { static void Player_username_assign_injection_2(std::string *target, const char *username) {
const std::string username_str = 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 // 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 // Change Texture
static std::string new_texture; static std::string new_texture;
if (new_texture.length() == 0) { if (new_texture.length() == 0) {
const std::string username = base64_encode(Strings::default_username); new_texture = get_skin_texture_path(Strings::default_username);
new_texture = '$' + username;
} }
// Call Original Method // Call Original Method
@ -91,7 +53,7 @@ void init_skin() {
overwrite_call_manual((void *) 0x7639c, (void *) Player_username_assign_injection_2); overwrite_call_manual((void *) 0x7639c, (void *) Player_username_assign_injection_2);
// HUD // HUD
overwrite_call((void *) 0x4c6d0, Textures_loadAndBindTexture, Textures_loadAndBindTexture_injection); overwrite_call((void *) 0x4c6d0, Textures_loadAndBindTexture, ItemInHandRenderer_render_Textures_loadAndBindTexture_injection);
// Loader // Loader
_init_skin_loader(); _init_skin_loader();

View File

@ -62,4 +62,5 @@ Python API!
Raspberry Pi! Raspberry Pi!
It's alive! It's alive!
Now with cake! Now with cake!
The bug attractor! The bug attractor!
I promise, this is the last time!

View File

@ -1,5 +1,11 @@
method std::string parse(ConnectedClient &client, const std::string &command) = 0x6aa8c; method std::string parse(ConnectedClient &client, const std::string &command) = 0x6aa8c;
method void dispatchPacket(Packet &packet) = 0x6a548; 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 Minecraft *minecraft = 0x18;
property OffsetPosTranslator pos_translator = 0x1c; property OffsetPosTranslator pos_translator = 0x1c;
static-property std::string Fail = 0x137dbc;
static-property std::string Ok = 0x138a08;
static-property std::string NullString = 0x137db8;

View File

@ -1,6 +1,6 @@
size 0x14; size 0x14;
property int entityId = 0x0; property int owner_id = 0x0;
property int x = 0x4; property int x = 0x4;
property int y = 0x8; property int y = 0x8;
property int z = 0xc; property int z = 0xc;

View File

@ -10,6 +10,9 @@ virtual-method bool shouldSave() = 0x8;
virtual-method void load(CompoundTag *tag) = 0xc; virtual-method void load(CompoundTag *tag) = 0xc;
virtual-method bool save(CompoundTag *tag) = 0x10; virtual-method bool save(CompoundTag *tag) = 0x10;
virtual-method void tick() = 0x14; virtual-method void tick() = 0x14;
virtual-method Packet *getUpdatePacket() = 0x20;
method void setChanged() = 0xd23a4;
property Level *level = 0x4; property Level *level = 0x4;
property int x = 0x8; property int x = 0x8;