From 483f6dd3fc64b345f6bf4722c70bce127bac9fd5 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 24 Nov 2024 12:26:12 -0800 Subject: [PATCH] add conversion functions for proto and v1 save files --- src/ChatCommands.cc | 66 +++-- src/Client.cc | 2 +- src/ItemData.cc | 9 +- src/ItemData.hh | 6 +- src/ReceiveCommands.cc | 8 +- src/SaveFileFormats.cc | 597 ++++++++++++++++++++++++++++------------- src/SaveFileFormats.hh | 29 +- src/ServerState.cc | 4 +- 8 files changed, 493 insertions(+), 228 deletions(-) diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 4f0ea175..7419b293 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -802,8 +802,26 @@ static void proxy_command_auction(shared_ptr ses, co ses->server_channel.send(0xC9, 0x00, &cmd, sizeof(cmd)); } +struct PatchCommandArgs { + string patch_name; + unordered_map label_writes; + + PatchCommandArgs(const string& args) { + auto tokens = phosg::split(args, ' '); + if (tokens.empty()) { + throw runtime_error("not enough arguments"); + } + this->patch_name = std::move(tokens[0]); + for (size_t z = 0; z < tokens.size() - 1; z++) { + this->label_writes.emplace(phosg::string_printf("arg%zu", z), stoul(tokens[z + 1], nullptr, 0)); + } + } +}; + static void server_command_patch(shared_ptr c, const std::string& args) { - prepare_client_for_patches(c, [wc = weak_ptr(c), args]() { + PatchCommandArgs pca(args); + + prepare_client_for_patches(c, [wc = weak_ptr(c), pca]() { auto c = wc.lock(); if (!c) { return; @@ -812,8 +830,8 @@ static void server_command_patch(shared_ptr c, const std::string& args) auto s = c->require_server_state(); // Note: We can't look this up outside of the closure because // c->specific_version can change during prepare_client_for_patches - auto fn = s->function_code_index->get_patch(args, c->config.specific_version); - send_function_call(c, fn); + auto fn = s->function_code_index->get_patch(pca.patch_name, c->config.specific_version); + send_function_call(c, fn, pca.label_writes); c->function_call_response_queue.emplace_back(empty_function_call_response_handler); } catch (const out_of_range&) { send_text_message(c, "Invalid patch name"); @@ -821,26 +839,26 @@ static void server_command_patch(shared_ptr c, const std::string& args) }); } -static void empty_patch_return_handler(uint32_t, uint32_t) {} - static void proxy_command_patch(shared_ptr ses, const std::string& args) { - auto send_call = [args, ses](uint32_t specific_version, uint32_t) { + PatchCommandArgs pca(args); + + auto send_call = [ses, pca](uint32_t specific_version, uint32_t) { try { if (ses->config.specific_version != specific_version) { ses->config.specific_version = specific_version; ses->log.info("Version detected as %08" PRIX32, ses->config.specific_version); } auto s = ses->require_server_state(); - auto fn = s->function_code_index->get_patch(args, ses->config.specific_version); - send_function_call(ses->client_channel, ses->config, fn); + auto fn = s->function_code_index->get_patch(pca.patch_name, ses->config.specific_version); + send_function_call(ses->client_channel, ses->config, fn, pca.label_writes); // Don't forward the patch response to the server - ses->function_call_return_handler_queue.emplace_back(empty_patch_return_handler); + ses->function_call_return_handler_queue.emplace_back(empty_function_call_response_handler); } catch (const out_of_range&) { send_text_message(ses->client_channel, "Invalid patch name"); } }; - auto send_version_detect_or_send_call = [args, ses, send_call]() { + auto send_version_detect_or_send_call = [ses, send_call]() { bool is_gc = ::is_gc(ses->version()); bool is_xb = (ses->version() == Version::XB_V3); if ((is_gc || is_xb) && specific_version_is_indeterminate(ses->config.specific_version)) { @@ -1698,7 +1716,7 @@ static void server_command_loadchar(shared_ptr c, const std::string& arg return; } - auto send_set_extended_player_info = [](shared_ptr c, shared_ptr char_file) -> void { + auto send_set_extended_player_info = [](shared_ptr c, const CharT& char_file) -> void { prepare_client_for_patches(c, [wc = weak_ptr(c), char_file]() { auto c = wc.lock(); if (!c) { @@ -1707,7 +1725,7 @@ static void server_command_loadchar(shared_ptr c, const std::string& arg try { auto s = c->require_server_state(); auto fn = s->function_code_index->get_patch("SetExtendedPlayerInfo", c->config.specific_version); - send_function_call(c, fn, {}, char_file.get(), sizeof(CharT)); + send_function_call(c, fn, {}, &char_file, sizeof(CharT)); c->function_call_response_queue.emplace_back([wc = weak_ptr(c)](uint32_t, uint32_t) -> void { auto c = wc.lock(); if (!c) { @@ -1728,25 +1746,27 @@ static void server_command_loadchar(shared_ptr c, const std::string& arg }; if (c->version() == Version::DC_V2) { - auto dc_char = make_shared(c->character()->to_dc_v2()); - send_set_extended_player_info.operator()(c, dc_char); + PSODCV2CharacterFile::Character dc_char = *c->character(); + send_set_extended_player_info(c, dc_char); } else if (c->version() == Version::GC_NTE) { - auto gc_char = make_shared(c->character()->to_gc_nte()); - send_set_extended_player_info.operator()(c, gc_char); + PSOGCNTECharacterFileCharacter gc_char = *c->character(); + send_set_extended_player_info(c, gc_char); } else if (c->version() == Version::GC_V3) { - auto gc_char = make_shared(c->character()->to_gc()); - send_set_extended_player_info.operator()(c, gc_char); + PSOGCCharacterFile::Character gc_char = *c->character(); + send_set_extended_player_info(c, gc_char); } else if (c->version() == Version::GC_EP3_NTE) { - auto nte_char = make_shared(*ep3_char); - send_set_extended_player_info.operator()(c, nte_char); + PSOGCEp3NTECharacter nte_char = *ep3_char; + send_set_extended_player_info(c, nte_char); } else if (c->version() == Version::GC_EP3) { - send_set_extended_player_info.operator()(c, ep3_char); + send_set_extended_player_info(c, *ep3_char); } else if (c->version() == Version::XB_V3) { if (!c->login || !c->login->xb_license) { throw runtime_error("XB client is not logged in"); } - auto xb_char = make_shared(c->character()->to_xb(c->login->xb_license->user_id)); - send_set_extended_player_info.operator()(c, xb_char); + PSOXBCharacterFileCharacter xb_char = *c->character(); + xb_char.guild_card.xb_user_id_high = (c->login->xb_license->user_id >> 32) & 0xFFFFFFFF; + xb_char.guild_card.xb_user_id_low = c->login->xb_license->user_id & 0xFFFFFFFF; + send_set_extended_player_info(c, xb_char); } else { throw logic_error("unimplemented extended player info version"); } diff --git a/src/Client.cc b/src/Client.cc index f1950941..70c7d8bc 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -991,7 +991,7 @@ void Client::load_backup_character(uint32_t account_id, size_t index) { shared_ptr Client::load_ep3_backup_character(uint32_t account_id, size_t index) { string filename = this->backup_character_filename(account_id, index, true); auto ch = make_shared(phosg::load_object_file(filename)); - this->character_data = PSOBBCharacterFile::create_from_ep3(*ch); + this->character_data = PSOBBCharacterFile::create_from_file(*ch); this->ep3_config = make_shared(ch->ep3_config); this->update_character_data_after_load(this->character_data); this->v1_v2_last_reported_disp.reset(); diff --git a/src/ItemData.cc b/src/ItemData.cc index 8d1e8103..498cae18 100644 --- a/src/ItemData.cc +++ b/src/ItemData.cc @@ -8,13 +8,20 @@ using namespace std; -const vector ItemData::StackLimits::DEFAULT_TOOL_LIMITS_DC_11_2000( +const vector ItemData::StackLimits::DEFAULT_TOOL_LIMITS_DC_NTE( {10}); const vector ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V1_V2( {10, 10, 1, 10, 10, 10, 10, 10, 10, 1}); const vector ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V3_V4( {10, 10, 1, 10, 10, 10, 10, 10, 10, 1, 1, 1, 1, 1, 1, 1, 99, 1}); +const ItemData::StackLimits ItemData::StackLimits::DEFAULT_STACK_LIMITS_DC_NTE( + Version::DC_NTE, ItemData::StackLimits::DEFAULT_TOOL_LIMITS_DC_NTE, 999999); +const ItemData::StackLimits ItemData::StackLimits::DEFAULT_STACK_LIMITS_V1_V2( + Version::DC_V1, ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V1_V2, 999999); +const ItemData::StackLimits ItemData::StackLimits::DEFAULT_STACK_LIMITS_V3_V4( + Version::GC_V3, ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V3_V4, 999999); + ItemData::StackLimits::StackLimits( Version version, const vector& max_tool_stack_sizes_by_data1_1, uint32_t max_meseta_stack_size) : version(version), diff --git a/src/ItemData.hh b/src/ItemData.hh index 3142bbc2..019068c1 100644 --- a/src/ItemData.hh +++ b/src/ItemData.hh @@ -70,9 +70,13 @@ struct ItemData { uint8_t get(uint8_t data1_0, uint8_t data1_1) const; - static const std::vector DEFAULT_TOOL_LIMITS_DC_11_2000; + static const std::vector DEFAULT_TOOL_LIMITS_DC_NTE; // Also for 11/2000 static const std::vector DEFAULT_TOOL_LIMITS_V1_V2; static const std::vector DEFAULT_TOOL_LIMITS_V3_V4; + + static const StackLimits DEFAULT_STACK_LIMITS_DC_NTE; + static const StackLimits DEFAULT_STACK_LIMITS_V1_V2; + static const StackLimits DEFAULT_STACK_LIMITS_V3_V4; }; // QUICK ITEM FORMAT REFERENCE diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 13cc4280..bdbae5c5 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -3416,16 +3416,16 @@ static void on_30(shared_ptr c, uint16_t, uint32_t, string& data) { shared_ptr bb_char; switch (c->version()) { case Version::DC_V2: - bb_char = PSOBBCharacterFile::create_from_dc_v2(check_size_t(data)); + bb_char = PSOBBCharacterFile::create_from_file(check_size_t(data)); break; case Version::GC_NTE: - bb_char = PSOBBCharacterFile::create_from_gc_nte(check_size_t(data)); + bb_char = PSOBBCharacterFile::create_from_file(check_size_t(data)); break; case Version::GC_V3: - bb_char = PSOBBCharacterFile::create_from_gc(check_size_t(data)); + bb_char = PSOBBCharacterFile::create_from_file(check_size_t(data)); break; case Version::XB_V3: - bb_char = PSOBBCharacterFile::create_from_xb(check_size_t(data)); + bb_char = PSOBBCharacterFile::create_from_file(check_size_t(data)); break; case Version::GC_EP3_NTE: case Version::GC_EP3: diff --git a/src/SaveFileFormats.cc b/src/SaveFileFormats.cc index 47265d94..70971f4e 100644 --- a/src/SaveFileFormats.cc +++ b/src/SaveFileFormats.cc @@ -523,7 +523,9 @@ shared_ptr PSOBBCharacterFile::create_from_config( ret->disp.config[z] = config[z]; } - level_table->reset_to_base(ret->disp.stats, ret->disp.visual.char_class); + if (level_table) { + level_table->reset_to_base(ret->disp.stats, ret->disp.visual.char_class); + } ret->disp.technique_levels_v1.clear(0xFF); if (ret->disp.visual.class_flags & 0x80) { ret->disp.technique_levels_v1[0] = 0x00; // Forces start with Foie Lv.1 @@ -553,256 +555,440 @@ shared_ptr PSOBBCharacterFile::create_from_preview( guild_card_number, language, preview.visual, preview.name.decode(language), level_table); } -shared_ptr PSOBBCharacterFile::create_from_dc_v2(const PSODCV2CharacterFile::Character& dc) { - auto ret = make_shared(); - ret->inventory = dc.inventory; +shared_ptr PSOBBCharacterFile::create_from_file(const PSODCNTECharacterFile::Character& src) { + auto ret = PSOBBCharacterFile::create_from_config( + src.guild_card.guild_card_number, + 0, + src.disp.visual, + src.disp.visual.name.decode(0), + nullptr); + ret->inventory = src.inventory; + ret->inventory.decode_from_client(Version::DC_V1); + uint8_t language = ret->inventory.language; + ret->disp = src.disp.to_bb(language, language); + ret->validation_flags = src.validation_flags; + ret->creation_timestamp = src.creation_timestamp; + ret->play_time_seconds = src.play_time_seconds; + ret->option_flags = src.option_flags; + ret->save_count = 1; + for (size_t difficulty = 0; difficulty < 4; difficulty++) { + // Copy the first half of the quest flags to all difficulties + ret->quest_flags.data[difficulty].data = src.quest_flags; + } + ret->bank.meseta = src.bank_meseta.load(); + ret->bank.num_items = src.num_bank_items.load(); + for (size_t z = 0; z < std::min(ret->bank.items.size(), src.bank_items.size()); z++) { + auto& dest_item = ret->bank.items[z]; + dest_item.data = src.bank_items[z]; + dest_item.data.decode_for_version(Version::DC_NTE); + dest_item.amount = dest_item.data.get_tool_item_amount(ItemData::StackLimits::DEFAULT_STACK_LIMITS_DC_NTE); + } + ret->bank.decode_from_client(Version::DC_V1); + ret->guild_card = src.guild_card; + return ret; +} + +shared_ptr PSOBBCharacterFile::create_from_file(const PSODC112000CharacterFile::Character& src) { + auto ret = PSOBBCharacterFile::create_from_config( + src.guild_card.guild_card_number, + src.inventory.language, + src.disp.visual, + src.disp.visual.name.decode(0), + nullptr); + ret->inventory = src.inventory; + ret->inventory.decode_from_client(Version::DC_V1); + uint8_t language = ret->inventory.language; + ret->disp = src.disp.to_bb(language, language); + ret->validation_flags = src.validation_flags; + ret->creation_timestamp = src.creation_timestamp; + ret->play_time_seconds = src.play_time_seconds; + ret->option_flags = src.option_flags; + ret->save_count = 1; + for (size_t difficulty = 0; difficulty < 4; difficulty++) { + // Copy the first half of the quest flags to all difficulties + ret->quest_flags.data[difficulty].data = src.quest_flags; + } + ret->bank.meseta = src.bank_meseta.load(); + ret->bank.num_items = src.num_bank_items.load(); + for (size_t z = 0; z < std::min(ret->bank.items.size(), src.bank_items.size()); z++) { + auto& dest_item = ret->bank.items[z]; + dest_item.data = src.bank_items[z]; + dest_item.data.decode_for_version(Version::DC_NTE); + dest_item.amount = dest_item.data.get_tool_item_amount(ItemData::StackLimits::DEFAULT_STACK_LIMITS_DC_NTE); + } + ret->bank.decode_from_client(Version::DC_V1); + ret->guild_card = src.guild_card; + for (size_t z = 0; z < std::min(ret->symbol_chats.size(), src.symbol_chats.size()); z++) { + auto& ret_sc = ret->symbol_chats[z]; + const auto& src_sc = src.symbol_chats[z]; + ret_sc.present = src_sc.present.load(); + ret_sc.name.encode(src_sc.name.decode(language), language); + ret_sc.spec = src_sc.spec; + } + for (size_t z = 0; z < std::min(ret->shortcuts.size(), src.shortcuts.size()); z++) { + ret->shortcuts[z] = src.shortcuts[z].convert(language); + } + return ret; +} + +shared_ptr PSOBBCharacterFile::create_from_file(const PSODCV1CharacterFile::Character& src) { + auto ret = PSOBBCharacterFile::create_from_config( + src.guild_card.guild_card_number, + src.inventory.language, + src.disp.visual, + src.disp.visual.name.decode(0), + nullptr); + ret->inventory = src.inventory; + ret->inventory.decode_from_client(Version::DC_V1); + uint8_t language = ret->inventory.language; + ret->disp = src.disp.to_bb(language, language); + ret->validation_flags = src.validation_flags; + ret->creation_timestamp = src.creation_timestamp; + ret->play_time_seconds = src.play_time_seconds; + ret->option_flags = src.option_flags; + ret->save_count = src.save_count; + ret->quest_flags = src.quest_flags; + ret->bank = src.bank; + ret->bank.decode_from_client(Version::DC_V1); + ret->guild_card = src.guild_card; + for (size_t z = 0; z < std::min(ret->symbol_chats.size(), src.symbol_chats.size()); z++) { + auto& ret_sc = ret->symbol_chats[z]; + const auto& src_sc = src.symbol_chats[z]; + ret_sc.present = src_sc.present.load(); + ret_sc.name.encode(src_sc.name.decode(language), language); + ret_sc.spec = src_sc.spec; + } + for (size_t z = 0; z < std::min(ret->shortcuts.size(), src.shortcuts.size()); z++) { + ret->shortcuts[z] = src.shortcuts[z].convert(language); + } + return ret; +} + +shared_ptr PSOBBCharacterFile::create_from_file(const PSODCV2CharacterFile::Character& src) { + auto ret = PSOBBCharacterFile::create_from_config( + src.guild_card.guild_card_number, + src.inventory.language, + src.disp.visual, + src.disp.visual.name.decode(0), + nullptr); + ret->inventory = src.inventory; ret->inventory.decode_from_client(Version::DC_V2); uint8_t language = ret->inventory.language; - ret->disp = dc.disp.to_bb(language, language); - ret->validation_flags = dc.validation_flags; - ret->creation_timestamp = dc.creation_timestamp; - ret->play_time_seconds = dc.play_time_seconds; - ret->option_flags = dc.option_flags; - ret->save_count = dc.save_count; - ret->quest_flags = dc.quest_flags; - ret->bank = dc.bank; + ret->disp = src.disp.to_bb(language, language); + ret->validation_flags = src.validation_flags; + ret->creation_timestamp = src.creation_timestamp; + ret->play_time_seconds = src.play_time_seconds; + ret->option_flags = src.option_flags; + ret->save_count = src.save_count; + ret->quest_flags = src.quest_flags; + ret->bank = src.bank; ret->bank.decode_from_client(Version::DC_V2); - ret->guild_card = dc.guild_card; - for (size_t z = 0; z < std::min(ret->symbol_chats.size(), dc.symbol_chats.size()); z++) { + ret->guild_card = src.guild_card; + for (size_t z = 0; z < std::min(ret->symbol_chats.size(), src.symbol_chats.size()); z++) { auto& ret_sc = ret->symbol_chats[z]; - const auto& dc_sc = dc.symbol_chats[z]; - ret_sc.present = dc_sc.present.load(); - ret_sc.name.encode(dc_sc.name.decode(language), language); - ret_sc.spec = dc_sc.spec; + const auto& src_sc = src.symbol_chats[z]; + ret_sc.present = src_sc.present.load(); + ret_sc.name.encode(src_sc.name.decode(language), language); + ret_sc.spec = src_sc.spec; } - for (size_t z = 0; z < std::min(ret->shortcuts.size(), dc.shortcuts.size()); z++) { - ret->shortcuts[z] = dc.shortcuts[z].convert(language); + for (size_t z = 0; z < std::min(ret->shortcuts.size(), src.shortcuts.size()); z++) { + ret->shortcuts[z] = src.shortcuts[z].convert(language); } - ret->battle_records = dc.battle_records; - ret->challenge_records = dc.challenge_records; - ret->tech_menu_shortcut_entries = dc.tech_menu_shortcut_entries; + ret->battle_records = src.battle_records; + ret->challenge_records = src.challenge_records; + ret->tech_menu_shortcut_entries = src.tech_menu_shortcut_entries; for (size_t z = 0; z < 5; z++) { - ret->choice_search_config.entries[z].parent_choice_id = dc.choice_search_config[z * 2].load(); - ret->choice_search_config.entries[z].choice_id = dc.choice_search_config[z * 2 + 1].load(); + ret->choice_search_config.entries[z].parent_choice_id = src.choice_search_config[z * 2].load(); + ret->choice_search_config.entries[z].choice_id = src.choice_search_config[z * 2 + 1].load(); } return ret; } -shared_ptr PSOBBCharacterFile::create_from_gc_nte(const PSOGCNTECharacterFileCharacter& gc_nte) { - auto ret = make_shared(); - ret->inventory = gc_nte.inventory; +shared_ptr PSOBBCharacterFile::create_from_file(const PSOGCNTECharacterFileCharacter& src) { + auto ret = PSOBBCharacterFile::create_from_config( + src.guild_card.guild_card_number, + src.inventory.language, + src.disp.visual, + src.disp.visual.name.decode(0), + nullptr); + ret->inventory = src.inventory; // Note: We intentionally do not call ret->inventory.decode_from_client here. // This is because the GC client byteswaps data2 in each item before sending // it to the server in the 61 and 98 commands, but GetExtendedPlayerInfo does // not do this, so the data2 fields are already in the correct order here. uint8_t language = ret->inventory.language; - ret->disp = gc_nte.disp.to_bb(language, language); - ret->validation_flags = gc_nte.validation_flags.load(); - ret->creation_timestamp = gc_nte.creation_timestamp.load(); - ret->play_time_seconds = gc_nte.play_time_seconds.load(); - ret->option_flags = gc_nte.option_flags.load(); - ret->save_count = gc_nte.save_count.load(); - ret->quest_flags = gc_nte.quest_flags; - ret->bank = gc_nte.bank; - ret->guild_card = gc_nte.guild_card; - for (size_t z = 0; z < std::min(ret->symbol_chats.size(), gc_nte.symbol_chats.size()); z++) { + ret->disp = src.disp.to_bb(language, language); + ret->validation_flags = src.validation_flags.load(); + ret->creation_timestamp = src.creation_timestamp.load(); + ret->play_time_seconds = src.play_time_seconds.load(); + ret->option_flags = src.option_flags.load(); + ret->save_count = src.save_count.load(); + ret->quest_flags = src.quest_flags; + ret->bank = src.bank; + ret->guild_card = src.guild_card; + for (size_t z = 0; z < std::min(ret->symbol_chats.size(), src.symbol_chats.size()); z++) { auto& ret_sc = ret->symbol_chats[z]; - const auto& gc_sc = gc_nte.symbol_chats[z]; + const auto& gc_sc = src.symbol_chats[z]; ret_sc.present = gc_sc.present.load(); ret_sc.name.encode(gc_sc.name.decode(language), language); ret_sc.spec = gc_sc.spec; } - for (size_t z = 0; z < std::min(ret->shortcuts.size(), gc_nte.shortcuts.size()); z++) { - ret->shortcuts[z] = gc_nte.shortcuts[z].convert(language); + for (size_t z = 0; z < std::min(ret->shortcuts.size(), src.shortcuts.size()); z++) { + ret->shortcuts[z] = src.shortcuts[z].convert(language); } - ret->battle_records = gc_nte.battle_records; - ret->unknown_a4 = gc_nte.unknown_a4; - ret->challenge_records = gc_nte.challenge_records; - for (size_t z = 0; z < std::min(ret->tech_menu_shortcut_entries.size(), gc_nte.tech_menu_shortcut_entries.size()); z++) { - ret->tech_menu_shortcut_entries[z] = gc_nte.tech_menu_shortcut_entries[z].load(); + ret->battle_records = src.battle_records; + ret->unknown_a4 = src.unknown_a4; + ret->challenge_records = src.challenge_records; + for (size_t z = 0; z < std::min(ret->tech_menu_shortcut_entries.size(), src.tech_menu_shortcut_entries.size()); z++) { + ret->tech_menu_shortcut_entries[z] = src.tech_menu_shortcut_entries[z].load(); } return ret; } -shared_ptr PSOBBCharacterFile::create_from_gc(const PSOGCCharacterFile::Character& gc) { - auto ret = make_shared(); - ret->inventory = gc.inventory; +shared_ptr PSOBBCharacterFile::create_from_file(const PSOGCCharacterFile::Character& src) { + auto ret = PSOBBCharacterFile::create_from_config( + src.guild_card.guild_card_number, + src.inventory.language, + src.disp.visual, + src.disp.visual.name.decode(0), + nullptr); + ret->inventory = src.inventory; // Note: We intentionally do not call ret->inventory.decode_from_client here. // This is because the GC client byteswaps data2 in each item before sending // it to the server in the 61 and 98 commands, but GetExtendedPlayerInfo does // not do this, so the data2 fields are already in the correct order here. uint8_t language = ret->inventory.language; - ret->disp = gc.disp.to_bb(language, language); - ret->validation_flags = gc.validation_flags.load(); - ret->creation_timestamp = gc.creation_timestamp.load(); - ret->play_time_seconds = gc.play_time_seconds.load(); - ret->option_flags = gc.option_flags.load(); - ret->save_count = gc.save_count.load(); - ret->quest_flags = gc.quest_flags; - ret->death_count = gc.death_count.load(); - ret->bank = gc.bank; - ret->guild_card = gc.guild_card; - for (size_t z = 0; z < std::min(ret->symbol_chats.size(), gc.symbol_chats.size()); z++) { + ret->disp = src.disp.to_bb(language, language); + ret->validation_flags = src.validation_flags.load(); + ret->creation_timestamp = src.creation_timestamp.load(); + ret->play_time_seconds = src.play_time_seconds.load(); + ret->option_flags = src.option_flags.load(); + ret->save_count = src.save_count.load(); + ret->quest_flags = src.quest_flags; + ret->death_count = src.death_count.load(); + ret->bank = src.bank; + ret->guild_card = src.guild_card; + for (size_t z = 0; z < std::min(ret->symbol_chats.size(), src.symbol_chats.size()); z++) { auto& ret_sc = ret->symbol_chats[z]; - const auto& gc_sc = gc.symbol_chats[z]; - ret_sc.present = gc_sc.present.load(); - ret_sc.name.encode(gc_sc.name.decode(language), language); - ret_sc.spec = gc_sc.spec; + const auto& src_sc = src.symbol_chats[z]; + ret_sc.present = src_sc.present.load(); + ret_sc.name.encode(src_sc.name.decode(language), language); + ret_sc.spec = src_sc.spec; } - for (size_t z = 0; z < std::min(ret->shortcuts.size(), gc.shortcuts.size()); z++) { - ret->shortcuts[z] = gc.shortcuts[z].convert(language); + for (size_t z = 0; z < std::min(ret->shortcuts.size(), src.shortcuts.size()); z++) { + ret->shortcuts[z] = src.shortcuts[z].convert(language); } - ret->auto_reply.encode(gc.auto_reply.decode(language), language); - ret->info_board.encode(gc.info_board.decode(language), language); - ret->battle_records = gc.battle_records; - ret->unknown_a4 = gc.unknown_a4; - ret->challenge_records = gc.challenge_records; - for (size_t z = 0; z < std::min(ret->tech_menu_shortcut_entries.size(), gc.tech_menu_shortcut_entries.size()); z++) { - ret->tech_menu_shortcut_entries[z] = gc.tech_menu_shortcut_entries[z].load(); + ret->auto_reply.encode(src.auto_reply.decode(language), language); + ret->info_board.encode(src.info_board.decode(language), language); + ret->battle_records = src.battle_records; + ret->unknown_a4 = src.unknown_a4; + ret->challenge_records = src.challenge_records; + for (size_t z = 0; z < std::min(ret->tech_menu_shortcut_entries.size(), src.tech_menu_shortcut_entries.size()); z++) { + ret->tech_menu_shortcut_entries[z] = src.tech_menu_shortcut_entries[z].load(); } - ret->choice_search_config = gc.choice_search_config; - ret->unknown_a6 = gc.unknown_a6; - for (size_t z = 0; z < std::min(ret->quest_counters.size(), gc.quest_counters.size()); z++) { - ret->quest_counters[z] = gc.quest_counters[z].load(); + ret->choice_search_config = src.choice_search_config; + ret->unknown_a6 = src.unknown_a6; + for (size_t z = 0; z < std::min(ret->quest_counters.size(), src.quest_counters.size()); z++) { + ret->quest_counters[z] = src.quest_counters[z].load(); } - ret->offline_battle_records = gc.offline_battle_records; - ret->unknown_a7 = gc.unknown_a7; + ret->offline_battle_records = src.offline_battle_records; + ret->unknown_a7 = src.unknown_a7; return ret; } -shared_ptr PSOBBCharacterFile::create_from_ep3(const PSOGCEp3CharacterFile::Character& ep3) { - auto ret = make_shared(); - ret->inventory = ep3.inventory; +shared_ptr PSOBBCharacterFile::create_from_file(const PSOGCEp3CharacterFile::Character& src) { + auto ret = PSOBBCharacterFile::create_from_config( + src.guild_card.guild_card_number, + src.inventory.language, + src.disp.visual, + src.disp.visual.name.decode(0), + nullptr); + ret->inventory = src.inventory; uint8_t language = ret->inventory.language; - ret->disp = ep3.disp.to_bb(language, language); - ret->validation_flags = ep3.validation_flags.load(); - ret->creation_timestamp = ep3.creation_timestamp.load(); - ret->play_time_seconds = ep3.play_time_seconds.load(); - ret->option_flags = ep3.option_flags.load(); - ret->save_count = ep3.save_count.load(); - ret->death_count = ep3.death_count.load(); - ret->bank = ep3.bank; - ret->guild_card = ep3.guild_card; - for (size_t z = 0; z < std::min(ret->symbol_chats.size(), ep3.symbol_chats.size()); z++) { + ret->disp = src.disp.to_bb(language, language); + ret->validation_flags = src.validation_flags.load(); + ret->creation_timestamp = src.creation_timestamp.load(); + ret->play_time_seconds = src.play_time_seconds.load(); + ret->option_flags = src.option_flags.load(); + ret->save_count = src.save_count.load(); + ret->death_count = src.death_count.load(); + ret->bank = src.bank; + ret->guild_card = src.guild_card; + for (size_t z = 0; z < std::min(ret->symbol_chats.size(), src.symbol_chats.size()); z++) { auto& ret_sc = ret->symbol_chats[z]; - const auto& gc_sc = ep3.symbol_chats[z]; + const auto& gc_sc = src.symbol_chats[z]; ret_sc.present = gc_sc.present.load(); ret_sc.name.encode(gc_sc.name.decode(language), language); ret_sc.spec = gc_sc.spec; } - for (size_t z = 0; z < std::min(ret->shortcuts.size(), ep3.chat_shortcuts.size()); z++) { - ret->shortcuts[z] = ep3.chat_shortcuts[z].convert(language); + for (size_t z = 0; z < std::min(ret->shortcuts.size(), src.chat_shortcuts.size()); z++) { + ret->shortcuts[z] = src.chat_shortcuts[z].convert(language); } - ret->auto_reply.encode(ep3.auto_reply.decode(language), language); - ret->info_board.encode(ep3.info_board.decode(language), language); - ret->battle_records = ep3.battle_records; - ret->unknown_a4 = ep3.ep3_config.unknown_a4; - ret->challenge_records.rank_title.encode(ep3.ep3_config.rank_text.decode(language), language); - for (size_t z = 0; z < std::min(ret->tech_menu_shortcut_entries.size(), ep3.ep3_config.tech_menu_shortcut_entries.size()); z++) { - ret->tech_menu_shortcut_entries[z] = ep3.ep3_config.tech_menu_shortcut_entries[z].load(); + ret->auto_reply.encode(src.auto_reply.decode(language), language); + ret->info_board.encode(src.info_board.decode(language), language); + ret->battle_records = src.battle_records; + ret->unknown_a4 = src.ep3_config.unknown_a4; + ret->challenge_records.rank_title.encode(src.ep3_config.rank_text.decode(language), language); + for (size_t z = 0; z < std::min(ret->tech_menu_shortcut_entries.size(), src.ep3_config.tech_menu_shortcut_entries.size()); z++) { + ret->tech_menu_shortcut_entries[z] = src.ep3_config.tech_menu_shortcut_entries[z].load(); } ret->choice_search_config.disabled = !!(ret->option_flags & 0x00040000); for (size_t z = 0; z < 5; z++) { - ret->choice_search_config.entries[z].parent_choice_id = ep3.ep3_config.choice_search_config[z * 2].load(); - ret->choice_search_config.entries[z].choice_id = ep3.ep3_config.choice_search_config[z * 2 + 1].load(); + ret->choice_search_config.entries[z].parent_choice_id = src.ep3_config.choice_search_config[z * 2].load(); + ret->choice_search_config.entries[z].choice_id = src.ep3_config.choice_search_config[z * 2 + 1].load(); } - for (size_t z = 0; z < std::min(ret->quest_counters.size(), ep3.ep3_config.scenario_progress.size()); z++) { - ret->quest_counters[z] = ep3.ep3_config.scenario_progress[z].load(); + for (size_t z = 0; z < std::min(ret->quest_counters.size(), src.ep3_config.scenario_progress.size()); z++) { + ret->quest_counters[z] = src.ep3_config.scenario_progress[z].load(); } - ret->offline_battle_records = ep3.ep3_config.unused_offline_records; + ret->offline_battle_records = src.ep3_config.unused_offline_records; return ret; } -shared_ptr PSOBBCharacterFile::create_from_xb(const PSOXBCharacterFileCharacter& xb) { - auto ret = make_shared(); - ret->inventory = xb.inventory; +shared_ptr PSOBBCharacterFile::create_from_file(const PSOXBCharacterFileCharacter& src) { + auto ret = PSOBBCharacterFile::create_from_config( + src.guild_card.guild_card_number, + src.inventory.language, + src.disp.visual, + src.disp.visual.name.decode(0), + nullptr); + ret->inventory = src.inventory; ret->inventory.decode_from_client(Version::XB_V3); uint8_t language = ret->inventory.language; - ret->disp = xb.disp.to_bb(language, language); - ret->validation_flags = xb.validation_flags; - ret->creation_timestamp = xb.creation_timestamp.load(); - ret->play_time_seconds = xb.play_time_seconds.load(); - ret->option_flags = xb.option_flags.load(); - ret->save_count = xb.save_count.load(); - ret->quest_flags = xb.quest_flags; - ret->death_count = xb.death_count.load(); - ret->bank = xb.bank; + ret->disp = src.disp.to_bb(language, language); + ret->validation_flags = src.validation_flags; + ret->creation_timestamp = src.creation_timestamp.load(); + ret->play_time_seconds = src.play_time_seconds.load(); + ret->option_flags = src.option_flags.load(); + ret->save_count = src.save_count.load(); + ret->quest_flags = src.quest_flags; + ret->death_count = src.death_count.load(); + ret->bank = src.bank; ret->bank.decode_from_client(Version::XB_V3); - ret->guild_card = xb.guild_card; - for (size_t z = 0; z < std::min(ret->symbol_chats.size(), xb.symbol_chats.size()); z++) { + ret->guild_card = src.guild_card; + for (size_t z = 0; z < std::min(ret->symbol_chats.size(), src.symbol_chats.size()); z++) { auto& ret_sc = ret->symbol_chats[z]; - const auto& xb_sc = xb.symbol_chats[z]; - ret_sc.present = xb_sc.present.load(); - ret_sc.name.encode(xb_sc.name.decode(language), language); - ret_sc.spec = xb_sc.spec; + const auto& src_sc = src.symbol_chats[z]; + ret_sc.present = src_sc.present.load(); + ret_sc.name.encode(src_sc.name.decode(language), language); + ret_sc.spec = src_sc.spec; } - for (size_t z = 0; z < std::min(ret->shortcuts.size(), xb.shortcuts.size()); z++) { - ret->shortcuts[z] = xb.shortcuts[z].convert(language); + for (size_t z = 0; z < std::min(ret->shortcuts.size(), src.shortcuts.size()); z++) { + ret->shortcuts[z] = src.shortcuts[z].convert(language); } - ret->auto_reply.encode(xb.auto_reply.decode(language), language); - ret->info_board.encode(xb.info_board.decode(language), language); - ret->battle_records = xb.battle_records; - ret->unknown_a4 = xb.unknown_a4; - ret->challenge_records = xb.challenge_records; - for (size_t z = 0; z < std::min(ret->tech_menu_shortcut_entries.size(), xb.tech_menu_shortcut_entries.size()); z++) { - ret->tech_menu_shortcut_entries[z] = xb.tech_menu_shortcut_entries[z].load(); + ret->auto_reply.encode(src.auto_reply.decode(language), language); + ret->info_board.encode(src.info_board.decode(language), language); + ret->battle_records = src.battle_records; + ret->unknown_a4 = src.unknown_a4; + ret->challenge_records = src.challenge_records; + for (size_t z = 0; z < std::min(ret->tech_menu_shortcut_entries.size(), src.tech_menu_shortcut_entries.size()); z++) { + ret->tech_menu_shortcut_entries[z] = src.tech_menu_shortcut_entries[z].load(); } - ret->choice_search_config = xb.choice_search_config; - ret->unknown_a6 = xb.unknown_a6; - for (size_t z = 0; z < std::min(ret->quest_counters.size(), xb.quest_counters.size()); z++) { - ret->quest_counters[z] = xb.quest_counters[z].load(); + ret->choice_search_config = src.choice_search_config; + ret->unknown_a6 = src.unknown_a6; + for (size_t z = 0; z < std::min(ret->quest_counters.size(), src.quest_counters.size()); z++) { + ret->quest_counters[z] = src.quest_counters[z].load(); } - ret->offline_battle_records = xb.offline_battle_records; - ret->unknown_a7 = xb.unknown_a7; + ret->offline_battle_records = src.offline_battle_records; + ret->unknown_a7 = src.unknown_a7; return ret; } -LoadedPSOCHARFile load_psochar(const string& filename, bool load_system) { - auto f = phosg::fopen_unique(filename, "rb"); - auto header = phosg::freadx(f.get()); - if (header.size != 0x399C) { - throw runtime_error("incorrect size in character file header"); - } - if (header.command != 0x00E7) { - throw runtime_error("incorrect command in character file header"); - } - if (header.flag != 0x00000000) { - throw runtime_error("incorrect flag in character file header"); - } - static_assert(sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership) == 0x3994, ".psochar size is incorrect"); +PSOBBCharacterFile::operator PSODCNTECharacterFile::Character() const { + uint8_t language = this->inventory.language; - LoadedPSOCHARFile ret; - ret.character_file = make_shared(phosg::freadx(f.get())); - if (load_system) { - ret.system_file = make_shared(phosg::freadx(f.get())); + PSODCNTECharacterFile::Character ret; + ret.inventory = this->inventory; + // We don't need to do the v1-compatible encoding (hence it is OK to pass + // nullptr here) but we do need to encode mag stats in the v2 format + ret.inventory.encode_for_client(Version::DC_NTE, nullptr); + ret.disp = this->disp.to_dcpcv3(language, language); + ret.disp.visual.enforce_lobby_join_limits_for_version(Version::DC_V2); + ret.validation_flags = this->validation_flags.load(); + ret.creation_timestamp = this->creation_timestamp.load(); + ret.play_time_seconds = this->play_time_seconds.load(); + ret.option_flags = this->option_flags.load(); + ret.quest_flags.clear(0); + ret.quest_flags = this->quest_flags.data[0].data; // Just use Normal difficulty flags + ret.bank_meseta = this->bank.meseta.load(); + ret.num_bank_items = this->bank.num_items.load(); + for (size_t z = 0; z < std::min(ret.bank_items.size(), this->bank.items.size()); z++) { + auto& dest_item = ret.bank_items[z]; + dest_item = this->bank.items[z].data; + dest_item.encode_for_version(Version::DC_NTE, nullptr); } return ret; } -void save_psochar( - const std::string& filename, - std::shared_ptr system, - std::shared_ptr character) { - auto f = phosg::fopen_unique(filename, "wb"); - PSOCommandHeaderBB header = {sizeof(PSOCommandHeaderBB) + sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership), 0x00E7, 0x00000000}; - phosg::fwritex(f.get(), header); - phosg::fwritex(f.get(), *character); - phosg::fwritex(f.get(), *system); - // TODO: Technically, we should write the actual team membership struct to - // the file here, but that would cause Client to depend on Account, which it - // currently does not. This data doesn't matter at all for correctness within - // newserv, since it ignores this data entirely and instead generates the - // membership struct from the team ID in the Account and the team's state. - // So, writing correct data here would mostly be for compatibility with other - // PSO servers. But if the other server is newserv, then this data wouldn't - // be used anyway, and if it's not, then it would presumably have a different - // set of teams with a different set of team IDs anyway, so the membership - // struct here would be useless either way. - static const PSOBBTeamMembership empty_membership; - phosg::fwritex(f.get(), empty_membership); +PSOBBCharacterFile::operator PSODC112000CharacterFile::Character() const { + uint8_t language = this->inventory.language; + + PSODC112000CharacterFile::Character ret; + ret.inventory = this->inventory; + // We don't need to do the v1-compatible encoding (hence it is OK to pass + // nullptr here) but we do need to encode mag stats in the v2 format + ret.inventory.encode_for_client(Version::DC_V1_11_2000_PROTOTYPE, nullptr); + ret.disp = this->disp.to_dcpcv3(language, language); + ret.disp.visual.enforce_lobby_join_limits_for_version(Version::DC_V2); + ret.validation_flags = this->validation_flags.load(); + ret.creation_timestamp = this->creation_timestamp.load(); + ret.play_time_seconds = this->play_time_seconds.load(); + ret.option_flags = this->option_flags.load(); + ret.save_count_since_last_inventory_erasure = this->save_count.load(); + ret.quest_flags = this->quest_flags.data[0].data; // Just use Normal difficulty flags + ret.bank_meseta = this->bank.meseta.load(); + ret.num_bank_items = this->bank.num_items.load(); + for (size_t z = 0; z < std::min(ret.bank_items.size(), this->bank.items.size()); z++) { + auto& dest_item = ret.bank_items[z]; + dest_item = this->bank.items[z].data; + dest_item.encode_for_version(Version::DC_NTE, nullptr); + } + ret.guild_card = this->guild_card; + for (size_t z = 0; z < std::min(ret.symbol_chats.size(), this->symbol_chats.size()); z++) { + auto& ret_sc = ret.symbol_chats[z]; + const auto& gc_sc = this->symbol_chats[z]; + ret_sc.present = gc_sc.present.load(); + ret_sc.name.encode(gc_sc.name.decode(language), language); + ret_sc.spec = gc_sc.spec; + } + for (size_t z = 0; z < std::min(ret.shortcuts.size(), this->shortcuts.size()); z++) { + ret.shortcuts[z] = this->shortcuts[z].convert(language); + } + return ret; } -PSODCV2CharacterFile::Character PSOBBCharacterFile::to_dc_v2() const { +PSOBBCharacterFile::operator PSODCV1CharacterFile::Character() const { + uint8_t language = this->inventory.language; + + PSODCV1CharacterFile::Character ret; + ret.inventory = this->inventory; + // We don't need to do the v1-compatible encoding (hence it is OK to pass + // nullptr here) but we do need to encode mag stats in the v2 format + ret.inventory.encode_for_client(Version::DC_V1, nullptr); + ret.disp = this->disp.to_dcpcv3(language, language); + ret.disp.visual.enforce_lobby_join_limits_for_version(Version::DC_V2); + ret.validation_flags = this->validation_flags.load(); + ret.creation_timestamp = this->creation_timestamp.load(); + ret.play_time_seconds = this->play_time_seconds.load(); + ret.option_flags = this->option_flags.load(); + ret.save_count = this->save_count.load(); + ret.quest_flags = this->quest_flags; + ret.bank = this->bank; + ret.bank.encode_for_client(Version::DC_V1); + ret.guild_card = this->guild_card; + for (size_t z = 0; z < std::min(ret.symbol_chats.size(), this->symbol_chats.size()); z++) { + auto& ret_sc = ret.symbol_chats[z]; + const auto& gc_sc = this->symbol_chats[z]; + ret_sc.present = gc_sc.present.load(); + ret_sc.name.encode(gc_sc.name.decode(language), language); + ret_sc.spec = gc_sc.spec; + } + for (size_t z = 0; z < std::min(ret.shortcuts.size(), this->shortcuts.size()); z++) { + ret.shortcuts[z] = this->shortcuts[z].convert(language); + } + return ret; +} + +PSOBBCharacterFile::operator PSODCV2CharacterFile::Character() const { uint8_t language = this->inventory.language; PSODCV2CharacterFile::Character ret; @@ -843,7 +1029,7 @@ PSODCV2CharacterFile::Character PSOBBCharacterFile::to_dc_v2() const { return ret; } -PSOGCNTECharacterFileCharacter PSOBBCharacterFile::to_gc_nte() const { +PSOBBCharacterFile::operator PSOGCNTECharacterFileCharacter() const { uint8_t language = this->inventory.language; PSOGCNTECharacterFileCharacter ret; @@ -881,7 +1067,7 @@ PSOGCNTECharacterFileCharacter PSOBBCharacterFile::to_gc_nte() const { return ret; } -PSOGCCharacterFile::Character PSOBBCharacterFile::to_gc() const { +PSOBBCharacterFile::operator PSOGCCharacterFile::Character() const { uint8_t language = this->inventory.language; PSOGCCharacterFile::Character ret; @@ -929,7 +1115,7 @@ PSOGCCharacterFile::Character PSOBBCharacterFile::to_gc() const { return ret; } -PSOXBCharacterFileCharacter PSOBBCharacterFile::to_xb(uint64_t xb_user_id) const { +PSOBBCharacterFile::operator PSOXBCharacterFileCharacter() const { uint8_t language = this->inventory.language; PSOXBCharacterFileCharacter ret; @@ -947,8 +1133,6 @@ PSOXBCharacterFileCharacter PSOBBCharacterFile::to_xb(uint64_t xb_user_id) const ret.bank = this->bank; ret.bank.encode_for_client(Version::XB_V3); ret.guild_card = this->guild_card; - ret.guild_card.xb_user_id_high = (xb_user_id >> 32) & 0xFFFFFFFF; - ret.guild_card.xb_user_id_low = xb_user_id & 0xFFFFFFFF; for (size_t z = 0; z < std::min(ret.symbol_chats.size(), this->symbol_chats.size()); z++) { auto& ret_sc = ret.symbol_chats[z]; const auto& gc_sc = this->symbol_chats[z]; @@ -977,6 +1161,51 @@ PSOXBCharacterFileCharacter PSOBBCharacterFile::to_xb(uint64_t xb_user_id) const return ret; } +LoadedPSOCHARFile load_psochar(const string& filename, bool load_system) { + auto f = phosg::fopen_unique(filename, "rb"); + auto header = phosg::freadx(f.get()); + if (header.size != 0x399C) { + throw runtime_error("incorrect size in character file header"); + } + if (header.command != 0x00E7) { + throw runtime_error("incorrect command in character file header"); + } + if (header.flag != 0x00000000) { + throw runtime_error("incorrect flag in character file header"); + } + static_assert(sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership) == 0x3994, ".psochar size is incorrect"); + + LoadedPSOCHARFile ret; + ret.character_file = make_shared(phosg::freadx(f.get())); + if (load_system) { + ret.system_file = make_shared(phosg::freadx(f.get())); + } + return ret; +} + +void save_psochar( + const std::string& filename, + std::shared_ptr system, + std::shared_ptr character) { + auto f = phosg::fopen_unique(filename, "wb"); + PSOCommandHeaderBB header = {sizeof(PSOCommandHeaderBB) + sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership), 0x00E7, 0x00000000}; + phosg::fwritex(f.get(), header); + phosg::fwritex(f.get(), *character); + phosg::fwritex(f.get(), *system); + // TODO: Technically, we should write the actual team membership struct to + // the file here, but that would cause Client to depend on Account, which it + // currently does not. This data doesn't matter at all for correctness within + // newserv, since it ignores this data entirely and instead generates the + // membership struct from the team ID in the Account and the team's state. + // So, writing correct data here would mostly be for compatibility with other + // PSO servers. But if the other server is newserv, then this data wouldn't + // be used anyway, and if it's not, then it would presumably have a different + // set of teams with a different set of team IDs anyway, so the membership + // struct here would be useless either way. + static const PSOBBTeamMembership empty_membership; + phosg::fwritex(f.get(), empty_membership); +} + // TODO: Eliminate duplication between this function and the parallel function // in PlayerBankT void PSOBBCharacterFile::add_item(const ItemData& item, const ItemData::StackLimits& limits) { diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index 9f1c1d1b..6caec162 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -345,9 +345,7 @@ struct PSODC112000CharacterFile { /* 0432:0016 */ le_uint16_t inventory_erasure_count = 0; /* 0434:0018 */ pstring ppp_username; /* 0450:0034 */ pstring ppp_password; - // TODO: Figure out how quest flags work; it's obviously different from 0x80 - // bytes per difficulty like in v1. Is it just 2048 flags shared across all - // difficulties, instead of 1024 in each difficulty? + // 11/2000 just has 0x800 quest flags, and they're not split by difficulty /* 0460:0044 */ parray quest_flags; /* 0560:0144 */ le_uint16_t bank_meseta; /* 0562:0146 */ le_uint16_t num_bank_items; @@ -787,16 +785,23 @@ struct PSOBBCharacterFile { uint8_t language, const PlayerDispDataBBPreview& preview, std::shared_ptr level_table); - static std::shared_ptr create_from_dc_v2(const PSODCV2CharacterFile::Character& dc); - static std::shared_ptr create_from_gc_nte(const PSOGCNTECharacterFileCharacter& gc_nte); - static std::shared_ptr create_from_gc(const PSOGCCharacterFile::Character& gc); - static std::shared_ptr create_from_ep3(const PSOGCEp3CharacterFile::Character& ep3); - static std::shared_ptr create_from_xb(const PSOXBCharacterFileCharacter& xb); + static std::shared_ptr create_from_file(const PSODCNTECharacterFile::Character& src); + static std::shared_ptr create_from_file(const PSODC112000CharacterFile::Character& src); + static std::shared_ptr create_from_file(const PSODCV1CharacterFile::Character& src); + static std::shared_ptr create_from_file(const PSODCV2CharacterFile::Character& src); + static std::shared_ptr create_from_file(const PSOGCNTECharacterFileCharacter& src); + static std::shared_ptr create_from_file(const PSOGCCharacterFile::Character& src); + static std::shared_ptr create_from_file(const PSOGCEp3CharacterFile::Character& src); + static std::shared_ptr create_from_file(const PSOXBCharacterFileCharacter& src); - PSODCV2CharacterFile::Character to_dc_v2() const; - PSOGCNTECharacterFileCharacter to_gc_nte() const; - PSOGCCharacterFile::Character to_gc() const; - PSOXBCharacterFileCharacter to_xb(uint64_t xb_user_id) const; + operator PSODCNTECharacterFile::Character() const; + operator PSODC112000CharacterFile::Character() const; + operator PSODCV1CharacterFile::Character() const; + operator PSODCV2CharacterFile::Character() const; + operator PSOGCNTECharacterFileCharacter() const; + operator PSOGCCharacterFile::Character() const; + operator PSOGCEp3CharacterFile::Character() const; + operator PSOXBCharacterFileCharacter() const; void add_item(const ItemData& item, const ItemData::StackLimits& limits); ItemData remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits); diff --git a/src/ServerState.cc b/src/ServerState.cc index 6fea306a..66ec6814 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -1018,9 +1018,9 @@ void ServerState::load_config_early() { for (size_t v_s = NUM_PATCH_VERSIONS; v_s < NUM_VERSIONS; v_s++) { if (!this->item_stack_limits_tables[v_s]) { Version v = static_cast(v_s); - if (v == Version::DC_V1_11_2000_PROTOTYPE) { + if ((v == Version::DC_NTE) || (v == Version::DC_V1_11_2000_PROTOTYPE)) { this->item_stack_limits_tables[v_s] = make_shared( - Version::DC_V1_11_2000_PROTOTYPE, ItemData::StackLimits::DEFAULT_TOOL_LIMITS_DC_11_2000, 999999); + v, ItemData::StackLimits::DEFAULT_TOOL_LIMITS_DC_NTE, 999999); } else if (v_s < static_cast(Version::GC_NTE)) { this->item_stack_limits_tables[v_s] = make_shared( v, ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V1_V2, 999999);