use .psochar format for BB characters

This commit is contained in:
Martin Michelsen
2023-11-13 13:00:22 -08:00
parent f5bfd4a3c6
commit 18ddfa4ef4
20 changed files with 1138 additions and 842 deletions
+138 -118
View File
@@ -1005,9 +1005,7 @@ static void on_93_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
string version_string = is_old_format
? cmd.var.old_client_config.as_string()
: cmd.var.new_clients.client_config.as_string();
print_data(stderr, version_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
// reports its language as Japanese, so we have to account for that
@@ -1019,7 +1017,7 @@ static void on_93_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
}
c->channel.language = c->config.check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB) ? 1 : cmd.language;
c->bb_connection_phase = cmd.connection_phase;
c->game_data.bb_player_index = cmd.character_slot;
c->game_data.bb_character_index = cmd.character_slot;
if (cmd.menu_id == MenuID::LOBBY) {
c->preferred_lobby_id = cmd.preferred_lobby_id;
@@ -1492,7 +1490,7 @@ static void on_CA_Ep3(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
l->battle_record.reset(new Episode3::BattleRecord(s->ep3_behavior_flags));
for (auto existing_c : l->clients) {
if (existing_c) {
auto existing_p = existing_c->game_data.player();
auto existing_p = existing_c->game_data.character();
PlayerLobbyDataDCGC lobby_data;
lobby_data.name.encode(existing_p->disp.name.decode(existing_c->language()), c->language());
lobby_data.player_tag = 0x00010000;
@@ -1677,7 +1675,7 @@ static void on_09(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
for (size_t x = 0; x < game->max_clients; x++) {
const auto& game_c = game->clients[x];
if (game_c.get()) {
auto player = game_c->game_data.player();
auto player = game_c->game_data.character();
string name = player->disp.name.decode(game_c->language());
if (game->is_ep3()) {
info += string_printf("%zu: $C6%s$C7 L%" PRIu32 "\n",
@@ -1860,12 +1858,12 @@ void set_lobby_quest(shared_ptr<Lobby> l, shared_ptr<const Quest> q) {
// If an overlay was created, item IDs need to be assigned
if (lc->game_data.has_overlay()) {
auto overlay = lc->game_data.player();
auto overlay = lc->game_data.character();
for (size_t z = 0; z < overlay->inventory.num_items; z++) {
overlay->inventory.items[z].data.id = l->generate_item_id(client_id);
}
lc->log.info("Assigned overlay item IDs");
lc->game_data.player()->print_inventory(stderr, lc->version(), s->item_name_index);
overlay->print_inventory(stderr, lc->version(), s->item_name_index);
}
string bin_filename = vq->bin_filename();
@@ -1955,7 +1953,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
}
case MainMenuItemID::PROXY_DESTINATIONS:
if (!c->game_data.player(false, false)) {
if (!c->game_data.character(false, false)) {
send_get_player_info(c);
}
send_proxy_destinations_menu(c);
@@ -2207,7 +2205,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
send_lobby_message_box(c, "$C6Incorrect password.");
break;
}
auto p = c->game_data.player();
auto p = c->game_data.character();
if (p->disp.stats.level < game->min_level) {
send_lobby_message_box(c, "$C6Your level is too\nlow to join this\ngame.");
break;
@@ -2361,7 +2359,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
break;
}
if (team_name.empty()) {
team_name = c->game_data.player()->disp.name.decode(c->language());
team_name = c->game_data.character()->disp.name.decode(c->language());
team_name += string_printf("/%" PRIX32, c->license->serial_number);
}
auto s = c->require_server_state();
@@ -2726,35 +2724,36 @@ static void on_13_A7_V3_BB(shared_ptr<Client> c, uint16_t command, uint32_t flag
static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, string& data) {
auto s = c->require_server_state();
auto player = c->game_data.player();
auto account = c->game_data.account();
auto player = c->game_data.character();
switch (c->version()) {
case GameVersion::DC: {
if (c->config.check_flag(Client::Flag::IS_DC_V1)) {
const auto& pd = check_size_t<C_CharacterData_DCv1_61_98>(data);
player->inventory = pd.inventory;
player->disp = pd.disp.to_bb(player->inventory.language, player->inventory.language);
const auto& cmd = check_size_t<C_CharacterData_DCv1_61_98>(data);
player->inventory = cmd.inventory;
player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language);
} else {
const auto& pd = check_size_t<C_CharacterData_DCv2_61_98>(data, 0xFFFF);
player->inventory = pd.inventory;
player->disp = pd.disp.to_bb(player->inventory.language, player->inventory.language);
player->battle_records = pd.records.battle;
player->challenge_records = pd.records.challenge;
// TODO: Parse choice search config
const auto& cmd = check_size_t<C_CharacterData_DCv2_61_98>(data, 0xFFFF);
player->inventory = cmd.inventory;
player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language);
player->battle_records = cmd.records.battle;
player->challenge_records = cmd.records.challenge;
player->choice_search_config = cmd.choice_search_config;
}
break;
}
case GameVersion::PC: {
const auto& pd = check_size_t<C_CharacterData_PC_61_98>(data, 0xFFFF);
player->inventory = pd.inventory;
player->disp = pd.disp.to_bb(player->inventory.language, player->inventory.language);
player->battle_records = pd.records.battle;
player->challenge_records = pd.records.challenge;
// TODO: Parse choice search config
account->blocked_senders = pd.blocked_senders;
if (pd.auto_reply_enabled) {
string auto_reply = data.substr(sizeof(pd));
const auto& cmd = check_size_t<C_CharacterData_PC_61_98>(data, 0xFFFF);
player->inventory = cmd.inventory;
player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language);
player->battle_records = cmd.records.battle;
player->challenge_records = cmd.records.challenge;
player->choice_search_config = cmd.choice_search_config;
for (size_t z = 0; z < cmd.blocked_senders.size(); z++) {
c->game_data.blocked_senders.at(z) = cmd.blocked_senders[z];
}
if (cmd.auto_reply_enabled) {
string auto_reply = data.substr(sizeof(cmd));
strip_trailing_zeroes(auto_reply);
if (auto_reply.size() & 1) {
auto_reply.push_back(0);
@@ -2772,9 +2771,9 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
if (!c->config.check_flag(Client::Flag::IS_EPISODE_3)) {
throw runtime_error("non-Episode 3 client sent Episode 3 player data");
}
const auto* pd3 = &check_size_t<C_CharacterData_GC_Ep3_61_98>(data);
c->game_data.ep3_config.reset(new Episode3::PlayerConfig(pd3->ep3_config));
cmd = reinterpret_cast<const C_CharacterData_V3_61_98*>(pd3);
const auto* cmd3 = &check_size_t<C_CharacterData_GC_Ep3_61_98>(data);
c->game_data.ep3_config.reset(new Episode3::PlayerConfig(cmd3->ep3_config));
cmd = reinterpret_cast<const C_CharacterData_V3_61_98*>(cmd3);
} else {
if (c->config.check_flag(Client::Flag::IS_EPISODE_3)) {
c->config.set_flag(Client::Flag::IS_EP3_TRIAL_EDITION);
@@ -2817,7 +2816,9 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
player->challenge_records = cmd->records.challenge;
// TODO: Parse choice search config
player->info_board.encode(cmd->info_board.decode(player->inventory.language), player->inventory.language);
account->blocked_senders = cmd->blocked_senders;
for (size_t z = 0; z < cmd->blocked_senders.size(); z++) {
c->game_data.blocked_senders.at(z) = cmd->blocked_senders[z];
}
if (cmd->auto_reply_enabled) {
string auto_reply = data.substr(sizeof(cmd), 0xAC);
strip_trailing_zeroes(auto_reply);
@@ -2836,7 +2837,9 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
player->challenge_records = cmd.records.challenge;
// TODO: Parse choice search config
player->info_board = cmd.info_board;
account->blocked_senders = cmd.blocked_senders;
for (size_t z = 0; z < cmd.blocked_senders.size(); z++) {
c->game_data.blocked_senders.at(z) = cmd.blocked_senders[z];
}
if (cmd.auto_reply_enabled) {
string auto_reply = data.substr(sizeof(cmd), 0xAC);
strip_trailing_zeroes(auto_reply);
@@ -2872,14 +2875,14 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
} else if (command == 0x61) {
if (!c->pending_bb_save_username.empty()) {
string prev_bb_username = c->game_data.bb_username;
size_t prev_bb_player_index = c->game_data.bb_player_index;
int8_t prev_bb_character_index = c->game_data.bb_character_index;
c->game_data.bb_username = c->pending_bb_save_username;
c->game_data.bb_player_index = c->pending_bb_save_player_index;
c->game_data.bb_character_index = c->pending_bb_save_character_index;
bool failure = false;
try {
c->game_data.save_player_data();
c->game_data.save_character_file();
} catch (const exception& e) {
send_text_message_printf(c, "$C6PSOBB player data could\nnot be saved:\n%s", e.what());
failure = true;
@@ -2888,12 +2891,12 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
if (!failure) {
send_text_message_printf(c,
"$C6BB player data saved\nas player %hhu for user\n%s",
static_cast<uint8_t>(c->pending_bb_save_player_index + 1),
static_cast<uint8_t>(c->pending_bb_save_character_index + 1),
c->pending_bb_save_username.c_str());
}
c->game_data.bb_username = prev_bb_username;
c->game_data.bb_player_index = prev_bb_player_index;
c->game_data.bb_character_index = prev_bb_character_index;
c->pending_bb_save_username.clear();
}
@@ -2951,7 +2954,7 @@ static void on_06(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
return;
}
auto p = c->game_data.player();
auto p = c->game_data.character();
string from_name = p->disp.name.decode(c->language());
if (from_name.size() >= 2 && from_name[0] == '\t' && (from_name[1] == 'E' || from_name[1] == 'J')) {
from_name = from_name.substr(2);
@@ -2990,6 +2993,8 @@ static void on_E0_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
static void on_E3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
const auto& cmd = check_size_t<C_PlayerPreviewRequest_BB_E3>(data);
c->game_data.bb_character_index = cmd.character_index;
if (c->bb_connection_phase != 0x00) {
send_approve_player_choice_bb(c);
@@ -3002,15 +3007,16 @@ static void on_E3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
ClientGameData temp_gd;
temp_gd.guild_card_number = c->license->serial_number;
temp_gd.bb_username = c->license->bb_username;
temp_gd.bb_player_index = cmd.player_index;
temp_gd.bb_character_index = cmd.character_index;
try {
auto preview = temp_gd.player()->disp.to_preview();
send_player_preview_bb(c, cmd.player_index, &preview);
auto preview = temp_gd.character()->disp.to_preview();
send_player_preview_bb(c, cmd.character_index, &preview);
} catch (const exception& e) {
// Player doesn't exist
send_player_preview_bb(c, cmd.player_index, nullptr);
c->log.warning("Can\'t load character data: %s", e.what());
send_player_preview_bb(c, cmd.character_index, nullptr);
}
}
}
@@ -3018,10 +3024,12 @@ static void on_E3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
static void on_E8_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& data) {
constexpr size_t max_count = sizeof(PSOBBGuildCardFile::entries) / sizeof(PSOBBGuildCardFile::Entry);
constexpr size_t max_blocked = sizeof(PSOBBGuildCardFile::blocked) / sizeof(GuildCardBB);
auto gcf = c->game_data.guild_cards();
bool should_save = false;
switch (command) {
case 0x01E8: { // Check guild card file checksum
const auto& cmd = check_size_t<C_GuildCardChecksum_01E8>(data);
uint32_t checksum = c->game_data.account()->guild_card_file.checksum();
uint32_t checksum = gcf->checksum();
c->log.info("(Guild card file) Server checksum = %08" PRIX32 ", client checksum = %08" PRIX32,
checksum, cmd.checksum.load());
S_GuildCardChecksumResponse_BB_02E8 response = {
@@ -3035,13 +3043,12 @@ static void on_E8_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
break;
case 0x04E8: { // Add guild card
auto& new_gc = check_size_t<GuildCardBB>(data);
auto& gcf = c->game_data.account()->guild_card_file;
for (size_t z = 0; z < max_count; z++) {
if (!gcf.entries[z].data.present) {
gcf.entries[z].data = new_gc;
gcf.entries[z].unknown_a1.clear(0);
c->log.info("Added guild card %" PRIu32 " at position %zu",
new_gc.guild_card_number.load(), z);
if (!gcf->entries[z].data.present) {
gcf->entries[z].data = new_gc;
gcf->entries[z].unknown_a1.clear(0);
c->log.info("Added guild card %" PRIu32 " at position %zu", new_gc.guild_card_number.load(), z);
should_save = true;
break;
}
}
@@ -3049,15 +3056,14 @@ static void on_E8_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
}
case 0x05E8: { // Delete guild card
auto& cmd = check_size_t<C_DeleteGuildCard_BB_05E8_08E8>(data);
auto& gcf = c->game_data.account()->guild_card_file;
for (size_t z = 0; z < max_count; z++) {
if (gcf.entries[z].data.guild_card_number == cmd.guild_card_number) {
c->log.info("Deleted guild card %" PRIu32 " at position %zu",
cmd.guild_card_number.load(), z);
if (gcf->entries[z].data.guild_card_number == cmd.guild_card_number) {
c->log.info("Deleted guild card %" PRIu32 " at position %zu", cmd.guild_card_number.load(), z);
for (z = 0; z < max_count - 1; z++) {
gcf.entries[z] = gcf.entries[z + 1];
gcf->entries[z] = gcf->entries[z + 1];
}
gcf.entries[max_count - 1].clear();
gcf->entries[max_count - 1].clear();
should_save = true;
break;
}
}
@@ -3065,26 +3071,24 @@ static void on_E8_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
}
case 0x06E8: { // Update guild card
auto& new_gc = check_size_t<GuildCardBB>(data);
auto& gcf = c->game_data.account()->guild_card_file;
for (size_t z = 0; z < max_count; z++) {
if (gcf.entries[z].data.guild_card_number == new_gc.guild_card_number) {
gcf.entries[z].data = new_gc;
c->log.info("Updated guild card %" PRIu32 " at position %zu",
new_gc.guild_card_number.load(), z);
if (gcf->entries[z].data.guild_card_number == new_gc.guild_card_number) {
gcf->entries[z].data = new_gc;
c->log.info("Updated guild card %" PRIu32 " at position %zu", new_gc.guild_card_number.load(), z);
should_save = true;
}
}
break;
}
case 0x07E8: { // Add blocked user
auto& new_gc = check_size_t<GuildCardBB>(data);
auto& gcf = c->game_data.account()->guild_card_file;
for (size_t z = 0; z < max_blocked; z++) {
if (!gcf.blocked[z].present) {
gcf.blocked[z] = new_gc;
c->log.info("Added blocked guild card %" PRIu32 " at position %zu",
new_gc.guild_card_number.load(), z);
if (!gcf->blocked[z].present) {
gcf->blocked[z] = new_gc;
c->log.info("Added blocked guild card %" PRIu32 " at position %zu", new_gc.guild_card_number.load(), z);
// Note: The client also sends a C6 command, so we don't have to
// manually sync the actual blocked senders list here
should_save = true;
break;
}
}
@@ -3092,17 +3096,17 @@ static void on_E8_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
}
case 0x08E8: { // Delete blocked user
auto& cmd = check_size_t<C_DeleteGuildCard_BB_05E8_08E8>(data);
auto& gcf = c->game_data.account()->guild_card_file;
for (size_t z = 0; z < max_blocked; z++) {
if (gcf.blocked[z].guild_card_number == cmd.guild_card_number) {
if (gcf->blocked[z].guild_card_number == cmd.guild_card_number) {
c->log.info("Deleted blocked guild card %" PRIu32 " at position %zu",
cmd.guild_card_number.load(), z);
for (z = 0; z < max_blocked - 1; z++) {
gcf.blocked[z] = gcf.blocked[z + 1];
gcf->blocked[z] = gcf->blocked[z + 1];
}
gcf.blocked[max_blocked - 1].clear();
gcf->blocked[max_blocked - 1].clear();
// Note: The client also sends a C6 command, so we don't have to
// manually sync the actual blocked senders list here
should_save = true;
break;
}
}
@@ -3110,12 +3114,11 @@ static void on_E8_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
}
case 0x09E8: { // Write comment
auto& cmd = check_size_t<C_WriteGuildCardComment_BB_09E8>(data);
auto& gcf = c->game_data.account()->guild_card_file;
for (size_t z = 0; z < max_count; z++) {
if (gcf.entries[z].data.guild_card_number == cmd.guild_card_number) {
gcf.entries[z].comment = cmd.comment;
c->log.info("Updated comment on guild card %" PRIu32 " at position %zu",
cmd.guild_card_number.load(), z);
if (gcf->entries[z].data.guild_card_number == cmd.guild_card_number) {
gcf->entries[z].comment = cmd.comment;
c->log.info("Updated comment on guild card %" PRIu32 " at position %zu", cmd.guild_card_number.load(), z);
should_save = true;
break;
}
}
@@ -3123,34 +3126,36 @@ static void on_E8_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
}
case 0x0AE8: { // Move guild card in list
auto& cmd = check_size_t<C_MoveGuildCard_BB_0AE8>(data);
auto& gcf = c->game_data.account()->guild_card_file;
if (cmd.position >= max_count) {
throw invalid_argument("invalid new position");
}
size_t index;
for (index = 0; index < max_count; index++) {
if (gcf.entries[index].data.guild_card_number == cmd.guild_card_number) {
if (gcf->entries[index].data.guild_card_number == cmd.guild_card_number) {
break;
}
}
if (index >= max_count) {
throw invalid_argument("player does not have requested guild card");
}
auto moved_gc = gcf.entries[index];
auto moved_gc = gcf->entries[index];
for (; index < cmd.position; index++) {
gcf.entries[index] = gcf.entries[index + 1];
gcf->entries[index] = gcf->entries[index + 1];
}
for (; index > cmd.position; index--) {
gcf.entries[index] = gcf.entries[index - 1];
gcf->entries[index] = gcf->entries[index - 1];
}
gcf.entries[index] = moved_gc;
c->log.info("Moved guild card %" PRIu32 " to position %zu",
cmd.guild_card_number.load(), index);
gcf->entries[index] = moved_gc;
c->log.info("Moved guild card %" PRIu32 " to position %zu", cmd.guild_card_number.load(), index);
should_save = true;
break;
}
default:
throw invalid_argument("invalid command");
}
if (should_save) {
c->game_data.save_guild_card_file();
}
}
static void on_DC_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
@@ -3184,15 +3189,17 @@ static void on_E5_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
return;
}
if (c->game_data.player(false).get()) {
if (c->game_data.character(false).get()) {
throw runtime_error("player already exists");
}
c->game_data.bb_player_index = cmd.player_index;
c->game_data.bb_character_index = -1;
c->game_data.system(); // Ensure system file is loaded
c->game_data.bb_character_index = cmd.character_index;
if (c->bb_connection_phase == 0x03) { // Dressing room
try {
c->game_data.player()->disp.apply_dressing_room(cmd.preview);
c->game_data.character()->disp.apply_dressing_room(cmd.preview);
} catch (const exception& e) {
send_message_box(c, string_printf("$C6Character could not be modified:\n%s", e.what()));
return;
@@ -3200,7 +3207,7 @@ static void on_E5_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
} else {
try {
auto s = c->require_server_state();
c->game_data.create_player(cmd.preview, s->level_table);
c->game_data.create_character_file(c->license->serial_number, c->language(), cmd.preview, s->level_table);
} catch (const exception& e) {
send_message_box(c, string_printf("$C6New character could not be created:\n%s", e.what()));
return;
@@ -3212,45 +3219,49 @@ static void on_E5_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
}
static void on_ED_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& data) {
auto p = c->game_data.character();
auto sys = c->game_data.system();
switch (command) {
case 0x01ED: {
const auto& cmd = check_size_t<C_UpdateOptionFlags_BB_01ED>(data);
c->game_data.account()->option_flags = cmd.option_flags;
p->option_flags = cmd.option_flags;
break;
}
case 0x02ED: {
const auto& cmd = check_size_t<C_UpdateSymbolChats_BB_02ED>(data);
c->game_data.account()->symbol_chats = cmd.symbol_chats;
p->symbol_chats = cmd.symbol_chats;
break;
}
case 0x03ED: {
const auto& cmd = check_size_t<C_UpdateChatShortcuts_BB_03ED>(data);
c->game_data.account()->shortcuts = cmd.chat_shortcuts;
p->shortcuts = cmd.chat_shortcuts;
break;
}
case 0x04ED: {
const auto& cmd = check_size_t<C_UpdateKeyConfig_BB_04ED>(data);
c->game_data.account()->system_file.key_config = cmd.key_config;
sys->key_config = cmd.key_config;
c->game_data.save_system_file();
break;
}
case 0x05ED: {
const auto& cmd = check_size_t<C_UpdatePadConfig_BB_05ED>(data);
c->game_data.account()->system_file.joystick_config = cmd.pad_config;
sys->joystick_config = cmd.pad_config;
c->game_data.save_system_file();
break;
}
case 0x06ED: {
const auto& cmd = check_size_t<C_UpdateTechMenu_BB_06ED>(data);
c->game_data.player()->tech_menu_config = cmd.tech_menu;
p->tech_menu_config = cmd.tech_menu;
break;
}
case 0x07ED: {
const auto& cmd = check_size_t<C_UpdateCustomizeMenu_BB_07ED>(data);
c->game_data.player()->disp.config = cmd.customize;
p->disp.config = cmd.customize;
break;
}
case 0x08ED: {
const auto& cmd = check_size_t<C_UpdateChallengeRecords_BB_08ED>(data);
c->game_data.player()->challenge_records = cmd.records;
p->challenge_records = cmd.records;
break;
}
default:
@@ -3259,22 +3270,26 @@ static void on_ED_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
}
static void on_E7_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
const auto& cmd = check_size_t<SC_SyncCharacterSaveFile_BB_00E7>(data);
const auto& cmd = check_size_t<SC_SyncSaveFiles_BB_E7>(data);
// We only trust the player's quest data and challenge data.
// TODO: In the future, we shouldn't even need to trust these fields. We
// should instead verify our copy of the player against what the client sent,
// and alert on anything that's out of sync.
// TODO: In the future, we should save battle records here too.
auto p = c->game_data.player();
p->challenge_records = cmd.challenge_records;
p->quest_data1 = cmd.quest_data1;
p->quest_data2 = cmd.quest_data2;
// TODO: In the future, we shouldn't need to trust any of the client's data
// here. We should instead verify our copy of the player against what the
// client sent, and alert on anything that's out of sync.
auto p = c->game_data.character();
p->challenge_records = cmd.char_file.challenge_records;
p->battle_records = cmd.char_file.battle_records;
p->death_count = cmd.char_file.death_count;
*c->game_data.system() = cmd.system_file;
}
static void on_E2_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
auto& cmd = check_size_t<PSOBBSystemFile>(data);
c->game_data.account()->system_file = cmd;
auto sys = c->game_data.system();
*sys = cmd;
c->game_data.save_system_file();
S_SystemFileCreated_00E1_BB out_cmd = {1};
send_command_t(c, 0x00E1, 0x00000000, out_cmd);
}
static void on_DF_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& data) {
@@ -3349,7 +3364,7 @@ static void on_DF_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
case 0x07DF: {
const auto& cmd = check_size_t<C_CreateChallengeModeAwardItem_BB_07DF>(data);
auto p = c->game_data.player(true, false);
auto p = c->game_data.character(true, false);
auto& award_state = (l->episode == Episode::EP2)
? p->challenge_records.ep2_online_award_state
: p->challenge_records.ep1_online_award_state;
@@ -3434,14 +3449,14 @@ static void on_81(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
} else {
// If the sender is blocked, don't forward the mail
for (size_t y = 0; y < 30; y++) {
if (target->game_data.account()->blocked_senders.data()[y] == c->license->serial_number) {
if (target->game_data.blocked_senders.data()[y] == c->license->serial_number) {
return;
}
}
// If the target has auto-reply enabled, send the autoreply. Note that we also
// forward the message in this case.
auto target_p = target->game_data.player();
auto target_p = target->game_data.character();
if (!target_p->auto_reply.empty()) {
send_simple_mail(
c,
@@ -3454,7 +3469,7 @@ static void on_81(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
send_simple_mail(
target,
c->license->serial_number,
c->game_data.player()->disp.name.decode(c->language()),
c->game_data.character()->disp.name.decode(c->language()),
message);
}
}
@@ -3470,7 +3485,7 @@ void on_D9(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
if (is_w && (data.size() & 1)) {
data.push_back(0);
}
c->game_data.player(true, false)->info_board.encode(tt_decode_marked(data, c->language(), is_w), c->language());
c->game_data.character(true, false)->info_board.encode(tt_decode_marked(data, c->language(), is_w), c->language());
}
void on_C7(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
@@ -3479,21 +3494,26 @@ void on_C7(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
if (is_w && (data.size() & 1)) {
data.push_back(0);
}
c->game_data.player(true, false)->auto_reply.encode(tt_decode_marked(data, c->language(), is_w), c->language());
c->game_data.character(true, false)->auto_reply.encode(tt_decode_marked(data, c->language(), is_w), c->language());
}
static void on_C8(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
check_size_v(data.size(), 0);
c->game_data.player(true, false)->auto_reply.clear();
c->game_data.character(true, false)->auto_reply.clear();
}
static void on_C6(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
c->game_data.blocked_senders.fill(0);
if (c->version() == GameVersion::BB) {
const auto& cmd = check_size_t<C_SetBlockedSenders_BB_C6>(data);
c->game_data.account()->blocked_senders = cmd.blocked_senders;
for (size_t z = 0; z < cmd.blocked_senders.size(); z++) {
c->game_data.blocked_senders[z] = cmd.blocked_senders[z];
}
} else {
const auto& cmd = check_size_t<C_SetBlockedSenders_V3_C6>(data);
c->game_data.account()->blocked_senders = cmd.blocked_senders;
for (size_t z = 0; z < cmd.blocked_senders.size(); z++) {
c->game_data.blocked_senders[z] = cmd.blocked_senders[z];
}
}
}
@@ -3553,7 +3573,7 @@ shared_ptr<Lobby> create_game_generic(
throw runtime_error("invalid episode");
}
auto p = c->game_data.player();
auto p = c->game_data.character();
if (!(c->license->flags & License::Flag::FREE_JOIN_GAMES) &&
(min_level > p->disp.stats.level)) {
// Note: We don't throw here because this is a situation players might