implement BB client's config scramble logic

This commit is contained in:
Martin Michelsen
2023-12-27 23:08:17 -08:00
parent 2aa699b5b0
commit d478e9b0be
5 changed files with 81 additions and 52 deletions
+19 -18
View File
@@ -1684,12 +1684,12 @@ struct C_LoginExtendedV1_DC_93 : C_LoginV1_DC_93 {
// 93 (C->S): Log in (BB) // 93 (C->S): Log in (BB)
struct C_Login_BB_93 { struct C_LoginBase_BB_93 {
le_uint32_t player_tag = 0x00010000; le_uint32_t player_tag = 0x00010000;
le_uint32_t guild_card_number = 0; le_uint32_t guild_card_number = 0;
le_uint32_t sub_version = 0; le_uint32_t sub_version = 0;
uint8_t language; uint8_t language = 0;
int8_t character_slot; int8_t character_slot = 0;
// Values for connection_phase: // Values for connection_phase:
// 00 - initial connection (client will request system file, characters, etc.) // 00 - initial connection (client will request system file, characters, etc.)
// 01 - choose character // 01 - choose character
@@ -1697,9 +1697,9 @@ struct C_Login_BB_93 {
// 03 - apply updates from dressing room // 03 - apply updates from dressing room
// 04 - login server // 04 - login server
// 05 - lobby server (and beyond) // 05 - lobby server (and beyond)
uint8_t connection_phase; uint8_t connection_phase = 0;
uint8_t client_code; uint8_t client_code = 0;
le_uint32_t team_id = 0; le_uint32_t security_token = 0;
pstring<TextEncoding::ASCII, 0x30> username; pstring<TextEncoding::ASCII, 0x30> username;
pstring<TextEncoding::ASCII, 0x30> password; pstring<TextEncoding::ASCII, 0x30> password;
@@ -1708,20 +1708,21 @@ struct C_Login_BB_93 {
// doesn't use it anyway). // doesn't use it anyway).
le_uint32_t menu_id = 0; le_uint32_t menu_id = 0;
le_uint32_t preferred_lobby_id = 0; le_uint32_t preferred_lobby_id = 0;
} __packed__;
struct C_LoginWithoutHardwareInfo_BB_93 : C_LoginBase_BB_93 {
// Note: Unlike other versions, BB puts the version string in the client // Note: Unlike other versions, BB puts the version string in the client
// config at connect time. So the first time the server gets this command, it // config at connect time. So the first time the server gets this command, it
// will be something like "Ver. 1.24.3". Note also that some old versions // will be something like "Ver. 1.24.3". This format is used on older client
// (before 1.23.8?) omit the hardware_info field before the client config, so // versions (before 1.23.8?)
// the client config starts 8 bytes earlier on those versions and the entire parray<uint8_t, 0x28> client_config;
// command is 8 bytes shorter, hence this odd-looking union. } __packed__;
union VariableLengthSection {
parray<uint8_t, 0x28> old_client_config; struct C_LoginWithHardwareInfo_BB_93 : C_LoginBase_BB_93 {
struct NewFormat { // See the comment in the above structure. This format is used on newer client
parray<le_uint32_t, 2> hardware_info; // versions.
parray<uint8_t, 0x28> client_config; parray<le_uint32_t, 2> hardware_info;
} __packed__ new_clients; parray<uint8_t, 0x28> client_config;
} __packed__ var;
} __packed__; } __packed__;
// 94: Invalid command // 94: Invalid command
@@ -3080,7 +3081,7 @@ struct S_ClientInit_BB_00E6 {
le_uint32_t error_code = 0; le_uint32_t error_code = 0;
le_uint32_t player_tag = 0x00010000; le_uint32_t player_tag = 0x00010000;
le_uint32_t guild_card_number = 0; le_uint32_t guild_card_number = 0;
le_uint32_t team_id = 0; le_uint32_t security_token = 0;
parray<uint8_t, 0x28> client_config; parray<uint8_t, 0x28> client_config;
uint8_t can_create_team = 1; uint8_t can_create_team = 1;
uint8_t episode_4_unlocked = 1; uint8_t episode_4_unlocked = 1;
+1 -1
View File
@@ -372,7 +372,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
if (command != 0x93) { if (command != 0x93) {
throw runtime_error("command is not 93"); throw runtime_error("command is not 93");
} }
const auto& cmd = check_size_t<C_Login_BB_93>(data); const auto& cmd = check_size_t<C_LoginBase_BB_93>(data, 0xFFFF);
try { try {
ses->license = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode()); ses->license = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode());
} catch (const LicenseIndex::missing_license&) { } catch (const LicenseIndex::missing_license&) {
+58 -30
View File
@@ -1043,24 +1043,58 @@ static void on_9E_XB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
send_command(c, 0x9F, 0x00); send_command(c, 0x9F, 0x00);
} }
static void scramble_bb_security_data(parray<uint8_t, 0x28>& data, uint8_t which, bool reverse) {
static const uint8_t forward_orders[8][5] = {
{2, 0, 1, 4, 3},
{3, 4, 0, 1, 2},
{2, 3, 4, 0, 1},
{2, 3, 0, 1, 4},
{0, 2, 3, 4, 1},
{1, 4, 2, 3, 0},
{2, 0, 1, 4, 3},
{1, 0, 3, 4, 2},
};
static const uint8_t reverse_orders[8][5] = {
{1, 2, 0, 4, 3},
{2, 3, 4, 0, 1},
{3, 4, 0, 1, 2},
{2, 3, 0, 1, 4},
{0, 4, 1, 2, 3},
{4, 0, 2, 3, 1},
{1, 2, 0, 4, 3},
{1, 0, 4, 2, 3},
};
const auto& order = reverse ? reverse_orders[which & 7] : forward_orders[which & 7];
parray<uint8_t, 0x28> scrambled_data;
for (size_t z = 0; z < 5; z++) {
for (size_t x = 0; x < 8; x++) {
scrambled_data[(z * 8) + x] = data[(order[z] * 8) + x];
}
}
data = scrambled_data;
}
static void on_93_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) { static void on_93_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
const auto& cmd = check_size_t<C_Login_BB_93>(data, sizeof(C_Login_BB_93) - 8, sizeof(C_Login_BB_93)); const auto& base_cmd = check_size_t<C_LoginBase_BB_93>(data, 0xFFFF);
auto s = c->require_server_state(); auto s = c->require_server_state();
bool is_old_format; parray<uint8_t, 0x28> config_data = (data.size() == sizeof(C_LoginWithoutHardwareInfo_BB_93))
if (data.size() == sizeof(C_Login_BB_93) - 8) { ? check_size_t<C_LoginWithoutHardwareInfo_BB_93>(data).client_config
is_old_format = true; : check_size_t<C_LoginWithHardwareInfo_BB_93>(data).client_config;
} else if (data.size() == sizeof(C_Login_BB_93)) {
is_old_format = false; // If security_token is zero, the game scrambles the client config data based
} else { // on the first character in the username. We undo the scramble here.
throw runtime_error("invalid size for 93 command"); if (base_cmd.security_token == 0) {
scramble_bb_security_data(config_data, base_cmd.username.at(0), true);
} }
c->config.set_flags_for_version(c->version(), cmd.sub_version); c->config.set_flags_for_version(c->version(), base_cmd.sub_version);
c->channel.language = cmd.language; c->channel.language = base_cmd.language;
string username = base_cmd.username.decode();
string password = base_cmd.password.decode();
try { try {
auto l = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode()); auto l = s->license_index->verify_bb(username, password);
c->set_license(l); c->set_license(l);
} catch (const LicenseIndex::no_username& e) { } catch (const LicenseIndex::no_username& e) {
@@ -1080,9 +1114,9 @@ static void on_93_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
return; return;
} else { } else {
auto l = s->license_index->create_license(); auto l = s->license_index->create_license();
l->serial_number = fnv1a32(cmd.username.decode()) & 0x7FFFFFFF; l->serial_number = fnv1a32(username) & 0x7FFFFFFF;
l->bb_username = cmd.username.decode(); l->bb_username = username;
l->bb_password = cmd.password.decode(); l->bb_password = password;
s->license_index->add(l); s->license_index->add(l);
if (!s->is_replay) { if (!s->is_replay) {
l->save(); l->save();
@@ -1093,16 +1127,10 @@ static void on_93_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
} }
} }
try { if (base_cmd.guild_card_number != 0) {
if (is_old_format) { c->config.parse_from(config_data);
c->config.parse_from(cmd.var.old_client_config); } else {
} else { string version_string = config_data.as_string();
c->config.parse_from(cmd.var.new_clients.client_config);
}
} catch (const invalid_argument&) {
string version_string = is_old_format
? cmd.var.old_client_config.as_string()
: cmd.var.new_clients.client_config.as_string();
strip_trailing_zeroes(version_string); strip_trailing_zeroes(version_string);
// Note: Tethealla PSOBB is actually Japanese PSOBB, but with most of the // Note: Tethealla PSOBB is actually Japanese PSOBB, but with most of the
// files replaced with English text/graphics/etc. For this reason, it still // files replaced with English text/graphics/etc. For this reason, it still
@@ -1113,17 +1141,17 @@ static void on_93_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
c->config.set_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB); c->config.set_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB);
} }
} }
c->channel.language = c->config.check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB) ? 1 : cmd.language; c->channel.language = c->config.check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB) ? 1 : base_cmd.language;
c->bb_connection_phase = cmd.connection_phase; c->bb_connection_phase = base_cmd.connection_phase;
c->bb_character_index = cmd.character_slot; c->bb_character_index = base_cmd.character_slot;
if (cmd.menu_id == MenuID::LOBBY) { if (base_cmd.menu_id == MenuID::LOBBY) {
c->preferred_lobby_id = cmd.preferred_lobby_id; c->preferred_lobby_id = base_cmd.preferred_lobby_id;
} }
send_client_init_bb(c, 0); send_client_init_bb(c, 0);
if (cmd.guild_card_number == 0) { if (base_cmd.guild_card_number == 0) {
// On first login, send the client to the data server port // On first login, send the client to the data server port
send_reconnect(c, s->connect_address_for_client(c), s->name_to_port_config.at("bb-data1")->port); send_reconnect(c, s->connect_address_for_client(c), s->name_to_port_config.at("bb-data1")->port);
+2 -2
View File
@@ -203,7 +203,7 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
if (header.command == 0x04) { if (header.command == 0x04) {
check_pw(check_size_t<C_LegacyLogin_BB_04>(cmd_data, cmd_size).password.decode()); check_pw(check_size_t<C_LegacyLogin_BB_04>(cmd_data, cmd_size).password.decode());
} else if (header.command == 0x93) { } else if (header.command == 0x93) {
check_pw(check_size_t<C_Login_BB_93>(cmd_data, cmd_size).password.decode()); check_pw(check_size_t<C_LoginBase_BB_93>(cmd_data, cmd_size, 0xFFFF).password.decode());
} else if (header.command == 0x9C) { } else if (header.command == 0x9C) {
check_pw(check_size_t<C_Register_BB_9C>(cmd_data, cmd_size).password.decode()); check_pw(check_size_t<C_Register_BB_9C>(cmd_data, cmd_size).password.decode());
} else if (header.command == 0x9E) { } else if (header.command == 0x9E) {
@@ -420,7 +420,7 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
} }
case 0x00E6: { case 0x00E6: {
auto& mask = check_size_t<S_ClientInit_BB_00E6>(mask_data, mask_size); auto& mask = check_size_t<S_ClientInit_BB_00E6>(mask_data, mask_size);
mask.team_id = 0; mask.security_token = 0;
break; break;
} }
} }
+1 -1
View File
@@ -506,7 +506,7 @@ void send_client_init_bb(shared_ptr<Client> c, uint32_t error_code) {
cmd.error_code = error_code; cmd.error_code = error_code;
cmd.player_tag = 0x00010000; cmd.player_tag = 0x00010000;
cmd.guild_card_number = c->license->serial_number; cmd.guild_card_number = c->license->serial_number;
cmd.team_id = team ? team->team_id : 0; cmd.security_token = team ? team->team_id : 0;
c->config.serialize_into(cmd.client_config); c->config.serialize_into(cmd.client_config);
cmd.can_create_team = 1; cmd.can_create_team = 1;
cmd.episode_4_unlocked = 1; cmd.episode_4_unlocked = 1;