Add Servers Tab To Launcher

This commit is contained in:
TheBrokenRail 2024-11-26 06:47:48 -05:00
parent bddc299664
commit c531e7ba7d
20 changed files with 333 additions and 183 deletions

@ -1 +1 @@
Subproject commit 043d926f32a3315d92bce1d9bbb0ccdcf99c11a2 Subproject commit c1b4b02770dee1f5dfca2ca21a627baf10942cde

View File

@ -107,6 +107,7 @@
* `overwrite_calls` Now Scans VTables * `overwrite_calls` Now Scans VTables
* Unify Server/Client Builds * Unify Server/Client Builds
* Controller Support Removed * Controller Support Removed
* Brand New Launcher UI!
**2.5.3** **2.5.3**
* Add `Replace Block Highlight With Outline` Feature Flag (Enabled By Default) * Add `Replace Block Highlight With Outline` Feature Flag (Enabled By Default)

View File

@ -1,16 +0,0 @@
# Multiplayer
MCPI-Reborn supports two ways to play multiplayer.
## Local Network (LAN)
This is also supported by vanilla MCPI. Just load a world in MCPI and other devices on the network can join.
## External Servers
Unlike vanilla MCPI, MCPI-Reborn allows you to natively join a server outside of the local network. Just modify `~/.minecraft-pi/servers.txt` and it should show up in MCPI's server list.
### Example `~/.minecraft-pi/servers.txt`
```
# Default Port Is 19132
example.com
# Custom Port
example.com:19133
```

View File

@ -3,7 +3,6 @@
* [View Dedicated Server](DEDICATED_SERVER.md) * [View Dedicated Server](DEDICATED_SERVER.md)
* [View Credits](CREDITS.md) * [View Credits](CREDITS.md)
* [View Terminology](TERMINOLOGY.md) * [View Terminology](TERMINOLOGY.md)
* [View Multiplayer](MULTIPLAYER.md)
* [View In-Game Controls](CONTROLS.md) * [View In-Game Controls](CONTROLS.md)
* [View Custom Skins](CUSTOM_SKINS.md) * [View Custom Skins](CUSTOM_SKINS.md)
* [View Changelog](CHANGELOG.md) * [View Changelog](CHANGELOG.md)

View File

@ -3,10 +3,10 @@
#include <string> #include <string>
#include "../options/parser.h" #include "../options/parser.h"
#include "cache.h"
#include "../ui/frame.h" #include "../ui/frame.h"
#include <libreborn/flags.h> #include <libreborn/flags.h>
#include <libreborn/servers.h>
// Default Configuration // Default Configuration
#define DEFAULT_USERNAME "StevePi" #define DEFAULT_USERNAME "StevePi"
@ -29,15 +29,25 @@ struct ConfigurationUI final : Frame {
explicit ConfigurationUI(State &state_, bool &save_settings_); explicit ConfigurationUI(State &state_, bool &save_settings_);
int render() override; int render() override;
private: private:
void update_render_distance(); // Bottom Row
int draw_bottom(); int get_render_distance_index() const;
void draw_main(); int draw_bottom(bool hide_reset_revert) const;
// General
void draw_main() const;
// Advanced
void draw_advanced() const; void draw_advanced() const;
static void draw_category(FlagNode &category); static void draw_category(FlagNode &category);
// Server List
bool are_servers_unsaved() const;
void draw_servers();
void draw_server_list();
// State
const State original_state; const State original_state;
State &state; State &state;
bool &save_settings; bool &save_settings;
int render_distance_index; // Server List
ServerList last_saved_servers;
ServerList servers;
}; };
// Handle Non-Launch Commands // Handle Non-Launch Commands

View File

@ -1,38 +1,38 @@
#include <vector> #include <vector>
#include <limits>
#include <libreborn/util.h>
#include "configuration.h" #include "configuration.h"
#include <imgui_stdlib.h> #include <imgui_stdlib.h>
// Render Distances // Render Distances
static std::vector render_distances = { static constexpr std::array render_distances = {
"Far", "Far",
"Normal", "Normal",
"Short", "Short",
"Tiny" "Tiny"
}; };
// Tooltips/Text
static constexpr std::string revert_text = "Revert";
static constexpr std::string revert_tooltip_text = "Last Saved";
static constexpr std::string make_tooltip(const std::string &text, const std::string &type) {
return "Use " + text + ' ' + type;
}
// Construct // Construct
static constexpr int size = 400; static constexpr int size = 400;
ConfigurationUI::ConfigurationUI(State &state_, bool &save_settings_): ConfigurationUI::ConfigurationUI(State &state_, bool &save_settings_):
Frame("Launcher", size, size), Frame("Launcher", size, size),
original_state(state_), original_state(state_),
state(state_), state(state_),
save_settings(save_settings_) { save_settings(save_settings_) {}
update_render_distance();
}
void ConfigurationUI::update_render_distance() {
render_distance_index = 0;
for (std::vector<std::string>::size_type i = 0; i < render_distances.size(); i++) {
if (std::string(render_distances[i]) == state.render_distance) {
render_distance_index = int(i);
break;
}
}
}
// Render // Render
int ConfigurationUI::render() { int ConfigurationUI::render() {
bool on_servers_tab = false;
if (ImGui::BeginChild("Main", ImVec2(0, -ImGui::GetFrameHeightWithSpacing() /* Leave Room For Bottom Row */), ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { if (ImGui::BeginChild("Main", ImVec2(0, -ImGui::GetFrameHeightWithSpacing() /* Leave Room For Bottom Row */), ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
// Tabs // Tabs
if (ImGui::BeginTabBar("TabBar")) { if (ImGui::BeginTabBar("TabBar")) {
@ -46,53 +46,82 @@ int ConfigurationUI::render() {
draw_advanced(); draw_advanced();
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
// Servers Tab
if (ImGui::BeginTabItem("Servers", nullptr, are_servers_unsaved() ? ImGuiTabItemFlags_UnsavedDocument : ImGuiTabItemFlags_None)) {
draw_servers();
ImGui::EndTabItem();
on_servers_tab = true;
}
ImGui::EndTabBar(); ImGui::EndTabBar();
} }
} }
ImGui::EndChild(); ImGui::EndChild();
// Bottom Row // Bottom Row
return draw_bottom(); return draw_bottom(on_servers_tab);
} }
// Bottom Row // Bottom Row
int ConfigurationUI::draw_bottom() { int ConfigurationUI::draw_bottom(const bool hide_reset_revert) const {
// Reset All Settings // Reset Settings
if (!hide_reset_revert) {
const State default_state; const State default_state;
std::vector<std::tuple<const char *, const char *, const State *>> reset_options = { constexpr const char *tooltip_type = "Settings";
{"Revert", "Last Saved", &original_state}, std::vector<std::tuple<std::string, std::string, const State *>> reset_options = {
{"Reset", "Default", &default_state}, {revert_text, make_tooltip(revert_tooltip_text, tooltip_type), &original_state},
{"Reset", make_tooltip("Default", tooltip_type), &default_state},
}; };
for (const std::tuple<const char *, const char *, const State *> &option : reset_options) { for (const std::tuple<std::string, std::string, const State *> &option : reset_options) {
const State &new_state = *std::get<2>(option); const State &new_state = *std::get<2>(option);
ImGui::BeginDisabled(state == new_state); ImGui::BeginDisabled(state == new_state);
if (ImGui::Button(std::get<0>(option))) { if (ImGui::Button(std::get<0>(option).c_str())) {
state = new_state; state = new_state;
update_render_distance();
} }
ImGui::SetItemTooltip("Use %s Settings", std::get<1>(option)); ImGui::SetItemTooltip("%s", std::get<1>(option).c_str());
ImGui::EndDisabled(); ImGui::EndDisabled();
ImGui::SameLine(); ImGui::SameLine();
} }
}
// Right-Align Buttons // Right-Align Buttons
int ret = 0; int ret = 0;
draw_right_aligned_buttons({"Quit", "Launch"}, [&ret](const int id, const bool was_clicked) { bool unsaved_servers = are_servers_unsaved();
draw_right_aligned_buttons({quit_text, "Launch"}, [&ret, unsaved_servers](const int id, const bool was_clicked) {
if (id == 0) { if (id == 0) {
// Quit // Quit
if (was_clicked) { if (was_clicked) {
ret = -1; ret = -1;
} }
ImGui::SetItemTooltip("Changes Will Not Be Saved!"); ImGui::SetItemTooltip("Changes Will Not Be Saved!");
} else if (was_clicked) { // Disable Launch if Server List Is Unsaved
if (unsaved_servers) {
ImGui::BeginDisabled();
}
} else if (id == 1) {
// Launch // Launch
if (unsaved_servers) {
ImGui::SetItemTooltip("Server List Is Unsaved");
ImGui::EndDisabled();
}
if (was_clicked) {
ret = 1; ret = 1;
} }
}
}); });
// Return // Return
return ret; return ret;
} }
// Main Tab // Main Tab
void ConfigurationUI::draw_main() { int ConfigurationUI::get_render_distance_index() const {
int render_distance_index = 0;
for (std::vector<std::string>::size_type i = 0; i < render_distances.size(); i++) {
if (std::string(render_distances[i]) == state.render_distance) {
render_distance_index = int(i);
break;
}
}
return render_distance_index;
}
void ConfigurationUI::draw_main() const {
const ImGuiStyle &style = ImGui::GetStyle(); const ImGuiStyle &style = ImGui::GetStyle();
const char *labels[] = {"Username", "Render Distance"}; const char *labels[] = {"Username", "Render Distance"};
// Calculate Label Size // Calculate Label Size
@ -103,21 +132,23 @@ void ConfigurationUI::draw_main() {
ImGui::PushItemWidth(-label_size); ImGui::PushItemWidth(-label_size);
// Options // Options
ImGui::InputText(labels[0], &state.username); ImGui::InputText(labels[0], &state.username);
ImGui::Combo(labels[1], &render_distance_index, render_distances.data(), int(render_distances.size())); int render_distance_index = get_render_distance_index();
if (ImGui::Combo(labels[1], &render_distance_index, render_distances.data(), int(render_distances.size()))) {
state.render_distance = render_distances[render_distance_index]; state.render_distance = render_distances[render_distance_index];
}
ImGui::PopItemWidth(); ImGui::PopItemWidth();
ImGui::Checkbox("Save Settings On Launch", &save_settings); ImGui::Checkbox("Save Settings On Launch", &save_settings);
} }
// Advanced Tab // Advanced Tab
static std::string get_label(const FlagNode &node) { static std::string get_label_for_flag_node(const FlagNode &node) {
return node.name + "##" + std::to_string(node.id); return node.name + "##FlagNode" + std::to_string(node.id);
} }
void ConfigurationUI::draw_advanced() const { void ConfigurationUI::draw_advanced() const {
if (ImGui::BeginChild("Features", ImVec2(0, 0), ImGuiChildFlags_Borders, ImGuiWindowFlags_HorizontalScrollbar)) { if (ImGui::BeginChild("Features", ImVec2(0, 0), ImGuiChildFlags_Borders, ImGuiWindowFlags_HorizontalScrollbar)) {
// Categories // Categories
for (FlagNode &category : state.flags.root.children) { for (FlagNode &category : state.flags.root.children) {
std::string label = get_label(category); const std::string label = get_label_for_flag_node(category);
if (ImGui::CollapsingHeader(label.c_str())) { if (ImGui::CollapsingHeader(label.c_str())) {
draw_category(category); draw_category(category);
} }
@ -125,18 +156,107 @@ void ConfigurationUI::draw_advanced() const {
} }
ImGui::EndChild(); ImGui::EndChild();
} }
// Feature Categories
void ConfigurationUI::draw_category(FlagNode &category) { void ConfigurationUI::draw_category(FlagNode &category) {
for (FlagNode &child : category.children) { for (FlagNode &child : category.children) {
std::string label = get_label(child); const std::string label = get_label_for_flag_node(child);
if (!child.children.empty()) { if (!child.children.empty()) {
// Sub-Category
if (ImGui::TreeNode(label.c_str())) { if (ImGui::TreeNode(label.c_str())) {
draw_category(child); draw_category(child);
ImGui::TreePop(); ImGui::TreePop();
} }
} else { } else {
// Flag
ImGui::Checkbox(label.c_str(), &child.value); ImGui::Checkbox(label.c_str(), &child.value);
} }
} }
} }
// Servers
bool ConfigurationUI::are_servers_unsaved() const {
return servers.to_string() != last_saved_servers.to_string();
}
void ConfigurationUI::draw_servers() {
// Add/Clear
bool scroll_to_bottom = false;
if (ImGui::Button("Add")) {
servers.entries.emplace_back("", DEFAULT_MULTIPLAYER_PORT);
scroll_to_bottom = true;
}
ImGui::SameLine();
ImGui::BeginDisabled(servers.entries.empty());
if (ImGui::Button("Clear")) {
servers.entries.clear();
}
ImGui::EndDisabled();
ImGui::SameLine();
// Revert/Save
int clicked_button = -1;
ImGui::BeginDisabled(!are_servers_unsaved());
draw_right_aligned_buttons({revert_text.c_str(), "Save"}, [&clicked_button](const int id, const bool was_clicked) {
if (id == 0) {
ImGui::SetItemTooltip("%s", make_tooltip(revert_tooltip_text, "Server List").c_str());
}
if (was_clicked) {
clicked_button = id;
}
});
ImGui::EndDisabled();
if (clicked_button == 1) {
// Save
servers.save();
last_saved_servers = servers;
} else if (clicked_button == 0) {
// Revert To Last Saved Server List
servers = last_saved_servers;
}
// List
if (ImGui::BeginChild("ServerList", ImVec2(0, 0), ImGuiChildFlags_Borders)) {
draw_server_list();
if (scroll_to_bottom) {
ImGui::SetScrollHereY(1.0f);
}
}
ImGui::EndChild();
}
void ConfigurationUI::draw_server_list() {
for (std::vector<ServerList::Entry>::size_type i = 0; i < servers.entries.size(); ++i) {
ServerList::Entry &entry = servers.entries[i];
// Calculate Item Widths
const ImGuiStyle &style = ImGui::GetStyle();
const std::string port_width_text = std::to_string(int(std::numeric_limits<ServerList::port_t>::max()) * 2); // Should Comfortably Fit All Port Numbers
const std::string delete_text = "Delete";
const float port_width = get_frame_width(port_width_text.c_str());
const float width_needed = (style.ItemSpacing.x * 2.0f) + port_width + get_frame_width(delete_text.c_str());
// Labels
const std::string base_label = "##ServerEntry" + std::to_string(i);
// Hints
const char *address_hint = "Address";
const char *port_hint = "Port";
// Address
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - width_needed);
ImGui::InputTextWithHint((base_label + address_hint).c_str(), address_hint, &entry.first, ImGuiInputTextFlags_CharsNoBlank);
ImGui::PopItemWidth();
// Port
ServerList::port_t &port = entry.second;
std::string port_str = port > 0 ? std::to_string(port) : "";
ImGui::SameLine();
ImGui::PushItemWidth(port_width);
if (ImGui::InputTextWithHint((base_label + port_hint).c_str(), port_hint, &port_str, ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_NoHorizontalScroll)) {
port = ServerList::parse_port(port_str);
}
ImGui::PopItemWidth();
// Delete
ImGui::SameLine();
if (ImGui::Button((delete_text + base_label).c_str())) {
servers.entries.erase(servers.entries.begin() + int(i));
i--;
}
}
}

View File

@ -11,7 +11,7 @@
struct CrashReport final : Frame { struct CrashReport final : Frame {
explicit CrashReport(const char *filename): Frame("Crash Report", 640, 480) { explicit CrashReport(const char *filename): Frame("Crash Report", 640, 480) {
// Open File // Open File
std::ifstream stream(filename, std::ios_base::binary | std::ios_base::ate); std::ifstream stream(filename, std::ios::binary | std::ios::ate);
if (stream) { if (stream) {
// Read File // Read File
const std::streamoff size = stream.tellg(); const std::streamoff size = stream.tellg();
@ -53,7 +53,7 @@ struct CrashReport final : Frame {
// Right-Aligned // Right-Aligned
int ret = 0; int ret = 0;
const std::string &log_ref = log; const std::string &log_ref = log;
draw_right_aligned_buttons({"Copy", "Quit"}, [&ret, &log_ref](const int id, const bool was_clicked) { draw_right_aligned_buttons({"Copy", quit_text}, [&ret, &log_ref](const int id, const bool was_clicked) {
if (was_clicked) { if (was_clicked) {
if (id == 0) { if (id == 0) {
// Copy Log // Copy Log

View File

@ -69,7 +69,7 @@ int Frame::run() {
int width, height; int width, height;
glfwGetFramebufferSize(window, &width, &height); glfwGetFramebufferSize(window, &width, &height);
ImGui::SetNextWindowSize({float(width), float(height)}); ImGui::SetNextWindowSize({float(width), float(height)});
if (ImGui::Begin("###Frame", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoScrollWithMouse)) { if (ImGui::Begin("##Frame", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoScrollWithMouse)) {
ret = render(); ret = render();
} }
ImGui::End(); ImGui::End();
@ -103,6 +103,10 @@ void Frame::setup_style(const float scale) {
} }
// Right-Aligned Buttons // Right-Aligned Buttons
float Frame::get_frame_width(const char *str) {
const ImGuiStyle &style = ImGui::GetStyle();
return ImGui::CalcTextSize(str).x + style.FramePadding.x * 2.0f;
}
void Frame::draw_right_aligned_buttons(const std::vector<const char *> &buttons, const std::function<void(int, bool)> &callback) { void Frame::draw_right_aligned_buttons(const std::vector<const char *> &buttons, const std::function<void(int, bool)> &callback) {
// Calculate Position // Calculate Position
const ImGuiStyle &style = ImGui::GetStyle(); const ImGuiStyle &style = ImGui::GetStyle();
@ -111,7 +115,7 @@ void Frame::draw_right_aligned_buttons(const std::vector<const char *> &buttons,
if (width_needed > 0) { if (width_needed > 0) {
width_needed += style.ItemSpacing.x; width_needed += style.ItemSpacing.x;
} }
width_needed += ImGui::CalcTextSize(text).x + style.FramePadding.x * 2.0f; width_needed += get_frame_width(text);
} }
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - width_needed); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - width_needed);
// Draw // Draw

View File

@ -19,7 +19,9 @@ struct Frame {
protected: protected:
// API For Sub-Classes // API For Sub-Classes
ImFont *monospace = nullptr; ImFont *monospace = nullptr;
static float get_frame_width(const char *str);
static void draw_right_aligned_buttons(const std::vector<const char *> &buttons, const std::function<void(int, bool)> &callback); static void draw_right_aligned_buttons(const std::vector<const char *> &buttons, const std::function<void(int, bool)> &callback);
static constexpr const char *quit_text = "Quit";
private: private:
// Properties // Properties
GLFWwindow *window = nullptr; GLFWwindow *window = nullptr;

View File

@ -16,6 +16,7 @@ add_library(reborn-util SHARED
src/util/flags/node.cpp src/util/flags/node.cpp
src/util/flags/flags.cpp src/util/flags/flags.cpp
src/util/flags/available-feature-flags # Show In IDE src/util/flags/available-feature-flags # Show In IDE
src/util/servers.cpp
) )
embed_resource(reborn-util src/util/flags/available-feature-flags) embed_resource(reborn-util src/util/flags/available-feature-flags)
target_link_libraries(reborn-util PRIVATE utf8cpp) target_link_libraries(reborn-util PRIVATE utf8cpp)

View File

@ -0,0 +1,20 @@
#pragma once
#include <utility>
#include <string>
#include <vector>
// Parse servers.txt
struct ServerList {
// Type
typedef unsigned short port_t;
typedef std::pair<std::string, port_t> Entry;
// Load
static port_t parse_port(const std::string &s);
ServerList();
// Save
std::string to_string() const;
void save() const;
// Entries
std::vector<Entry> entries{};
};

View File

@ -61,3 +61,7 @@ std::string home_get();
// Format Time // Format Time
std::string format_time(const char *fmt); std::string format_time(const char *fmt);
std::string format_time(const char *fmt, int time); std::string format_time(const char *fmt, int time);
// Default MCPI Port
// This Macro DOES NOT Control MCPI
#define DEFAULT_MULTIPLAYER_PORT 19132

View File

@ -0,0 +1,82 @@
#include <fstream>
#include <sstream>
#include <limits>
#include <libreborn/servers.h>
#include <libreborn/util.h>
// File
static std::string get_server_list_path() {
return home_get() + "/servers.txt";
}
// Seperator
#define PORT_SEPERATOR_CHAR ':'
// Load
ServerList::port_t ServerList::parse_port(const std::string &s) {
unsigned long port = std::strtoul(s.c_str(), nullptr, 10);
constexpr port_t max = std::numeric_limits<port_t>::max();
if (port > max) {
port = max;
}
return static_cast<port_t>(port);
}
ServerList::ServerList() {
// Open File
std::ifstream server_list_file(get_server_list_path());
if (!server_list_file) {
// File Does Not Exist
return;
}
// Iterate Lines
std::string line;
while (std::getline(server_list_file, line)) {
// Check Line
if (!line.empty()) {
// Skip Comments
if (line[0] == '#') {
continue;
}
// Parse
std::string address;
std::string port_str;
size_t separator_pos = line.find_last_of(PORT_SEPERATOR_CHAR);
if (separator_pos == std::string::npos) {
port_str = std::to_string(DEFAULT_MULTIPLAYER_PORT);
address = line;
} else {
address = line.substr(0, separator_pos);
port_str = line.substr(separator_pos + 1);
}
// Done
entries.emplace_back(address, parse_port(port_str));
}
}
// Close
server_list_file.close();
}
// Save
std::string ServerList::to_string() const {
std::stringstream out;
out << "# External Servers File\n";
for (const Entry &entry : entries) {
out << entry.first << PORT_SEPERATOR_CHAR << std::to_string(entry.second) << '\n';
}
return out.str();
}
void ServerList::save() const {
// Open File
std::ofstream file(get_server_list_path());
if (!file) {
// Failure
WARN("Unable To Save Server List");
}
// Write
file << to_string();
// Close
file.close();
}

View File

@ -1,16 +1,15 @@
#pragma once #pragma once
#include <string> #include <string>
#include <istream>
#include <map> #include <map>
#include <vector> #include <vector>
struct ServerProperty { struct ServerProperty {
static std::vector<const ServerProperty *> &get_all(); static std::vector<const ServerProperty *> &get_all();
const char *const key; const std::string key;
const char *const def; const std::string def;
const char *const comment; const std::string comment;
ServerProperty(const char *const key_, const char *const def_, const char *const comment_): ServerProperty(const std::string &key_, const std::string &def_, const std::string &comment_):
key(key_), key(key_),
def(def_), def(def_),
comment(comment_) { comment(comment_) {

View File

@ -4,6 +4,7 @@
#include <symbols/minecraft.h> #include <symbols/minecraft.h>
#include <libreborn/patch.h> #include <libreborn/patch.h>
#include <libreborn/util.h>
#include <mods/text-input-box/TextInputScreen.h> #include <mods/text-input-box/TextInputScreen.h>
#include <mods/touch/touch.h> #include <mods/touch/touch.h>
@ -209,7 +210,7 @@ static void create_world(Minecraft *minecraft, std::string name, const bool is_c
minecraft->selectLevel(folder, name, settings); minecraft->selectLevel(folder, name, settings);
// Multiplayer // Multiplayer
minecraft->hostMultiplayer(19132); minecraft->hostMultiplayer(DEFAULT_MULTIPLAYER_PORT);
// Open ProgressScreen // Open ProgressScreen
ProgressScreen *screen = ProgressScreen::allocate(); ProgressScreen *screen = ProgressScreen::allocate();

View File

@ -204,7 +204,7 @@ static void Player_stopUsingItem_injection(Player_stopUsingItem_t original, Play
// Read Asset File // Read Asset File
static AppPlatform_readAssetFile_return_value AppPlatform_readAssetFile_injection(__attribute__((unused)) AppPlatform_readAssetFile_t original, __attribute__((unused)) AppPlatform *app_platform, const std::string &path) { static AppPlatform_readAssetFile_return_value AppPlatform_readAssetFile_injection(__attribute__((unused)) AppPlatform_readAssetFile_t original, __attribute__((unused)) AppPlatform *app_platform, const std::string &path) {
// Open File // Open File
std::ifstream stream("data/" + path, std::ios_base::binary | std::ios_base::ate); std::ifstream stream("data/" + path, std::ios::binary | std::ios::ate);
if (!stream) { if (!stream) {
// Does Not Exist // Does Not Exist
AppPlatform_readAssetFile_return_value ret; AppPlatform_readAssetFile_return_value ret;

View File

@ -1,4 +1,3 @@
#include <fstream>
#include <functional> #include <functional>
#include <string> #include <string>
#include <vector> #include <vector>
@ -6,102 +5,25 @@
#include <symbols/minecraft.h> #include <symbols/minecraft.h>
#include <libreborn/patch.h> #include <libreborn/patch.h>
#include <libreborn/util.h> #include <libreborn/servers.h>
#include <mods/init/init.h> #include <mods/init/init.h>
#include <mods/feature/feature.h> #include <mods/feature/feature.h>
// Load Server List // Iterate Server List
struct server_list_entry { static void iterate_servers(const std::function<void(const char *address, ServerList::port_t port)> &callback) {
std::string address;
int port;
};
static std::vector<server_list_entry> server_list_entries;
static bool server_list_loaded = false;
static void load_servers() {
// Prepare
server_list_entries.clear();
// Open Servers File
std::string file(home_get());
file.append("/servers.txt");
// Create Stream
std::ifstream server_list_file(file);
if (!server_list_file.good()) {
// Write Defaults
std::ofstream server_list_file_output(file);
server_list_file_output << "# External Servers File\n";
server_list_file_output << "# Example: thebrokenrail.com\n";
server_list_file_output.close();
// Re-Open Stream
server_list_file = std::ifstream(file);
}
// Check Servers File
if (!server_list_file.is_open()) {
ERR("Unable To Open %s", file.c_str());
}
// Iterate Lines
{
std::string line;
while (std::getline(server_list_file, line)) {
// Check Line
if (!line.empty()) {
if (line[0] == '#') {
continue;
}
// Parse
std::string address;
std::string port_str;
size_t separator_pos = line.find_last_of(':');
if (separator_pos == std::string::npos) {
port_str = "19132";
address = line;
} else {
address = line.substr(0, separator_pos);
port_str = line.substr(separator_pos + 1);
}
// Check Line
if (address.empty() || port_str.empty() || port_str.find_first_not_of("0123456789") != std::string::npos) {
// Invalid Line
WARN("Invalid Server: %s", line.c_str());
continue;
}
// Parse Port
int port = std::stoi(port_str);
// Done
server_list_entry entry = {
.address = address,
.port = port
};
server_list_entries.push_back(entry);
}
}
}
// Close
server_list_file.close();
}
// Iterare Server List
static void iterate_servers(const std::function<void(const char *address, int port)> &callback) {
// Load // Load
if (!server_list_loaded) { static ServerList server_list;
load_servers();
server_list_loaded = true;
}
// Loop // Loop
for (const server_list_entry &entry : server_list_entries) { for (const ServerList::Entry &entry : server_list.entries) {
callback(entry.address.c_str(), entry.port); if (!entry.first.empty() && entry.second > 0) {
callback(entry.first.c_str(), entry.second);
}
} }
} }
// Ping External Servers // Ping External Servers
static void RakNetInstance_pingForHosts_injection(RakNetInstance_pingForHosts_t original, RakNetInstance *rak_net_instance, int32_t base_port) { static void RakNetInstance_pingForHosts_injection(RakNetInstance_pingForHosts_t original, RakNetInstance *rak_net_instance, const int32_t base_port) {
// Call Original Method // Call Original Method
original(rak_net_instance, base_port); original(rak_net_instance, base_port);
@ -109,7 +31,7 @@ static void RakNetInstance_pingForHosts_injection(RakNetInstance_pingForHosts_t
RakNet_RakPeer *rak_peer = rak_net_instance->peer; RakNet_RakPeer *rak_peer = rak_net_instance->peer;
// Add External Servers // Add External Servers
iterate_servers([rak_peer](const char *address, int port) { iterate_servers([rak_peer](const char *address, ServerList::port_t port) {
rak_peer->Ping(address, port, true, 0); rak_peer->Ping(address, port, true, 0);
}); });
} }

View File

@ -99,7 +99,7 @@ static std::vector<std::string> OptionsFile_getOptionStrings_v2(OptionsFile *opt
const std::string path = options_file->options_txt_path; const std::string path = options_file->options_txt_path;
// Parse // Parse
std::vector<std::string> ret; std::vector<std::string> ret;
std::ifstream stream(path, std::ios::binary); std::ifstream stream(path);
if (stream) { if (stream) {
std::string line; std::string line;
while (std::getline(stream, line)) { while (std::getline(stream, line)) {

View File

@ -38,11 +38,12 @@ ServerProperties &get_server_properties() {
} }
// Default Server Properties // Default Server Properties
struct ServerPropertyTypes { static auto &get_property_types() {
static struct {
const ServerProperty message_of_the_day = ServerProperty("motd", "Minecraft Server", "Message Of The Day"); const ServerProperty message_of_the_day = ServerProperty("motd", "Minecraft Server", "Message Of The Day");
const ServerProperty show_minecon_badge = ServerProperty("show-minecon-badge", "false", "Show The MineCon Badge Next To MOTD In Server List"); const ServerProperty show_minecon_badge = ServerProperty("show-minecon-badge", "false", "Show The MineCon Badge Next To MOTD In Server List");
const ServerProperty game_mode = ServerProperty("game-mode", "0", "Game Mode (0 = Survival, 1 = Creative)"); const ServerProperty game_mode = ServerProperty("game-mode", "0", "Game Mode (0 = Survival, 1 = Creative)");
const ServerProperty port = ServerProperty("port", "19132", "Port"); const ServerProperty port = ServerProperty("port", std::to_string(DEFAULT_MULTIPLAYER_PORT), "Port");
const ServerProperty seed = ServerProperty("seed", "", "World Seed (Blank = Random Seed)"); const ServerProperty seed = ServerProperty("seed", "", "World Seed (Blank = Random Seed)");
const ServerProperty force_mob_spawning = ServerProperty("force-mob-spawning", "false", "Force Mob Spawning (false = Disabled, true = Enabled)"); const ServerProperty force_mob_spawning = ServerProperty("force-mob-spawning", "false", "Force Mob Spawning (false = Disabled, true = Enabled)");
const ServerProperty peaceful_mode = ServerProperty("peaceful-mode", "false", "Peaceful Mode (false = Disabled, true = Enabled)"); const ServerProperty peaceful_mode = ServerProperty("peaceful-mode", "false", "Peaceful Mode (false = Disabled, true = Enabled)");
@ -51,9 +52,7 @@ struct ServerPropertyTypes {
const ServerProperty enable_whitelist = ServerProperty("whitelist", "false", "Enable Whitelist"); const ServerProperty enable_whitelist = ServerProperty("whitelist", "false", "Enable Whitelist");
const ServerProperty enable_death_messages = ServerProperty("death-messages", "true", "Enable Death Messages"); const ServerProperty enable_death_messages = ServerProperty("death-messages", "true", "Enable Death Messages");
const ServerProperty enable_cave_generation = ServerProperty("generate-caves", "true", "Generate Caves"); const ServerProperty enable_cave_generation = ServerProperty("generate-caves", "true", "Generate Caves");
}; } types;
static ServerPropertyTypes &get_property_types() {
static ServerPropertyTypes types;
return types; return types;
} }

View File

@ -1,3 +1,5 @@
#include <fstream>
#include <mods/server/server_properties.h> #include <mods/server/server_properties.h>
std::vector<const ServerProperty *> &ServerProperty::get_all() { std::vector<const ServerProperty *> &ServerProperty::get_all() {
@ -6,7 +8,7 @@ std::vector<const ServerProperty *> &ServerProperty::get_all() {
} }
static bool is_true(std::string const& val) { static bool is_true(std::string const& val) {
return (val == "true" || val == "yes" || val == "1"); return val == "true" || val == "yes" || val == "1";
} }
void ServerProperties::load(std::istream &stream) { void ServerProperties::load(std::istream &stream) {