implement some parts of DC NTE (but not all of it)

This commit is contained in:
Martin Michelsen
2022-09-23 00:33:03 -07:00
parent 9524d05279
commit a9cf98a24f
9 changed files with 178 additions and 47 deletions
+4 -4
View File
@@ -77,8 +77,8 @@ void CatSession::on_channel_input(
uint16_t command, uint32_t flag, std::string& data) { uint16_t command, uint32_t flag, std::string& data) {
if (this->channel.version != GameVersion::BB) { if (this->channel.version != GameVersion::BB) {
if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) { if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) {
const auto& cmd = check_size_t<S_ServerInit_DC_PC_V3_02_17_91_9B>(data, const auto& cmd = check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(data,
offsetof(S_ServerInit_DC_PC_V3_02_17_91_9B, after_message), 0xFFFF); sizeof(S_ServerInitDefault_DC_PC_V3_02_17_91_9B), 0xFFFF);
if ((this->channel.version == GameVersion::GC) || if ((this->channel.version == GameVersion::GC) ||
(this->channel.version == GameVersion::XB)) { (this->channel.version == GameVersion::XB)) {
this->channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key)); this->channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key));
@@ -97,8 +97,8 @@ void CatSession::on_channel_input(
if (!this->bb_key_file) { if (!this->bb_key_file) {
throw runtime_error("BB encryption requires a key file"); throw runtime_error("BB encryption requires a key file");
} }
const auto& cmd = check_size_t<S_ServerInit_BB_03_9B>(data, const auto& cmd = check_size_t<S_ServerInitDefault_BB_03_9B>(data,
offsetof(S_ServerInit_DC_PC_V3_02_17_91_9B, after_message), 0xFFFF); sizeof(S_ServerInitDefault_BB_03_9B), 0xFFFF);
this->channel.crypt_in.reset(new PSOBBEncryption(*this->bb_key_file, &cmd.server_key[0], sizeof(cmd.server_key))); this->channel.crypt_in.reset(new PSOBBEncryption(*this->bb_key_file, &cmd.server_key[0], sizeof(cmd.server_key)));
this->channel.crypt_out.reset(new PSOBBEncryption(*this->bb_key_file, &cmd.client_key[0], sizeof(cmd.client_key))); this->channel.crypt_out.reset(new PSOBBEncryption(*this->bb_key_file, &cmd.client_key[0], sizeof(cmd.client_key)));
this->log.info("Enabled BB encryption"); this->log.info("Enabled BB encryption");
+7 -8
View File
@@ -204,16 +204,15 @@ Channel::Message Channel::recv(bool print_contents) {
throw logic_error("enough bytes available, but could not remove them"); throw logic_error("enough bytes available, but could not remove them");
} }
// Some versions of PSO DC can send commands whose sizes are not a multiple of
// 4, but the server is expected to always use a multiple of 4 bytes when
// decrypting (the extra cipher bytes are lost). To emulate this behavior, we
// have to round up the size for DC commands here.
if (version == GameVersion::DC) {
command_data.resize((command_data.size() + 3) & (~3));
}
if (this->crypt_in.get()) { if (this->crypt_in.get()) {
// Some versions of PSO DC can send commands whose sizes are not a multiple
// of 4, but the server is expected to always use a multiple of 4 bytes when
// decrypting (the extra cipher bytes are lost). To emulate this behavior,
// we have to round up the size for DC commands here.
size_t orig_size = command_data.size();
command_data.resize((orig_size + 3) & (~3), 0);
this->crypt_in->decrypt(command_data.data(), command_data.size()); this->crypt_in->decrypt(command_data.data(), command_data.size());
command_data.resize(orig_size);
} }
command_data.resize(command_logical_size - header_size); command_data.resize(command_logical_size - header_size);
+61 -7
View File
@@ -312,13 +312,17 @@ struct SC_TextHeader_01_06_11_B0_EE {
// (The above text is required on all versions that use this command, including // (The above text is required on all versions that use this command, including
// those versions that don't run on the DreamCast.) // those versions that don't run on the DreamCast.)
struct S_ServerInit_DC_PC_V3_02_17_91_9B { struct S_ServerInitDefault_DC_PC_V3_02_17_91_9B {
ptext<char, 0x40> copyright; ptext<char, 0x40> copyright;
le_uint32_t server_key; // Key for data sent by server le_uint32_t server_key; // Key for data sent by server
le_uint32_t client_key; // Key for data sent by client le_uint32_t client_key; // Key for data sent by client
};
template <size_t AfterBytes>
struct S_ServerInitWithAfterMessage_DC_PC_V3_02_17_91_9B : S_ServerInitDefault_DC_PC_V3_02_17_91_9B {
// This field is not part of SEGA's implementation; the client ignores it. // This field is not part of SEGA's implementation; the client ignores it.
// newserv sends a message here disavowing the preceding copyright notice. // newserv sends a message here disavowing the preceding copyright notice.
ptext<char, 0xC0> after_message; ptext<char, AfterBytes> after_message;
}; };
// 03 (C->S): Legacy login (non-BB) // 03 (C->S): Legacy login (non-BB)
@@ -352,10 +356,13 @@ struct C_LegacyLogin_PC_V3_03 {
// The copyright field in the below structure must contain the following text: // The copyright field in the below structure must contain the following text:
// "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM." // "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM."
struct S_ServerInit_BB_03_9B { struct S_ServerInitDefault_BB_03_9B {
ptext<char, 0x60> copyright; ptext<char, 0x60> copyright;
parray<uint8_t, 0x30> server_key; parray<uint8_t, 0x30> server_key;
parray<uint8_t, 0x30> client_key; parray<uint8_t, 0x30> client_key;
};
struct S_ServerInitWithAfterMessage_BB_03_9B : S_ServerInitDefault_BB_03_9B {
// As in 02, this field is not part of SEGA's implementation. // As in 02, this field is not part of SEGA's implementation.
ptext<char, 0xC0> after_message; ptext<char, 0xC0> after_message;
}; };
@@ -1144,6 +1151,19 @@ struct C_LobbySelection_84 {
// 86: Invalid command // 86: Invalid command
// 87: Invalid command // 87: Invalid command
// 88 (C->S): License check (DC NTE only)
// The server should respond with an 88 command.
struct C_Login_DCNTE_88 {
ptext<char, 0x11> serial_number;
ptext<char, 0x11> access_key;
};
// 88 (S->C): License check result (DC NTE only)
// No arguemnts except header.flag.
// If header.flag is zero, client will respond with an 8A command. Otherwise, it
// will respond with an 8B command.
// 88 (S->C): Update lobby arrows // 88 (S->C): Update lobby arrows
// If this command is sent while a client is joining a lobby, the client may // If this command is sent while a client is joining a lobby, the client may
// ignore it. For this reason, the server should wait a few seconds after a // ignore it. For this reason, the server should wait a few seconds after a
@@ -1178,17 +1198,51 @@ struct S_ArrowUpdateEntry_88 {
// header.flag = arrow color number (see above); no other arguments. // header.flag = arrow color number (see above); no other arguments.
// Server should send an 88 command to all players in the lobby. // Server should send an 88 command to all players in the lobby.
// 8A (C->S): Request lobby/game name // 8A (C->S): Connection information (DC NTE only)
// The server should respond with an 8A command.
struct C_ConnectionInfo_DCNTE_8A {
ptext<char, 0x08> hardware_id;
le_uint32_t sub_version; // 0x20
le_uint32_t unknown_a1;
ptext<char, 0x30> username;
ptext<char, 0x30> password;
ptext<char, 0x30> email_address; // From Sylverant documentation
};
// 8A (S->C): Connection information result (DC NTE only)
// header.flag is a success flag. If 0 is sent, the client shows an error
// message and disconnects. Otherwise, the client responds with an 8B command.
// 8A (C->S): Request lobby/game name (except DC NTE)
// No arguments // No arguments
// 8A (S->C): Lobby/game name // 8A (S->C): Lobby/game name (except DC NTE)
// Contents is a string (char16_t on PC/BB, char on DC/V3) containing the lobby // Contents is a string (char16_t on PC/BB, char on DC/V3) containing the lobby
// or game name. The client generally only sends this immediately after joining // or game name. The client generally only sends this immediately after joining
// a game, but Sega's servers also replied to it if it was sent in a lobby. They // a game, but Sega's servers also replied to it if it was sent in a lobby. They
// would return a string like "LOBBY01" even though this would never be used // would return a string like "LOBBY01" even though this would never be used
// under normal circumstances. // under normal circumstances.
// 8B: Invalid command // 8B: Log in (DC NTE only)
struct C_Login_DCNTE_8B {
le_uint32_t player_tag;
le_uint32_t guild_card_number;
parray<uint8_t, 0x08> hardware_id;
le_uint32_t sub_version;
uint8_t is_extended;
uint8_t language;
parray<uint8_t, 2> unused1;
ptext<char, 0x11> serial_number;
ptext<char, 0x11> access_key;
ptext<char, 0x30> username;
ptext<char, 0x30> password;
ptext<char, 0x10> name;
parray<uint8_t, 2> unused;
SC_MeetUserExtension<char> extension;
};
// 8C: Invalid command // 8C: Invalid command
// 8D: Invalid command // 8D: Invalid command
// 8E: Invalid command // 8E: Invalid command
@@ -1752,7 +1806,7 @@ struct S_RankUpdate_GC_Ep3_B7 {
struct S_Unknown_GC_Ep3_B9 { struct S_Unknown_GC_Ep3_B9 {
le_uint32_t unknown_a1; // Must be 1-4 (inclusive) le_uint32_t unknown_a1; // Must be 1-4 (inclusive)
le_uint32_t unknown_a2; le_uint32_t unknown_a2;
le_uint16_t unknown_a3; le_uint16_t unknown_a3; // Looks like a size of some sort
le_uint16_t unused; le_uint16_t unused;
parray<uint8_t, 0x3800> unknown_a5; parray<uint8_t, 0x3800> unknown_a5;
}; };
+4 -4
View File
@@ -171,8 +171,8 @@ static HandlerResult on_server_dc_pc_v3_patch_02_17(
// Most servers don't include after_message or have a shorter // Most servers don't include after_message or have a shorter
// after_message than newserv does, so don't require it // after_message than newserv does, so don't require it
const auto& cmd = check_size_t<S_ServerInit_DC_PC_V3_02_17_91_9B>(data, const auto& cmd = check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(data,
offsetof(S_ServerInit_DC_PC_V3_02_17_91_9B, after_message), 0xFFFF); sizeof(S_ServerInitDefault_DC_PC_V3_02_17_91_9B), 0xFFFF);
if (!session.license) { if (!session.license) {
session.log.info("No license in linked session"); session.log.info("No license in linked session");
@@ -335,8 +335,8 @@ static HandlerResult on_server_bb_03(shared_ptr<ServerState> s,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
// Most servers don't include after_message or have a shorter after_message // Most servers don't include after_message or have a shorter after_message
// than newserv does, so don't require it // than newserv does, so don't require it
const auto& cmd = check_size_t<S_ServerInit_BB_03_9B>(data, const auto& cmd = check_size_t<S_ServerInitDefault_BB_03_9B>(data,
offsetof(S_ServerInit_BB_03_9B, after_message), 0xFFFF); sizeof(S_ServerInitDefault_BB_03_9B), 0xFFFF);
// If the session has a detector crypt, then it was resumed from an unlinked // If the session has a detector crypt, then it was resumed from an unlinked
// session, during which we already sent an 03 command. // session, during which we already sent an 03 command.
+80 -8
View File
@@ -212,6 +212,70 @@ static void on_verify_license_v3(shared_ptr<ServerState> s, shared_ptr<Client> c
} }
} }
static void on_login_8_dcnte(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 88
const auto& cmd = check_size_t<C_Login_DCNTE_88>(data);
c->channel.version = GameVersion::DC;
c->flags |= flags_for_version(c->version(), -1);
c->flags |= Client::Flag::IS_DC_V1 | Client::Flag::IS_TRIAL_EDITION;
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
try {
shared_ptr<const License> l = s->license_manager->verify_pc(
serial_number, cmd.access_key);
c->set_license(l);
send_command(c, 0x88, 0x00);
} catch (const incorrect_access_key& e) {
send_message_box(c, u"Incorrect access key");
c->should_disconnect = true;
} catch (const missing_license& e) {
if (!s->allow_unregistered_users) {
send_message_box(c, u"Incorrect serial number");
c->should_disconnect = true;
} else {
auto l = LicenseManager::create_license_pc(
serial_number, cmd.access_key, true);
s->license_manager->add(l);
c->set_license(l);
send_command(c, 0x88, 0x00);
}
}
}
static void on_login_b_dcnte(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 8B
const auto& cmd = check_size_t<C_Login_DCNTE_8B>(data);
c->channel.version = GameVersion::DC;
c->flags |= flags_for_version(c->version(), -1);
c->flags |= Client::Flag::IS_DC_V1 | Client::Flag::IS_TRIAL_EDITION;
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
try {
shared_ptr<const License> l = s->license_manager->verify_pc(
serial_number, cmd.access_key);
c->set_license(l);
send_command(c, 0x8B, 0x01);
} catch (const incorrect_access_key& e) {
send_message_box(c, u"Incorrect access key");
c->should_disconnect = true;
} catch (const missing_license& e) {
if (!s->allow_unregistered_users) {
send_message_box(c, u"Incorrect serial number");
c->should_disconnect = true;
} else {
auto l = LicenseManager::create_license_pc(
serial_number, cmd.access_key, true);
s->license_manager->add(l);
c->set_license(l);
send_command(c, 0x8B, 0x01);
}
}
}
static void on_login_0_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c, static void on_login_0_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 90 uint16_t, uint32_t, const string& data) { // 90
const auto& cmd = check_size_t<C_LoginV1_DC_PC_V3_90>(data); const auto& cmd = check_size_t<C_LoginV1_DC_PC_V3_90>(data);
@@ -2364,12 +2428,19 @@ static void on_create_game_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
static void on_lobby_name_request(shared_ptr<ServerState> s, shared_ptr<Client> c, static void on_lobby_name_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 8A uint16_t, uint32_t, const string& data) { // 8A
check_size_v(data.size(), 0); if ((c->version() == GameVersion::DC) && (c->flags & Client::Flag::IS_TRIAL_EDITION)) {
auto l = s->find_lobby(c->lobby_id); const auto& cmd = check_size_t<C_ConnectionInfo_DCNTE_8A>(data);
if (!l) { set_console_client_flags(c, cmd.sub_version);
throw invalid_argument("client not in any lobby"); send_command(c, 0x8A, 0x01);
} else {
check_size_v(data.size(), 0);
auto l = s->find_lobby(c->lobby_id);
if (!l) {
throw invalid_argument("client not in any lobby");
}
send_lobby_name(c, l->name.c_str());
} }
send_lobby_name(c, l->name.c_str());
} }
static void on_client_ready(shared_ptr<ServerState> s, shared_ptr<Client> c, static void on_client_ready(shared_ptr<ServerState> s, shared_ptr<Client> c,
@@ -2822,10 +2893,10 @@ static on_command_t handlers[0x100][6] = {
/* 85 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 85 */ /* 85 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 85 */
/* 86 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 86 */ /* 86 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 86 */
/* 87 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 87 */ /* 87 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 87 */
/* 88 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 88 */ /* 88 */ {nullptr, on_login_8_dcnte, nullptr, on_login_8_dcnte, nullptr, nullptr, }, /* 88 */
/* 89 */ {nullptr, on_change_arrow_color, on_change_arrow_color, on_change_arrow_color, on_change_arrow_color, on_change_arrow_color, }, /* 89 */ /* 89 */ {nullptr, on_change_arrow_color, on_change_arrow_color, on_change_arrow_color, on_change_arrow_color, on_change_arrow_color, }, /* 89 */
/* 8A */ {nullptr, on_lobby_name_request, on_lobby_name_request, on_lobby_name_request, on_lobby_name_request, on_lobby_name_request, }, /* 8A */ /* 8A */ {nullptr, on_lobby_name_request, on_lobby_name_request, on_lobby_name_request, on_lobby_name_request, on_lobby_name_request, }, /* 8A */
/* 8B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 8B */ /* 8B */ {nullptr, on_login_b_dcnte, nullptr, nullptr, nullptr, nullptr, }, /* 8B */
/* 8C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 8C */ /* 8C */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 8C */
/* 8D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 8D */ /* 8D */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 8D */
/* 8E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 8E */ /* 8E */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, }, /* 8E */
@@ -2963,7 +3034,8 @@ static void check_unlicensed_command(GameVersion version, uint8_t command) {
case GameVersion::GC: case GameVersion::GC:
case GameVersion::XB: case GameVersion::XB:
// See comment in the DC case above for why DC commands are included here. // See comment in the DC case above for why DC commands are included here.
if (command != 0x90 && // DC v1 if (command != 0x88 && // DC NTE
command != 0x90 && // DC v1
command != 0x93 && // DC v1 command != 0x93 && // DC v1
command != 0x9A && // DC v2 command != 0x9A && // DC v2
command != 0x9D && // DC v2, GC trial edition command != 0x9D && // DC v2, GC trial edition
+8 -8
View File
@@ -218,8 +218,8 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
case 0x17: case 0x17:
case 0x91: case 0x91:
case 0x9B: { case 0x9B: {
auto& cmd_mask = check_size_t<S_ServerInit_DC_PC_V3_02_17_91_9B>( auto& cmd_mask = check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(
cmd_data, cmd_size, sizeof(S_ServerInit_DC_PC_V3_02_17_91_9B), 0xFFFF); cmd_data, cmd_size, sizeof(S_ServerInitDefault_DC_PC_V3_02_17_91_9B), 0xFFFF);
cmd_mask.server_key = 0; cmd_mask.server_key = 0;
cmd_mask.client_key = 0; cmd_mask.client_key = 0;
break; break;
@@ -275,8 +275,8 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
ev->data, sizeof(PSOCommandHeaderBB), 0xFFFF).command; ev->data, sizeof(PSOCommandHeaderBB), 0xFFFF).command;
switch (command) { switch (command) {
case 0x0003: { case 0x0003: {
auto& cmd_mask = check_size_t<S_ServerInit_BB_03_9B>( auto& cmd_mask = check_size_t<S_ServerInitDefault_BB_03_9B>(
cmd_data, cmd_size, sizeof(S_ServerInit_BB_03_9B), 0xFFFF); cmd_data, cmd_size, sizeof(S_ServerInitDefault_BB_03_9B), 0xFFFF);
cmd_mask.server_key.clear(0); cmd_mask.server_key.clear(0);
cmd_mask.client_key.clear(0); cmd_mask.client_key.clear(0);
break; break;
@@ -593,8 +593,8 @@ void ReplaySession::on_command_received(
case GameVersion::GC: case GameVersion::GC:
case GameVersion::XB: case GameVersion::XB:
if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) { if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) {
auto& cmd = check_size_t<S_ServerInit_DC_PC_V3_02_17_91_9B>(data, auto& cmd = check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(data,
offsetof(S_ServerInit_DC_PC_V3_02_17_91_9B, after_message), 0xFFFF); sizeof(S_ServerInitDefault_DC_PC_V3_02_17_91_9B), 0xFFFF);
if ((c->version == GameVersion::DC) || (c->version == GameVersion::PC)) { if ((c->version == GameVersion::DC) || (c->version == GameVersion::PC)) {
c->channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key)); c->channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key));
c->channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key)); c->channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key));
@@ -606,8 +606,8 @@ void ReplaySession::on_command_received(
break; break;
case GameVersion::BB: case GameVersion::BB:
if (command == 0x03 || command == 0x9B) { if (command == 0x03 || command == 0x9B) {
auto& cmd = check_size_t<S_ServerInit_BB_03_9B>(data, auto& cmd = check_size_t<S_ServerInitDefault_BB_03_9B>(data,
sizeof(S_ServerInit_BB_03_9B), 0xFFFF); sizeof(S_ServerInitDefault_BB_03_9B), 0xFFFF);
// TODO: At some point it may matter which BB private key file we use. // TODO: At some point it may matter which BB private key file we use.
// Don't just blindly use the first one here. // Don't just blindly use the first one here.
c->channel.crypt_in.reset(new PSOBBEncryption( c->channel.crypt_in.reset(new PSOBBEncryption(
+8 -6
View File
@@ -27,6 +27,7 @@ extern FileContentsCache file_cache;
const unordered_set<uint32_t> v2_crypt_initial_client_commands({ const unordered_set<uint32_t> v2_crypt_initial_client_commands({
0x00260088, // (17) DCNTE license check
0x00280090, // (17) DCv1 license check 0x00280090, // (17) DCv1 license check
0x00B00093, // (02) DCv1 login 0x00B00093, // (02) DCv1 login
0x01140093, // (02) DCv1 extended login 0x01140093, // (02) DCv1 extended login
@@ -111,17 +112,18 @@ void send_command_with_header(Channel& ch, const void* data, size_t size) {
static const char* anti_copyright = "This server is in no way affiliated, sponsored, or supported by SEGA Enterprises or SONICTEAM. The preceding message exists only in order to remain compatible with programs that expect it."; static const char* anti_copyright = "This server is in no way affiliated, sponsored, or supported by SEGA Enterprises or SONICTEAM. The preceding message exists only to remain compatible with programs that expect it.";
static const char* dc_port_map_copyright = "DreamCast Port Map. Copyright SEGA Enterprises. 1999"; static const char* dc_port_map_copyright = "DreamCast Port Map. Copyright SEGA Enterprises. 1999";
static const char* dc_lobby_server_copyright = "DreamCast Lobby Server. Copyright SEGA Enterprises. 1999"; static const char* dc_lobby_server_copyright = "DreamCast Lobby Server. Copyright SEGA Enterprises. 1999";
static const char* bb_game_server_copyright = "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM."; static const char* bb_game_server_copyright = "Phantasy Star Online Blue Burst Game Server. Copyright 1999-2004 SONICTEAM.";
static const char* bb_pm_server_copyright = "PSO NEW PM Server. Copyright 1999-2002 SONICTEAM."; static const char* bb_pm_server_copyright = "PSO NEW PM Server. Copyright 1999-2002 SONICTEAM.";
static const char* patch_server_copyright = "Patch Server. Copyright SonicTeam, LTD. 2001"; static const char* patch_server_copyright = "Patch Server. Copyright SonicTeam, LTD. 2001";
S_ServerInit_DC_PC_V3_02_17_91_9B prepare_server_init_contents_console( S_ServerInitWithAfterMessage_DC_PC_V3_02_17_91_9B<0xCC>
prepare_server_init_contents_console(
uint32_t server_key, uint32_t client_key, uint8_t flags) { uint32_t server_key, uint32_t client_key, uint8_t flags) {
bool initial_connection = (flags & SendServerInitFlag::IS_INITIAL_CONNECTION); bool initial_connection = (flags & SendServerInitFlag::IS_INITIAL_CONNECTION);
S_ServerInit_DC_PC_V3_02_17_91_9B cmd; S_ServerInitWithAfterMessage_DC_PC_V3_02_17_91_9B<0xCC> cmd;
cmd.copyright = initial_connection cmd.copyright = initial_connection
? dc_port_map_copyright : dc_lobby_server_copyright; ? dc_port_map_copyright : dc_lobby_server_copyright;
cmd.server_key = server_key; cmd.server_key = server_key;
@@ -138,7 +140,7 @@ void send_server_init_dc_pc_v3(shared_ptr<Client> c, uint8_t flags) {
auto cmd = prepare_server_init_contents_console( auto cmd = prepare_server_init_contents_console(
server_key, client_key, initial_connection); server_key, client_key, initial_connection);
send_command_t(c, command, 0x00, cmd); send_command_t(c, command, 0x01, cmd);
switch (c->version()) { switch (c->version()) {
case GameVersion::PC: case GameVersion::PC:
@@ -159,12 +161,12 @@ void send_server_init_dc_pc_v3(shared_ptr<Client> c, uint8_t flags) {
} }
} }
S_ServerInit_BB_03_9B prepare_server_init_contents_bb( S_ServerInitWithAfterMessage_BB_03_9B prepare_server_init_contents_bb(
const parray<uint8_t, 0x30>& server_key, const parray<uint8_t, 0x30>& server_key,
const parray<uint8_t, 0x30>& client_key, const parray<uint8_t, 0x30>& client_key,
uint8_t flags) { uint8_t flags) {
bool use_secondary_message = (flags & SendServerInitFlag::USE_SECONDARY_MESSAGE); bool use_secondary_message = (flags & SendServerInitFlag::USE_SECONDARY_MESSAGE);
S_ServerInit_BB_03_9B cmd; S_ServerInitWithAfterMessage_BB_03_9B cmd;
cmd.copyright = use_secondary_message ? bb_pm_server_copyright : bb_game_server_copyright; cmd.copyright = use_secondary_message ? bb_pm_server_copyright : bb_game_server_copyright;
cmd.server_key = server_key; cmd.server_key = server_key;
cmd.client_key = client_key; cmd.client_key = client_key;
+3 -2
View File
@@ -102,9 +102,10 @@ enum SendServerInitFlag {
USE_SECONDARY_MESSAGE = 0x02, USE_SECONDARY_MESSAGE = 0x02,
}; };
S_ServerInit_DC_PC_V3_02_17_91_9B prepare_server_init_contents_console( S_ServerInitWithAfterMessage_DC_PC_V3_02_17_91_9B<0xCC>
prepare_server_init_contents_console(
uint32_t server_key, uint32_t client_key, uint8_t flags); uint32_t server_key, uint32_t client_key, uint8_t flags);
S_ServerInit_BB_03_9B prepare_server_init_contents_bb( S_ServerInitWithAfterMessage_BB_03_9B prepare_server_init_contents_bb(
const parray<uint8_t, 0x30>& server_key, const parray<uint8_t, 0x30>& server_key,
const parray<uint8_t, 0x30>& client_key, const parray<uint8_t, 0x30>& client_key,
uint8_t flags); uint8_t flags);
+3
View File
@@ -32,6 +32,9 @@ uint16_t flags_for_version(GameVersion version, int64_t sub_version) {
break; break;
// TODO: Which other sub_versions of DC v1 and v2 exist? // TODO: Which other sub_versions of DC v1 and v2 exist?
case 0x20: // DCNTE
// In the case of DCNTE, the IS_TRIAL_EDITION flag is already set when we
// get here, so the remaining flags are the same as DCv1
case 0x21: // DCv1 US case 0x21: // DCv1 US
return Client::Flag::IS_DC_V1 | return Client::Flag::IS_DC_V1 |
Client::Flag::NO_D6 | Client::Flag::NO_D6 |