Split Up server.cpp

This commit is contained in:
TheBrokenRail 2025-03-19 22:06:06 -04:00
parent 6f027ba397
commit 14331ce871
9 changed files with 294 additions and 285 deletions

View File

@ -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

View File

@ -2,7 +2,7 @@
#include <symbols/minecraft.h>
#include "server_properties.h"
#include "properties.h"
struct ServerCommand {
const std::string name;

View File

@ -0,0 +1,246 @@
#include <fstream>
#include <libreborn/log.h>
#include <libreborn/util/string.h>
#include <symbols/minecraft.h>
#include <mods/misc/misc.h>
#include <mods/compat/compat.h>
#include <mods/fps/fps.h>
#include <mods/server/server.h>
#include "internal.h"
// Get Vector Of Players In Level
static std::vector<Player *> 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<Player *> 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 += "<Arguments>";
}
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<ServerCommand> *server_get_commands(Minecraft *minecraft, ServerSideNetworkHandler *server_side_network_handler) {
std::vector<ServerCommand> *commands = new std::vector<ServerCommand>;
// 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<ServerCommand> *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;
}
}
}

View File

@ -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();

View File

@ -1,12 +1,14 @@
#include <fstream>
#include <mods/server/server_properties.h>
#include <mods/server/properties.h>
// Get All Possible Properties
std::vector<const ServerProperty *> &ServerProperty::get_all() {
static std::vector<const ServerProperty *> 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";
}

View File

@ -23,7 +23,8 @@
#include <mods/compat/compat.h>
#include <mods/misc/misc.h>
#include <mods/game-mode/game-mode.h>
#include <mods/fps/fps.h>
#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<Player *> 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<Player *> 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 += "<Arguments>";
}
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<ServerCommand> *server_get_commands(Minecraft *minecraft, ServerSideNetworkHandler *server_side_network_handler) {
std::vector<ServerCommand> *commands = new std::vector<ServerCommand>;
// 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<ServerCommand> *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<std::string> 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

View File

@ -2,6 +2,9 @@
set -e
# Change Directory
cd "$(dirname "$0")/../"
# Setup
export XDG_SESSION_TYPE=x11
unset MCPI_GUI_SCALE

View File

@ -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}"