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
+8 -4
View File
@@ -69,8 +69,6 @@ void Client::set_license(shared_ptr<const License> l) {
ClientConfig Client::export_config() const {
ClientConfig cc;
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.proxy_destination_address = this->proxy_destination_address;
cc.proxy_destination_port = this->proxy_destination_port;
@@ -81,6 +79,8 @@ ClientConfig Client::export_config() const {
ClientConfigBB Client::export_config_bb() const {
ClientConfigBB cc;
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);
return cc;
}
@@ -89,9 +89,13 @@ void Client::import_config(const ClientConfig& cc) {
if (cc.magic != CLIENT_CONFIG_MAGIC) {
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->proxy_destination_address = cc.proxy_destination_address;
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 {
enum Flag {
// For patch server clients, client is Blue Burst rather than PC
@@ -121,4 +112,5 @@ struct Client {
ClientConfig export_config() const;
ClientConfigBB export_config_bb() const;
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
// can use the server's native-endian types instead of being explicit as we do
// 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 {
uint64_t magic;
uint8_t bb_game_state;
uint8_t bb_player_index;
uint16_t flags;
uint32_t proxy_destination_address;
uint16_t proxy_destination_port;
parray<uint8_t, 0x0E> unused;
parray<uint8_t, 0x10> unused;
};
struct ClientConfigBB {
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)
struct C_GuildCardDataRequest_BB_DC {
struct C_GuildCardDataRequest_BB_03DC {
le_uint32_t unknown;
le_uint32_t chunk_index;
le_uint32_t cont;
@@ -1171,7 +1188,7 @@ struct C_GuildCardDataRequest_BB_DC {
struct S_GuildCardHeader_BB_01DC {
le_uint32_t unknown; // should be 1
le_uint32_t filesize; // 0x0000490
le_uint32_t filesize; // 0x0000D590
le_uint32_t checksum;
};
@@ -1210,18 +1227,13 @@ struct S_TournamentEntry_GC_Ep3_E0 {
// E2 (S->C): Team and key config (BB)
// See KeyAndTeamConfigBB in Player.hh for format
// E3: Player previews (BB)
// E3: Player preview request (BB)
struct C_PlayerPreviewRequest_BB_E3 {
le_uint32_t player_index;
le_uint32_t unused;
};
struct S_PlayerPreview_BB_E3 {
le_uint32_t player_index;
PlayerDispDataBBPreview preview;
};
// E4: CARD lobby game (Episode 3)
// When client sends an E4, server should respond with another E4 (but these
// commands have different formats).
@@ -1253,9 +1265,10 @@ struct S_PlayerPreview_NoPlayer_BB_E4 {
le_uint32_t error;
};
// E5 (S->C): Player preview (BB)
// E5 (C->S): Create character (BB)
struct C_CreateCharacter_BB_E5 {
struct SC_PlayerPreview_CreateCharacter_BB_E5 {
le_uint32_t player_index;
PlayerDispDataBBPreview preview;
};
@@ -1293,8 +1306,8 @@ struct S_AcceptClientChecksum_BB_02E8 {
// Command is a list of these; header.flag is the entry count.
struct S_StreamFileIndexEntry_BB_01EB {
le_uint32_t size;
le_uint32_t checksum;
le_uint32_t offset;
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;
};
+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) { }
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();
try {
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) { }
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.emplace(piecewise_construct, forward_as_tuple(name),
forward_as_tuple(name, contents, t));
@@ -29,6 +37,7 @@ shared_ptr<const string> FileContentsCache::get(const std::string& name) {
return contents;
}
shared_ptr<const string> FileContentsCache::get(const char* name) {
return this->get(string(name));
shared_ptr<const string> FileContentsCache::get(const char* 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 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:
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({
{"gc-jp10", 9000, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-jp11", 9001, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-jp3", 9003, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-us10", 9100, GameVersion::PC, ServerBehavior::SPLIT_RECONNECT},
{"gc-us3", 9103, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-eu10", 9200, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-eu11", 9201, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-eu3", 9203, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"pc-login", 9300, GameVersion::PC, ServerBehavior::LOGIN_SERVER},
{"pc-patch", 10000, GameVersion::PATCH, ServerBehavior::PATCH_SERVER},
{"bb-patch", 11000, GameVersion::PATCH, ServerBehavior::PATCH_SERVER},
{"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},
});
static vector<PortConfiguration> parse_port_configuration(
shared_ptr<const JSONObject> json) {
vector<PortConfiguration> ret;
for (const auto& item_json_it : json->as_dict()) {
auto item_list = item_json_it.second->as_list();
PortConfiguration& pc = ret.emplace_back();
pc.name = item_json_it.first;
pc.port = item_list[0]->as_int();
pc.version = version_for_name(item_list[1]->as_string().c_str());
pc.behavior = server_behavior_for_name(item_list[2]->as_string().c_str());
}
return ret;
}
template <typename T>
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&) { }
// TODO: make this configurable
s->set_port_configuration(default_port_to_behavior);
s->set_port_configuration(parse_port_configuration(d.at("PortConfiguration")));
auto enemy_categories = parse_int_vector<uint32_t>(d.at("CommonItemDropRates-Enemy"));
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**) {
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);
if (isatty(fileno(stderr))) {
@@ -344,7 +338,8 @@ int main(int, char**) {
log(INFO, "Opening sockets");
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()) {
log(INFO, "Starting proxy server");
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
// destination is supported, and we have to manually specify the
// destination netloc here.
if (it.second->version == GameVersion::PATCH) {
if (pc->version == GameVersion::PATCH) {
struct sockaddr_storage ss = make_sockaddr_storage(
state->proxy_destination_patch.first,
state->proxy_destination_patch.second).first;
state->proxy_server->listen(it.second->port, it.second->version, &ss);
} if (it.second->version == GameVersion::BB) {
state->proxy_server->listen(pc->port, pc->version, &ss);
} else if (pc->version == GameVersion::BB) {
struct sockaddr_storage ss = make_sockaddr_storage(
state->proxy_destination_bb.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 {
state->proxy_server->listen(it.second->port, it.second->version);
state->proxy_server->listen(pc->port, pc->version);
}
}
} else {
@@ -374,7 +369,10 @@ int main(int, char**) {
log(INFO, "Starting game server");
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() {
if (!this->bb_username.empty()) {
if (this->account_data.get()) {
@@ -360,9 +343,6 @@ string ClientGameData::player_data_filename() const {
if (this->bb_username.empty()) {
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",
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 player = this->player();
+3 -1
View File
@@ -425,7 +425,9 @@ public:
void import_player(const PSOPlayerDataPC& pd);
void import_player(const PSOPlayerDataGC& 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");
// This isn't forwarded to the client, so don't recreate the client's crypts
if (session.version == GameVersion::PATCH) {
throw logic_error("patch session is indirect");
} else if (session.version == GameVersion::PC) {
if ((session.version == GameVersion::PATCH) || (session.version == GameVersion::PC)) {
session.server_input_crypt.reset(new PSOPCEncryption(cmd.server_key));
session.server_output_crypt.reset(new PSOPCEncryption(cmd.client_key));
} 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");
}
// Respond with an appropriate login command. We don't let the
// client do this because it believes it already did (when it was
// in an unlinked session).
if (session.version == GameVersion::PC) {
// Respond with an appropriate login command. We don't let the client do this
// because it believes it already did (when it was in an unlinked session, or
// in the patch server case, during the current session due to a hidden
// 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;
if (session.remote_guild_card_number == 0) {
cmd.player_tag = 0xFFFF0000;
@@ -390,8 +393,8 @@ static bool process_server_gc_E4(shared_ptr<ServerState>,
return true;
}
static bool process_server_19(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
static bool process_server_game_19_patch_14(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t command, uint32_t, string& data) {
// This weird maximum size is here to properly handle the version-split
// command that some servers (including newserv) use on port 9100
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()) {
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 {
// 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 {
cmd.port = session.local_port;
}
return true;
}
return true;
}
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] = {
/* 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,
/* 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,
@@ -824,7 +844,7 @@ static process_command_t dc_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,
/* 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,
/* 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,
@@ -842,7 +862,7 @@ static process_command_t pc_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,
/* 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,
/* 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,
@@ -860,7 +880,7 @@ static process_command_t gc_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,
/* 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,
/* 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,
@@ -878,7 +898,7 @@ static process_command_t bb_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,
/* 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,
/* 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,
+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({
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),
@@ -103,9 +86,9 @@ void process_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c)
}
break;
case ServerBehavior::LOBBY_SERVER:
case ServerBehavior::DATA_SERVER_BB:
case ServerBehavior::PATCH_SERVER:
case ServerBehavior::LOBBY_SERVER:
send_server_init(s, c, false);
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) {
if (c->server_behavior == ServerBehavior::LOGIN_SERVER) {
// on the login server, send the ep3 updates and the main menu or welcome
// On the BB data server, this function is called only on the last connection
// (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
if (c->flags & Client::Flag::EPISODE_3) {
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
// to newserv before, so we should show the 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;
}
@@ -342,10 +328,12 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
}
try {
c->import_config(cmd.client_config.cfg.cfg);
c->bb_game_state++;
c->import_config(cmd.client_config.cfg);
if (c->bb_game_state < ClientStateBB::IN_GAME) {
c->bb_game_state++;
}
} catch (const invalid_argument&) {
c->bb_game_state = 0;
c->bb_game_state = ClientStateBB::INITIAL_LOGIN;
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);
break;
case ClientStateBB::IN_GAME:
break;
default:
send_reconnect(c, s->connect_address_for_client(c),
s->name_to_port_config.at("bb-login")->port);
throw runtime_error("invalid bb game state");
}
}
@@ -1213,14 +1203,15 @@ void process_player_preview_request_bb(shared_ptr<ServerState>, shared_ptr<Clien
ClientGameData temp_gd;
temp_gd.serial_number = c->license->serial_number;
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 {
auto preview = temp_gd.player()->disp.to_preview();
send_player_preview_bb(c, cmd.player_index, &preview);
} catch (const exception&) {
} catch (const exception& e) {
// Player doesn't exist
log(INFO, "[BB debug] No player in slot: %s", e.what());
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,
uint16_t command, uint32_t, const string& data) {
check_size_v(data.size(), 0);
if (command == 0x01E8) {
check_size_v(data.size(), 8);
send_accept_client_checksum_bb(c);
} else if (command == 0x03E8) {
check_size_v(data.size(), 0);
send_guild_card_header_bb(c);
} else {
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,
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) {
send_guild_card_chunk_bb(c, cmd.chunk_index);
}
}
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);
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 {
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,
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) {
send_message_box(c, u"$C6You are not logged in.");
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;
}
@@ -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,
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] = {
{0, 19, 39, 79}, // episode 1
{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,
game->episode - 1, game->difficulty);
if (game->mode == 3) {
if (episode > 0 && episode < 4) {
variation_maxes = variation_maxes_solo[episode - 1];
}
for (size_t x = 0; x < 0x10; x++) {
for (const char* type_char = "sm"; *type_char; type_char++) {
try {
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++) {
const char* type_chars = (game->mode == 3) ? "sm" : "m";
if (episode > 0 && episode < 4) {
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 = type_chars; *type; type++) {
auto filename = string_printf(
"system/blueburst/map/m%hhu%zu%" PRIu32 "%" PRIu32 ".dat",
game->episode, x,
"system/blueburst/map/%c%hhX%zX%" PRIX32 "%" PRIX32 ".dat",
*type, 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);
try {
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 {
// In non-BB games, just set the variations (we don't track items/enemies/
// etc.)
@@ -1686,6 +1683,8 @@ void process_team_command_bb(shared_ptr<ServerState>, shared_ptr<Client> c,
if (command == 0x01EA) {
send_lobby_message_box(c, u"$C6Teams are not supported.");
} else if (command == 0x14EA) {
// Do nothing (for now)
} else {
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");
forward_subcommand(l, c, command, flag, &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;
}
@@ -756,7 +756,7 @@ static void process_subcommand_phase_setup(shared_ptr<ServerState>,
0.0f,
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.unused = 0;
res.item = c->game_data.identify_result.data;
send_command(l, 0x60, 0x00, res);
send_command_t(l, 0x60, 0x00, res);
} else {
forward_subcommand(l, c, command, flag, data);
+101 -85
View File
@@ -9,6 +9,7 @@
#include <phosg/Random.hh>
#include <phosg/Strings.hh>
#include <phosg/Time.hh>
#include <phosg/Hash.hh>
#include "PSOProtocol.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(
initial_connection, server_key, client_key);
send_command(c, command, 0x00, cmd);
send_command_t(c, command, 0x00, cmd);
switch (c->version) {
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.client_key.data(), cmd.client_key.bytes());
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);
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.server_key = server_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_in.reset(new PSOPCEncryption(client_key));
@@ -259,17 +260,17 @@ void send_update_client_config(shared_ptr<Client> c) {
cmd.player_tag = 0x00010000;
cmd.guild_card_number = c->license->serial_number;
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) {
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
void send_pc_gc_split_reconnect(shared_ptr<Client> c, uint32_t address,
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_address = address;
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.cfg = c->export_config_bb();
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) {
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,
@@ -307,24 +308,24 @@ void send_player_preview_bb(shared_ptr<Client> c, uint8_t player_index,
if (!preview) {
// no player exists
S_PlayerPreview_NoPlayer_BB_E4 cmd = {player_index, 0x00000002};
send_command(c, 0x00E4, 0x00000000, cmd);
send_command_t(c, 0x00E4, 0x00000000, cmd);
} else {
S_PlayerPreview_BB_E3 cmd = {player_index, *preview};
send_command(c, 0x00E3, 0x00000000, cmd);
SC_PlayerPreview_CreateCharacter_BB_E5 cmd = {player_index, *preview};
send_command_t(c, 0x00E5, 0x00000000, cmd);
}
}
void send_accept_client_checksum_bb(shared_ptr<Client> c) {
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) {
uint32_t checksum = compute_guild_card_checksum(
uint32_t checksum = crc32(
&c->game_data.account()->guild_cards, sizeof(GuildCardFileBB));
S_GuildCardHeader_BB_01DC cmd = {1, 0x00000490, checksum};
send_command(c, 0x01DC, 0x00000000, cmd);
S_GuildCardHeader_BB_01DC cmd = {1, sizeof(GuildCardFileBB), checksum};
send_command_t(c, 0x01DC, 0x00000000, cmd);
}
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());
}
void send_stream_file_bb(shared_ptr<Client> c) {
auto index_data = file_cache.get("system/blueburst/streamfile.ind");
if (index_data->size() % sizeof(S_StreamFileIndexEntry_BB_01EB)) {
throw invalid_argument("stream file index not a multiple of entry size");
static const vector<string> stream_file_entries = {
"ItemMagEdit.prs",
"ItemPMT.prs",
"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);
send_command(c, 0x01EB, entry_count, index_data);
void send_stream_file_chunk_bb(shared_ptr<Client> c, uint32_t chunk_index) {
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;
chunk_cmd.chunk_index = 0;
uint32_t buffer_offset = 0;
for (size_t x = 0; x < entry_count; x++) {
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);
}
chunk_cmd.chunk_index = chunk_index;
size_t offset = sizeof(chunk_cmd.data) * chunk_index;
if (offset > contents->size()) {
throw runtime_error("client requested chunk beyond end of stream file");
}
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) {
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) {
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) {
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.to_guild_card_number = c->license->serial_number;
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,
@@ -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;
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) {
@@ -589,7 +605,7 @@ void send_card_search_result_t(
cmd.lobby_id = result->lobby_id;
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(
@@ -628,7 +644,7 @@ void send_guild_card_pc_gc(shared_ptr<Client> c, shared_ptr<Client> source) {
cmd.reserved2 = 1;
cmd.section_id = source->game_data.player()->disp.section_id;
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) {
@@ -644,7 +660,7 @@ void send_guild_card_bb(shared_ptr<Client> c, shared_ptr<Client> source) {
cmd.reserved2 = 1;
cmd.section_id = source->game_data.player()->disp.section_id;
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) {
@@ -696,7 +712,7 @@ void send_menu_t(
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,
@@ -749,7 +765,7 @@ void send_game_menu_t(shared_ptr<Client> c, shared_ptr<ServerState> s) {
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) {
@@ -777,7 +793,7 @@ void send_quest_menu_t(
e.short_desc = quest->short_description;
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>
@@ -795,7 +811,7 @@ void send_quest_menu_t(
e.short_desc = item.description;
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,
@@ -843,7 +859,7 @@ void send_lobby_list(shared_ptr<Client> c, shared_ptr<ServerState> s) {
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) {
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) {
@@ -1045,7 +1061,7 @@ void send_arrow_update(shared_ptr<Lobby> l) {
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
@@ -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) {
@@ -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) {
G_DropItem_6x5F cmd = {
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)
@@ -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).
G_DropStackedItem_6x5D cmd = {
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
@@ -1157,7 +1173,7 @@ void send_pick_up_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
uint32_t item_id, uint8_t area) {
G_PickUpItem_6x59 cmd = {
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)
@@ -1165,7 +1181,7 @@ void send_create_inventory_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
const ItemData& item) {
G_CreateInventoryItem_BB_6xBE cmd = {
0xBE, 0x07, c->lobby_client_id, item, 0};
send_command(l, 0x60, 0x00, cmd);
send_command_t(l, 0x60, 0x00, cmd);
}
// 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) {
G_ItemSubcommand cmd = {
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
@@ -1188,7 +1204,7 @@ void send_bank(shared_ptr<Client> c) {
size_t size = 8 + sizeof(cmd) + items.size() * sizeof(PlayerBankItem);
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
@@ -1241,7 +1257,7 @@ void send_level_up(shared_ptr<Lobby> l, shared_ptr<Client> c) {
stats.dfp,
stats.ata,
c->game_data.player()->disp.level};
send_command(l, 0x60, 0x00, cmd);
send_command_t(l, 0x60, 0x00, cmd);
}
// gives a player EXP
@@ -1249,7 +1265,7 @@ void send_give_experience(shared_ptr<Lobby> l, shared_ptr<Client> c,
uint32_t amount) {
G_GiveExperience_BB_6xBF cmd = {
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) {
S_RankUpdate_GC_Ep3_B7 cmd = {
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
@@ -1327,7 +1343,7 @@ void send_quest_open_file_t(
cmd.file_size = file_size;
cmd.name = "PSO/" + quest_name;
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(
@@ -1349,7 +1365,7 @@ void send_quest_file_chunk(
}
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,
+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>
static void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag,
const StructT& data) {
static void send_command_t(std::shared_ptr<TargetT> c, uint16_t command,
uint32_t flag, const StructT& data) {
send_command(c, command, flag, &data, sizeof(data));
}
template <typename TargetT>
static void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag,
const std::string& data) {
static void send_command(std::shared_ptr<TargetT> c, uint16_t command,
uint32_t flag, const std::string& data) {
send_command(c, command, flag, data.data(), data.size());
}
template <typename TargetT, typename StructT>
void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag,
const std::vector<StructT>& data) {
void send_command_vt(std::shared_ptr<TargetT> c, uint16_t command,
uint32_t flag, const std::vector<StructT>& data) {
send_command(c, command, flag, data.data(), data.size() * sizeof(StructT));
}
template <typename TargetT, typename StructT, typename EntryT>
void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag,
const StructT& data, const std::vector<EntryT>& array_data) {
void send_command_t_vt(std::shared_ptr<TargetT> c, uint16_t command,
uint32_t flag, const StructT& data, const std::vector<EntryT>& array_data) {
std::string all_data(reinterpret_cast<const char*>(&data), sizeof(StructT));
all_data.append(reinterpret_cast<const char*>(array_data.data()),
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_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_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_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;
}
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,
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
@@ -227,37 +228,48 @@ Server::Server(
base(base),
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) {
int fd = ::listen(socket_path, 0, SOMAXCONN);
this->log(INFO, "Listening on Unix socket %s (%s) on fd %d",
socket_path.c_str(), name_for_version(version), fd);
this->add_socket(fd, version, behavior);
this->log(INFO, "Listening on Unix socket %s (%s) on fd %d (name: %s)",
socket_path.c_str(), name_for_version(version), fd, name.c_str());
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) {
int fd = ::listen(addr, port, SOMAXCONN);
string netloc_str = render_netloc(addr, port);
this->log(INFO, "Listening on TCP interface %s (%s) on fd %d",
netloc_str.c_str(), name_for_version(version), fd);
this->add_socket(fd, version, behavior);
this->log(INFO, "Listening on TCP interface %s (%s) on fd %d (name: %s)",
netloc_str.c_str(), name_for_version(version), fd, name.c_str());
this->add_socket(name, fd, version, behavior);
}
void Server::listen(int port, GameVersion version, ServerBehavior behavior) {
this->listen("", port, version, behavior);
void Server::listen(const std::string& name, int port, GameVersion version, ServerBehavior behavior) {
this->listen(name, "", port, version, behavior);
}
Server::ListeningSocket::ListeningSocket(Server* s, int fd,
GameVersion version, ServerBehavior behavior) :
fd(fd), version(version), behavior(behavior), listener(
Server::ListeningSocket::ListeningSocket(Server* s, const std::string& name,
int fd, GameVersion version, ServerBehavior behavior) :
name(name), fd(fd), version(version), behavior(behavior), listener(
evconnlistener_new(s->base.get(), Server::dispatch_on_listen_accept, s,
LEV_OPT_REUSEABLE, 0, this->fd), evconnlistener_free) {
evconnlistener_set_error_cb(this->listener.get(),
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),
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);
virtual ~Server() = default;
void listen(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(int port, GameVersion version, ServerBehavior initial_state);
void add_socket(int fd, 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& name, const std::string& addr, int port, GameVersion version, ServerBehavior initial_state);
void listen(const std::string& name, int port, 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,
GameVersion version, ServerBehavior initial_state);
@@ -34,12 +34,17 @@ private:
std::shared_ptr<struct event_base> base;
struct ListeningSocket {
std::string name;
int fd;
GameVersion version;
ServerBehavior behavior;
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);
};
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) {
if (!strcasecmp(name, "DC") || !strcasecmp(name, "DreamCast")) {
return GameVersion::DC;
}
if (!strcasecmp(name, "PC")) {
} else if (!strcasecmp(name, "PC")) {
return GameVersion::PC;
}
if (!strcasecmp(name, "GC") || !strcasecmp(name, "GameCube")) {
} else if (!strcasecmp(name, "GC") || !strcasecmp(name, "GameCube")) {
return GameVersion::GC;
}
if (!strcasecmp(name, "BB") || !strcasecmp(name, "BlueBurst") ||
} else if (!strcasecmp(name, "BB") || !strcasecmp(name, "BlueBurst") ||
!strcasecmp(name, "Blue Burst")) {
return GameVersion::BB;
} else if (!strcasecmp(name, "Patch")) {
return GameVersion::PATCH;
} else {
throw invalid_argument("incorrect version name");
}
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");
}
}
+12
View File
@@ -12,6 +12,18 @@ enum class GameVersion {
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);
const char* name_for_version(GameVersion version);
GameVersion version_for_name(const char* name);
const char* name_for_server_behavior(ServerBehavior behavior);
ServerBehavior server_behavior_for_name(const char* name);