diff --git a/VERSION b/VERSION index 2165f8f..e010258 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.4 +2.0.5 diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e23910f..4fd3fea 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -72,7 +72,7 @@ This sub-component implements stubs for various redundant libraries used by MCPI This is always compiled for ARM. ##### What To Stub And What To Patch? -Most libraries (like ``bcm_host``) can just be replaced with stubs, because they don't need to do anything and aren't used by anything else. However, some libraries (like EGL) might be used by some of MCPI-Reborn's dependencies (like GLFW) so instead of being replaced by a stub, each call is manually patched out from MCPI. A stub is still generated just in case that library isn't present on the system to silence linker errors, but it is only loaded if no other version is available. +Most libraries (like ``bcm_host``) can just be replaced with stubs, because they don't need to do anything and aren't used by anything else. However, some libraries (like EGL and X11) might be used by some of MCPI-Reborn's dependencies (like GLFW) so instead of being replaced by a stub, each call is manually patched out from MCPI. A stub is still generated just in case that library isn't present on the system to silence linker errors, but it is only loaded if no other version is available. #### Headers This sub-component includes headers for SDL, GLES, and EGL allowing easy (cross-)compilation. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3641a65..a13da1e 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +**2.0.5** +* External Server Support + **2.0.4** * Optimize Media Layer Proxy diff --git a/docs/MULTIPLAYER.md b/docs/MULTIPLAYER.md new file mode 100644 index 0000000..4678630 --- /dev/null +++ b/docs/MULTIPLAYER.md @@ -0,0 +1,14 @@ +# 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`` +``` +# Comment +example.com:19132 +``` diff --git a/docs/README.md b/docs/README.md index 1231fd3..92b055c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,4 +7,5 @@ * [View Building](BUILDING.md) * [View Architecture](ARCHITECTURE.md) * [View Command Line Arguments](COMMAND_LINE.md) +* [View Multiplayer](MULTIPLAYER.md) * [View Changelog](CHANGELOG.md) diff --git a/libreborn/include/libreborn/minecraft.h b/libreborn/include/libreborn/minecraft.h index 39c79e3..64e5da5 100644 --- a/libreborn/include/libreborn/minecraft.h +++ b/libreborn/include/libreborn/minecraft.h @@ -345,6 +345,11 @@ static uint32_t RakNetInstance_send_vtable_offset = 0x38; typedef uint32_t (*RakNetInstance_isServer_t)(unsigned char *rak_net_instance); static uint32_t RakNetInstance_isServer_vtable_offset = 0x48; +typedef void (*RakNetInstance_pingForHosts_t)(unsigned char *rak_net_instance, int32_t base_port); +static RakNetInstance_pingForHosts_t RakNetInstance_pingForHosts = (RakNetInstance_pingForHosts_t) 0x73538; +static uint32_t RakNetInstance_pingForHosts_vtable_offset = 0x14; +static void *RakNetInstance_pingForHosts_vtable_addr = (void *) 0x109afc; + static uint32_t RakNetInstance_peer_property_offset = 0x4; // RakNet::RakPeer * // RakNet::RakPeer @@ -355,6 +360,9 @@ static uint32_t RakNet_RakPeer_GetSystemAddressFromGuid_vtable_offset = 0xd0; typedef bool (*RakNet_RakPeer_IsBanned_t)(unsigned char *rak_peer, const char *ip); static RakNet_RakPeer_IsBanned_t RakNet_RakPeer_IsBanned = (RakNet_RakPeer_IsBanned_t) 0xda3b4; +typedef bool (*RakNet_RakPeer_Ping_t)(unsigned char *rak_peer, const char *host, unsigned short remotePort, bool onlyReplyOnAcceptingConnections, uint32_t connectionSocketIndex); +static RakNet_RakPeer_Ping_t RakNet_RakPeer_Ping = (RakNet_RakPeer_Ping_t) 0xd9c2c; + // RakNet::SystemAddress typedef char *(*RakNet_SystemAddress_ToString_t)(struct RakNet_SystemAddress *system_address, bool print_delimiter, char delimiter); diff --git a/media-layer/CMakeLists.txt b/media-layer/CMakeLists.txt index 3403405..1b9a49c 100644 --- a/media-layer/CMakeLists.txt +++ b/media-layer/CMakeLists.txt @@ -30,11 +30,5 @@ add_subdirectory(extras) # Add Symlinks So MCPI Can Locate Libraries if(BUILD_ARM_COMPONENTS) - if(MCPI_SERVER_MODE OR MCPI_USE_MEDIA_LAYER_PROXY) - install_symlink("libmedia-layer-core.so" "${MCPI_LIB_DIR}/libX11.so.6") - else() - # When Loading In Client Mode On An ARM Host, Use Native X11 By Default - install_symlink("../lib/libmedia-layer-core.so" "${MCPI_FALLBACK_LIB_DIR}/libX11.so.6") - endif() install_symlink("libmedia-layer-core.so" "${MCPI_LIB_DIR}/libSDL-1.2.so.0") endif() diff --git a/media-layer/extras/CMakeLists.txt b/media-layer/extras/CMakeLists.txt index 3629892..3968152 100644 --- a/media-layer/extras/CMakeLists.txt +++ b/media-layer/extras/CMakeLists.txt @@ -2,6 +2,5 @@ project(media-layer-extras) if(BUILD_ARM_COMPONENTS) # Add Source To Media Core - target_sources(media-layer-core PRIVATE src/SDL.c src/X11.cpp) - target_link_libraries(media-layer-core dl) + target_sources(media-layer-core PRIVATE src/SDL.c) endif() diff --git a/media-layer/extras/src/X11.cpp b/media-layer/extras/src/X11.cpp deleted file mode 100644 index 5723c06..0000000 --- a/media-layer/extras/src/X11.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include -#include - -#include - -#include -#include - -#ifndef MEDIA_LAYER_PROXY_SERVER -// Store Adresses That Are Part Of MCPI -struct text_section_data { - void *section; - Elf32_Word size; -}; -static std::vector &get_text_sections() { - static std::vector sections; - return sections; -} -static void text_section_callback(void *section, Elf32_Word size, __attribute__((unused)) void *data) { - text_section_data section_data; - section_data.section = section; - section_data.size = size; - get_text_sections().push_back(section_data); -} -// Check If The Current Return Address Is Part Of MCPI Or An External Library -static inline int _is_returning_to_external_library(void *return_addr) { - // Load Text Sections If Not Loaded - if (get_text_sections().size() < 1) { - iterate_text_sections(text_section_callback, NULL); - } - // Iterate Text Sections - for (std::vector::size_type i = 0; i < get_text_sections().size(); i++) { - text_section_data section_data = get_text_sections()[i]; - // Check Text Section - if (return_addr >= section_data.section && return_addr < (((unsigned char *) section_data.section) + section_data.size)) { - // Part Of MCPI - return 0; - } - } - // Part Of An External Library - return 1; -} -#define is_returning_to_external_library() _is_returning_to_external_library(__builtin_extract_return_addr(__builtin_return_address(0))) -#else -// When Using The Media Layer Proxy, None Of These Functions Are Ever Called By An External Library -#define is_returning_to_external_library() 0 -#endif - -// Don't Directly Use XLib Unless Returning To An External library -HOOK(XTranslateCoordinates, int, (void *display, XID src_w, XID dest_w, int src_x, int src_y, int *dest_x_return, int *dest_y_return, XID *child_return)) { - if (is_returning_to_external_library()) { - // Use Real Function - ensure_XTranslateCoordinates(); - return (*real_XTranslateCoordinates)(display, src_w, dest_w, src_x, src_y, dest_x_return, dest_y_return, child_return); - } else { - // Use MCPI Replacemnt Function - *dest_x_return = src_x; - *dest_y_return = src_y; - return 1; - } -} -HOOK(XGetWindowAttributes, int, (void *display, XID w, XWindowAttributes *window_attributes_return)) { - if (is_returning_to_external_library()) { - // Use Real Function - ensure_XGetWindowAttributes(); - return (*real_XGetWindowAttributes)(display, w, window_attributes_return); - } else { - // Use MCPI Replacemnt Function - XWindowAttributes attributes; - attributes.x = 0; - attributes.y = 0; - media_get_framebuffer_size(&attributes.width, &attributes.height); - *window_attributes_return = attributes; - return 1; - } -} diff --git a/media-layer/include/GLES/gl.h b/media-layer/include/GLES/gl.h index 7671121..8918c1c 100644 --- a/media-layer/include/GLES/gl.h +++ b/media-layer/include/GLES/gl.h @@ -5,15 +5,15 @@ extern "C" { #endif #define GL_FALSE 0 -#define GL_FOG_COLOR 0x0B66 +#define GL_FOG_COLOR 0xb66 #define GL_ARRAY_BUFFER_BINDING 0x8894 #define GL_UNSIGNED_BYTE 0x1401 #define GL_RGB 0x1907 #define GL_RGBA 0x1908 -#define GL_MODELVIEW_MATRIX 0x0BA6 -#define GL_PROJECTION_MATRIX 0x0BA7 -#define GL_VIEWPORT 0x0BA2 -#define GL_DEPTH_TEST 0x0B71 +#define GL_MODELVIEW_MATRIX 0xba6 +#define GL_PROJECTION_MATRIX 0xba7 +#define GL_VIEWPORT 0xba2 +#define GL_DEPTH_TEST 0xb71 #include #include diff --git a/media-layer/stubs/CMakeLists.txt b/media-layer/stubs/CMakeLists.txt index 4066efa..5ba3db8 100644 --- a/media-layer/stubs/CMakeLists.txt +++ b/media-layer/stubs/CMakeLists.txt @@ -14,11 +14,15 @@ if(BUILD_ARM_COMPONENTS) # Stub EGL add_library(EGL SHARED src/EGL.c) target_link_libraries(EGL reborn-headers media-layer-headers) + # Stub X11 + add_library(X11 SHARED src/X11.c) + target_link_libraries(X11 reborn-headers media-layer-headers) + set_target_properties(X11 PROPERTIES SOVERSION "6") # Install if(MCPI_SERVER_MODE OR MCPI_USE_MEDIA_LAYER_PROXY) - install(TARGETS EGL DESTINATION "${MCPI_LIB_DIR}") + install(TARGETS EGL X11 DESTINATION "${MCPI_LIB_DIR}") else() - install(TARGETS EGL DESTINATION "${MCPI_FALLBACK_LIB_DIR}") # Place At The End Of LD_LIBRARY_PATH + install(TARGETS EGL X11 DESTINATION "${MCPI_FALLBACK_LIB_DIR}") # Place At The End Of LD_LIBRARY_PATH endif() # Install GLESv1_CM Stubs In Server Mode diff --git a/media-layer/stubs/src/EGL.c b/media-layer/stubs/src/EGL.c index 512a0ba..93a442f 100644 --- a/media-layer/stubs/src/EGL.c +++ b/media-layer/stubs/src/EGL.c @@ -1,15 +1,3 @@ -/* - * This is only loaded when no other EGL library is present on the system to silence linker errors. - * - * All EGL calls are manually patched out in mods/src/compat/egl.c. - * Unlike with bcm_host, EGL can't just be always stubbed out with LD_PRELOAD, because in some situations GLFW has it as a dependency. - * - * So normally, MCPI just loads the real EGL and never uses it (because MCPI's EGL calls were patched out). - * However, since the real EGL is still loaded, GLFW can use it if it needs to. - * - * This stub library is just in case the system has no EGL library present. - */ - #include #include diff --git a/media-layer/stubs/src/X11.c b/media-layer/stubs/src/X11.c new file mode 100644 index 0000000..56ab67c --- /dev/null +++ b/media-layer/stubs/src/X11.c @@ -0,0 +1,14 @@ +#include + +#include + +#define IMPOSSIBLE() ERR("(%s:%i) This Should Never Be Called", __FILE__, __LINE__) + +// Raw X11 Is Replaced With GLFW + +int XTranslateCoordinates(__attribute__((unused)) void *display, __attribute__((unused)) XID src_w, __attribute__((unused)) XID dest_w, __attribute__((unused)) int src_x, __attribute__((unused)) int src_y, __attribute__((unused)) int *dest_x_return, __attribute__((unused)) int *dest_y_return, __attribute__((unused)) XID *child_return) { + IMPOSSIBLE(); +} +int XGetWindowAttributes(__attribute__((unused)) void *display, __attribute__((unused)) XID w, __attribute__((unused)) XWindowAttributes *window_attributes_return) { + IMPOSSIBLE(); +} diff --git a/mods/CMakeLists.txt b/mods/CMakeLists.txt index 66bec6e..7020850 100644 --- a/mods/CMakeLists.txt +++ b/mods/CMakeLists.txt @@ -7,7 +7,7 @@ add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) ## Mods -add_library(compat SHARED src/compat/compat.c src/compat/egl.c) +add_library(compat SHARED src/compat/compat.c src/compat/egl.c src/compat/x11.c) target_link_libraries(compat feature input media-layer-core dl) add_library(readdir SHARED src/readdir/readdir.c) @@ -18,6 +18,9 @@ target_link_libraries(feature reborn) if(MCPI_SERVER_MODE) add_library(server SHARED src/server/server.cpp src/server/server_properties.cpp) target_link_libraries(server reborn feature home compat dl media-layer-core pthread) +else() + add_library(multiplayer SHARED src/multiplayer/multiplayer.cpp) + target_link_libraries(multiplayer reborn home) endif() add_library(camera SHARED src/camera/camera.cpp) @@ -60,10 +63,14 @@ add_library(init SHARED src/init/init.c) target_link_libraries(init compat game_mode camera input misc death options touch textures chat home test) if(MCPI_SERVER_MODE) target_link_libraries(init server) +else() + target_link_libraries(init multiplayer) endif() ## Install Mods install(TARGETS init compat readdir feature override game_mode camera input misc death options touch textures chat home test DESTINATION "${MCPI_INSTALL_DIR}/mods") if(MCPI_SERVER_MODE) install(TARGETS server DESTINATION "${MCPI_INSTALL_DIR}/mods") +else() + install(TARGETS multiplayer DESTINATION "${MCPI_INSTALL_DIR}/mods") endif() diff --git a/mods/src/compat/README.md b/mods/src/compat/README.md index 82cae72..3eaf3c5 100644 --- a/mods/src/compat/README.md +++ b/mods/src/compat/README.md @@ -1,2 +1,2 @@ # ``compat`` Mod -This utility mod sends keyboard input to other mods. It also patches out all EGL calls. +This utility mod sends keyboard input to other mods. It also patches out all EGL and X11 calls. diff --git a/mods/src/compat/x11.c b/mods/src/compat/x11.c new file mode 100644 index 0000000..9beeba4 --- /dev/null +++ b/mods/src/compat/x11.c @@ -0,0 +1,28 @@ +#include + +#include +#include + +// Functions That Have Their Return Values Used +static int XTranslateCoordinates_injection(__attribute__((unused)) void *display, __attribute__((unused)) XID src_w, __attribute__((unused)) XID dest_w, int src_x, int src_y, int *dest_x_return, int *dest_y_return, __attribute__((unused)) XID *child_return) { + // Use MCPI Replacemnt Function + *dest_x_return = src_x; + *dest_y_return = src_y; + return 1; +} +static int XGetWindowAttributes_injection(__attribute__((unused)) void *display, __attribute__((unused)) XID w, XWindowAttributes *window_attributes_return) { + // Use MCPI Replacemnt Function + XWindowAttributes attributes; + attributes.x = 0; + attributes.y = 0; + media_get_framebuffer_size(&attributes.width, &attributes.height); + *window_attributes_return = attributes; + return 1; +} + +// Patch X11 Calls +__attribute__((constructor)) static void patch_x11_calls() { + // Disable X11 Calls + overwrite_call((void *) 0x132a4, (void *) XGetWindowAttributes_injection); // XGetWindowAttributes + overwrite_call((void *) 0x132d4, (void *) XTranslateCoordinates_injection); // XTranslateCoordinates +} diff --git a/mods/src/home/home.c b/mods/src/home/home.c index d79c6e4..ef11fb1 100644 --- a/mods/src/home/home.c +++ b/mods/src/home/home.c @@ -12,14 +12,13 @@ // Store Game Data In Launch Directory #define NEW_PATH "" +// Store Launch Directory static char *launch_directory = NULL; __attribute__((constructor)) static void init_launch_directory() { launch_directory = getcwd(NULL, 0); } -char *home_get_launch_directory() { - return launch_directory; -} +// Pretend $HOME Is Launch Directory HOOK(getenv, char *, (const char *name)) { if (strcmp(name, "HOME") == 0) { return launch_directory; diff --git a/mods/src/home/home.h b/mods/src/home/home.h index 44d6ce8..9f72d65 100644 --- a/mods/src/home/home.h +++ b/mods/src/home/home.h @@ -4,10 +4,6 @@ extern "C" { #endif -#ifdef MCPI_SERVER_MODE -char *home_get_launch_directory(); -#endif - char *home_get(); // Remember To free() #ifdef __cplusplus diff --git a/mods/src/init/init.c b/mods/src/init/init.c index 5a71d88..a273f9e 100644 --- a/mods/src/init/init.c +++ b/mods/src/init/init.c @@ -5,6 +5,8 @@ __attribute__((constructor)) static void init() { init_compat(); #ifdef MCPI_SERVER_MODE init_server(); +#else + init_multiplayer(); #endif init_game_mode(); init_input(); diff --git a/mods/src/init/init.h b/mods/src/init/init.h index ac501a1..b52efa5 100644 --- a/mods/src/init/init.h +++ b/mods/src/init/init.h @@ -8,6 +8,8 @@ void run_tests(); void init_compat(); #ifdef MCPI_SERVER_MODE void init_server(); +#else +void init_multiplayer(); #endif void init_game_mode(); void init_input(); diff --git a/mods/src/multiplayer/README.md b/mods/src/multiplayer/README.md new file mode 100644 index 0000000..d07d003 --- /dev/null +++ b/mods/src/multiplayer/README.md @@ -0,0 +1,4 @@ +# ``multiplayer`` Mod +This mod implements connecting to external (non-LAN) servers. This mod is client mode only. + +It is controlled by the ``~/.minecraft-pi/servers.txt`` file. diff --git a/mods/src/multiplayer/multiplayer.cpp b/mods/src/multiplayer/multiplayer.cpp new file mode 100644 index 0000000..0fdee57 --- /dev/null +++ b/mods/src/multiplayer/multiplayer.cpp @@ -0,0 +1,126 @@ +#ifdef MCPI_SERVER_MODE +#error "External Server Code Requires Client Mode" +#endif + +#include +#include +#include +#include + +#include +#include + +#include "../home/home.h" +#include "../init/init.h" + +// Load Server List +struct server_list_entry { + std::string address; + int port; +}; +static std::vector 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 << "thebrokenrail.com:19132\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.length() > 0) { + if (line[0] == '#') { + continue; + } + // Parse + std::string address; + std::string port_str; + // Loop + size_t last_colon = line.find_last_of(':'); + if (last_colon != std::string::npos) { + for (std::string::size_type i = 0; i < line.length(); i++) { + if (i > last_colon) { + port_str.push_back(line[i]); + } else if (i < last_colon) { + address.push_back(line[i]); + } + } + } + // Check Line + if (address.length() < 1 || port_str.length() < 1 || 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 + struct server_list_entry entry; + entry.address = address; + entry.port = port; + server_list_entries.push_back(entry); + } + } + } + + // Close + server_list_file.close(); +} + +// Iterare Server List +static void iterate_servers(std::function callback) { + // Load + if (!server_list_loaded) { + load_servers(); + server_list_loaded = true; + } + + // Loop + for (std::vector::size_type i = 0; i < server_list_entries.size(); i++) { + struct server_list_entry entry = server_list_entries[i]; + callback(entry.address.c_str(), entry.port); + } +} + +static void RakNetInstance_pingForHosts_injection(unsigned char *rak_net_instance, int32_t base_port) { + // Call Original + (*RakNetInstance_pingForHosts)(rak_net_instance, base_port); + + // Get RakNet::RakPeer + unsigned char *rak_peer = *(unsigned char **) (rak_net_instance + RakNetInstance_peer_property_offset); + + // Add External Servers + iterate_servers([rak_peer](const char *address, int port) { + (*RakNet_RakPeer_Ping)(rak_peer, address, port, 1, 0); + }); +} + +// Init +void init_multiplayer() { + // Inject Code + patch_address(RakNetInstance_pingForHosts_vtable_addr, (void *) RakNetInstance_pingForHosts_injection); +} diff --git a/mods/src/server/server.cpp b/mods/src/server/server.cpp index 41e839a..f285461 100644 --- a/mods/src/server/server.cpp +++ b/mods/src/server/server.cpp @@ -144,7 +144,7 @@ static bool is_whitelist() { } // Get Path Of Blacklist (Or Whitelist) File static std::string get_blacklist_file() { - std::string file(home_get_launch_directory()); + std::string file(home_get()); file.append(is_whitelist() ? "/whitelist.txt" : "/blacklist.txt"); return file; } @@ -457,12 +457,12 @@ static unsigned char get_max_players() { static void server_init() { // Open Properties File - std::string file(home_get_launch_directory()); + std::string file(home_get()); file.append("/server.properties"); std::ifstream properties_file(file); - if (!properties_file || !properties_file.good()) { + if (!properties_file.good()) { // Write Defaults std::ofstream properties_file_output(file); properties_file_output << "# Message Of The Day\n"; @@ -490,9 +490,9 @@ static void server_init() { properties_file = std::ifstream(file); } - // Open Properties File + // Check Properties File if (!properties_file.is_open()) { - ERR("%s", "Unable To Open server.properties"); + ERR("Unable To Open %s", file.c_str()); } // Load Properties get_server_properties().load(properties_file); @@ -502,7 +502,7 @@ static void server_init() { // Create Empty Blacklist/Whitelist File std::string blacklist_file_path = get_blacklist_file(); std::ifstream blacklist_file(blacklist_file_path); - if (!blacklist_file || !blacklist_file.good()) { + if (!blacklist_file.good()) { // Write Default std::ofstream blacklist_output(blacklist_file_path); blacklist_output << "# Blacklist/Whitelist; Each Line Is One IP Address\n";