diff --git a/mods/CMakeLists.txt b/mods/CMakeLists.txt index 22502db8..bcec3dbc 100644 --- a/mods/CMakeLists.txt +++ b/mods/CMakeLists.txt @@ -86,7 +86,8 @@ add_library(mods SHARED src/fps/fps.cpp # server src/server/server.cpp - src/server/server_properties.cpp + src/server/commands.cpp + src/server/properties.cpp # multiplayer src/multiplayer/multiplayer.cpp # benchmark diff --git a/mods/include/mods/server/server_properties.h b/mods/include/mods/server/properties.h similarity index 100% rename from mods/include/mods/server/server_properties.h rename to mods/include/mods/server/properties.h diff --git a/mods/include/mods/server/server.h b/mods/include/mods/server/server.h index 7a6db404..88d38b22 100644 --- a/mods/include/mods/server/server.h +++ b/mods/include/mods/server/server.h @@ -2,7 +2,7 @@ #include -#include "server_properties.h" +#include "properties.h" struct ServerCommand { const std::string name; diff --git a/mods/src/server/commands.cpp b/mods/src/server/commands.cpp new file mode 100644 index 00000000..7a040e41 --- /dev/null +++ b/mods/src/server/commands.cpp @@ -0,0 +1,246 @@ +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include "internal.h" + +// Get Vector Of Players In Level +static std::vector get_players_in_level(Level *level) { + return level->players; +} +// Get Level From Minecraft +static Level *get_level(const Minecraft *minecraft) { + return minecraft->level; +} + +// Find Players With Username And Run Callback +typedef void (*player_callback_t)(Minecraft *minecraft, const std::string &username, Player *player); +static void find_players(Minecraft *minecraft, const std::string &target_username, const player_callback_t callback, const bool all_players) { + Level *level = get_level(minecraft); + const std::vector players = get_players_in_level(level); + bool found_player = false; + for (Player *player : players) { + // Iterate Players + std::string username = misc_get_player_username_utf(player); + if (all_players || username == target_username) { + // Run Callback + callback(minecraft, username, player); + found_player = true; + } + } + if (!all_players && !found_player) { + INFO("Invalid Player: %s", target_username.c_str()); + } +} + +// Get IP From Player +static char *get_player_ip(const Minecraft *minecraft, Player *player) { + RakNet_RakPeer *rak_peer = minecraft->rak_net_instance->peer; + const RakNet_RakNetGUID guid = ((ServerPlayer *) player)->guid; + // Return + return get_rak_net_guid_ip(rak_peer, guid); +} + +// Ban Player +static void ban_callback(Minecraft *minecraft, const std::string &username, Player *player) { + // Get IP + char *ip = get_player_ip(minecraft, player); + + // Ban Player + INFO("Banned: %s (%s)", username.c_str(), ip); + // Write To File + std::ofstream blacklist_output(get_blacklist_file(), std::ios_base::app); + if (blacklist_output) { + if (blacklist_output.good()) { + blacklist_output << "# " << username << '\n' << ip << '\n'; + } + if (blacklist_output.is_open()) { + blacklist_output.close(); + } + } + // Reload + is_ip_in_blacklist(nullptr); +} + +// Kill Player +static void kill_callback(__attribute__((unused)) Minecraft *minecraft, __attribute__((unused)) const std::string &username, Player *player) { + player->hurt(nullptr, INT32_MAX); + INFO("Killed: %s", username.c_str()); +} + +// List Player +static void list_callback(Minecraft *minecraft, const std::string &username, Player *player) { + INFO(" - %s (%s)", username.c_str(), get_player_ip(minecraft, player)); +} + +// Read STDIN Thread +static pthread_t read_stdin_thread_obj; +static volatile bool stdin_line_ready = false; +static std::string stdin_line; +static void *read_stdin_thread(__attribute__((unused)) void *data) { + // Loop + char *line = nullptr; + size_t len = 0; + while (getline(&line, &len, stdin) != -1) { + stdin_line = line; + stdin_line_ready = true; + // Wait For Line To Be Read + while (stdin_line_ready) {} + } + free(line); + return nullptr; +} +void start_reading_commands() { + pthread_create(&read_stdin_thread_obj, nullptr, read_stdin_thread, nullptr); +} +void stop_reading_commands() { + pthread_cancel(read_stdin_thread_obj); + pthread_join(read_stdin_thread_obj, nullptr); + stdin_line_ready = false; +} + +// Handle Commands +bool ServerCommand::has_args() const { + return name[name.length() - 1] == ' '; +} +std::string ServerCommand::get_lhs_help() const { + std::string out; + out.append(4, ' '); + out += name; + if (has_args()) { + out += ""; + } + return out; +} +std::string ServerCommand::get_full_help(const int max_lhs_length) const { + std::string out = get_lhs_help(); + out.append(max_lhs_length - out.length(), ' '); + out += " - "; + out += comment; + return out; +} +std::vector *server_get_commands(Minecraft *minecraft, ServerSideNetworkHandler *server_side_network_handler) { + std::vector *commands = new std::vector; + // Ban Player + if (!is_whitelist()) { + commands->push_back({ + .name = "ban ", + .comment = "IP-Ban All Players With Specified Username", + .callback = [minecraft](const std::string &cmd) { + find_players(minecraft, cmd, ban_callback, false); + } + }); + } + // Reload White/Blacklist + commands->push_back({ + .name = "reload", + .comment = std::string("Reload The ") + (is_whitelist() ? "Whitelist" : "Blacklist"), + .callback = [](__attribute__((unused)) const std::string &cmd) { + INFO("Reloading %s", is_whitelist() ? "Whitelist" : "Blacklist"); + is_ip_in_blacklist(nullptr); + } + }); + // Kill Player + commands->push_back({ + .name = "kill ", + .comment = "Kill All Players With Specified Username", + .callback = [minecraft](const std::string &cmd) { + find_players(minecraft, cmd, kill_callback, false); + } + }); + // Post Message + commands->push_back({ + .name = "say ", + .comment = "Print Specified Message To Chat", + .callback = [server_side_network_handler](const std::string &cmd) { + // Format Message + const std::string message = "[Server] " + cmd; + std::string cpp_string = to_cp437(message); + // Post Message To Chat + server_side_network_handler->displayGameMessage(cpp_string); + } + }); + // List Players + commands->push_back({ + .name = "list", + .comment = "List All Players", + .callback = [minecraft](__attribute__((unused)) const std::string &cmd) { + INFO("All Players:"); + find_players(minecraft, "", list_callback, true); + } + }); + // Ticks-Per-Second + commands->push_back({ + .name = "tps", + .comment = "Print TPS", + .callback = [](__attribute__((unused)) const std::string &cmd) { + INFO("TPS: %f", tps); + } + }); + // Stop + commands->push_back({ + .name = "stop", + .comment = "Stop Server", + .callback = [](__attribute__((unused)) const std::string &cmd) { + compat_request_exit(); + } + }); + // Help Page + commands->push_back({ + .name = "help", + .comment = "Print This Message", + .callback = [commands](__attribute__((unused)) const std::string &cmd) { + INFO("All Commands:"); + std::string::size_type max_lhs_length = 0; + for (const ServerCommand &command : *commands) { + const std::string::size_type lhs_length = command.get_lhs_help().length(); + if (lhs_length > max_lhs_length) { + max_lhs_length = lhs_length; + } + } + for (const ServerCommand &command : *commands) { + INFO("%s", command.get_full_help(max_lhs_length).c_str()); + } + } + }); + // Return + return commands; +} +void handle_commands(Minecraft *minecraft) { + // Check If Level Is Generated + if (minecraft->isLevelGenerated() && stdin_line_ready) { + // Read Line + std::string data = std::move(stdin_line); + data.pop_back(); // Remove Newline + stdin_line_ready = false; + // Command Ready; Run It + ServerSideNetworkHandler *server_side_network_handler = (ServerSideNetworkHandler *) minecraft->network_handler; + if (server_side_network_handler != nullptr) { + // Generate Command List + const std::vector *commands = server_get_commands(minecraft, server_side_network_handler); + // Run + bool success = false; + for (const ServerCommand &command : *commands) { + const bool valid = command.has_args() ? data.rfind(command.name, 0) == 0 : data == command.name; + if (valid) { + command.callback(data.substr(command.name.length())); + success = true; + break; + } + } + if (!success) { + INFO("Invalid Command: %s", data.c_str()); + } + // Free + delete commands; + } + } +} \ No newline at end of file diff --git a/mods/src/server/internal.h b/mods/src/server/internal.h new file mode 100644 index 00000000..0567fbe1 --- /dev/null +++ b/mods/src/server/internal.h @@ -0,0 +1,11 @@ +#pragma once + +__attribute__((visibility("internal"))) bool is_whitelist(); +__attribute__((visibility("internal"))) std::string get_blacklist_file(); +__attribute__((visibility("internal"))) bool is_ip_in_blacklist(const char *ip); + +__attribute__((visibility("internal"))) char *get_rak_net_guid_ip(RakNet_RakPeer *rak_peer, const RakNet_RakNetGUID &guid); + +__attribute__((visibility("internal"))) void handle_commands(Minecraft *minecraft); +__attribute__((visibility("internal"))) void start_reading_commands(); +__attribute__((visibility("internal"))) void stop_reading_commands(); \ No newline at end of file diff --git a/mods/src/server/server_properties.cpp b/mods/src/server/properties.cpp similarity index 92% rename from mods/src/server/server_properties.cpp rename to mods/src/server/properties.cpp index 8f178d25..76fc170f 100644 --- a/mods/src/server/server_properties.cpp +++ b/mods/src/server/properties.cpp @@ -1,12 +1,14 @@ #include -#include +#include +// Get All Possible Properties std::vector &ServerProperty::get_all() { static std::vector out; return out; } +// Load File void ServerProperties::load(std::istream &stream) { std::string line; while (std::getline(stream, line)) { @@ -23,10 +25,10 @@ void ServerProperties::load(std::istream &stream) { } } +// Get Value std::string ServerProperties::get_string(const ServerProperty &property) const { return properties.contains(property.key) ? properties.at(property.key) : property.def; } - int ServerProperties::get_int(const ServerProperty &property) const { try { return std::stoi(get_string(property)); @@ -34,7 +36,6 @@ int ServerProperties::get_int(const ServerProperty &property) const { return std::stoi(property.def); } } - static bool is_true(const std::string &val) { return val == "true" || val == "yes" || val == "1"; } diff --git a/mods/src/server/server.cpp b/mods/src/server/server.cpp index 1f678b0a..7cfc251d 100644 --- a/mods/src/server/server.cpp +++ b/mods/src/server/server.cpp @@ -23,7 +23,8 @@ #include #include #include -#include + +#include "internal.h" // --only-generate: Ony Generate World And Then Exit static bool only_generate = false; @@ -56,20 +57,12 @@ static auto &get_property_types() { return types; } -// Get World Name -static std::string get_world_name() { - const std::string name = get_server_properties().get_string(get_property_types().world_name); - std::string safe_name = to_cp437(name); - return safe_name; -} - // Create/Start World static void start_world(Minecraft *minecraft) { // Get World Name - const std::string world_name = get_world_name(); - - // Log + std::string world_name = get_server_properties().get_string(get_property_types().world_name); INFO("Loading World: %s", world_name.c_str()); + world_name = to_cp437(world_name); // Peaceful Mode Options *options = &minecraft->options; @@ -100,11 +93,11 @@ static void start_world(Minecraft *minecraft) { } // Check If Running In Whitelist Mode -static bool is_whitelist() { +bool is_whitelist() { return get_server_properties().get_bool(get_property_types().enable_whitelist); } // Get Path Of Blacklist (Or Whitelist) File -static std::string get_blacklist_file() { +std::string get_blacklist_file() { std::string file = home_get(); file += '/'; file += is_whitelist() ? "whitelist" : "blacklist"; @@ -112,130 +105,18 @@ static std::string get_blacklist_file() { return file; } -// Get Vector Of Players In Level -static std::vector get_players_in_level(Level *level) { - return level->players; -} -// Get Level From Minecraft -static Level *get_level(const Minecraft *minecraft) { - return minecraft->level; -} - -// Find Players With Username And Run Callback -typedef void (*player_callback_t)(Minecraft *minecraft, const std::string &username, Player *player); -static void find_players(Minecraft *minecraft, const std::string &target_username, const player_callback_t callback, const bool all_players) { - Level *level = get_level(minecraft); - const std::vector players = get_players_in_level(level); - bool found_player = false; - for (Player *player : players) { - // Iterate Players - std::string username = misc_get_player_username_utf(player); - if (all_players || username == target_username) { - // Run Callback - callback(minecraft, username, player); - found_player = true; - } - } - if (!all_players && !found_player) { - INFO("Invalid Player: %s", target_username.c_str()); - } -} - -// Get RakNet Objects -static RakNet_RakNetGUID get_rak_net_guid(Player *player) { - return ((ServerPlayer *) player)->guid; -} -static RakNet_SystemAddress get_system_address(RakNet_RakPeer *rak_peer, RakNet_RakNetGUID guid) { - // Get SystemAddress - return rak_peer->GetSystemAddressFromGuid(guid); -} -static RakNet_RakPeer *get_rak_peer(const Minecraft *minecraft) { - return minecraft->rak_net_instance->peer; -} -static char *get_rak_net_guid_ip(RakNet_RakPeer *rak_peer, const RakNet_RakNetGUID &guid) { - RakNet_SystemAddress address = get_system_address(rak_peer, guid); - // Get IP - return address.ToString(false, '|'); -} - -// Get IP From Player -static char *get_player_ip(const Minecraft *minecraft, Player *player) { - RakNet_RakPeer *rak_peer = get_rak_peer(minecraft); - const RakNet_RakNetGUID guid = get_rak_net_guid(player); - // Return - return get_rak_net_guid_ip(rak_peer, guid); -} - -// Ban Player -static bool is_ip_in_blacklist(const char *ip); -static void ban_callback(Minecraft *minecraft, const std::string &username, Player *player) { - // Get IP - char *ip = get_player_ip(minecraft, player); - - // Ban Player - INFO("Banned: %s (%s)", username.c_str(), ip); - // Write To File - std::ofstream blacklist_output(get_blacklist_file(), std::ios_base::app); - if (blacklist_output) { - if (blacklist_output.good()) { - blacklist_output << "# " << username << '\n' << ip << '\n'; - } - if (blacklist_output.is_open()) { - blacklist_output.close(); - } - } - // Reload - is_ip_in_blacklist(nullptr); -} - -// Kill Player -static void kill_callback(__attribute__((unused)) Minecraft *minecraft, __attribute__((unused)) const std::string &username, Player *player) { - player->hurt(nullptr, INT32_MAX); - INFO("Killed: %s", username.c_str()); -} - -// List Player -static void list_callback(Minecraft *minecraft, const std::string &username, Player *player) { - INFO(" - %s (%s)", username.c_str(), get_player_ip(minecraft, player)); -} - -// Get ServerSideNetworkHandler From Minecraft -static ServerSideNetworkHandler *get_server_side_network_handler(const Minecraft *minecraft) { - return (ServerSideNetworkHandler *) minecraft->network_handler; -} - -// Read STDIN Thread -static pthread_t read_stdin_thread_obj; -static volatile bool stdin_line_ready = false; -static std::string stdin_line; -static void *read_stdin_thread(__attribute__((unused)) void *data) { - // Loop - char *line = nullptr; - size_t len = 0; - while (getline(&line, &len, stdin) != -1) { - stdin_line = line; - stdin_line_ready = true; - // Wait For Line To Be Read - while (stdin_line_ready) {} - } - free(line); - return nullptr; -} - // Handle Server Stop static void handle_server_stop(Minecraft *minecraft) { if (compat_check_exit_requested()) { INFO("Stopping Server"); // Save And Exit - Level *level = get_level(minecraft); + Level *level = minecraft->level; if (level != nullptr) { level->saveLevelData(); } minecraft->leaveGame(false); // Kill Reader Thread - pthread_cancel(read_stdin_thread_obj); - pthread_join(read_stdin_thread_obj, nullptr); - stdin_line_ready = false; + stop_reading_commands(); // Stop Game SDL_Event event; event.type = SDL_QUIT; @@ -243,144 +124,6 @@ static void handle_server_stop(Minecraft *minecraft) { } } -// Handle Commands -bool ServerCommand::has_args() const { - return name[name.length() - 1] == ' '; -} -std::string ServerCommand::get_lhs_help() const { - std::string out; - out.append(4, ' '); - out += name; - if (has_args()) { - out += ""; - } - return out; -} -std::string ServerCommand::get_full_help(const int max_lhs_length) const { - std::string out = get_lhs_help(); - out.append(max_lhs_length - out.length(), ' '); - out += " - "; - out += comment; - return out; -} -std::vector *server_get_commands(Minecraft *minecraft, ServerSideNetworkHandler *server_side_network_handler) { - std::vector *commands = new std::vector; - // Ban Player - if (!is_whitelist()) { - commands->push_back({ - .name = "ban ", - .comment = "IP-Ban All Players With Specified Username", - .callback = [minecraft](const std::string &cmd) { - find_players(minecraft, cmd, ban_callback, false); - } - }); - } - // Reload White/Blacklist - commands->push_back({ - .name = "reload", - .comment = std::string("Reload The ") + (is_whitelist() ? "Whitelist" : "Blacklist"), - .callback = [](__attribute__((unused)) const std::string &cmd) { - INFO("Reloading %s", is_whitelist() ? "Whitelist" : "Blacklist"); - is_ip_in_blacklist(nullptr); - } - }); - // Kill Player - commands->push_back({ - .name = "kill ", - .comment = "Kill All Players With Specified Username", - .callback = [minecraft](const std::string &cmd) { - find_players(minecraft, cmd, kill_callback, false); - } - }); - // Post Message - commands->push_back({ - .name = "say ", - .comment = "Print Specified Message To Chat", - .callback = [server_side_network_handler](const std::string &cmd) { - // Format Message - const std::string message = "[Server] " + cmd; - std::string cpp_string = to_cp437(message); - // Post Message To Chat - server_side_network_handler->displayGameMessage(cpp_string); - } - }); - // List Players - commands->push_back({ - .name = "list", - .comment = "List All Players", - .callback = [minecraft](__attribute__((unused)) const std::string &cmd) { - INFO("All Players:"); - find_players(minecraft, "", list_callback, true); - } - }); - // Ticks-Per-Second - commands->push_back({ - .name = "tps", - .comment = "Print TPS", - .callback = [](__attribute__((unused)) const std::string &cmd) { - INFO("TPS: %f", tps); - } - }); - // Stop - commands->push_back({ - .name = "stop", - .comment = "Stop Server", - .callback = [](__attribute__((unused)) const std::string &cmd) { - compat_request_exit(); - } - }); - // Help Page - commands->push_back({ - .name = "help", - .comment = "Print This Message", - .callback = [commands](__attribute__((unused)) const std::string &cmd) { - INFO("All Commands:"); - std::string::size_type max_lhs_length = 0; - for (const ServerCommand &command : *commands) { - const std::string::size_type lhs_length = command.get_lhs_help().length(); - if (lhs_length > max_lhs_length) { - max_lhs_length = lhs_length; - } - } - for (const ServerCommand &command : *commands) { - INFO("%s", command.get_full_help(max_lhs_length).c_str()); - } - } - }); - // Return - return commands; -} -static void handle_commands(Minecraft *minecraft) { - // Check If Level Is Generated - if (minecraft->isLevelGenerated() && stdin_line_ready) { - // Read Line - std::string data = std::move(stdin_line); - data.pop_back(); // Remove Newline - stdin_line_ready = false; - // Command Ready; Run It - ServerSideNetworkHandler *server_side_network_handler = get_server_side_network_handler(minecraft); - if (server_side_network_handler != nullptr) { - // Generate Command List - const std::vector *commands = server_get_commands(minecraft, server_side_network_handler); - // Run - bool success = false; - for (const ServerCommand &command : *commands) { - const bool valid = command.has_args() ? data.rfind(command.name, 0) == 0 : data == command.name; - if (valid) { - command.callback(data.substr(command.name.length())); - success = true; - break; - } - } - if (!success) { - INFO("Invalid Command: %s", data.c_str()); - } - // Free - delete commands; - } - } -} - // Runs Every Tick static bool loaded = false; static void Minecraft_update_injection(Minecraft *minecraft) { @@ -406,7 +149,7 @@ static void Minecraft_update_injection(Minecraft *minecraft) { } // Check Blacklist/Whitelist -static bool is_ip_in_blacklist(const char *ip) { +bool is_ip_in_blacklist(const char *ip) { static std::vector ips; if (ip == nullptr) { // Reload @@ -449,6 +192,13 @@ static bool RakNet_RakPeer_IsBanned_injection(__attribute__((unused)) RakNet_Rak } } +// Get IP Address +char *get_rak_net_guid_ip(RakNet_RakPeer *rak_peer, const RakNet_RakNetGUID &guid) { + RakNet_SystemAddress address = rak_peer->GetSystemAddressFromGuid(guid); + // Get IP + return address.ToString(false, '|'); +} + // Log IPs static Player *ServerSideNetworkHandler_onReady_ClientGeneration_ServerSideNetworkHandler_popPendingPlayer_injection(ServerSideNetworkHandler *server_side_network_handler, const RakNet_RakNetGUID &guid) { // Call Original Method @@ -459,7 +209,7 @@ static Player *ServerSideNetworkHandler_onReady_ClientGeneration_ServerSideNetwo // Get Data const std::string *username = &player->username; const Minecraft *minecraft = server_side_network_handler->minecraft; - RakNet_RakPeer *rak_peer = get_rak_peer(minecraft); + RakNet_RakPeer *rak_peer = minecraft->rak_net_instance->peer; char *ip = get_rak_net_guid_ip(rak_peer, guid); // Log @@ -571,7 +321,7 @@ static void server_init() { overwrite_call((void *) 0x75e54, ServerSideNetworkHandler_popPendingPlayer, ServerSideNetworkHandler_onReady_ClientGeneration_ServerSideNetworkHandler_popPendingPlayer_injection); // Start Reading STDIN - pthread_create(&read_stdin_thread_obj, nullptr, read_stdin_thread, nullptr); + start_reading_commands(); } // Init Server diff --git a/scripts/screenshot.sh b/scripts/screenshot.sh index 73343b10..e4dd4458 100755 --- a/scripts/screenshot.sh +++ b/scripts/screenshot.sh @@ -2,6 +2,9 @@ set -e +# Change Directory +cd "$(dirname "$0")/../" + # Setup export XDG_SESSION_TYPE=x11 unset MCPI_GUI_SCALE diff --git a/scripts/test.sh b/scripts/test.sh index d822aceb..f1846895 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -17,26 +17,23 @@ if [ ! -f "${APPIMAGE}" ]; then fi # Make Test Directory -TEST_WORKING_DIR="$(pwd)/.testing-tmp" -rm -rf "${TEST_WORKING_DIR}" -mkdir "${TEST_WORKING_DIR}" -ROOT="$(pwd)" -cd "${TEST_WORKING_DIR}" +export MCPI_PROFILE_DIRECTORY="$(pwd)/.testing-tmp" +rm -rf "${MCPI_PROFILE_DIRECTORY}" +mkdir "${MCPI_PROFILE_DIRECTORY}" # Prepare AppImage For Docker -cp "${APPIMAGE}" tmp.AppImage -"${ROOT}/scripts/fix-appimage-for-docker.sh" tmp.AppImage -chmod +x tmp.AppImage +EXE="$(mktemp)" +cp "${APPIMAGE}" "${EXE}" +./scripts/fix-appimage-for-docker.sh "${EXE}" # Run if [ "${MODE}" = "server" ]; then # Server Test - ./tmp.AppImage --appimage-extract-and-run --server --only-generate + "${EXE}" --appimage-extract-and-run --server --only-generate else # Client Test - export MCPI_PROFILE_DIRECTORY="${TEST_WORKING_DIR}" - ./tmp.AppImage --appimage-extract-and-run --default --no-cache --benchmark --force-headless + "${EXE}" --appimage-extract-and-run --default --no-cache --benchmark --force-headless fi # Clean Up -rm -rf "${TEST_WORKING_DIR}" +rm -f "${EXE}"