WIP RJ API
This commit is contained in:
parent
240d07e1ec
commit
49f654916d
2
dependencies/symbol-processor/src
vendored
2
dependencies/symbol-processor/src
vendored
@ -1 +1 @@
|
||||
Subproject commit 01a64f321d5f98a59d1432f659b8a9e11d3d60c6
|
||||
Subproject commit 3db1515ecf3a5ab8f9b3b642dc564ca84ab3182c
|
@ -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`
|
||||
|
@ -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 |
|
||||
| [ReMinecraftPE](https://github.com/ReMinecraftPE/mcpe) | A Lot Of Decompiled Code |
|
||||
| [Bigjango](https://github.com/Bigjango13) | A Ton Of Programming Contributions |
|
@ -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")
|
||||
|
@ -149,4 +149,5 @@ CATEGORY Miscellaneous
|
||||
TRUE Screenshot Support
|
||||
TRUE Add Camera Functionality
|
||||
TRUE Update Default Options
|
||||
TRUE Remove Chest Placement Restrictions
|
||||
TRUE Remove Chest Placement Restrictions
|
||||
TRUE Implement RaspberryJuice API
|
@ -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
|
||||
|
8
mods/include/mods/api/api.h
Normal file
8
mods/include/mods/api/api.h
Normal 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);
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <symbols/minecraft.h>
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -28,5 +28,6 @@ void init_screenshot();
|
||||
void init_f3();
|
||||
void init_multidraw();
|
||||
void init_classic_ui();
|
||||
void init_api();
|
||||
void init_shading();
|
||||
}
|
||||
|
@ -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_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;
|
823
mods/src/api/api.cpp
Normal file
823
mods/src/api/api.cpp
Normal 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);
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
#include <mods/feature/feature.h>
|
||||
#include "chat-internal.h"
|
||||
#include <mods/chat/chat.h>
|
||||
#include <mods/api/api.h>
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,38 +6,38 @@
|
||||
|
||||
#include <mods/init/init.h>
|
||||
#include <mods/feature/feature.h>
|
||||
#include <mods/misc/misc.h>
|
||||
|
||||
// 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<void(ParentSelf *, Entity *
|
||||
template <typename OriginalSelf, typename Self>
|
||||
static void Player_actuallyHurt_injection(const std::function<void(OriginalSelf *, int)> &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);
|
||||
|
@ -134,7 +134,7 @@ static std::vector<std::string> 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;
|
||||
|
@ -38,6 +38,7 @@ __attribute__((constructor)) static void init() {
|
||||
init_bucket();
|
||||
init_cake();
|
||||
init_override();
|
||||
init_api();
|
||||
if (!reborn_is_server()) {
|
||||
init_benchmark();
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <libreborn/patch.h>
|
||||
#include <libreborn/util/util.h>
|
||||
#include <libreborn/util/string.h>
|
||||
|
||||
#include <symbols/minecraft.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;
|
||||
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<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
|
||||
void _init_misc_api() {
|
||||
// Handle Custom Creative Inventory Setup Behavior
|
||||
|
96
mods/src/misc/base64.cpp
Normal file
96
mods/src/misc/base64.cpp
Normal 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;
|
||||
}
|
@ -116,12 +116,6 @@ static std::string get_blacklist_file() {
|
||||
static std::vector<Player *> 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);
|
||||
|
@ -5,50 +5,13 @@
|
||||
|
||||
#include <mods/init/init.h>
|
||||
#include <mods/feature/feature.h>
|
||||
#include <mods/misc/misc.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
|
||||
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();
|
||||
|
@ -62,4 +62,5 @@ Python API!
|
||||
Raspberry Pi!
|
||||
It's alive!
|
||||
Now with cake!
|
||||
The bug attractor!
|
||||
The bug attractor!
|
||||
I promise, this is the last time!
|
@ -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;
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user