fix bb login, char creation, and some lobby/game behaviors
This commit is contained in:
@@ -4,7 +4,7 @@ newserv is a game server and proxy for Phantasy Star Online (PSO).
|
||||
|
||||
This project includes code that was reverse-engineered by the community in ages long past, and has been included in many projects since then. It also includes some game data from Phantasy Star Online itself; this data was originally created by Sega.
|
||||
|
||||
This project is a rewrite of a rewrite of a game server that I wrote many years ago. So far, it works well with PSO GC Episodes 1 & 2, and lobbies (but not games) are implemented on Episode 3. Some basic functionality works on PSO PC, but there are probably still some cases that lead to errors (which will disconnect the client). newserv is based on an older project of mine that supported BB as well, but I no longer have a way to test BB, so the implementation here probably doesn't work for it.
|
||||
This project is a rewrite of a rewrite of a game server that I wrote many years ago. So far, it works well with PSO GC Episodes 1 & 2, and lobbies (but not games) are implemented on Episode 3. Some basic functionality works on PSO PC and PSO BB, but there are probably still some cases that lead to errors (which will disconnect the client).
|
||||
|
||||
Feel free to submit GitHub issues if you find bugs or have feature requests. I'd like to make the server as stable and complete as possible, but I can't promise that I'll respond to issues in a timely manner.
|
||||
|
||||
@@ -15,7 +15,7 @@ This project is primarily for my own nostalgia; I offer no guarantees on how or
|
||||
Current known issues / missing features:
|
||||
- Test all the communication features (info board, simple mail, card search, etc.)
|
||||
- The trade window isn't implemented yet.
|
||||
- PSO PC and PSOBB are essentially entirely untested. Only GC is fairly well-tested.
|
||||
- PSO PC and PSOBB are not well-tested and likely will disconnect when clients try to use unimplemented features. Only GC is known to be stable and mostly complete.
|
||||
- Add all the chat commands that khyller used to have. (Most, but not all, currently exist in newserv.)
|
||||
|
||||
## Usage
|
||||
|
||||
+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);
|
||||
|
||||
Binary file not shown.
@@ -15,6 +15,49 @@
|
||||
// out or set it to zero.
|
||||
"DNSServerPort": 53,
|
||||
|
||||
// Ports to listen for game connections on.
|
||||
"PortConfiguration": {
|
||||
// name: [port, version, behavior]
|
||||
|
||||
// Various versions of PSO hardcode these ports in the clients. Don't change
|
||||
// these unless you don't want to support certain versions of PSO.
|
||||
"gc-jp10": [9000, "gc", "login_server"],
|
||||
"gc-jp11": [9001, "gc", "login_server"],
|
||||
"gc-jp3": [9003, "gc", "login_server"],
|
||||
"gc-us10": [9100, "pc", "split_reconnect"],
|
||||
"gc-us3": [9103, "gc", "login_server"],
|
||||
"gc-eu10": [9200, "gc", "login_server"],
|
||||
"gc-eu11": [9201, "gc", "login_server"],
|
||||
"gc-eu3": [9203, "gc", "login_server"],
|
||||
"pc-login": [9300, "pc", "login_server"],
|
||||
"pc-patch": [10000, "patch", "patch_server"],
|
||||
"bb-patch": [11000, "patch", "patch_server"],
|
||||
"bb-init": [12000, "bb", "data_server_bb"],
|
||||
|
||||
// Schthack PSOBB uses these ports.
|
||||
// "bb-patch2": [10500, "patch", "patch_server"],
|
||||
// "bb-init2": [13000, "bb", "data_server_bb"],
|
||||
|
||||
// Ephinea PSOBB uses these ports. Note that 13000 is also used by Schthack
|
||||
// PSOBB, but not for the patch server; this means you unfortunately can't
|
||||
// support both Schthack and Ephinea PSOBB clients at the same time.
|
||||
// "bb-patch3": [13000, "patch", "patch_server"],
|
||||
// "bb-init3": [14000, "bb", "data_server_bb"],
|
||||
|
||||
// newserv uses these ports, but there is no external reason that these
|
||||
// numbers were chosen. You can change the port numbers here without any
|
||||
// issues. Note that the bb-data1 and bb-data2 ports must be sequential;
|
||||
// that is, the bb-data2 port must be the bb-data1 port + 1.
|
||||
"pc-lobby": [9420, "pc", "lobby_server"],
|
||||
"gc-lobby": [9421, "gc", "lobby_server"],
|
||||
"bb-lobby": [9422, "bb", "lobby_server"],
|
||||
"pc-proxy": [9520, "pc", "proxy_server"],
|
||||
"gc-proxy": [9521, "gc", "proxy_server"],
|
||||
"bb-proxy": [9522, "bb", "proxy_server"],
|
||||
"bb-data1": [12004, "bb", "data_server_bb"],
|
||||
"bb-data2": [12005, "bb", "data_server_bb"],
|
||||
},
|
||||
|
||||
// Where to listen for IP stack clients. This exists to interface with PSO GC
|
||||
// clients running in a local Dolphin emulator. To enable local Dolphin
|
||||
// clients to connect, set this to ["/tmp/dolphin-tap"] and configure Dolphin
|
||||
|
||||
Reference in New Issue
Block a user