Skip to content

Commit

Permalink
fix up local/external address handling
Browse files Browse the repository at this point in the history
  • Loading branch information
fuzziqersoftware committed Nov 11, 2024
1 parent 8cb7b46 commit a59a2d7
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 46 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

newserv is a game server, proxy, and reverse-engineering tool for Phantasy Star Online (PSO). To get started using newserv as a PSO server or as a proxy, see the [Server setup](#server-setup) section.

**To quickly get started using newserv, just read the [server setup](#server-setup) and [How to connect](#how-to-connect) sections.**

This project includes code that was reverse-engineered by the community in ages long past, and has been included in many projects since then. It also includes some game data from Phantasy Star Online itself, which was originally created by Sega.

Feel free to submit GitHub issues if you find bugs or have feature requests. I'd like to make the server as stable and complete as possible, but I can't promise that I'll respond to issues in a timely manner, because this is a personal project undertaken primarily for the fun of reverse-engineering. If you want to contribute to newserv yourself, pull requests are welcome as well.
Expand Down
69 changes: 44 additions & 25 deletions src/Main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <phosg/JSON.hh>
#include <phosg/Math.hh>
#include <phosg/Network.hh>
#include <phosg/Platform.hh>
#include <phosg/Strings.hh>
#include <phosg/Tools.hh>
#include <set>
Expand Down Expand Up @@ -53,6 +54,12 @@

using namespace std;

#ifdef PHOSG_WINDOWS
static constexpr bool IS_WINDOWS = true;
#else
static constexpr bool IS_WINDOWS = false;
#endif

bool use_terminal_colors = false;

void print_version_info();
Expand Down Expand Up @@ -3167,9 +3174,21 @@ Action a_run_server_replay_log(
state->ip_stack_simulator->listen(
spec, netloc.first, netloc.second, IPStackSimulator::Protocol::HDLC_RAW);
if (netloc.second) {
if (state->local_address == state->external_address) {
if (state->local_address == 0 && state->external_address == 0) {
config_log.info(
"Cannot generate Devolution phone numbers for %s because LocalAddress and ExternalAddress are not specified in the configuration",
spec.c_str());
} else if (state->local_address == 0) {
config_log.info(
"Note: The Devolution phone number for %s is %" PRIu64 " (external)",
spec.c_str(), devolution_phone_number_for_netloc(state->external_address, netloc.second));
} else if (state->external_address == 0) {
config_log.info(
"Note: The Devolution phone number for %s is %" PRIu64 " (local)",
spec.c_str(), devolution_phone_number_for_netloc(state->local_address, netloc.second));
} else if (state->local_address == state->external_address) {
config_log.info(
"Note: The Devolution phone number for %s is %" PRIu64,
"Note: The Devolution phone number for %s is %" PRIu64 " (local+external)",
spec.c_str(), devolution_phone_number_for_netloc(state->local_address, netloc.second));
} else {
config_log.info(
Expand Down Expand Up @@ -3328,30 +3347,30 @@ int main(int argc, char** argv) {
phosg::log_error("Unknown or invalid action; try --help");
return 1;
}
#ifdef PHOSG_WINDOWS
// Cygwin just gives a stackdump when an exception falls out of main(), so
// unlike Linux and macOS, we have to manually catch exceptions here just to
// see what the exception message was.
try {
if (IS_WINDOWS) {
// Cygwin just gives a stackdump when an exception falls out of main(), so
// unlike Linux and macOS, we have to manually catch exceptions here just to
// see what the exception message was.
try {
a->run(args);
} catch (const phosg::cannot_open_file& e) {
phosg::log_error("Top-level exception (cannot_open_file): %s", e.what());
throw;
} catch (const invalid_argument& e) {
phosg::log_error("Top-level exception (invalid_argument): %s", e.what());
throw;
} catch (const out_of_range& e) {
phosg::log_error("Top-level exception (out_of_range): %s", e.what());
throw;
} catch (const runtime_error& e) {
phosg::log_error("Top-level exception (runtime_error): %s", e.what());
throw;
} catch (const exception& e) {
phosg::log_error("Top-level exception: %s", e.what());
throw;
}
} else {
a->run(args);
} catch (const phosg::cannot_open_file& e) {
phosg::log_error("Top-level exception (cannot_open_file): %s", e.what());
throw;
} catch (const invalid_argument& e) {
phosg::log_error("Top-level exception (invalid_argument): %s", e.what());
throw;
} catch (const out_of_range& e) {
phosg::log_error("Top-level exception (out_of_range): %s", e.what());
throw;
} catch (const runtime_error& e) {
phosg::log_error("Top-level exception (runtime_error): %s", e.what());
throw;
} catch (const exception& e) {
phosg::log_error("Top-level exception: %s", e.what());
throw;
}
#else
a->run(args);
#endif
return 0;
}
11 changes: 9 additions & 2 deletions src/NetworkAddresses.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,16 @@ map<string, uint32_t> get_local_addresses() {
return ret;
}

bool is_loopback_address(uint32_t addr) {
return ((addr & 0xFF000000) == 0x7F000000); // 127.0.0.0/8
}

bool is_local_address(uint32_t addr) {
uint8_t net = (addr >> 24) & 0xFF;
return ((net == 127) || (net == 172) || (net == 10) || (net == 192));
return is_loopback_address(addr) || // 127.0.0.0/8
((addr & 0xFF000000) == 0x0A000000) || // 10.0.0.0/8
((addr & 0xFFF00000) == 0xAC100000) || // 172.16.0.0/12
((addr & 0xFFFF0000) == 0xC0A80000) || // 192.168.0.0/16
((addr & 0xFFFF0000) == 0xA9FE0000); // 169.254.0.0/16
}

bool is_local_address(const sockaddr_storage& daddr) {
Expand Down
1 change: 1 addition & 0 deletions src/NetworkAddresses.hh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
uint32_t resolve_address(const char* address);
std::map<std::string, uint32_t> get_local_addresses();
uint32_t get_connected_address(int fd);
bool is_loopback_address(uint32_t addr);
bool is_local_address(uint32_t daddr);
bool is_local_address(const sockaddr_storage& daddr);

Expand Down
50 changes: 44 additions & 6 deletions src/ServerState.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <memory>
#include <phosg/Image.hh>
#include <phosg/Network.hh>
#include <phosg/Platform.hh>

#include "Compression.hh"
#include "EventUtils.hh"
Expand All @@ -19,6 +20,12 @@

using namespace std;

#ifdef PHOSG_WINDOWS
static constexpr bool IS_WINDOWS = true;
#else
static constexpr bool IS_WINDOWS = false;
#endif

CheatFlags::CheatFlags(const phosg::JSON& json) : CheatFlags() {
unordered_set<std::string> enabled_keys;
for (const auto& it : json.as_list()) {
Expand Down Expand Up @@ -673,8 +680,10 @@ void ServerState::load_config_early() {
for (const auto& item : this->config_json->at("IPStackListen").as_list()) {
if (item->is_int()) {
this->ip_stack_addresses.emplace_back(phosg::string_printf("0.0.0.0:%" PRId64, item->as_int()));
} else {
} else if (!IS_WINDOWS) {
this->ip_stack_addresses.emplace_back(item->as_string());
} else {
config_log.warning("Unix sockets are not supported on Windows; skipping address %s", item->as_string().c_str());
}
}
} catch (const out_of_range&) {
Expand All @@ -683,8 +692,10 @@ void ServerState::load_config_early() {
for (const auto& item : this->config_json->at("PPPStackListen").as_list()) {
if (item->is_int()) {
this->ppp_stack_addresses.emplace_back(phosg::string_printf("0.0.0.0:%" PRId64, item->as_int()));
} else {
} else if (!IS_WINDOWS) {
this->ppp_stack_addresses.emplace_back(item->as_string());
} else {
config_log.warning("Unix sockets are not supported on Windows; skipping address %s", item->as_string().c_str());
}
}
} catch (const out_of_range&) {
Expand All @@ -693,8 +704,10 @@ void ServerState::load_config_early() {
for (const auto& item : this->config_json->at("PPPRawListen").as_list()) {
if (item->is_int()) {
this->ppp_raw_addresses.emplace_back(phosg::string_printf("0.0.0.0:%" PRId64, item->as_int()));
} else {
} else if (!IS_WINDOWS) {
this->ppp_raw_addresses.emplace_back(item->as_string());
} else {
config_log.warning("Unix sockets are not supported on Windows; skipping address %s", item->as_string().c_str());
}
}
} catch (const out_of_range&) {
Expand All @@ -703,8 +716,10 @@ void ServerState::load_config_early() {
for (const auto& item : this->config_json->at("HTTPListen").as_list()) {
if (item->is_int()) {
this->http_addresses.emplace_back(phosg::string_printf("0.0.0.0:%" PRId64, item->as_int()));
} else {
} else if (!IS_WINDOWS) {
this->http_addresses.emplace_back(item->as_string());
} else {
config_log.warning("Unix sockets are not supported on Windows; skipping address %s", item->as_string().c_str());
}
}
} catch (const out_of_range&) {
Expand All @@ -727,7 +742,18 @@ void ServerState::load_config_early() {
this->all_addresses.erase("<local>");
this->all_addresses.emplace("<local>", this->local_address);
} catch (const out_of_range&) {
config_log.warning("Local address not specified; interface defaults will be used");
for (const auto& it : this->all_addresses) {
// Choose any local interface except the loopback interface
if (!is_loopback_address(it.second) && is_local_address(it.second)) {
this->local_address = it.second;
}
}
if (this->local_address) {
string addr_str = string_for_address(this->local_address);
config_log.warning("Local address not specified; using %s as default", addr_str.c_str());
} else {
config_log.warning("Local address not specified and no default is available");
}
}

try {
Expand All @@ -744,7 +770,19 @@ void ServerState::load_config_early() {
this->all_addresses.erase("<external>");
this->all_addresses.emplace("<external>", this->external_address);
} catch (const out_of_range&) {
config_log.warning("External address not specified; only local clients will be able to connect");
for (const auto& it : this->all_addresses) {
// Choose any non-local address, if any exist
if (!is_local_address(it.second)) {
this->external_address = it.second;
break;
}
}
if (this->external_address) {
string addr_str = string_for_address(this->external_address);
config_log.warning("External address not specified; using %s as default", addr_str.c_str());
} else {
config_log.warning("External address not specified and no default is available; only local clients will be able to connect");
}
}

try {
Expand Down
42 changes: 29 additions & 13 deletions system/config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,15 @@
// set this to ["127.0.0.1:5059"] (which listens on port 5059 but only accepts
// connections from the local machine), and configure Dolphin's tapserver BBA
// to connect to 127.0.0.1:5059.
"IPStackListen": [],
"PPPStackListen": [],
"IPStackListen": ["/tmp/dolphin-tap", 5059],
"PPPStackListen": ["/tmp/dolphin-modem-tap", 5058],

// Where to listen for PPP clients. This exists to interface with PSO GC
// clients running on a Wii with Devolution, which emulates the modem adapter.
// The PPP stream can be forwarded directly to newserv on any port specified
// here without using pppd or another PPP server.
"PPPRawListen": [],
// here without using pppd or another PPP server. When you start newserv, it
// will tell you the Devolution phone number you can use to connect.
"PPPRawListen": [5057],

// Where to listen for HTTP connections. The HTTP server is intended as a
// private interface to interact with newserv from e.g. an in-house Web portal
Expand All @@ -193,10 +194,26 @@
// Note that PSO GameCube Episodes 1&2 Trial Edition uses the DC's
// ProxyDestinations dictionary here. This is because other servers that
// support that version treat it as PSO DC v2.
"ProxyDestinations-DC": {},
"ProxyDestinations-PC": {},
"ProxyDestinations-GC": {},
"ProxyDestinations-XB": {},
"ProxyDestinations-DC": {
"Schtserv": "psobb.dyndns.org:9200",
"Sylverant": "sylverant.net:9200",
"EU/Ragol": "ragol.org:9200",
},
"ProxyDestinations-PC": {
"Schtserv": "psobb.dyndns.org:9100",
"Sylverant": "sylverant.net:9100",
"EU/Ragol": "ragol.org:9100",
},
"ProxyDestinations-GC": {
"Schtserv": "psobb.dyndns.org:9103",
"Sylverant": "sylverant.net:9103",
"EU/Ragol": "ragol.org:9103",
},
"ProxyDestinations-XB": {
"Schtserv": "psobb.dyndns.org:9500",
"Sylverant": "sylverant.net:9500",
"EU/Ragol": "ragol.org:9500",
},
// Proxy destination for patch server clients. If this is given, the internal
// patch server (for PC and BB) is bypassed, and any client that connects to
// the patch server is instead proxied to this destination.
Expand Down Expand Up @@ -414,9 +431,7 @@
"none", // Lobby C4 (Episode 3 only)
"none", // Lobby C5 (Episode 3 only)
],

// Menu event. This is the holiday event during the lobby overview while at
// the main menu.
// Menu event. This is the holiday event the player sees at the main menu.
"MenuEvent": "none",

// Episode 3 menu song. If set, Episode 3 clients will hear this song when
Expand Down Expand Up @@ -494,7 +509,8 @@
// any Meseta when a song is played. The check for 100 or more meseta happens
// client-side, however, so even if this option is enabled, the client must
// either have 100 or more Meseta or use the "Jukebox is free" Action Replay
// code to be able to play songs.
// code to be able to play songs. (In the Git repository, the code is in
// notes/ar-codes.txt.)
"Episode3JukeboxIsFree": false,

// Episode 3 battle behavior flags. When set to zero, battles behave as they
Expand All @@ -514,7 +530,7 @@
// 0x0100 => Disable interference (COMs randomly coming to each other's
// rescue)
// 0x0200 => Allow interference even when neither player is a COM
"Episode3BehaviorFlags": 0x0002,
"Episode3BehaviorFlags": 0x0042,

// Trap assist cards for each trap type in Episode 3 battles. These are the
// default values used offline, but you can change the trap types online here.
Expand Down

0 comments on commit a59a2d7

Please sign in to comment.