fix bb login, char creation, and some lobby/game behaviors

This commit is contained in:
Martin Michelsen
2022-05-07 22:38:58 -07:00
parent 4079400784
commit 8ef256917c
20 changed files with 447 additions and 299 deletions
+2 -2
View File
@@ -4,7 +4,7 @@ newserv is a game server and proxy for Phantasy Star Online (PSO).
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; this data was originally created by Sega. 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; this data was originally created by Sega.
This project is a rewrite of a rewrite of a game server that I wrote many years ago. So far, it works well with PSO GC Episodes 1 & 2, and lobbies (but not games) are implemented on Episode 3. Some basic functionality works on PSO PC, but there are probably still some cases that lead to errors (which will disconnect the client). newserv is based on an older project of mine that supported BB as well, but I no longer have a way to test BB, so the implementation here probably doesn't work for it. This project is a rewrite of a rewrite of a game server that I wrote many years ago. So far, it works well with PSO GC Episodes 1 & 2, and lobbies (but not games) are implemented on Episode 3. Some basic functionality works on PSO PC and PSO BB, but there are probably still some cases that lead to errors (which will disconnect the client).
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. 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.
@@ -15,7 +15,7 @@ This project is primarily for my own nostalgia; I offer no guarantees on how or
Current known issues / missing features: Current known issues / missing features:
- Test all the communication features (info board, simple mail, card search, etc.) - Test all the communication features (info board, simple mail, card search, etc.)
- The trade window isn't implemented yet. - The trade window isn't implemented yet.
- PSO PC and PSOBB are essentially entirely untested. Only GC is fairly well-tested. - PSO PC and PSOBB are not well-tested and likely will disconnect when clients try to use unimplemented features. Only GC is known to be stable and mostly complete.
- Add all the chat commands that khyller used to have. (Most, but not all, currently exist in newserv.) - Add all the chat commands that khyller used to have. (Most, but not all, currently exist in newserv.)
## Usage ## Usage
+8 -4
View File
@@ -69,8 +69,6 @@ void Client::set_license(shared_ptr<const License> l) {
ClientConfig Client::export_config() const { ClientConfig Client::export_config() const {
ClientConfig cc; ClientConfig cc;
cc.magic = CLIENT_CONFIG_MAGIC; cc.magic = CLIENT_CONFIG_MAGIC;
cc.bb_game_state = this->bb_game_state;
cc.bb_player_index = this->bb_player_index;
cc.flags = this->flags; cc.flags = this->flags;
cc.proxy_destination_address = this->proxy_destination_address; cc.proxy_destination_address = this->proxy_destination_address;
cc.proxy_destination_port = this->proxy_destination_port; cc.proxy_destination_port = this->proxy_destination_port;
@@ -81,6 +79,8 @@ ClientConfig Client::export_config() const {
ClientConfigBB Client::export_config_bb() const { ClientConfigBB Client::export_config_bb() const {
ClientConfigBB cc; ClientConfigBB cc;
cc.cfg = this->export_config(); cc.cfg = this->export_config();
cc.bb_game_state = this->bb_game_state;
cc.bb_player_index = this->bb_player_index;
cc.unused.clear(0xFF); cc.unused.clear(0xFF);
return cc; return cc;
} }
@@ -89,9 +89,13 @@ void Client::import_config(const ClientConfig& cc) {
if (cc.magic != CLIENT_CONFIG_MAGIC) { if (cc.magic != CLIENT_CONFIG_MAGIC) {
throw invalid_argument("invalid client config"); throw invalid_argument("invalid client config");
} }
this->bb_game_state = cc.bb_game_state;
this->bb_player_index = cc.bb_player_index;
this->flags = cc.flags; this->flags = cc.flags;
this->proxy_destination_address = cc.proxy_destination_address; this->proxy_destination_address = cc.proxy_destination_address;
this->proxy_destination_port = cc.proxy_destination_port; this->proxy_destination_port = cc.proxy_destination_port;
} }
void Client::import_config(const ClientConfigBB& cc) {
this->import_config(cc.cfg);
this->bb_game_state = cc.bb_game_state;
this->bb_player_index = cc.bb_player_index;
}
+1 -9
View File
@@ -18,15 +18,6 @@ extern const uint64_t CLIENT_CONFIG_MAGIC;
enum class ServerBehavior {
SPLIT_RECONNECT = 0,
LOGIN_SERVER,
LOBBY_SERVER,
DATA_SERVER_BB,
PATCH_SERVER,
PROXY_SERVER,
};
struct Client { struct Client {
enum Flag { enum Flag {
// For patch server clients, client is Blue Burst rather than PC // For patch server clients, client is Blue Burst rather than PC
@@ -121,4 +112,5 @@ struct Client {
ClientConfig export_config() const; ClientConfig export_config() const;
ClientConfigBB export_config_bb() const; ClientConfigBB export_config_bb() const;
void import_config(const ClientConfig& cc); void import_config(const ClientConfig& cc);
void import_config(const ClientConfigBB& cc);
}; };
+28 -15
View File
@@ -43,19 +43,36 @@
// technically part of the PSO protocol. Because it is opaque to the client, we // technically part of the PSO protocol. Because it is opaque to the client, we
// can use the server's native-endian types instead of being explicit as we do // can use the server's native-endian types instead of being explicit as we do
// for all the other structs in this file. // for all the other structs in this file.
enum ClientStateBB : uint8_t {
// Initial connection; server will redirect client to another port
INITIAL_LOGIN = 0x00,
// Second connection; server will send client game data and account data
DOWNLOAD_DATA = 0x01,
// Third connection; client will show the choose character menu
CHOOSE_PLAYER = 0x02,
// Fourth connection; used for saving characters only. If you do not create a
// character, the server sets this state during the third connection so this
// connection is effectively skipped.
SAVE_PLAYER = 0x03,
// Fifth connection; redirects client to login server
SHIP_SELECT = 0x04,
// All other connections
IN_GAME = 0x05,
};
struct ClientConfig { struct ClientConfig {
uint64_t magic; uint64_t magic;
uint8_t bb_game_state;
uint8_t bb_player_index;
uint16_t flags; uint16_t flags;
uint32_t proxy_destination_address; uint32_t proxy_destination_address;
uint16_t proxy_destination_port; uint16_t proxy_destination_port;
parray<uint8_t, 0x0E> unused; parray<uint8_t, 0x10> unused;
}; };
struct ClientConfigBB { struct ClientConfigBB {
ClientConfig cfg; ClientConfig cfg;
parray<uint8_t, 0x08> unused; uint8_t bb_game_state;
uint8_t bb_player_index;
parray<uint8_t, 0x06> unused;
}; };
@@ -1163,7 +1180,7 @@ struct C_VerifyLicense_GC_DB {
// DC: Guild card data (BB) // DC: Guild card data (BB)
struct C_GuildCardDataRequest_BB_DC { struct C_GuildCardDataRequest_BB_03DC {
le_uint32_t unknown; le_uint32_t unknown;
le_uint32_t chunk_index; le_uint32_t chunk_index;
le_uint32_t cont; le_uint32_t cont;
@@ -1171,7 +1188,7 @@ struct C_GuildCardDataRequest_BB_DC {
struct S_GuildCardHeader_BB_01DC { struct S_GuildCardHeader_BB_01DC {
le_uint32_t unknown; // should be 1 le_uint32_t unknown; // should be 1
le_uint32_t filesize; // 0x0000490 le_uint32_t filesize; // 0x0000D590
le_uint32_t checksum; le_uint32_t checksum;
}; };
@@ -1210,18 +1227,13 @@ struct S_TournamentEntry_GC_Ep3_E0 {
// E2 (S->C): Team and key config (BB) // E2 (S->C): Team and key config (BB)
// See KeyAndTeamConfigBB in Player.hh for format // See KeyAndTeamConfigBB in Player.hh for format
// E3: Player previews (BB) // E3: Player preview request (BB)
struct C_PlayerPreviewRequest_BB_E3 { struct C_PlayerPreviewRequest_BB_E3 {
le_uint32_t player_index; le_uint32_t player_index;
le_uint32_t unused; le_uint32_t unused;
}; };
struct S_PlayerPreview_BB_E3 {
le_uint32_t player_index;
PlayerDispDataBBPreview preview;
};
// E4: CARD lobby game (Episode 3) // E4: CARD lobby game (Episode 3)
// When client sends an E4, server should respond with another E4 (but these // When client sends an E4, server should respond with another E4 (but these
// commands have different formats). // commands have different formats).
@@ -1253,9 +1265,10 @@ struct S_PlayerPreview_NoPlayer_BB_E4 {
le_uint32_t error; le_uint32_t error;
}; };
// E5 (S->C): Player preview (BB)
// E5 (C->S): Create character (BB) // E5 (C->S): Create character (BB)
struct C_CreateCharacter_BB_E5 { struct SC_PlayerPreview_CreateCharacter_BB_E5 {
le_uint32_t player_index; le_uint32_t player_index;
PlayerDispDataBBPreview preview; PlayerDispDataBBPreview preview;
}; };
@@ -1293,8 +1306,8 @@ struct S_AcceptClientChecksum_BB_02E8 {
// Command is a list of these; header.flag is the entry count. // Command is a list of these; header.flag is the entry count.
struct S_StreamFileIndexEntry_BB_01EB { struct S_StreamFileIndexEntry_BB_01EB {
le_uint32_t size; le_uint32_t size;
le_uint32_t checksum; le_uint32_t checksum; // crc32 of file data
le_uint32_t offset; le_uint32_t offset; // offset in stream (== sum of all previous files' sizes)
ptext<char, 0x40> filename; ptext<char, 0x40> filename;
}; };
+12 -3
View File
@@ -12,7 +12,15 @@ FileContentsCache::File::File(const string& name, shared_ptr<const string> conte
uint64_t load_time) : name(name), contents(contents), load_time(load_time) { } uint64_t load_time) : name(name), contents(contents), load_time(load_time) { }
shared_ptr<const string> FileContentsCache::get(const std::string& name) { shared_ptr<const string> FileContentsCache::get(const std::string& name) {
return this->get(name, [name]() -> string { return load_file(name); });
}
shared_ptr<const string> FileContentsCache::get(const char* name) {
return this->get(string(name));
}
shared_ptr<const string> FileContentsCache::get(const std::string& name,
std::function<std::string()> generate) {
uint64_t t = now(); uint64_t t = now();
try { try {
auto& entry = this->name_to_file.at(name); auto& entry = this->name_to_file.at(name);
@@ -21,7 +29,7 @@ shared_ptr<const string> FileContentsCache::get(const std::string& name) {
} }
} catch (const out_of_range& e) { } } catch (const out_of_range& e) { }
shared_ptr<const string> contents(new string(load_file(name))); shared_ptr<const string> contents(new string(generate()));
this->name_to_file.erase(name); this->name_to_file.erase(name);
this->name_to_file.emplace(piecewise_construct, forward_as_tuple(name), this->name_to_file.emplace(piecewise_construct, forward_as_tuple(name),
forward_as_tuple(name, contents, t)); forward_as_tuple(name, contents, t));
@@ -29,6 +37,7 @@ shared_ptr<const string> FileContentsCache::get(const std::string& name) {
return contents; return contents;
} }
shared_ptr<const string> FileContentsCache::get(const char* name) { shared_ptr<const string> FileContentsCache::get(const char* name,
return this->get(string(name)); std::function<std::string()> generate) {
return this->get(string(name), generate);
} }
+5
View File
@@ -35,6 +35,11 @@ public:
std::shared_ptr<const std::string> get(const std::string& name); std::shared_ptr<const std::string> get(const std::string& name);
std::shared_ptr<const std::string> get(const char* name); std::shared_ptr<const std::string> get(const char* name);
std::shared_ptr<const std::string> get(
const std::string& name, std::function<std::string()> generate);
std::shared_ptr<const std::string> get(
const char* name, std::function<std::string()> generate);
private: private:
std::unordered_map<std::string, File> name_to_file; std::unordered_map<std::string, File> name_to_file;
}; };
+36 -38
View File
@@ -30,35 +30,19 @@ bool use_terminal_colors = false;
static const vector<PortConfiguration> default_port_to_behavior({ static vector<PortConfiguration> parse_port_configuration(
{"gc-jp10", 9000, GameVersion::GC, ServerBehavior::LOGIN_SERVER}, shared_ptr<const JSONObject> json) {
{"gc-jp11", 9001, GameVersion::GC, ServerBehavior::LOGIN_SERVER}, vector<PortConfiguration> ret;
{"gc-jp3", 9003, GameVersion::GC, ServerBehavior::LOGIN_SERVER}, for (const auto& item_json_it : json->as_dict()) {
{"gc-us10", 9100, GameVersion::PC, ServerBehavior::SPLIT_RECONNECT}, auto item_list = item_json_it.second->as_list();
{"gc-us3", 9103, GameVersion::GC, ServerBehavior::LOGIN_SERVER}, PortConfiguration& pc = ret.emplace_back();
{"gc-eu10", 9200, GameVersion::GC, ServerBehavior::LOGIN_SERVER}, pc.name = item_json_it.first;
{"gc-eu11", 9201, GameVersion::GC, ServerBehavior::LOGIN_SERVER}, pc.port = item_list[0]->as_int();
{"gc-eu3", 9203, GameVersion::GC, ServerBehavior::LOGIN_SERVER}, pc.version = version_for_name(item_list[1]->as_string().c_str());
{"pc-login", 9300, GameVersion::PC, ServerBehavior::LOGIN_SERVER}, pc.behavior = server_behavior_for_name(item_list[2]->as_string().c_str());
{"pc-patch", 10000, GameVersion::PATCH, ServerBehavior::PATCH_SERVER}, }
{"bb-patch", 11000, GameVersion::PATCH, ServerBehavior::PATCH_SERVER}, return ret;
{"bb-init", 12000, GameVersion::BB, ServerBehavior::DATA_SERVER_BB}, }
{"bb-patch2", 13000, GameVersion::PATCH, ServerBehavior::PATCH_SERVER},
{"bb-init2", 14000, GameVersion::BB, ServerBehavior::DATA_SERVER_BB},
// These aren't hardcoded in clients; user should be allowed to override them
{"bb-data1", 12004, GameVersion::BB, ServerBehavior::DATA_SERVER_BB},
{"bb-data2", 12005, GameVersion::BB, ServerBehavior::DATA_SERVER_BB},
{"bb-login", 12008, GameVersion::BB, ServerBehavior::LOGIN_SERVER},
{"pc-lobby", 9420, GameVersion::PC, ServerBehavior::LOBBY_SERVER},
{"gc-lobby", 9421, GameVersion::GC, ServerBehavior::LOBBY_SERVER},
{"bb-lobby", 9422, GameVersion::BB, ServerBehavior::LOBBY_SERVER},
{"pc-proxy", 9520, GameVersion::PC, ServerBehavior::PROXY_SERVER},
{"gc-proxy", 9521, GameVersion::GC, ServerBehavior::PROXY_SERVER},
{"bb-proxy", 9522, GameVersion::BB, ServerBehavior::PROXY_SERVER},
});
template <typename T> template <typename T>
vector<T> parse_int_vector(shared_ptr<const JSONObject> o) { vector<T> parse_int_vector(shared_ptr<const JSONObject> o) {
@@ -88,8 +72,7 @@ void populate_state_from_config(shared_ptr<ServerState> s,
} }
} catch (const out_of_range&) { } } catch (const out_of_range&) { }
// TODO: make this configurable s->set_port_configuration(parse_port_configuration(d.at("PortConfiguration")));
s->set_port_configuration(default_port_to_behavior);
auto enemy_categories = parse_int_vector<uint32_t>(d.at("CommonItemDropRates-Enemy")); auto enemy_categories = parse_int_vector<uint32_t>(d.at("CommonItemDropRates-Enemy"));
auto box_categories = parse_int_vector<uint32_t>(d.at("CommonItemDropRates-Box")); auto box_categories = parse_int_vector<uint32_t>(d.at("CommonItemDropRates-Box"));
@@ -297,6 +280,17 @@ void drop_privileges(const string& username) {
int main(int, char**) { int main(int, char**) {
if (true) {
static const string seed("\x33\xF7\x40\xA2\xB4\xFD\x5C\x07\xD8\x94\x09\x9F\x8B\x76\x35\xF9\x97\x76\x8B\x16\x5C\x73\x9F\x2E\xF1\x1F\x1A\xC0\xB9\x53\xFE\x59\xE4\xDD\xC5\xC8\x11\xA0\x78\xD5\x56\x5A\xF7\xC3\x47\xD5\xCA\x67", 0x30);
static const string encrypted_data("\x83\x9A\xE7\xE1\xDD\xB2\x41\x38", 0x08);
string decrypted_data = encrypted_data;
auto private_key = load_object_file<PSOBBEncryption::KeyFile>("system/blueburst/keys/default.nsk");
PSOBBEncryption crypt(private_key, seed.data(), seed.size());
crypt.decrypt(decrypted_data.data(), decrypted_data.size());
fprintf(stderr, "decrypted\n");
print_data(stderr, decrypted_data);
}
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
if (isatty(fileno(stderr))) { if (isatty(fileno(stderr))) {
@@ -344,7 +338,8 @@ int main(int, char**) {
log(INFO, "Opening sockets"); log(INFO, "Opening sockets");
for (const auto& it : state->name_to_port_config) { for (const auto& it : state->name_to_port_config) {
if (it.second->behavior == ServerBehavior::PROXY_SERVER) { const auto& pc = it.second;
if (pc->behavior == ServerBehavior::PROXY_SERVER) {
if (!state->proxy_server.get()) { if (!state->proxy_server.get()) {
log(INFO, "Starting proxy server"); log(INFO, "Starting proxy server");
state->proxy_server.reset(new ProxyServer(base, state)); state->proxy_server.reset(new ProxyServer(base, state));
@@ -355,18 +350,18 @@ int main(int, char**) {
// no way to ask the client which destination they want, so only one // no way to ask the client which destination they want, so only one
// destination is supported, and we have to manually specify the // destination is supported, and we have to manually specify the
// destination netloc here. // destination netloc here.
if (it.second->version == GameVersion::PATCH) { if (pc->version == GameVersion::PATCH) {
struct sockaddr_storage ss = make_sockaddr_storage( struct sockaddr_storage ss = make_sockaddr_storage(
state->proxy_destination_patch.first, state->proxy_destination_patch.first,
state->proxy_destination_patch.second).first; state->proxy_destination_patch.second).first;
state->proxy_server->listen(it.second->port, it.second->version, &ss); state->proxy_server->listen(pc->port, pc->version, &ss);
} if (it.second->version == GameVersion::BB) { } else if (pc->version == GameVersion::BB) {
struct sockaddr_storage ss = make_sockaddr_storage( struct sockaddr_storage ss = make_sockaddr_storage(
state->proxy_destination_bb.first, state->proxy_destination_bb.first,
state->proxy_destination_bb.second).first; state->proxy_destination_bb.second).first;
state->proxy_server->listen(it.second->port, it.second->version, &ss); state->proxy_server->listen(pc->port, pc->version, &ss);
} else { } else {
state->proxy_server->listen(it.second->port, it.second->version); state->proxy_server->listen(pc->port, pc->version);
} }
} }
} else { } else {
@@ -374,7 +369,10 @@ int main(int, char**) {
log(INFO, "Starting game server"); log(INFO, "Starting game server");
game_server.reset(new Server(base, state)); game_server.reset(new Server(base, state));
} }
game_server->listen("", it.second->port, it.second->version, it.second->behavior); string name = string_printf("%s (%s, %s) on port %hu",
pc->name.c_str(), name_for_version(pc->version),
name_for_server_behavior(pc->behavior), pc->port);
game_server->listen(name, "", pc->port, pc->version, pc->behavior);
} }
} }
+1 -21
View File
@@ -284,23 +284,6 @@ void PlayerBank::save(const string& filename) const {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
uint32_t compute_guild_card_checksum(const void* vdata, size_t size) {
const uint8_t* data = reinterpret_cast<const uint8_t*>(vdata);
uint32_t cs = 0xFFFFFFFF;
for (size_t offset = 0; offset < size; offset++) {
cs ^= data[offset];
for (size_t y = 0; y < 8; y++) {
if (!(cs & 1)) {
cs = (cs >> 1) & 0x7FFFFFFF;
} else {
cs = ((cs >> 1) & 0x7FFFFFFF) ^ 0xEDB88320;
}
}
}
return (cs ^ 0xFFFFFFFF);
}
ClientGameData::~ClientGameData() { ClientGameData::~ClientGameData() {
if (!this->bb_username.empty()) { if (!this->bb_username.empty()) {
if (this->account_data.get()) { if (this->account_data.get()) {
@@ -360,9 +343,6 @@ string ClientGameData::player_data_filename() const {
if (this->bb_username.empty()) { if (this->bb_username.empty()) {
throw logic_error("non-BB players do not have account data"); throw logic_error("non-BB players do not have account data");
} }
if (this->bb_player_index == 0) {
throw logic_error("0 is not a valid player index");
}
return string_printf("system/players/player_%s_%zu.nsc", return string_printf("system/players/player_%s_%zu.nsc",
this->bb_username.c_str(), this->bb_player_index + 1); this->bb_username.c_str(), this->bb_player_index + 1);
} }
@@ -477,7 +457,7 @@ void ClientGameData::import_player(const PSOPlayerDataBB& bb) {
} }
} }
PlayerBB ClientGameData::export_player_bb() const { PlayerBB ClientGameData::export_player_bb() {
auto account = this->account(); auto account = this->account();
auto player = this->player(); auto player = this->player();
+3 -1
View File
@@ -425,7 +425,9 @@ public:
void import_player(const PSOPlayerDataPC& pd); void import_player(const PSOPlayerDataPC& pd);
void import_player(const PSOPlayerDataGC& pd); void import_player(const PSOPlayerDataGC& pd);
void import_player(const PSOPlayerDataBB& pd); void import_player(const PSOPlayerDataBB& pd);
PlayerBB export_player_bb() const; // Note: this function is not const because it can cause player and account
// data to be loaded
PlayerBB export_player_bb();
}; };
+35 -15
View File
@@ -142,9 +142,7 @@ static bool process_server_pc_gc_patch_02_17(shared_ptr<ServerState> s,
session.log(INFO, "Existing license in linked session"); session.log(INFO, "Existing license in linked session");
// This isn't forwarded to the client, so don't recreate the client's crypts // This isn't forwarded to the client, so don't recreate the client's crypts
if (session.version == GameVersion::PATCH) { if ((session.version == GameVersion::PATCH) || (session.version == GameVersion::PC)) {
throw logic_error("patch session is indirect");
} else if (session.version == GameVersion::PC) {
session.server_input_crypt.reset(new PSOPCEncryption(cmd.server_key)); session.server_input_crypt.reset(new PSOPCEncryption(cmd.server_key));
session.server_output_crypt.reset(new PSOPCEncryption(cmd.client_key)); session.server_output_crypt.reset(new PSOPCEncryption(cmd.client_key));
} else if (session.version == GameVersion::GC) { } else if (session.version == GameVersion::GC) {
@@ -154,10 +152,15 @@ static bool process_server_pc_gc_patch_02_17(shared_ptr<ServerState> s,
throw invalid_argument("unsupported version"); throw invalid_argument("unsupported version");
} }
// Respond with an appropriate login command. We don't let the // Respond with an appropriate login command. We don't let the client do this
// client do this because it believes it already did (when it was // because it believes it already did (when it was in an unlinked session, or
// in an unlinked session). // in the patch server case, during the current session due to a hidden
if (session.version == GameVersion::PC) { // redirect).
if (session.version == GameVersion::PATCH) {
session.send_to_end(true, 0x02, 0x00);
return false;
} else if (session.version == GameVersion::PC) {
C_Login_PC_9D cmd; C_Login_PC_9D cmd;
if (session.remote_guild_card_number == 0) { if (session.remote_guild_card_number == 0) {
cmd.player_tag = 0xFFFF0000; cmd.player_tag = 0xFFFF0000;
@@ -390,8 +393,8 @@ static bool process_server_gc_E4(shared_ptr<ServerState>,
return true; return true;
} }
static bool process_server_19(shared_ptr<ServerState>, static bool process_server_game_19_patch_14(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { ProxyServer::LinkedSession& session, uint16_t command, uint32_t, string& data) {
// This weird maximum size is here to properly handle the version-split // This weird maximum size is here to properly handle the version-split
// command that some servers (including newserv) use on port 9100 // command that some servers (including newserv) use on port 9100
auto& cmd = check_size_t<S_Reconnect_19>(data, sizeof(S_Reconnect_19), 0xB0); auto& cmd = check_size_t<S_Reconnect_19>(data, sizeof(S_Reconnect_19), 0xB0);
@@ -404,6 +407,23 @@ static bool process_server_19(shared_ptr<ServerState>,
if (!session.client_bev.get()) { if (!session.client_bev.get()) {
session.log(WARNING, "Received reconnect command with no destination present"); session.log(WARNING, "Received reconnect command with no destination present");
return false;
} else if (command == 0x14) {
// On the patch server, hide redirects from the client completely. The new
// destination server will presumably send a new 02 command to start
// encryption; it appears that PSOBB doesn't fail if this happens, and
// simply re-initializes its encryption appropriately.
session.server_input_crypt.reset();
session.server_output_crypt.reset();
struct sockaddr_in* dest_sin = reinterpret_cast<sockaddr_in*>(
&session.next_destination);
dest_sin->sin_family = AF_INET;
dest_sin->sin_addr.s_addr = cmd.address.load_raw();
dest_sin->sin_port = cmd.port;
session.connect();
return false;
} else { } else {
// If the client is on a virtual connection (fd < 0), only change // If the client is on a virtual connection (fd < 0), only change
@@ -427,8 +447,8 @@ static bool process_server_19(shared_ptr<ServerState>,
} else { } else {
cmd.port = session.local_port; cmd.port = session.local_port;
} }
return true;
} }
return true;
} }
static bool process_server_gc_1A_D5(shared_ptr<ServerState>, static bool process_server_gc_1A_D5(shared_ptr<ServerState>,
@@ -806,7 +826,7 @@ auto defh = process_default;
static process_command_t dc_server_handlers[0x100] = { static process_command_t dc_server_handlers[0x100] = {
/* 00 */ defh, defh, defh, defh, process_server_dc_pc_gc_04, defh, process_server_dc_pc_gc_06, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 00 */ defh, defh, defh, defh, process_server_dc_pc_gc_04, defh, process_server_dc_pc_gc_06, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, defh, defh, process_server_19, defh, defh, defh, defh, defh, defh, /* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, defh, defh, process_server_game_19_patch_14, defh, defh, defh, defh, defh, defh,
/* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 40 */ defh, process_server_41<S_GuildCardSearchResult_DC_GC_41>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 40 */ defh, process_server_41<S_GuildCardSearchResult_DC_GC_41>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
@@ -824,7 +844,7 @@ static process_command_t dc_server_handlers[0x100] = {
}; };
static process_command_t pc_server_handlers[0x100] = { static process_command_t pc_server_handlers[0x100] = {
/* 00 */ defh, defh, process_server_pc_gc_patch_02_17, defh, process_server_dc_pc_gc_04, defh, process_server_dc_pc_gc_06, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 00 */ defh, defh, process_server_pc_gc_patch_02_17, defh, process_server_dc_pc_gc_04, defh, process_server_dc_pc_gc_06, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, process_server_pc_gc_patch_02_17, defh, process_server_19, defh, defh, defh, defh, defh, defh, /* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, process_server_pc_gc_patch_02_17, defh, process_server_game_19_patch_14, defh, defh, defh, defh, defh, defh,
/* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 40 */ defh, process_server_41<S_GuildCardSearchResult_PC_41>, defh, defh, process_server_44_A6<S_OpenFile_PC_GC_44_A6>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 40 */ defh, process_server_41<S_GuildCardSearchResult_PC_41>, defh, defh, process_server_44_A6<S_OpenFile_PC_GC_44_A6>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
@@ -842,7 +862,7 @@ static process_command_t pc_server_handlers[0x100] = {
}; };
static process_command_t gc_server_handlers[0x100] = { static process_command_t gc_server_handlers[0x100] = {
/* 00 */ defh, defh, process_server_pc_gc_patch_02_17, defh, process_server_dc_pc_gc_04, defh, process_server_dc_pc_gc_06, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 00 */ defh, defh, process_server_pc_gc_patch_02_17, defh, process_server_dc_pc_gc_04, defh, process_server_dc_pc_gc_06, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, process_server_pc_gc_patch_02_17, defh, process_server_19, process_server_gc_1A_D5, defh, defh, defh, defh, defh, /* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, process_server_pc_gc_patch_02_17, defh, process_server_game_19_patch_14, process_server_gc_1A_D5, defh, defh, defh, defh, defh,
/* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 40 */ defh, process_server_41<S_GuildCardSearchResult_DC_GC_41>, defh, defh, process_server_44_A6<S_OpenFile_PC_GC_44_A6>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 40 */ defh, process_server_41<S_GuildCardSearchResult_DC_GC_41>, defh, defh, process_server_44_A6<S_OpenFile_PC_GC_44_A6>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
@@ -860,7 +880,7 @@ static process_command_t gc_server_handlers[0x100] = {
}; };
static process_command_t bb_server_handlers[0x100] = { static process_command_t bb_server_handlers[0x100] = {
/* 00 */ defh, defh, defh, process_server_bb_03, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 00 */ defh, defh, defh, process_server_bb_03, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, defh, defh, process_server_19, defh, defh, defh, defh, defh, defh, /* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, defh, defh, process_server_game_19_patch_14, defh, defh, defh, defh, defh, defh,
/* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 40 */ defh, process_server_41<S_GuildCardSearchResult_BB_41>, defh, defh, process_server_44_A6<S_OpenFile_BB_44_A6>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 40 */ defh, process_server_41<S_GuildCardSearchResult_BB_41>, defh, defh, process_server_44_A6<S_OpenFile_BB_44_A6>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
@@ -878,7 +898,7 @@ static process_command_t bb_server_handlers[0x100] = {
}; };
static process_command_t patch_server_handlers[0x100] = { static process_command_t patch_server_handlers[0x100] = {
/* 00 */ defh, defh, process_server_pc_gc_patch_02_17, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 00 */ defh, defh, process_server_pc_gc_patch_02_17, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 10 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 10 */ defh, defh, defh, defh, process_server_game_19_patch_14, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 40 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, /* 40 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
+65 -66
View File
@@ -28,23 +28,6 @@ extern FileContentsCache file_cache;
enum ClientStateBB {
// Initial connection; server will redirect client to another port
INITIAL_LOGIN = 0x00,
// Second connection; server will send client game data and account data
DOWNLOAD_DATA = 0x01,
// Third connection; client will show the choose character menu
CHOOSE_PLAYER = 0x02,
// Fourth connection; used for saving characters only. If you do not create a
// character, the server sets this state during the third connection so this
// connection is effectively skipped.
SAVE_PLAYER = 0x03,
// Last connection; redirects client to login server
SHIP_SELECT = 0x04,
};
vector<MenuItem> quest_categories_menu({ vector<MenuItem> quest_categories_menu({
MenuItem(static_cast<uint32_t>(QuestCategory::RETRIEVAL), u"Retrieval", u"$E$C6Quests that involve\nretrieving an object", 0), MenuItem(static_cast<uint32_t>(QuestCategory::RETRIEVAL), u"Retrieval", u"$E$C6Quests that involve\nretrieving an object", 0),
MenuItem(static_cast<uint32_t>(QuestCategory::EXTERMINATION), u"Extermination", u"$E$C6Quests that involve\ndestroying all\nmonsters", 0), MenuItem(static_cast<uint32_t>(QuestCategory::EXTERMINATION), u"Extermination", u"$E$C6Quests that involve\ndestroying all\nmonsters", 0),
@@ -103,9 +86,9 @@ void process_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c)
} }
break; break;
case ServerBehavior::LOBBY_SERVER:
case ServerBehavior::DATA_SERVER_BB: case ServerBehavior::DATA_SERVER_BB:
case ServerBehavior::PATCH_SERVER: case ServerBehavior::PATCH_SERVER:
case ServerBehavior::LOBBY_SERVER:
send_server_init(s, c, false); send_server_init(s, c, false);
break; break;
@@ -116,8 +99,11 @@ void process_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c)
} }
void process_login_complete(shared_ptr<ServerState> s, shared_ptr<Client> c) { void process_login_complete(shared_ptr<ServerState> s, shared_ptr<Client> c) {
if (c->server_behavior == ServerBehavior::LOGIN_SERVER) { // On the BB data server, this function is called only on the last connection
// on the login server, send the ep3 updates and the main menu or welcome // (when we should send ths ship select menu).
if ((c->server_behavior == ServerBehavior::LOGIN_SERVER) ||
(c->server_behavior == ServerBehavior::DATA_SERVER_BB)) {
// On the login server, send the ep3 updates and the main menu or welcome
// message // message
if (c->flags & Client::Flag::EPISODE_3) { if (c->flags & Client::Flag::EPISODE_3) {
send_ep3_card_list_update(c); send_ep3_card_list_update(c);
@@ -285,7 +271,7 @@ void process_login_d_e_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
// If we can't import the config, assume that the client was not connected // If we can't import the config, assume that the client was not connected
// to newserv before, so we should show the welcome message. // to newserv before, so we should show the welcome message.
c->flags |= Client::Flag::AT_WELCOME_MESSAGE; c->flags |= Client::Flag::AT_WELCOME_MESSAGE;
c->bb_game_state = 0; c->bb_game_state = ClientStateBB::INITIAL_LOGIN;
c->bb_player_index = 0; c->bb_player_index = 0;
} }
@@ -342,10 +328,12 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
} }
try { try {
c->import_config(cmd.client_config.cfg.cfg); c->import_config(cmd.client_config.cfg);
c->bb_game_state++; if (c->bb_game_state < ClientStateBB::IN_GAME) {
c->bb_game_state++;
}
} catch (const invalid_argument&) { } catch (const invalid_argument&) {
c->bb_game_state = 0; c->bb_game_state = ClientStateBB::INITIAL_LOGIN;
c->bb_player_index = 0; c->bb_player_index = 0;
} }
@@ -369,9 +357,11 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
process_login_complete(s, c); process_login_complete(s, c);
break; break;
case ClientStateBB::IN_GAME:
break;
default: default:
send_reconnect(c, s->connect_address_for_client(c), throw runtime_error("invalid bb game state");
s->name_to_port_config.at("bb-login")->port);
} }
} }
@@ -1213,14 +1203,15 @@ void process_player_preview_request_bb(shared_ptr<ServerState>, shared_ptr<Clien
ClientGameData temp_gd; ClientGameData temp_gd;
temp_gd.serial_number = c->license->serial_number; temp_gd.serial_number = c->license->serial_number;
temp_gd.bb_username = c->license->username; temp_gd.bb_username = c->license->username;
temp_gd.bb_player_index = c->bb_player_index; temp_gd.bb_player_index = cmd.player_index;
try { try {
auto preview = temp_gd.player()->disp.to_preview(); auto preview = temp_gd.player()->disp.to_preview();
send_player_preview_bb(c, cmd.player_index, &preview); send_player_preview_bb(c, cmd.player_index, &preview);
} catch (const exception&) { } catch (const exception& e) {
// Player doesn't exist // Player doesn't exist
log(INFO, "[BB debug] No player in slot: %s", e.what());
send_player_preview_bb(c, cmd.player_index, nullptr); send_player_preview_bb(c, cmd.player_index, nullptr);
} }
} }
@@ -1228,11 +1219,11 @@ void process_player_preview_request_bb(shared_ptr<ServerState>, shared_ptr<Clien
void process_client_checksum_bb(shared_ptr<ServerState>, shared_ptr<Client> c, void process_client_checksum_bb(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t command, uint32_t, const string& data) { uint16_t command, uint32_t, const string& data) {
check_size_v(data.size(), 0);
if (command == 0x01E8) { if (command == 0x01E8) {
check_size_v(data.size(), 8);
send_accept_client_checksum_bb(c); send_accept_client_checksum_bb(c);
} else if (command == 0x03E8) { } else if (command == 0x03E8) {
check_size_v(data.size(), 0);
send_guild_card_header_bb(c); send_guild_card_header_bb(c);
} else { } else {
throw invalid_argument("unimplemented command"); throw invalid_argument("unimplemented command");
@@ -1241,18 +1232,20 @@ void process_client_checksum_bb(shared_ptr<ServerState>, shared_ptr<Client> c,
void process_guild_card_data_request_bb(shared_ptr<ServerState>, shared_ptr<Client> c, void process_guild_card_data_request_bb(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { uint16_t, uint32_t, const string& data) {
const auto& cmd = check_size_t<C_GuildCardDataRequest_BB_DC>(data); const auto& cmd = check_size_t<C_GuildCardDataRequest_BB_03DC>(data);
if (cmd.cont) { if (cmd.cont) {
send_guild_card_chunk_bb(c, cmd.chunk_index); send_guild_card_chunk_bb(c, cmd.chunk_index);
} }
} }
void process_stream_file_request_bb(shared_ptr<ServerState>, shared_ptr<Client> c, void process_stream_file_request_bb(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t command, uint32_t, const string& data) { uint16_t command, uint32_t flag, const string& data) {
check_size_v(data.size(), 0); check_size_v(data.size(), 0);
if (command == 0x04EB) { if (command == 0x04EB) {
send_stream_file_bb(c); send_stream_file_index_bb(c);
} else if (command == 0x03EB) {
send_stream_file_chunk_bb(c, flag);
} else { } else {
throw invalid_argument("unimplemented command"); throw invalid_argument("unimplemented command");
} }
@@ -1260,14 +1253,25 @@ void process_stream_file_request_bb(shared_ptr<ServerState>, shared_ptr<Client>
void process_create_character_bb(shared_ptr<ServerState> s, shared_ptr<Client> c, void process_create_character_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { uint16_t, uint32_t, const string& data) {
const auto& cmd = check_size_t<C_CreateCharacter_BB_E5>(data); const auto& cmd = check_size_t<SC_PlayerPreview_CreateCharacter_BB_E5>(data);
if (!c->license) { if (!c->license) {
send_message_box(c, u"$C6You are not logged in."); send_message_box(c, u"$C6You are not logged in.");
return; return;
} }
if (!c->game_data.player()->disp.name.empty()) {
send_message_box(c, u"$C6You have already loaded a character."); // Hack: We use the security data to indicate to the server which phase the
// client is in (download data, character select, lobby, etc.). This presents
// a problem: the client expects to get an E4 (approve player choice) command
// immediately after the E5 (create character) command, but the client also
// will disconnect immediately after it receives that command. If we send an
// E6 before the E5 to update the security data (setting the correct next
// state), then the client sends ANOTHER E5, but this time it's blank! So, to
// be able to both create characters correctly and set security data
// correctly, we need to process only the first E5, and ignore the second. We
// do this by only creating a player if the current connection has no loaded
// player data.
if (c->game_data.player(false).get()) {
return; return;
} }
@@ -1470,6 +1474,8 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
{1, 1, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 1, 1, 3, {1, 1, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 1, 1, 3,
3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}; 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};
// A player's actual level is their displayed level - 1, so the minimums for
// Episode 1 (for example) are actually 1, 20, 40, 80.
static const uint32_t default_minimum_levels[3][4] = { static const uint32_t default_minimum_levels[3][4] = {
{0, 19, 39, 79}, // episode 1 {0, 19, 39, 79}, // episode 1
{0, 29, 49, 89}, // episode 2 {0, 29, 49, 89}, // episode 2
@@ -1537,42 +1543,33 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
const auto* bp_subtable = s->battle_params->get_subtable(game->mode == 3, const auto* bp_subtable = s->battle_params->get_subtable(game->mode == 3,
game->episode - 1, game->difficulty); game->episode - 1, game->difficulty);
if (game->mode == 3) { const char* type_chars = (game->mode == 3) ? "sm" : "m";
if (episode > 0 && episode < 4) { if (episode > 0 && episode < 4) {
variation_maxes = variation_maxes_solo[episode - 1]; variation_maxes = (game->mode == 3) ? variation_maxes_solo[episode - 1] : variation_maxes_online[episode - 1];
} }
for (size_t x = 0; x < 0x10; x++) {
for (const char* type_char = "sm"; *type_char; type_char++) { for (size_t x = 0; x < 0x10; x++) {
try { for (const char* type = type_chars; *type; type++) {
auto filename = string_printf(
"system/blueburst/map/%c%hhu%zu%" PRIu32 "%" PRIu32 ".dat",
*type_char, game->episode, x,
game->variations.data()[x * 2].load(),
game->variations.data()[(x * 2) + 1].load());
game->enemies = load_map(filename.c_str(), game->episode,
game->difficulty, bp_subtable, false);
break;
} catch (const exception& e) { }
}
if (game->enemies.empty()) {
throw runtime_error("failed to load any map data");
}
}
} else {
if (episode > 0 && episode < 4) {
variation_maxes = variation_maxes_online[episode - 1];
}
for (size_t x = 0; x < 0x10; x++) {
auto filename = string_printf( auto filename = string_printf(
"system/blueburst/map/m%hhu%zu%" PRIu32 "%" PRIu32 ".dat", "system/blueburst/map/%c%hhX%zX%" PRIX32 "%" PRIX32 ".dat",
game->episode, x, *type, game->episode, x,
game->variations.data()[x * 2].load(), game->variations.data()[x * 2].load(),
game->variations.data()[(x * 2) + 1].load()); game->variations.data()[(x * 2) + 1].load());
game->enemies = load_map(filename.c_str(), game->episode, try {
game->difficulty, bp_subtable, false); game->enemies = load_map(filename.c_str(), game->episode,
game->difficulty, bp_subtable, false);
log(INFO, "Loaded map %s", filename.c_str());
break;
} catch (const exception& e) {
log(WARNING, "Failed to load map %s: %s", filename.c_str(), e.what());
}
} }
} }
if (game->enemies.empty()) {
throw runtime_error("failed to load any map data");
}
} else { } else {
// In non-BB games, just set the variations (we don't track items/enemies/ // In non-BB games, just set the variations (we don't track items/enemies/
// etc.) // etc.)
@@ -1686,6 +1683,8 @@ void process_team_command_bb(shared_ptr<ServerState>, shared_ptr<Client> c,
if (command == 0x01EA) { if (command == 0x01EA) {
send_lobby_message_box(c, u"$C6Teams are not supported."); send_lobby_message_box(c, u"$C6Teams are not supported.");
} else if (command == 0x14EA) {
// Do nothing (for now)
} else { } else {
throw invalid_argument("unimplemented team command"); throw invalid_argument("unimplemented team command");
} }
+3 -3
View File
@@ -220,7 +220,7 @@ static void process_subcommand_switch_state_changed(shared_ptr<ServerState>,
log(INFO, "[Switch assist] Replaying previous enable command"); log(INFO, "[Switch assist] Replaying previous enable command");
forward_subcommand(l, c, command, flag, &c->last_switch_enabled_command, forward_subcommand(l, c, command, flag, &c->last_switch_enabled_command,
sizeof(c->last_switch_enabled_command)); sizeof(c->last_switch_enabled_command));
send_command(c, command, flag, c->last_switch_enabled_command); send_command_t(c, command, flag, c->last_switch_enabled_command);
} }
c->last_switch_enabled_command = cmd; c->last_switch_enabled_command = cmd;
} }
@@ -756,7 +756,7 @@ static void process_subcommand_phase_setup(shared_ptr<ServerState>,
0.0f, 0.0f,
0xE0AEDC0100000002, 0xE0AEDC0100000002,
}; };
send_command(c, 0x62, l->leader_id, req); send_command_t(c, 0x62, l->leader_id, req);
} }
} }
} }
@@ -904,7 +904,7 @@ static void process_subcommand_identify_item_bb(shared_ptr<ServerState>,
res.client_id = c->lobby_client_id; res.client_id = c->lobby_client_id;
res.unused = 0; res.unused = 0;
res.item = c->game_data.identify_result.data; res.item = c->game_data.identify_result.data;
send_command(l, 0x60, 0x00, res); send_command_t(l, 0x60, 0x00, res);
} else { } else {
forward_subcommand(l, c, command, flag, data); forward_subcommand(l, c, command, flag, data);
+101 -85
View File
@@ -9,6 +9,7 @@
#include <phosg/Random.hh> #include <phosg/Random.hh>
#include <phosg/Strings.hh> #include <phosg/Strings.hh>
#include <phosg/Time.hh> #include <phosg/Time.hh>
#include <phosg/Hash.hh>
#include "PSOProtocol.hh" #include "PSOProtocol.hh"
#include "CommandFormats.hh" #include "CommandFormats.hh"
@@ -186,7 +187,7 @@ void send_server_init_dc_pc_gc(shared_ptr<Client> c,
auto cmd = prepare_server_init_contents_dc_pc_gc( auto cmd = prepare_server_init_contents_dc_pc_gc(
initial_connection, server_key, client_key); initial_connection, server_key, client_key);
send_command(c, command, 0x00, cmd); send_command_t(c, command, 0x00, cmd);
switch (c->version) { switch (c->version) {
case GameVersion::DC: case GameVersion::DC:
@@ -209,7 +210,7 @@ void send_server_init_bb(shared_ptr<ServerState> s, shared_ptr<Client> c) {
random_data(cmd.server_key.data(), cmd.server_key.bytes()); random_data(cmd.server_key.data(), cmd.server_key.bytes());
random_data(cmd.client_key.data(), cmd.client_key.bytes()); random_data(cmd.client_key.data(), cmd.client_key.bytes());
cmd.after_message = anti_copyright; cmd.after_message = anti_copyright;
send_command(c, 0x03, 0x00, cmd); send_command_t(c, 0x03, 0x00, cmd);
static const string expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8); static const string expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
shared_ptr<PSOBBMultiKeyClientEncryption> client_encr(new PSOBBMultiKeyClientEncryption( shared_ptr<PSOBBMultiKeyClientEncryption> client_encr(new PSOBBMultiKeyClientEncryption(
@@ -226,7 +227,7 @@ void send_server_init_patch(shared_ptr<Client> c) {
cmd.copyright = patch_server_copyright; cmd.copyright = patch_server_copyright;
cmd.server_key = server_key; cmd.server_key = server_key;
cmd.client_key = client_key; cmd.client_key = client_key;
send_command(c, 0x02, 0x00, cmd); send_command_t(c, 0x02, 0x00, cmd);
c->crypt_out.reset(new PSOPCEncryption(server_key)); c->crypt_out.reset(new PSOPCEncryption(server_key));
c->crypt_in.reset(new PSOPCEncryption(client_key)); c->crypt_in.reset(new PSOPCEncryption(client_key));
@@ -259,17 +260,17 @@ void send_update_client_config(shared_ptr<Client> c) {
cmd.player_tag = 0x00010000; cmd.player_tag = 0x00010000;
cmd.guild_card_number = c->license->serial_number; cmd.guild_card_number = c->license->serial_number;
cmd.cfg = c->export_config(); cmd.cfg = c->export_config();
send_command(c, 0x04, 0x00, cmd); send_command_t(c, 0x04, 0x00, cmd);
} }
void send_reconnect(shared_ptr<Client> c, uint32_t address, uint16_t port) { void send_reconnect(shared_ptr<Client> c, uint32_t address, uint16_t port) {
S_Reconnect_19 cmd = {address, port, 0}; S_Reconnect_19 cmd = {address, port, 0};
send_command(c, 0x19, 0x00, cmd); send_command_t(c, 0x19, 0x00, cmd);
} }
// sends the command (first used by Schthack) that separates PC and GC users // Sends the command (first used by Schthack) that separates PC and GC users
// that connect on the same port // that connect on the same port
void send_pc_gc_split_reconnect(shared_ptr<Client> c, uint32_t address, void send_pc_gc_split_reconnect(shared_ptr<Client> c, uint32_t address,
uint16_t pc_port, uint16_t gc_port) { uint16_t pc_port, uint16_t gc_port) {
@@ -281,7 +282,7 @@ void send_pc_gc_split_reconnect(shared_ptr<Client> c, uint32_t address,
cmd.gc_size = 0x97; cmd.gc_size = 0x97;
cmd.gc_address = address; cmd.gc_address = address;
cmd.gc_port = gc_port; cmd.gc_port = gc_port;
send_command(c, 0x19, 0x00, cmd); send_command_t(c, 0x19, 0x00, cmd);
} }
@@ -294,11 +295,11 @@ void send_client_init_bb(shared_ptr<Client> c, uint32_t error) {
cmd.team_id = static_cast<uint32_t>(random_object<uint32_t>()); cmd.team_id = static_cast<uint32_t>(random_object<uint32_t>());
cmd.cfg = c->export_config_bb(); cmd.cfg = c->export_config_bb();
cmd.caps = 0x00000102; cmd.caps = 0x00000102;
send_command(c, 0x00E6, 0x00000000, cmd); send_command_t(c, 0x00E6, 0x00000000, cmd);
} }
void send_team_and_key_config_bb(shared_ptr<Client> c) { void send_team_and_key_config_bb(shared_ptr<Client> c) {
send_command(c, 0x00E2, 0x00000000, c->game_data.account()->key_config); send_command_t(c, 0x00E2, 0x00000000, c->game_data.account()->key_config);
} }
void send_player_preview_bb(shared_ptr<Client> c, uint8_t player_index, void send_player_preview_bb(shared_ptr<Client> c, uint8_t player_index,
@@ -307,24 +308,24 @@ void send_player_preview_bb(shared_ptr<Client> c, uint8_t player_index,
if (!preview) { if (!preview) {
// no player exists // no player exists
S_PlayerPreview_NoPlayer_BB_E4 cmd = {player_index, 0x00000002}; S_PlayerPreview_NoPlayer_BB_E4 cmd = {player_index, 0x00000002};
send_command(c, 0x00E4, 0x00000000, cmd); send_command_t(c, 0x00E4, 0x00000000, cmd);
} else { } else {
S_PlayerPreview_BB_E3 cmd = {player_index, *preview}; SC_PlayerPreview_CreateCharacter_BB_E5 cmd = {player_index, *preview};
send_command(c, 0x00E3, 0x00000000, cmd); send_command_t(c, 0x00E5, 0x00000000, cmd);
} }
} }
void send_accept_client_checksum_bb(shared_ptr<Client> c) { void send_accept_client_checksum_bb(shared_ptr<Client> c) {
S_AcceptClientChecksum_BB_02E8 cmd = {1, 0}; S_AcceptClientChecksum_BB_02E8 cmd = {1, 0};
send_command(c, 0x02E8, 0x00000000, cmd); send_command_t(c, 0x02E8, 0x00000000, cmd);
} }
void send_guild_card_header_bb(shared_ptr<Client> c) { void send_guild_card_header_bb(shared_ptr<Client> c) {
uint32_t checksum = compute_guild_card_checksum( uint32_t checksum = crc32(
&c->game_data.account()->guild_cards, sizeof(GuildCardFileBB)); &c->game_data.account()->guild_cards, sizeof(GuildCardFileBB));
S_GuildCardHeader_BB_01DC cmd = {1, 0x00000490, checksum}; S_GuildCardHeader_BB_01DC cmd = {1, sizeof(GuildCardFileBB), checksum};
send_command(c, 0x01DC, 0x00000000, cmd); send_command_t(c, 0x01DC, 0x00000000, cmd);
} }
void send_guild_card_chunk_bb(shared_ptr<Client> c, size_t chunk_index) { void send_guild_card_chunk_bb(shared_ptr<Client> c, size_t chunk_index) {
@@ -346,63 +347,78 @@ void send_guild_card_chunk_bb(shared_ptr<Client> c, size_t chunk_index) {
send_command(c, 0x02DC, 0x00000000, w.str()); send_command(c, 0x02DC, 0x00000000, w.str());
} }
void send_stream_file_bb(shared_ptr<Client> c) { static const vector<string> stream_file_entries = {
auto index_data = file_cache.get("system/blueburst/streamfile.ind"); "ItemMagEdit.prs",
if (index_data->size() % sizeof(S_StreamFileIndexEntry_BB_01EB)) { "ItemPMT.prs",
throw invalid_argument("stream file index not a multiple of entry size"); "BattleParamEntry.dat",
"BattleParamEntry_on.dat",
"BattleParamEntry_lab.dat",
"BattleParamEntry_lab_on.dat",
"BattleParamEntry_ep4.dat",
"BattleParamEntry_ep4_on.dat",
"PlyLevelTbl.prs",
};
void send_stream_file_index_bb(shared_ptr<Client> c) {
struct S_StreamFileIndexEntry_BB_01EB {
le_uint32_t size;
le_uint32_t checksum; // crc32 of file data
le_uint32_t offset; // offset in stream (== sum of all previous files' sizes)
ptext<char, 0x40> filename;
};
vector<S_StreamFileIndexEntry_BB_01EB> entries;
size_t offset = 0;
for (const string& filename : stream_file_entries) {
auto file_data = file_cache.get("system/blueburst/" + filename);
auto& e = entries.emplace_back();
e.size = file_data->size();
// TODO: memoize the checksum somewhere; computing it can be slow
e.checksum = crc32(file_data->data(), e.size);
e.offset = offset;
e.filename = filename;
offset += e.size;
} }
send_command_vt(c, 0x01EB, entries.size(), entries);
}
size_t entry_count = index_data->size() / sizeof(S_StreamFileIndexEntry_BB_01EB); void send_stream_file_chunk_bb(shared_ptr<Client> c, uint32_t chunk_index) {
send_command(c, 0x01EB, entry_count, index_data); auto contents = file_cache.get("<BB stream file>", +[]() -> string {
size_t bytes = 0;
for (const auto& name : stream_file_entries) {
bytes += file_cache.get("system/blueburst/" + name)->size();
}
auto* entries = reinterpret_cast<const S_StreamFileIndexEntry_BB_01EB*>(index_data->data()); string ret;
ret.reserve(bytes);
for (const auto& name : stream_file_entries) {
ret += *file_cache.get("system/blueburst/" + name);
}
return ret;
});
S_StreamFileChunk_BB_02EB chunk_cmd; S_StreamFileChunk_BB_02EB chunk_cmd;
chunk_cmd.chunk_index = 0; chunk_cmd.chunk_index = chunk_index;
size_t offset = sizeof(chunk_cmd.data) * chunk_index;
uint32_t buffer_offset = 0; if (offset > contents->size()) {
for (size_t x = 0; x < entry_count; x++) { throw runtime_error("client requested chunk beyond end of stream file");
string base_filename = entries[x].filename;
auto filename = "system/blueburst/" + base_filename;
auto file_data = file_cache.get(filename);
size_t file_data_remaining = file_data->size();
if (file_data_remaining != entries[x].size) {
throw invalid_argument(filename + " does not match size in stream file index");
}
while (file_data_remaining) {
size_t read_size = 0x6800 - buffer_offset;
if (read_size > file_data_remaining) {
read_size = file_data_remaining;
}
memcpy(&chunk_cmd.data[buffer_offset],
file_data->data() + file_data->size() - file_data_remaining, read_size);
// TODO: We probably should clear the rest of the buffer on the last chunk
buffer_offset += read_size;
file_data_remaining -= read_size;
if (buffer_offset == 0x6800) {
// Note: the client sends 0x03EB in response to these, but we'll just
// ignore them because we don't need any of the contents
send_command(c, 0x02EB, 0x00000000, chunk_cmd);
buffer_offset = 0;
chunk_cmd.chunk_index++;
}
}
if (buffer_offset > 0) {
send_command(c, 0x02EB, 0x00000000, &chunk_cmd, (buffer_offset + 15) & ~3);
}
} }
size_t bytes = min<size_t>(contents->size() - offset, sizeof(chunk_cmd.data));
memcpy(chunk_cmd.data, contents->data() + offset, bytes);
size_t cmd_size = offsetof(S_StreamFileChunk_BB_02EB, data) + bytes;
cmd_size = (cmd_size + 3) & ~3;
send_command(c, 0x02EB, 0x00000000, &chunk_cmd, cmd_size);
} }
void send_approve_player_choice_bb(shared_ptr<Client> c) { void send_approve_player_choice_bb(shared_ptr<Client> c) {
S_ApprovePlayerChoice_BB_00E4 cmd = {c->bb_player_index, 1}; S_ApprovePlayerChoice_BB_00E4 cmd = {c->bb_player_index, 1};
send_command(c, 0x00E4, 0x00000000, cmd); send_command_t(c, 0x00E4, 0x00000000, cmd);
} }
void send_complete_player_bb(shared_ptr<Client> c) { void send_complete_player_bb(shared_ptr<Client> c) {
send_command(c, 0x00E7, 0x00000000, c->game_data.export_player_bb()); send_command_t(c, 0x00E7, 0x00000000, c->game_data.export_player_bb());
} }
@@ -412,7 +428,7 @@ void send_complete_player_bb(shared_ptr<Client> c) {
void send_check_directory_patch(shared_ptr<Client> c, const string& dir) { void send_check_directory_patch(shared_ptr<Client> c, const string& dir) {
S_CheckDirectory_Patch_09 cmd = {dir}; S_CheckDirectory_Patch_09 cmd = {dir};
send_command(c, 0x09, 0x00, cmd); send_command_t(c, 0x09, 0x00, cmd);
} }
@@ -508,7 +524,7 @@ void send_simple_mail_gc(std::shared_ptr<Client> c, uint32_t from_guild_card_num
cmd.from_name = from_name; cmd.from_name = from_name;
cmd.to_guild_card_number = c->license->serial_number; cmd.to_guild_card_number = c->license->serial_number;
cmd.text = text; cmd.text = text;
send_command(c, 0x81, 0x00, cmd); send_command_t(c, 0x81, 0x00, cmd);
} }
void send_simple_mail(std::shared_ptr<Client> c, uint32_t from_guild_card_number, void send_simple_mail(std::shared_ptr<Client> c, uint32_t from_guild_card_number,
@@ -537,7 +553,7 @@ void send_info_board_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
e.message = c->game_data.player()->info_board; e.message = c->game_data.player()->info_board;
add_color_inplace(e.message); add_color_inplace(e.message);
} }
send_command(c, 0xD8, entries.size(), entries); send_command_vt(c, 0xD8, entries.size(), entries);
} }
void send_info_board(shared_ptr<Client> c, shared_ptr<Lobby> l) { void send_info_board(shared_ptr<Client> c, shared_ptr<Lobby> l) {
@@ -589,7 +605,7 @@ void send_card_search_result_t(
cmd.lobby_id = result->lobby_id; cmd.lobby_id = result->lobby_id;
cmd.name = result->game_data.player()->disp.name; cmd.name = result->game_data.player()->disp.name;
send_command(c, 0x41, 0x00, cmd); send_command_t(c, 0x41, 0x00, cmd);
} }
void send_card_search_result( void send_card_search_result(
@@ -628,7 +644,7 @@ void send_guild_card_pc_gc(shared_ptr<Client> c, shared_ptr<Client> source) {
cmd.reserved2 = 1; cmd.reserved2 = 1;
cmd.section_id = source->game_data.player()->disp.section_id; cmd.section_id = source->game_data.player()->disp.section_id;
cmd.char_class = source->game_data.player()->disp.char_class; cmd.char_class = source->game_data.player()->disp.char_class;
send_command(c, 0x62, c->lobby_client_id, cmd); send_command_t(c, 0x62, c->lobby_client_id, cmd);
} }
void send_guild_card_bb(shared_ptr<Client> c, shared_ptr<Client> source) { void send_guild_card_bb(shared_ptr<Client> c, shared_ptr<Client> source) {
@@ -644,7 +660,7 @@ void send_guild_card_bb(shared_ptr<Client> c, shared_ptr<Client> source) {
cmd.reserved2 = 1; cmd.reserved2 = 1;
cmd.section_id = source->game_data.player()->disp.section_id; cmd.section_id = source->game_data.player()->disp.section_id;
cmd.char_class = source->game_data.player()->disp.char_class; cmd.char_class = source->game_data.player()->disp.char_class;
send_command(c, 0x62, c->lobby_client_id, cmd); send_command_t(c, 0x62, c->lobby_client_id, cmd);
} }
void send_guild_card(shared_ptr<Client> c, shared_ptr<Client> source) { void send_guild_card(shared_ptr<Client> c, shared_ptr<Client> source) {
@@ -696,7 +712,7 @@ void send_menu_t(
e.text = item.name; e.text = item.name;
} }
send_command(c, is_info_menu ? 0x1F : 0x07, entries.size() - 1, entries); send_command_vt(c, is_info_menu ? 0x1F : 0x07, entries.size() - 1, entries);
} }
void send_menu(shared_ptr<Client> c, const u16string& menu_name, void send_menu(shared_ptr<Client> c, const u16string& menu_name,
@@ -749,7 +765,7 @@ void send_game_menu_t(shared_ptr<Client> c, shared_ptr<ServerState> s) {
e.name = l->name; e.name = l->name;
} }
send_command(c, 0x08, entries.size() - 1, entries); send_command_vt(c, 0x08, entries.size() - 1, entries);
} }
void send_game_menu(shared_ptr<Client> c, shared_ptr<ServerState> s) { void send_game_menu(shared_ptr<Client> c, shared_ptr<ServerState> s) {
@@ -777,7 +793,7 @@ void send_quest_menu_t(
e.short_desc = quest->short_description; e.short_desc = quest->short_description;
add_color_inplace(e.short_desc); add_color_inplace(e.short_desc);
} }
send_command(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries); send_command_vt(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries);
} }
template <typename EntryT> template <typename EntryT>
@@ -795,7 +811,7 @@ void send_quest_menu_t(
e.short_desc = item.description; e.short_desc = item.description;
add_color_inplace(e.short_desc); add_color_inplace(e.short_desc);
} }
send_command(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries); send_command_vt(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries);
} }
void send_quest_menu(shared_ptr<Client> c, uint32_t menu_id, void send_quest_menu(shared_ptr<Client> c, uint32_t menu_id,
@@ -843,7 +859,7 @@ void send_lobby_list(shared_ptr<Client> c, shared_ptr<ServerState> s) {
e.unused = 0; e.unused = 0;
} }
send_command(c, 0x83, entries.size(), entries); send_command_vt(c, 0x83, entries.size(), entries);
} }
@@ -1020,7 +1036,7 @@ void send_player_join_notification(shared_ptr<Client> c,
void send_player_leave_notification(shared_ptr<Lobby> l, uint8_t leaving_client_id) { void send_player_leave_notification(shared_ptr<Lobby> l, uint8_t leaving_client_id) {
S_LeaveLobby_66_69 cmd = {leaving_client_id, l->leader_id, 0}; S_LeaveLobby_66_69 cmd = {leaving_client_id, l->leader_id, 0};
send_command(l, l->is_game() ? 0x66 : 0x69, leaving_client_id, cmd); send_command_t(l, l->is_game() ? 0x66 : 0x69, leaving_client_id, cmd);
} }
void send_get_player_info(shared_ptr<Client> c) { void send_get_player_info(shared_ptr<Client> c) {
@@ -1045,7 +1061,7 @@ void send_arrow_update(shared_ptr<Lobby> l) {
e.arrow_color = l->clients[x]->lobby_arrow_color; e.arrow_color = l->clients[x]->lobby_arrow_color;
} }
send_command(l, 0x88, entries.size(), entries); send_command_vt(l, 0x88, entries.size(), entries);
} }
// tells the player that the joining player is done joining, and the game can resume // tells the player that the joining player is done joining, and the game can resume
@@ -1086,7 +1102,7 @@ void send_player_stats_change(shared_ptr<Lobby> l, shared_ptr<Client> c,
} }
} }
send_command(l, 0x60, 0x00, subs); send_command_vt(l, 0x60, 0x00, subs);
} }
void send_warp(shared_ptr<Client> c, uint32_t area) { void send_warp(shared_ptr<Client> c, uint32_t area) {
@@ -1139,7 +1155,7 @@ void send_drop_item(shared_ptr<Lobby> l, const ItemData& item,
bool from_enemy, uint8_t area, float x, float z, uint16_t request_id) { bool from_enemy, uint8_t area, float x, float z, uint16_t request_id) {
G_DropItem_6x5F cmd = { G_DropItem_6x5F cmd = {
0x5F, 0x0A, 0x0000, area, from_enemy, request_id, x, z, 0, item, 0}; 0x5F, 0x0A, 0x0000, area, from_enemy, request_id, x, z, 0, item, 0};
send_command(l, 0x60, 0x00, cmd); send_command_t(l, 0x60, 0x00, cmd);
} }
// notifies other players that a stack was split and part of it dropped (a new item was created) // notifies other players that a stack was split and part of it dropped (a new item was created)
@@ -1149,7 +1165,7 @@ void send_drop_stacked_item(shared_ptr<Lobby> l, const ItemData& item,
// GC sends {0, item} (the last two fields in the struct are switched). // GC sends {0, item} (the last two fields in the struct are switched).
G_DropStackedItem_6x5D cmd = { G_DropStackedItem_6x5D cmd = {
0x5D, 0x09, 0x00, 0x00, area, 0, x, z, item, 0}; 0x5D, 0x09, 0x00, 0x00, area, 0, x, z, item, 0};
send_command(l, 0x60, 0x00, cmd); send_command_t(l, 0x60, 0x00, cmd);
} }
// notifies other players that an item was picked up // notifies other players that an item was picked up
@@ -1157,7 +1173,7 @@ void send_pick_up_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
uint32_t item_id, uint8_t area) { uint32_t item_id, uint8_t area) {
G_PickUpItem_6x59 cmd = { G_PickUpItem_6x59 cmd = {
0x59, 0x03, c->lobby_client_id, c->lobby_client_id, area, item_id}; 0x59, 0x03, c->lobby_client_id, c->lobby_client_id, area, item_id};
send_command(l, 0x60, 0x00, cmd); send_command_t(l, 0x60, 0x00, cmd);
} }
// creates an item in a player's inventory (used for withdrawing items from the bank) // creates an item in a player's inventory (used for withdrawing items from the bank)
@@ -1165,7 +1181,7 @@ void send_create_inventory_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
const ItemData& item) { const ItemData& item) {
G_CreateInventoryItem_BB_6xBE cmd = { G_CreateInventoryItem_BB_6xBE cmd = {
0xBE, 0x07, c->lobby_client_id, item, 0}; 0xBE, 0x07, c->lobby_client_id, item, 0};
send_command(l, 0x60, 0x00, cmd); send_command_t(l, 0x60, 0x00, cmd);
} }
// destroys an item // destroys an item
@@ -1173,7 +1189,7 @@ void send_destroy_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
uint32_t item_id, uint32_t amount) { uint32_t item_id, uint32_t amount) {
G_ItemSubcommand cmd = { G_ItemSubcommand cmd = {
0x29, 0x03, c->lobby_client_id, 0x00, item_id, amount}; 0x29, 0x03, c->lobby_client_id, 0x00, item_id, amount};
send_command(l, 0x60, 0x00, cmd); send_command_t(l, 0x60, 0x00, cmd);
} }
// sends the player their bank data // sends the player their bank data
@@ -1188,7 +1204,7 @@ void send_bank(shared_ptr<Client> c) {
size_t size = 8 + sizeof(cmd) + items.size() * sizeof(PlayerBankItem); size_t size = 8 + sizeof(cmd) + items.size() * sizeof(PlayerBankItem);
cmd.size = size; cmd.size = size;
send_command(c, 0x6C, 0x00, cmd, items); send_command_t_vt(c, 0x6C, 0x00, cmd, items);
} }
// sends the player a shop's contents // sends the player a shop's contents
@@ -1241,7 +1257,7 @@ void send_level_up(shared_ptr<Lobby> l, shared_ptr<Client> c) {
stats.dfp, stats.dfp,
stats.ata, stats.ata,
c->game_data.player()->disp.level}; c->game_data.player()->disp.level};
send_command(l, 0x60, 0x00, cmd); send_command_t(l, 0x60, 0x00, cmd);
} }
// gives a player EXP // gives a player EXP
@@ -1249,7 +1265,7 @@ void send_give_experience(shared_ptr<Lobby> l, shared_ptr<Client> c,
uint32_t amount) { uint32_t amount) {
G_GiveExperience_BB_6xBF cmd = { G_GiveExperience_BB_6xBF cmd = {
0xBF, sizeof(G_GiveExperience_BB_6xBF) / 4, c->lobby_client_id, 0, amount}; 0xBF, sizeof(G_GiveExperience_BB_6xBF) / 4, c->lobby_client_id, 0, amount};
send_command(l, 0x60, 0x00, cmd); send_command_t(l, 0x60, 0x00, cmd);
} }
@@ -1272,7 +1288,7 @@ void send_ep3_card_list_update(shared_ptr<Client> c) {
void send_ep3_rank_update(shared_ptr<Client> c) { void send_ep3_rank_update(shared_ptr<Client> c) {
S_RankUpdate_GC_Ep3_B7 cmd = { S_RankUpdate_GC_Ep3_B7 cmd = {
0, "\0\0\0\0\0\0\0\0\0\0\0", 0x00FFFFFF, 0x00FFFFFF, 0xFFFFFFFF}; 0, "\0\0\0\0\0\0\0\0\0\0\0", 0x00FFFFFF, 0x00FFFFFF, 0xFFFFFFFF};
send_command(c, 0xB7, 0x00, cmd); send_command_t(c, 0xB7, 0x00, cmd);
} }
// sends the map list (used for battle setup) to all players in a game // sends the map list (used for battle setup) to all players in a game
@@ -1327,7 +1343,7 @@ void send_quest_open_file_t(
cmd.file_size = file_size; cmd.file_size = file_size;
cmd.name = "PSO/" + quest_name; cmd.name = "PSO/" + quest_name;
cmd.filename = filename.c_str(); cmd.filename = filename.c_str();
send_command(c, is_download_quest ? 0xA6 : 0x44, 0x00, cmd); send_command_t(c, is_download_quest ? 0xA6 : 0x44, 0x00, cmd);
} }
void send_quest_file_chunk( void send_quest_file_chunk(
@@ -1349,7 +1365,7 @@ void send_quest_file_chunk(
} }
cmd.data_size = size; cmd.data_size = size;
send_command(c, is_download_quest ? 0xA7 : 0x13, chunk_index, cmd); send_command_t(c, is_download_quest ? 0xA7 : 0x13, chunk_index, cmd);
} }
void send_quest_file(shared_ptr<Client> c, const string& quest_name, void send_quest_file(shared_ptr<Client> c, const string& quest_name,
+10 -9
View File
@@ -62,26 +62,26 @@ inline void send_command(std::shared_ptr<ServerState> s, uint16_t command,
} }
template <typename TargetT, typename StructT> template <typename TargetT, typename StructT>
static void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag, static void send_command_t(std::shared_ptr<TargetT> c, uint16_t command,
const StructT& data) { uint32_t flag, const StructT& data) {
send_command(c, command, flag, &data, sizeof(data)); send_command(c, command, flag, &data, sizeof(data));
} }
template <typename TargetT> template <typename TargetT>
static void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag, static void send_command(std::shared_ptr<TargetT> c, uint16_t command,
const std::string& data) { uint32_t flag, const std::string& data) {
send_command(c, command, flag, data.data(), data.size()); send_command(c, command, flag, data.data(), data.size());
} }
template <typename TargetT, typename StructT> template <typename TargetT, typename StructT>
void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag, void send_command_vt(std::shared_ptr<TargetT> c, uint16_t command,
const std::vector<StructT>& data) { uint32_t flag, const std::vector<StructT>& data) {
send_command(c, command, flag, data.data(), data.size() * sizeof(StructT)); send_command(c, command, flag, data.data(), data.size() * sizeof(StructT));
} }
template <typename TargetT, typename StructT, typename EntryT> template <typename TargetT, typename StructT, typename EntryT>
void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag, void send_command_t_vt(std::shared_ptr<TargetT> c, uint16_t command,
const StructT& data, const std::vector<EntryT>& array_data) { uint32_t flag, const StructT& data, const std::vector<EntryT>& array_data) {
std::string all_data(reinterpret_cast<const char*>(&data), sizeof(StructT)); std::string all_data(reinterpret_cast<const char*>(&data), sizeof(StructT));
all_data.append(reinterpret_cast<const char*>(array_data.data()), all_data.append(reinterpret_cast<const char*>(array_data.data()),
array_data.size() * sizeof(EntryT)); array_data.size() * sizeof(EntryT));
@@ -109,7 +109,8 @@ void send_player_preview_bb(std::shared_ptr<Client> c, uint8_t player_index,
void send_accept_client_checksum_bb(std::shared_ptr<Client> c); void send_accept_client_checksum_bb(std::shared_ptr<Client> c);
void send_guild_card_header_bb(std::shared_ptr<Client> c); void send_guild_card_header_bb(std::shared_ptr<Client> c);
void send_guild_card_chunk_bb(std::shared_ptr<Client> c, size_t chunk_index); void send_guild_card_chunk_bb(std::shared_ptr<Client> c, size_t chunk_index);
void send_stream_file_bb(std::shared_ptr<Client> c); void send_stream_file_index_bb(std::shared_ptr<Client> c);
void send_stream_file_chunk_bb(std::shared_ptr<Client> c, uint32_t chunk_index);
void send_approve_player_choice_bb(std::shared_ptr<Client> c); void send_approve_player_choice_bb(std::shared_ptr<Client> c);
void send_complete_player_bb(std::shared_ptr<Client> c); void send_complete_player_bb(std::shared_ptr<Client> c);
+28 -16
View File
@@ -106,7 +106,8 @@ void Server::on_listen_accept(struct evconnlistener* listener,
return; return;
} }
this->log(INFO, "Client fd %d connected via fd %d", fd, listen_fd); this->log(INFO, "Client fd %d connected via fd %d (%s)",
fd, listen_fd, listening_socket->name.c_str());
struct bufferevent *bev = bufferevent_socket_new(this->base.get(), fd, struct bufferevent *bev = bufferevent_socket_new(this->base.get(), fd,
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
@@ -227,37 +228,48 @@ Server::Server(
base(base), base(base),
state(state) { } state(state) { }
void Server::listen(const string& socket_path, GameVersion version, void Server::listen(
const std::string& name,
const string& socket_path,
GameVersion version,
ServerBehavior behavior) { ServerBehavior behavior) {
int fd = ::listen(socket_path, 0, SOMAXCONN); int fd = ::listen(socket_path, 0, SOMAXCONN);
this->log(INFO, "Listening on Unix socket %s (%s) on fd %d", this->log(INFO, "Listening on Unix socket %s (%s) on fd %d (name: %s)",
socket_path.c_str(), name_for_version(version), fd); socket_path.c_str(), name_for_version(version), fd, name.c_str());
this->add_socket(fd, version, behavior); this->add_socket(name, fd, version, behavior);
} }
void Server::listen(const string& addr, int port, GameVersion version, void Server::listen(
const std::string& name,
const string& addr,
int port,
GameVersion version,
ServerBehavior behavior) { ServerBehavior behavior) {
int fd = ::listen(addr, port, SOMAXCONN); int fd = ::listen(addr, port, SOMAXCONN);
string netloc_str = render_netloc(addr, port); string netloc_str = render_netloc(addr, port);
this->log(INFO, "Listening on TCP interface %s (%s) on fd %d", this->log(INFO, "Listening on TCP interface %s (%s) on fd %d (name: %s)",
netloc_str.c_str(), name_for_version(version), fd); netloc_str.c_str(), name_for_version(version), fd, name.c_str());
this->add_socket(fd, version, behavior); this->add_socket(name, fd, version, behavior);
} }
void Server::listen(int port, GameVersion version, ServerBehavior behavior) { void Server::listen(const std::string& name, int port, GameVersion version, ServerBehavior behavior) {
this->listen("", port, version, behavior); this->listen(name, "", port, version, behavior);
} }
Server::ListeningSocket::ListeningSocket(Server* s, int fd, Server::ListeningSocket::ListeningSocket(Server* s, const std::string& name,
GameVersion version, ServerBehavior behavior) : int fd, GameVersion version, ServerBehavior behavior) :
fd(fd), version(version), behavior(behavior), listener( name(name), fd(fd), version(version), behavior(behavior), listener(
evconnlistener_new(s->base.get(), Server::dispatch_on_listen_accept, s, evconnlistener_new(s->base.get(), Server::dispatch_on_listen_accept, s,
LEV_OPT_REUSEABLE, 0, this->fd), evconnlistener_free) { LEV_OPT_REUSEABLE, 0, this->fd), evconnlistener_free) {
evconnlistener_set_error_cb(this->listener.get(), evconnlistener_set_error_cb(this->listener.get(),
Server::dispatch_on_listen_error); Server::dispatch_on_listen_error);
} }
void Server::add_socket(int fd, GameVersion version, ServerBehavior behavior) { void Server::add_socket(
const std::string& name,
int fd,
GameVersion version,
ServerBehavior behavior) {
this->listening_sockets.emplace(piecewise_construct, forward_as_tuple(fd), this->listening_sockets.emplace(piecewise_construct, forward_as_tuple(fd),
forward_as_tuple(this, fd, version, behavior)); forward_as_tuple(this, name, fd, version, behavior));
} }
+10 -5
View File
@@ -21,10 +21,10 @@ public:
std::shared_ptr<ServerState> state); std::shared_ptr<ServerState> state);
virtual ~Server() = default; virtual ~Server() = default;
void listen(const std::string& socket_path, GameVersion version, ServerBehavior initial_state); void listen(const std::string& name, const std::string& socket_path, GameVersion version, ServerBehavior initial_state);
void listen(const std::string& addr, int port, GameVersion version, ServerBehavior initial_state); void listen(const std::string& name, const std::string& addr, int port, GameVersion version, ServerBehavior initial_state);
void listen(int port, GameVersion version, ServerBehavior initial_state); void listen(const std::string& name, int port, GameVersion version, ServerBehavior initial_state);
void add_socket(int fd, GameVersion version, ServerBehavior initial_state); void add_socket(const std::string& name, int fd, GameVersion version, ServerBehavior initial_state);
void connect_client(struct bufferevent* bev, uint32_t address, uint16_t port, void connect_client(struct bufferevent* bev, uint32_t address, uint16_t port,
GameVersion version, ServerBehavior initial_state); GameVersion version, ServerBehavior initial_state);
@@ -34,12 +34,17 @@ private:
std::shared_ptr<struct event_base> base; std::shared_ptr<struct event_base> base;
struct ListeningSocket { struct ListeningSocket {
std::string name;
int fd; int fd;
GameVersion version; GameVersion version;
ServerBehavior behavior; ServerBehavior behavior;
std::unique_ptr<struct evconnlistener, void(*)(struct evconnlistener*)> listener; std::unique_ptr<struct evconnlistener, void(*)(struct evconnlistener*)> listener;
ListeningSocket(Server* s, int fd, GameVersion version, ListeningSocket(
Server* s,
const std::string& name,
int fd,
GameVersion version,
ServerBehavior behavior); ServerBehavior behavior);
}; };
std::unordered_map<int, ListeningSocket> listening_sockets; std::unordered_map<int, ListeningSocket> listening_sockets;
+44 -7
View File
@@ -67,16 +67,53 @@ const char* name_for_version(GameVersion version) {
GameVersion version_for_name(const char* name) { GameVersion version_for_name(const char* name) {
if (!strcasecmp(name, "DC") || !strcasecmp(name, "DreamCast")) { if (!strcasecmp(name, "DC") || !strcasecmp(name, "DreamCast")) {
return GameVersion::DC; return GameVersion::DC;
} } else if (!strcasecmp(name, "PC")) {
if (!strcasecmp(name, "PC")) {
return GameVersion::PC; return GameVersion::PC;
} } else if (!strcasecmp(name, "GC") || !strcasecmp(name, "GameCube")) {
if (!strcasecmp(name, "GC") || !strcasecmp(name, "GameCube")) {
return GameVersion::GC; return GameVersion::GC;
} } else if (!strcasecmp(name, "BB") || !strcasecmp(name, "BlueBurst") ||
if (!strcasecmp(name, "BB") || !strcasecmp(name, "BlueBurst") ||
!strcasecmp(name, "Blue Burst")) { !strcasecmp(name, "Blue Burst")) {
return GameVersion::BB; return GameVersion::BB;
} else if (!strcasecmp(name, "Patch")) {
return GameVersion::PATCH;
} else {
throw invalid_argument("incorrect version name");
}
}
const char* name_for_server_behavior(ServerBehavior behavior) {
switch (behavior) {
case ServerBehavior::SPLIT_RECONNECT:
return "split_reconnect";
case ServerBehavior::LOGIN_SERVER:
return "login_server";
case ServerBehavior::LOBBY_SERVER:
return "lobby_server";
case ServerBehavior::DATA_SERVER_BB:
return "data_server_bb";
case ServerBehavior::PATCH_SERVER:
return "patch_server";
case ServerBehavior::PROXY_SERVER:
return "proxy_server";
default:
throw logic_error("invalid server behavior");
}
}
ServerBehavior server_behavior_for_name(const char* name) {
if (!strcasecmp(name, "split_reconnect")) {
return ServerBehavior::SPLIT_RECONNECT;
} else if (!strcasecmp(name, "login_server") || !strcasecmp(name, "login")) {
return ServerBehavior::LOGIN_SERVER;
} else if (!strcasecmp(name, "lobby_server") || !strcasecmp(name, "lobby")) {
return ServerBehavior::LOBBY_SERVER;
} else if (!strcasecmp(name, "data_server_bb") || !strcasecmp(name, "data_server") || !strcasecmp(name, "data")) {
return ServerBehavior::DATA_SERVER_BB;
} else if (!strcasecmp(name, "patch_server") || !strcasecmp(name, "patch")) {
return ServerBehavior::PATCH_SERVER;
} else if (!strcasecmp(name, "proxy_server") || !strcasecmp(name, "proxy")) {
return ServerBehavior::PROXY_SERVER;
} else {
throw invalid_argument("incorrect server behavior name");
} }
throw invalid_argument("incorrect version name");
} }
+12
View File
@@ -12,6 +12,18 @@ enum class GameVersion {
BB, BB,
}; };
enum class ServerBehavior {
SPLIT_RECONNECT = 0,
LOGIN_SERVER,
LOBBY_SERVER,
DATA_SERVER_BB,
PATCH_SERVER,
PROXY_SERVER,
};
uint16_t flags_for_version(GameVersion version, uint8_t sub_version); uint16_t flags_for_version(GameVersion version, uint8_t sub_version);
const char* name_for_version(GameVersion version); const char* name_for_version(GameVersion version);
GameVersion version_for_name(const char* name); GameVersion version_for_name(const char* name);
const char* name_for_server_behavior(ServerBehavior behavior);
ServerBehavior server_behavior_for_name(const char* name);
Binary file not shown.
+43
View File
@@ -15,6 +15,49 @@
// out or set it to zero. // out or set it to zero.
"DNSServerPort": 53, "DNSServerPort": 53,
// Ports to listen for game connections on.
"PortConfiguration": {
// name: [port, version, behavior]
// Various versions of PSO hardcode these ports in the clients. Don't change
// these unless you don't want to support certain versions of PSO.
"gc-jp10": [9000, "gc", "login_server"],
"gc-jp11": [9001, "gc", "login_server"],
"gc-jp3": [9003, "gc", "login_server"],
"gc-us10": [9100, "pc", "split_reconnect"],
"gc-us3": [9103, "gc", "login_server"],
"gc-eu10": [9200, "gc", "login_server"],
"gc-eu11": [9201, "gc", "login_server"],
"gc-eu3": [9203, "gc", "login_server"],
"pc-login": [9300, "pc", "login_server"],
"pc-patch": [10000, "patch", "patch_server"],
"bb-patch": [11000, "patch", "patch_server"],
"bb-init": [12000, "bb", "data_server_bb"],
// Schthack PSOBB uses these ports.
// "bb-patch2": [10500, "patch", "patch_server"],
// "bb-init2": [13000, "bb", "data_server_bb"],
// Ephinea PSOBB uses these ports. Note that 13000 is also used by Schthack
// PSOBB, but not for the patch server; this means you unfortunately can't
// support both Schthack and Ephinea PSOBB clients at the same time.
// "bb-patch3": [13000, "patch", "patch_server"],
// "bb-init3": [14000, "bb", "data_server_bb"],
// newserv uses these ports, but there is no external reason that these
// numbers were chosen. You can change the port numbers here without any
// issues. Note that the bb-data1 and bb-data2 ports must be sequential;
// that is, the bb-data2 port must be the bb-data1 port + 1.
"pc-lobby": [9420, "pc", "lobby_server"],
"gc-lobby": [9421, "gc", "lobby_server"],
"bb-lobby": [9422, "bb", "lobby_server"],
"pc-proxy": [9520, "pc", "proxy_server"],
"gc-proxy": [9521, "gc", "proxy_server"],
"bb-proxy": [9522, "bb", "proxy_server"],
"bb-data1": [12004, "bb", "data_server_bb"],
"bb-data2": [12005, "bb", "data_server_bb"],
},
// Where to listen for IP stack clients. This exists to interface with PSO GC // Where to listen for IP stack clients. This exists to interface with PSO GC
// clients running in a local Dolphin emulator. To enable local Dolphin // clients running in a local Dolphin emulator. To enable local Dolphin
// clients to connect, set this to ["/tmp/dolphin-tap"] and configure Dolphin // clients to connect, set this to ["/tmp/dolphin-tap"] and configure Dolphin