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)
struct C_Login_BB_93 {
struct C_LoginBase_BB_93 {
le_uint32_t player_tag = 0x00010000;
le_uint32_t guild_card_number = 0;
le_uint32_t sub_version = 0;
uint8_t language;
int8_t character_slot;
uint8_t language = 0;
int8_t character_slot = 0;
// Values for connection_phase:
// 00 - initial connection (client will request system file, characters, etc.)
// 01 - choose character
@@ -1697,9 +1697,9 @@ struct C_Login_BB_93 {
// 03 - apply updates from dressing room
// 04 - login server
// 05 - lobby server (and beyond)
uint8_t connection_phase;
uint8_t client_code;
le_uint32_t team_id = 0;
uint8_t connection_phase = 0;
uint8_t client_code = 0;
le_uint32_t security_token = 0;
pstring<TextEncoding::ASCII, 0x30> username;
pstring<TextEncoding::ASCII, 0x30> password;
@@ -1708,20 +1708,21 @@ struct C_Login_BB_93 {
// doesn't use it anyway).
le_uint32_t menu_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
// 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
// (before 1.23.8?) omit the hardware_info field before the client config, so
// the client config starts 8 bytes earlier on those versions and the entire
// command is 8 bytes shorter, hence this odd-looking union.
union VariableLengthSection {
parray<uint8_t, 0x28> old_client_config;
struct NewFormat {
parray<le_uint32_t, 2> hardware_info;
parray<uint8_t, 0x28> client_config;
} __packed__ new_clients;
} __packed__ var;
// will be something like "Ver. 1.24.3". This format is used on older client
// versions (before 1.23.8?)
parray<uint8_t, 0x28> client_config;
} __packed__;
struct C_LoginWithHardwareInfo_BB_93 : C_LoginBase_BB_93 {
// See the comment in the above structure. This format is used on newer client
// versions.
parray<le_uint32_t, 2> hardware_info;
parray<uint8_t, 0x28> client_config;
} __packed__;
// 94: Invalid command
@@ -3080,7 +3081,7 @@ struct S_ClientInit_BB_00E6 {
le_uint32_t error_code = 0;
le_uint32_t player_tag = 0x00010000;
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;
uint8_t can_create_team = 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) {
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 {
ses->license = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode());
} 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);
}
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) {
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();
bool is_old_format;
if (data.size() == sizeof(C_Login_BB_93) - 8) {
is_old_format = true;
} else if (data.size() == sizeof(C_Login_BB_93)) {
is_old_format = false;
} else {
throw runtime_error("invalid size for 93 command");
parray<uint8_t, 0x28> config_data = (data.size() == sizeof(C_LoginWithoutHardwareInfo_BB_93))
? check_size_t<C_LoginWithoutHardwareInfo_BB_93>(data).client_config
: check_size_t<C_LoginWithHardwareInfo_BB_93>(data).client_config;
// If security_token is zero, the game scrambles the client config data based
// on the first character in the username. We undo the scramble here.
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->channel.language = cmd.language;
c->config.set_flags_for_version(c->version(), base_cmd.sub_version);
c->channel.language = base_cmd.language;
string username = base_cmd.username.decode();
string password = base_cmd.password.decode();
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);
} 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;
} else {
auto l = s->license_index->create_license();
l->serial_number = fnv1a32(cmd.username.decode()) & 0x7FFFFFFF;
l->bb_username = cmd.username.decode();
l->bb_password = cmd.password.decode();
l->serial_number = fnv1a32(username) & 0x7FFFFFFF;
l->bb_username = username;
l->bb_password = password;
s->license_index->add(l);
if (!s->is_replay) {
l->save();
@@ -1093,16 +1127,10 @@ static void on_93_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
}
}
try {
if (is_old_format) {
c->config.parse_from(cmd.var.old_client_config);
} else {
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();
if (base_cmd.guild_card_number != 0) {
c->config.parse_from(config_data);
} else {
string version_string = config_data.as_string();
strip_trailing_zeroes(version_string);
// Note: Tethealla PSOBB is actually Japanese PSOBB, but with most of the
// 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->channel.language = c->config.check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB) ? 1 : cmd.language;
c->bb_connection_phase = cmd.connection_phase;
c->bb_character_index = cmd.character_slot;
c->channel.language = c->config.check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB) ? 1 : base_cmd.language;
c->bb_connection_phase = base_cmd.connection_phase;
c->bb_character_index = base_cmd.character_slot;
if (cmd.menu_id == MenuID::LOBBY) {
c->preferred_lobby_id = cmd.preferred_lobby_id;
if (base_cmd.menu_id == MenuID::LOBBY) {
c->preferred_lobby_id = base_cmd.preferred_lobby_id;
}
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
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) {
check_pw(check_size_t<C_LegacyLogin_BB_04>(cmd_data, cmd_size).password.decode());
} 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) {
check_pw(check_size_t<C_Register_BB_9C>(cmd_data, cmd_size).password.decode());
} else if (header.command == 0x9E) {
@@ -420,7 +420,7 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
}
case 0x00E6: {
auto& mask = check_size_t<S_ClientInit_BB_00E6>(mask_data, mask_size);
mask.team_id = 0;
mask.security_token = 0;
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.player_tag = 0x00010000;
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);
cmd.can_create_team = 1;
cmd.episode_4_unlocked = 1;