External Server Support

This commit is contained in:
TheBrokenRail 2021-06-27 22:16:37 -04:00
parent b1d81e860f
commit 2b0d1d55ff
23 changed files with 233 additions and 120 deletions

View File

@ -1 +1 @@
2.0.4 2.0.5

View File

@ -72,7 +72,7 @@ This sub-component implements stubs for various redundant libraries used by MCPI
This is always compiled for ARM. This is always compiled for ARM.
##### What To Stub And What To Patch? ##### 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 #### Headers
This sub-component includes headers for SDL, GLES, and EGL allowing easy (cross-)compilation. This sub-component includes headers for SDL, GLES, and EGL allowing easy (cross-)compilation.

View File

@ -1,5 +1,8 @@
# Changelog # Changelog
**2.0.5**
* External Server Support
**2.0.4** **2.0.4**
* Optimize Media Layer Proxy * Optimize Media Layer Proxy

14
docs/MULTIPLAYER.md Normal file
View File

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

View File

@ -7,4 +7,5 @@
* [View Building](BUILDING.md) * [View Building](BUILDING.md)
* [View Architecture](ARCHITECTURE.md) * [View Architecture](ARCHITECTURE.md)
* [View Command Line Arguments](COMMAND_LINE.md) * [View Command Line Arguments](COMMAND_LINE.md)
* [View Multiplayer](MULTIPLAYER.md)
* [View Changelog](CHANGELOG.md) * [View Changelog](CHANGELOG.md)

View File

@ -345,6 +345,11 @@ static uint32_t RakNetInstance_send_vtable_offset = 0x38;
typedef uint32_t (*RakNetInstance_isServer_t)(unsigned char *rak_net_instance); typedef uint32_t (*RakNetInstance_isServer_t)(unsigned char *rak_net_instance);
static uint32_t RakNetInstance_isServer_vtable_offset = 0x48; 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 * static uint32_t RakNetInstance_peer_property_offset = 0x4; // RakNet::RakPeer *
// 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); 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; 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 // RakNet::SystemAddress
typedef char *(*RakNet_SystemAddress_ToString_t)(struct RakNet_SystemAddress *system_address, bool print_delimiter, char delimiter); typedef char *(*RakNet_SystemAddress_ToString_t)(struct RakNet_SystemAddress *system_address, bool print_delimiter, char delimiter);

View File

@ -30,11 +30,5 @@ add_subdirectory(extras)
# Add Symlinks So MCPI Can Locate Libraries # Add Symlinks So MCPI Can Locate Libraries
if(BUILD_ARM_COMPONENTS) 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") install_symlink("libmedia-layer-core.so" "${MCPI_LIB_DIR}/libSDL-1.2.so.0")
endif() endif()

View File

@ -2,6 +2,5 @@ project(media-layer-extras)
if(BUILD_ARM_COMPONENTS) if(BUILD_ARM_COMPONENTS)
# Add Source To Media Core # Add Source To Media Core
target_sources(media-layer-core PRIVATE src/SDL.c src/X11.cpp) target_sources(media-layer-core PRIVATE src/SDL.c)
target_link_libraries(media-layer-core dl)
endif() endif()

View File

@ -1,76 +0,0 @@
#include <elf.h>
#include <vector>
#include <X11/Xlib.h>
#include <libreborn/libreborn.h>
#include <libreborn/media-layer/core.h>
#ifndef MEDIA_LAYER_PROXY_SERVER
// Store Adresses That Are Part Of MCPI
struct text_section_data {
void *section;
Elf32_Word size;
};
static std::vector<text_section_data> &get_text_sections() {
static std::vector<text_section_data> 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<text_section_data>::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;
}
}

View File

@ -5,15 +5,15 @@ extern "C" {
#endif #endif
#define GL_FALSE 0 #define GL_FALSE 0
#define GL_FOG_COLOR 0x0B66 #define GL_FOG_COLOR 0xb66
#define GL_ARRAY_BUFFER_BINDING 0x8894 #define GL_ARRAY_BUFFER_BINDING 0x8894
#define GL_UNSIGNED_BYTE 0x1401 #define GL_UNSIGNED_BYTE 0x1401
#define GL_RGB 0x1907 #define GL_RGB 0x1907
#define GL_RGBA 0x1908 #define GL_RGBA 0x1908
#define GL_MODELVIEW_MATRIX 0x0BA6 #define GL_MODELVIEW_MATRIX 0xba6
#define GL_PROJECTION_MATRIX 0x0BA7 #define GL_PROJECTION_MATRIX 0xba7
#define GL_VIEWPORT 0x0BA2 #define GL_VIEWPORT 0xba2
#define GL_DEPTH_TEST 0x0B71 #define GL_DEPTH_TEST 0xb71
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>

View File

@ -14,11 +14,15 @@ if(BUILD_ARM_COMPONENTS)
# Stub EGL # Stub EGL
add_library(EGL SHARED src/EGL.c) add_library(EGL SHARED src/EGL.c)
target_link_libraries(EGL reborn-headers media-layer-headers) 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 # Install
if(MCPI_SERVER_MODE OR MCPI_USE_MEDIA_LAYER_PROXY) 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() 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() endif()
# Install GLESv1_CM Stubs In Server Mode # Install GLESv1_CM Stubs In Server Mode

View File

@ -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 <EGL/egl.h> #include <EGL/egl.h>
#include <libreborn/libreborn.h> #include <libreborn/libreborn.h>

View File

@ -0,0 +1,14 @@
#include <X11/Xlib.h>
#include <libreborn/libreborn.h>
#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();
}

View File

@ -7,7 +7,7 @@ add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0)
## Mods ## 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) target_link_libraries(compat feature input media-layer-core dl)
add_library(readdir SHARED src/readdir/readdir.c) add_library(readdir SHARED src/readdir/readdir.c)
@ -18,6 +18,9 @@ target_link_libraries(feature reborn)
if(MCPI_SERVER_MODE) if(MCPI_SERVER_MODE)
add_library(server SHARED src/server/server.cpp src/server/server_properties.cpp) 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) 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() endif()
add_library(camera SHARED src/camera/camera.cpp) 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) target_link_libraries(init compat game_mode camera input misc death options touch textures chat home test)
if(MCPI_SERVER_MODE) if(MCPI_SERVER_MODE)
target_link_libraries(init server) target_link_libraries(init server)
else()
target_link_libraries(init multiplayer)
endif() endif()
## Install Mods ## 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") 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) if(MCPI_SERVER_MODE)
install(TARGETS server DESTINATION "${MCPI_INSTALL_DIR}/mods") install(TARGETS server DESTINATION "${MCPI_INSTALL_DIR}/mods")
else()
install(TARGETS multiplayer DESTINATION "${MCPI_INSTALL_DIR}/mods")
endif() endif()

View File

@ -1,2 +1,2 @@
# ``compat`` Mod # ``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.

28
mods/src/compat/x11.c Normal file
View File

@ -0,0 +1,28 @@
#include <X11/Xlib.h>
#include <libreborn/libreborn.h>
#include <libreborn/media-layer/core.h>
// 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
}

View File

@ -12,14 +12,13 @@
// Store Game Data In Launch Directory // Store Game Data In Launch Directory
#define NEW_PATH "" #define NEW_PATH ""
// Store Launch Directory
static char *launch_directory = NULL; static char *launch_directory = NULL;
__attribute__((constructor)) static void init_launch_directory() { __attribute__((constructor)) static void init_launch_directory() {
launch_directory = getcwd(NULL, 0); launch_directory = getcwd(NULL, 0);
} }
char *home_get_launch_directory() {
return launch_directory;
}
// Pretend $HOME Is Launch Directory
HOOK(getenv, char *, (const char *name)) { HOOK(getenv, char *, (const char *name)) {
if (strcmp(name, "HOME") == 0) { if (strcmp(name, "HOME") == 0) {
return launch_directory; return launch_directory;

View File

@ -4,10 +4,6 @@
extern "C" { extern "C" {
#endif #endif
#ifdef MCPI_SERVER_MODE
char *home_get_launch_directory();
#endif
char *home_get(); // Remember To free() char *home_get(); // Remember To free()
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -5,6 +5,8 @@ __attribute__((constructor)) static void init() {
init_compat(); init_compat();
#ifdef MCPI_SERVER_MODE #ifdef MCPI_SERVER_MODE
init_server(); init_server();
#else
init_multiplayer();
#endif #endif
init_game_mode(); init_game_mode();
init_input(); init_input();

View File

@ -8,6 +8,8 @@ void run_tests();
void init_compat(); void init_compat();
#ifdef MCPI_SERVER_MODE #ifdef MCPI_SERVER_MODE
void init_server(); void init_server();
#else
void init_multiplayer();
#endif #endif
void init_game_mode(); void init_game_mode();
void init_input(); void init_input();

View File

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

View File

@ -0,0 +1,126 @@
#ifdef MCPI_SERVER_MODE
#error "External Server Code Requires Client Mode"
#endif
#include <fstream>
#include <functional>
#include <string>
#include <vector>
#include <libreborn/libreborn.h>
#include <libreborn/minecraft.h>
#include "../home/home.h"
#include "../init/init.h"
// Load Server List
struct server_list_entry {
std::string address;
int port;
};
static std::vector<struct 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 << "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<void(const char *address, int port)> callback) {
// Load
if (!server_list_loaded) {
load_servers();
server_list_loaded = true;
}
// Loop
for (std::vector<struct server_list_entry>::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);
}

View File

@ -144,7 +144,7 @@ static bool is_whitelist() {
} }
// Get Path Of Blacklist (Or Whitelist) File // Get Path Of Blacklist (Or Whitelist) File
static std::string get_blacklist_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"); file.append(is_whitelist() ? "/whitelist.txt" : "/blacklist.txt");
return file; return file;
} }
@ -457,12 +457,12 @@ static unsigned char get_max_players() {
static void server_init() { static void server_init() {
// Open Properties File // Open Properties File
std::string file(home_get_launch_directory()); std::string file(home_get());
file.append("/server.properties"); file.append("/server.properties");
std::ifstream properties_file(file); std::ifstream properties_file(file);
if (!properties_file || !properties_file.good()) { if (!properties_file.good()) {
// Write Defaults // Write Defaults
std::ofstream properties_file_output(file); std::ofstream properties_file_output(file);
properties_file_output << "# Message Of The Day\n"; properties_file_output << "# Message Of The Day\n";
@ -490,9 +490,9 @@ static void server_init() {
properties_file = std::ifstream(file); properties_file = std::ifstream(file);
} }
// Open Properties File // Check Properties File
if (!properties_file.is_open()) { if (!properties_file.is_open()) {
ERR("%s", "Unable To Open server.properties"); ERR("Unable To Open %s", file.c_str());
} }
// Load Properties // Load Properties
get_server_properties().load(properties_file); get_server_properties().load(properties_file);
@ -502,7 +502,7 @@ static void server_init() {
// Create Empty Blacklist/Whitelist File // Create Empty Blacklist/Whitelist File
std::string blacklist_file_path = get_blacklist_file(); std::string blacklist_file_path = get_blacklist_file();
std::ifstream blacklist_file(blacklist_file_path); std::ifstream blacklist_file(blacklist_file_path);
if (!blacklist_file || !blacklist_file.good()) { if (!blacklist_file.good()) {
// Write Default // Write Default
std::ofstream blacklist_output(blacklist_file_path); std::ofstream blacklist_output(blacklist_file_path);
blacklist_output << "# Blacklist/Whitelist; Each Line Is One IP Address\n"; blacklist_output << "# Blacklist/Whitelist; Each Line Is One IP Address\n";