add conversion functions for proto and v1 save files
This commit is contained in:
+43
-23
@@ -802,8 +802,26 @@ static void proxy_command_auction(shared_ptr<ProxyServer::LinkedSession> ses, co
|
||||
ses->server_channel.send(0xC9, 0x00, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
struct PatchCommandArgs {
|
||||
string patch_name;
|
||||
unordered_map<string, uint32_t> 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<Client> c, const std::string& args) {
|
||||
prepare_client_for_patches(c, [wc = weak_ptr<Client>(c), args]() {
|
||||
PatchCommandArgs pca(args);
|
||||
|
||||
prepare_client_for_patches(c, [wc = weak_ptr<Client>(c), pca]() {
|
||||
auto c = wc.lock();
|
||||
if (!c) {
|
||||
return;
|
||||
@@ -812,8 +830,8 @@ static void server_command_patch(shared_ptr<Client> 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<Client> c, const std::string& args)
|
||||
});
|
||||
}
|
||||
|
||||
static void empty_patch_return_handler(uint32_t, uint32_t) {}
|
||||
|
||||
static void proxy_command_patch(shared_ptr<ProxyServer::LinkedSession> 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<Client> c, const std::string& arg
|
||||
return;
|
||||
}
|
||||
|
||||
auto send_set_extended_player_info = []<typename CharT>(shared_ptr<Client> c, shared_ptr<const CharT> char_file) -> void {
|
||||
auto send_set_extended_player_info = []<typename CharT>(shared_ptr<Client> c, const CharT& char_file) -> void {
|
||||
prepare_client_for_patches(c, [wc = weak_ptr<Client>(c), char_file]() {
|
||||
auto c = wc.lock();
|
||||
if (!c) {
|
||||
@@ -1707,7 +1725,7 @@ static void server_command_loadchar(shared_ptr<Client> 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<Client>(c)](uint32_t, uint32_t) -> void {
|
||||
auto c = wc.lock();
|
||||
if (!c) {
|
||||
@@ -1728,25 +1746,27 @@ static void server_command_loadchar(shared_ptr<Client> c, const std::string& arg
|
||||
};
|
||||
|
||||
if (c->version() == Version::DC_V2) {
|
||||
auto dc_char = make_shared<PSODCV2CharacterFile::Character>(c->character()->to_dc_v2());
|
||||
send_set_extended_player_info.operator()<PSODCV2CharacterFile::Character>(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<PSOGCNTECharacterFileCharacter>(c->character()->to_gc_nte());
|
||||
send_set_extended_player_info.operator()<PSOGCNTECharacterFileCharacter>(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<PSOGCCharacterFile::Character>(c->character()->to_gc());
|
||||
send_set_extended_player_info.operator()<PSOGCCharacterFile::Character>(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<PSOGCEp3NTECharacter>(*ep3_char);
|
||||
send_set_extended_player_info.operator()<PSOGCEp3NTECharacter>(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()<PSOGCEp3CharacterFile::Character>(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<PSOXBCharacterFileCharacter>(c->character()->to_xb(c->login->xb_license->user_id));
|
||||
send_set_extended_player_info.operator()<PSOXBCharacterFileCharacter>(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");
|
||||
}
|
||||
|
||||
+1
-1
@@ -991,7 +991,7 @@ void Client::load_backup_character(uint32_t account_id, size_t index) {
|
||||
shared_ptr<PSOGCEp3CharacterFile::Character> 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<PSOGCEp3CharacterFile::Character>(phosg::load_object_file<PSOGCEp3CharacterFile::Character>(filename));
|
||||
this->character_data = PSOBBCharacterFile::create_from_ep3(*ch);
|
||||
this->character_data = PSOBBCharacterFile::create_from_file(*ch);
|
||||
this->ep3_config = make_shared<Episode3::PlayerConfig>(ch->ep3_config);
|
||||
this->update_character_data_after_load(this->character_data);
|
||||
this->v1_v2_last_reported_disp.reset();
|
||||
|
||||
+8
-1
@@ -8,13 +8,20 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
const vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_DC_11_2000(
|
||||
const vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_DC_NTE(
|
||||
{10});
|
||||
const vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V1_V2(
|
||||
{10, 10, 1, 10, 10, 10, 10, 10, 10, 1});
|
||||
const vector<uint8_t> 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<uint8_t>& max_tool_stack_sizes_by_data1_1, uint32_t max_meseta_stack_size)
|
||||
: version(version),
|
||||
|
||||
+5
-1
@@ -70,9 +70,13 @@ struct ItemData {
|
||||
|
||||
uint8_t get(uint8_t data1_0, uint8_t data1_1) const;
|
||||
|
||||
static const std::vector<uint8_t> DEFAULT_TOOL_LIMITS_DC_11_2000;
|
||||
static const std::vector<uint8_t> DEFAULT_TOOL_LIMITS_DC_NTE; // Also for 11/2000
|
||||
static const std::vector<uint8_t> DEFAULT_TOOL_LIMITS_V1_V2;
|
||||
static const std::vector<uint8_t> 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
|
||||
|
||||
@@ -3416,16 +3416,16 @@ static void on_30(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
shared_ptr<PSOBBCharacterFile> bb_char;
|
||||
switch (c->version()) {
|
||||
case Version::DC_V2:
|
||||
bb_char = PSOBBCharacterFile::create_from_dc_v2(check_size_t<PSODCV2CharacterFile::Character>(data));
|
||||
bb_char = PSOBBCharacterFile::create_from_file(check_size_t<PSODCV2CharacterFile::Character>(data));
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
bb_char = PSOBBCharacterFile::create_from_gc_nte(check_size_t<PSOGCNTECharacterFileCharacter>(data));
|
||||
bb_char = PSOBBCharacterFile::create_from_file(check_size_t<PSOGCNTECharacterFileCharacter>(data));
|
||||
break;
|
||||
case Version::GC_V3:
|
||||
bb_char = PSOBBCharacterFile::create_from_gc(check_size_t<PSOGCCharacterFile::Character>(data));
|
||||
bb_char = PSOBBCharacterFile::create_from_file(check_size_t<PSOGCCharacterFile::Character>(data));
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
bb_char = PSOBBCharacterFile::create_from_xb(check_size_t<PSOXBCharacterFileCharacter>(data));
|
||||
bb_char = PSOBBCharacterFile::create_from_file(check_size_t<PSOXBCharacterFileCharacter>(data));
|
||||
break;
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
|
||||
+413
-184
@@ -523,7 +523,9 @@ shared_ptr<PSOBBCharacterFile> 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> PSOBBCharacterFile::create_from_preview(
|
||||
guild_card_number, language, preview.visual, preview.name.decode(language), level_table);
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_dc_v2(const PSODCV2CharacterFile::Character& dc) {
|
||||
auto ret = make_shared<PSOBBCharacterFile>();
|
||||
ret->inventory = dc.inventory;
|
||||
shared_ptr<PSOBBCharacterFile> 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<size_t>(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> 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<size_t>(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<size_t>(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<size_t>(ret->shortcuts.size(), src.shortcuts.size()); z++) {
|
||||
ret->shortcuts[z] = src.shortcuts[z].convert<false, TextEncoding::UTF16, 0x50>(language);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> 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<size_t>(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<size_t>(ret->shortcuts.size(), src.shortcuts.size()); z++) {
|
||||
ret->shortcuts[z] = src.shortcuts[z].convert<false, TextEncoding::UTF16, 0x50>(language);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> 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<size_t>(ret->symbol_chats.size(), dc.symbol_chats.size()); z++) {
|
||||
ret->guild_card = src.guild_card;
|
||||
for (size_t z = 0; z < std::min<size_t>(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<size_t>(ret->shortcuts.size(), dc.shortcuts.size()); z++) {
|
||||
ret->shortcuts[z] = dc.shortcuts[z].convert<false, TextEncoding::UTF16, 0x50>(language);
|
||||
for (size_t z = 0; z < std::min<size_t>(ret->shortcuts.size(), src.shortcuts.size()); z++) {
|
||||
ret->shortcuts[z] = src.shortcuts[z].convert<false, TextEncoding::UTF16, 0x50>(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> PSOBBCharacterFile::create_from_gc_nte(const PSOGCNTECharacterFileCharacter& gc_nte) {
|
||||
auto ret = make_shared<PSOBBCharacterFile>();
|
||||
ret->inventory = gc_nte.inventory;
|
||||
shared_ptr<PSOBBCharacterFile> 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<size_t>(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<size_t>(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<size_t>(ret->shortcuts.size(), gc_nte.shortcuts.size()); z++) {
|
||||
ret->shortcuts[z] = gc_nte.shortcuts[z].convert<false, TextEncoding::UTF16, 0x50>(language);
|
||||
for (size_t z = 0; z < std::min<size_t>(ret->shortcuts.size(), src.shortcuts.size()); z++) {
|
||||
ret->shortcuts[z] = src.shortcuts[z].convert<false, TextEncoding::UTF16, 0x50>(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<size_t>(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<size_t>(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> PSOBBCharacterFile::create_from_gc(const PSOGCCharacterFile::Character& gc) {
|
||||
auto ret = make_shared<PSOBBCharacterFile>();
|
||||
ret->inventory = gc.inventory;
|
||||
shared_ptr<PSOBBCharacterFile> 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<size_t>(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<size_t>(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<size_t>(ret->shortcuts.size(), gc.shortcuts.size()); z++) {
|
||||
ret->shortcuts[z] = gc.shortcuts[z].convert<false, TextEncoding::UTF16, 0x50>(language);
|
||||
for (size_t z = 0; z < std::min<size_t>(ret->shortcuts.size(), src.shortcuts.size()); z++) {
|
||||
ret->shortcuts[z] = src.shortcuts[z].convert<false, TextEncoding::UTF16, 0x50>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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> PSOBBCharacterFile::create_from_ep3(const PSOGCEp3CharacterFile::Character& ep3) {
|
||||
auto ret = make_shared<PSOBBCharacterFile>();
|
||||
ret->inventory = ep3.inventory;
|
||||
shared_ptr<PSOBBCharacterFile> 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<size_t>(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<size_t>(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<size_t>(ret->shortcuts.size(), ep3.chat_shortcuts.size()); z++) {
|
||||
ret->shortcuts[z] = ep3.chat_shortcuts[z].convert<false, TextEncoding::UTF16, 0x50>(language);
|
||||
for (size_t z = 0; z < std::min<size_t>(ret->shortcuts.size(), src.chat_shortcuts.size()); z++) {
|
||||
ret->shortcuts[z] = src.chat_shortcuts[z].convert<false, TextEncoding::UTF16, 0x50>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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> PSOBBCharacterFile::create_from_xb(const PSOXBCharacterFileCharacter& xb) {
|
||||
auto ret = make_shared<PSOBBCharacterFile>();
|
||||
ret->inventory = xb.inventory;
|
||||
shared_ptr<PSOBBCharacterFile> 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<size_t>(ret->symbol_chats.size(), xb.symbol_chats.size()); z++) {
|
||||
ret->guild_card = src.guild_card;
|
||||
for (size_t z = 0; z < std::min<size_t>(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<size_t>(ret->shortcuts.size(), xb.shortcuts.size()); z++) {
|
||||
ret->shortcuts[z] = xb.shortcuts[z].convert<false, TextEncoding::UTF16, 0x50>(language);
|
||||
for (size_t z = 0; z < std::min<size_t>(ret->shortcuts.size(), src.shortcuts.size()); z++) {
|
||||
ret->shortcuts[z] = src.shortcuts[z].convert<false, TextEncoding::UTF16, 0x50>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<PSOCommandHeaderBB>(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<PSOBBCharacterFile>(phosg::freadx<PSOBBCharacterFile>(f.get()));
|
||||
if (load_system) {
|
||||
ret.system_file = make_shared<PSOBBBaseSystemFile>(phosg::freadx<PSOBBBaseSystemFile>(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<false>(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<size_t>(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<const PSOBBBaseSystemFile> system,
|
||||
std::shared_ptr<const PSOBBCharacterFile> 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<false>(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<size_t>(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<size_t>(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<size_t>(ret.shortcuts.size(), this->shortcuts.size()); z++) {
|
||||
ret.shortcuts[z] = this->shortcuts[z].convert<false, TextEncoding::MARKED, 0x3C>(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<false>(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<size_t>(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<size_t>(ret.shortcuts.size(), this->shortcuts.size()); z++) {
|
||||
ret.shortcuts[z] = this->shortcuts[z].convert<false, TextEncoding::MARKED, 0x3C>(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<size_t>(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<PSOCommandHeaderBB>(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<PSOBBCharacterFile>(phosg::freadx<PSOBBCharacterFile>(f.get()));
|
||||
if (load_system) {
|
||||
ret.system_file = make_shared<PSOBBBaseSystemFile>(phosg::freadx<PSOBBBaseSystemFile>(f.get()));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void save_psochar(
|
||||
const std::string& filename,
|
||||
std::shared_ptr<const PSOBBBaseSystemFile> system,
|
||||
std::shared_ptr<const PSOBBCharacterFile> 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) {
|
||||
|
||||
+17
-12
@@ -345,9 +345,7 @@ struct PSODC112000CharacterFile {
|
||||
/* 0432:0016 */ le_uint16_t inventory_erasure_count = 0;
|
||||
/* 0434:0018 */ pstring<TextEncoding::ASCII, 0x1C> ppp_username;
|
||||
/* 0450:0034 */ pstring<TextEncoding::ASCII, 0x10> 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<uint8_t, 0x100> 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<const LevelTable> level_table);
|
||||
static std::shared_ptr<PSOBBCharacterFile> create_from_dc_v2(const PSODCV2CharacterFile::Character& dc);
|
||||
static std::shared_ptr<PSOBBCharacterFile> create_from_gc_nte(const PSOGCNTECharacterFileCharacter& gc_nte);
|
||||
static std::shared_ptr<PSOBBCharacterFile> create_from_gc(const PSOGCCharacterFile::Character& gc);
|
||||
static std::shared_ptr<PSOBBCharacterFile> create_from_ep3(const PSOGCEp3CharacterFile::Character& ep3);
|
||||
static std::shared_ptr<PSOBBCharacterFile> create_from_xb(const PSOXBCharacterFileCharacter& xb);
|
||||
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSODCNTECharacterFile::Character& src);
|
||||
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSODC112000CharacterFile::Character& src);
|
||||
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSODCV1CharacterFile::Character& src);
|
||||
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSODCV2CharacterFile::Character& src);
|
||||
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSOGCNTECharacterFileCharacter& src);
|
||||
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSOGCCharacterFile::Character& src);
|
||||
static std::shared_ptr<PSOBBCharacterFile> create_from_file(const PSOGCEp3CharacterFile::Character& src);
|
||||
static std::shared_ptr<PSOBBCharacterFile> 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);
|
||||
|
||||
+2
-2
@@ -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<Version>(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<ItemData::StackLimits>(
|
||||
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<size_t>(Version::GC_NTE)) {
|
||||
this->item_stack_limits_tables[v_s] = make_shared<ItemData::StackLimits>(
|
||||
v, ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V1_V2, 999999);
|
||||
|
||||
Reference in New Issue
Block a user