fix bb login, char creation, and some lobby/game behaviors
This commit is contained in:
+8
-4
@@ -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
@@ -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
@@ -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,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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,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);
|
||||
|
||||
Reference in New Issue
Block a user