implement BB client's config scramble logic
This commit is contained in:
+19
-18
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user