implement $savechar on Episode 3

This commit is contained in:
Martin Michelsen
2024-06-27 23:33:22 -07:00
parent 52644695a3
commit 4bd6ef12a9
38 changed files with 474 additions and 82 deletions
+5 -1
View File
@@ -357,10 +357,14 @@ Exactly which data is saved and loaded depends on the game version:
| PSO PC (v2) | Yes | Yes | No | No | No | Save only |
| PSO GC NTE | Yes | Yes | Yes | Yes | Yes | Yes |
| PSO GC (not Plus) | Yes | Yes | Yes | Yes | Yes | Yes |
| PSO GC Plus | Save only | Save only | No | No | No | Save only |
| PSO GC Plus (1) | Save only | Save only | No | No | No | Save only |
| PSO GC Ep3 (1) | No | Save only | No | No | No | Save only |
| PSO Xbox | Yes | Yes | Yes | Yes | Yes | Yes |
| PSO BB | Yes | Yes | Yes | Yes | Yes | Yes |
*Notes*:
1. *If EnableSendFunctionCallQuestNumber is enabled in config.json, then $savechar and $loadchar can save and restore all character data on these versions, just like on GC non-Plus. Episode 3 characters exist in a separate namespace; that is, you can't use $savechar and $loadchar to convert an Ep3 character to non-Ep3, or vice versa.*
## Episode 3 features
newserv supports many features unique to Episode 3:
+19 -9
View File
@@ -1449,6 +1449,11 @@ static void server_command_bbchar_savechar(shared_ptr<Client> c, const std::stri
auto l = c->require_lobby();
check_is_game(l, false);
if (is_bb_conversion && is_ep3(c->version())) {
send_text_message(c, "$C6Episode 3 players\ncannot be converted\nto BB format");
return;
}
auto pending_export = make_unique<Client::PendingCharacterExport>();
if (is_bb_conversion) {
@@ -1503,14 +1508,6 @@ static void server_command_savechar(shared_ptr<Client> c, const std::string& arg
}
static void server_command_loadchar(shared_ptr<Client> c, const std::string& args) {
if (!is_v1_or_v2(c->version()) &&
(c->version() != Version::GC_V3) &&
(c->version() != Version::GC_NTE) &&
(c->version() != Version::XB_V3) &&
(c->version() != Version::BB_V4)) {
send_text_message(c, "$C7This command cannot\nbe used on your\ngame version");
return;
}
if (c->login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT)) {
send_text_message(c, "$C7This command cannot\nbe used on a shared\naccount");
return;
@@ -1523,7 +1520,13 @@ static void server_command_loadchar(shared_ptr<Client> c, const std::string& arg
send_text_message(c, "$C6Player index must\nbe in range 1-16");
return;
}
c->load_backup_character(c->login->account->account_id, index);
shared_ptr<PSOGCEp3CharacterFile::Character> ep3_char;
if (is_ep3(c->version())) {
ep3_char = c->load_ep3_backup_character(c->login->account->account_id, index);
} else {
c->load_backup_character(c->login->account->account_id, index);
}
if (c->version() == Version::BB_V4) {
// On BB, it suffices to simply send the character file again
@@ -1535,6 +1538,8 @@ static void server_command_loadchar(shared_ptr<Client> c, const std::string& arg
} else if ((c->version() == Version::DC_V2) ||
(c->version() == Version::GC_NTE) ||
(c->version() == Version::GC_V3) ||
(c->version() == Version::GC_EP3_NTE) ||
(c->version() == Version::GC_EP3) ||
(c->version() == Version::XB_V3)) {
// TODO: Support extended player info on other versions
auto s = c->require_server_state();
@@ -1582,6 +1587,11 @@ static void server_command_loadchar(shared_ptr<Client> c, const std::string& arg
} 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);
} 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);
} else if (c->version() == Version::GC_EP3) {
send_set_extended_player_info.operator()<PSOGCEp3CharacterFile::Character>(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");
+21 -3
View File
@@ -656,8 +656,9 @@ string Client::character_filename(const std::string& bb_username, int8_t index)
return string_printf("system/players/player_%s_%hhd.psochar", bb_username.c_str(), index);
}
string Client::backup_character_filename(uint32_t account_id, size_t index) {
return string_printf("system/players/backup_player_%" PRIu32 "_%zu.psochar", account_id, index);
string Client::backup_character_filename(uint32_t account_id, size_t index, bool is_ep3) {
return string_printf("system/players/backup_player_%" PRIu32 "_%zu.%s",
account_id, index, is_ep3 ? "pso3char" : "psochar");
}
string Client::character_filename(int8_t index) const {
@@ -963,6 +964,13 @@ void Client::save_character_file(
player_data_log.info("Saved character file %s", filename.c_str());
}
void Client::save_ep3_character_file(
const string& filename,
const PSOGCEp3CharacterFile::Character& character) {
save_file(filename, &character, sizeof(character));
player_data_log.info("Saved Episode 3 character file %s", filename.c_str());
}
void Client::save_character_file() {
if (!this->system_data.get()) {
throw logic_error("no system file loaded");
@@ -993,7 +1001,7 @@ void Client::save_guild_card_file() const {
}
void Client::load_backup_character(uint32_t account_id, size_t index) {
string filename = this->backup_character_filename(account_id, index);
string filename = this->backup_character_filename(account_id, index, false);
auto f = fopen_unique(filename, "rb");
auto header = freadx<PSOCommandHeaderBB>(f.get());
if (header.size != 0x399C) {
@@ -1010,6 +1018,16 @@ void Client::load_backup_character(uint32_t account_id, size_t index) {
this->v1_v2_last_reported_disp.reset();
}
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>(load_object_file<PSOGCEp3CharacterFile::Character>(filename));
this->character_data = PSOBBCharacterFile::create_from_ep3(*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();
return ch;
}
void Client::save_and_unload_character() {
if (this->character_data) {
this->save_character_file();
+5 -1
View File
@@ -364,7 +364,7 @@ public:
std::string system_filename() const;
static std::string character_filename(const std::string& bb_username, int8_t index);
static std::string backup_character_filename(uint32_t account_id, size_t index);
static std::string backup_character_filename(uint32_t account_id, size_t index, bool is_ep3);
std::string character_filename(int8_t index = -1) const;
std::string guild_card_filename() const;
std::string shared_bank_filename() const;
@@ -378,11 +378,15 @@ public:
const std::string& filename,
std::shared_ptr<const PSOBBBaseSystemFile> sys,
std::shared_ptr<const PSOBBCharacterFile> character);
static void save_ep3_character_file(
const std::string& filename,
const PSOGCEp3CharacterFile::Character& character);
// Note: This function is not const because it updates the player's play time.
void save_character_file();
void save_guild_card_file() const;
void load_backup_character(uint32_t account_id, size_t index);
std::shared_ptr<PSOGCEp3CharacterFile::Character> load_ep3_backup_character(uint32_t account_id, size_t index);
void save_and_unload_character();
PlayerBank200& current_bank();
+56
View File
@@ -1158,6 +1158,62 @@ void PlayerConfig::encrypt(uint8_t basis) {
this->basis = basis;
}
PlayerConfigNTE::PlayerConfigNTE(const PlayerConfig& config)
: rank_text(config.rank_text),
unknown_a1(config.unknown_a1),
tech_menu_shortcut_entries(config.tech_menu_shortcut_entries),
choice_search_config(config.choice_search_config),
scenario_progress(config.scenario_progress),
unused_offline_records(config.unused_offline_records),
unknown_a4(config.unknown_a4),
is_encrypted(config.is_encrypted),
basis(config.basis),
unused(config.unused),
card_counts(config.card_counts),
card_count_checksums(config.card_count_checksums),
rare_tokens(config.rare_tokens),
decks(config.decks),
unknown_a8(config.unknown_a8),
offline_clv_exp(config.offline_clv_exp),
online_clv_exp(config.online_clv_exp),
recent_human_opponents(config.recent_human_opponents),
unknown_a10(config.unknown_a10),
init_timestamp(config.init_timestamp),
last_online_battle_start_timestamp(config.last_online_battle_start_timestamp),
unknown_t3(config.unknown_t3),
unknown_a14(config.unknown_a14) {
// TODO: Do we need to recompute card_count_checksums? (Here or in operator
// PlayerConfig?)
}
PlayerConfigNTE::operator PlayerConfig() const {
return {
.rank_text = this->rank_text,
.unknown_a1 = this->unknown_a1,
.tech_menu_shortcut_entries = this->tech_menu_shortcut_entries,
.choice_search_config = this->choice_search_config,
.scenario_progress = this->scenario_progress,
.unused_offline_records = this->unused_offline_records,
.unknown_a4 = this->unknown_a4,
.is_encrypted = this->is_encrypted,
.basis = this->basis,
.unused = this->unused,
.card_counts = this->card_counts,
.card_count_checksums = this->card_count_checksums,
.rare_tokens = this->rare_tokens,
.decks = this->decks,
.unknown_a8 = this->unknown_a8,
.offline_clv_exp = this->offline_clv_exp,
.online_clv_exp = this->online_clv_exp,
.recent_human_opponents = this->recent_human_opponents,
.unknown_a10 = this->unknown_a10,
.init_timestamp = this->init_timestamp,
.last_online_battle_start_timestamp = this->last_online_battle_start_timestamp,
.unknown_t3 = this->unknown_t3,
.unknown_a14 = this->unknown_a14,
};
}
Rules::Rules(const JSON& json) {
this->clear();
this->overall_time_limit = json.get_int("overall_time_limit", this->overall_time_limit);
+36 -3
View File
@@ -907,12 +907,12 @@ struct PlayerConfig {
// card counts array is encrypted in memory most of the time, and they went
// out of their way to ensure the game uses an area of memory that almost no
// other game uses, which is also used by the Action Replay.)
/* 05A4:0450 */ parray<be_uint64_t, 0x1C2> rare_tokens;
/* 05A4:0450 */ parray<be_uint64_t, 450> rare_tokens;
/* 13B4:1260 */ parray<uint8_t, 0x80> unknown_a7;
/* 1434:12E0 */ parray<DeckDefinition, 25> decks;
/* 2118:1FC4 */ parray<uint8_t, 0x08> unknown_a8;
/* 2120:1FCC */ be_uint32_t offline_clv_exp; // CLvOff = this / 100
/* 2124:1FD0 */ be_uint32_t online_clv_exp; // CLvOn = this / 100
/* 2120:1FCC */ be_uint32_t offline_clv_exp; // CLvOff = (this / 100) + 1
/* 2124:1FD0 */ be_uint32_t online_clv_exp; // CLvOn = (this / 100) + 1
struct PlayerReference {
/* 00 */ be_uint32_t guild_card_number;
/* 04 */ pstring<TextEncoding::MARKED, 0x18> name;
@@ -941,6 +941,39 @@ struct PlayerConfig {
void encrypt(uint8_t basis);
} __packed_ws__(PlayerConfig, 0x2350);
struct PlayerConfigNTE {
/* 0000 */ pstring<TextEncoding::MARKED, 12> rank_text;
/* 000C */ parray<uint8_t, 0x1C> unknown_a1;
/* 0028 */ parray<be_uint16_t, 20> tech_menu_shortcut_entries;
/* 0050 */ parray<be_uint32_t, 10> choice_search_config;
/* 0078 */ parray<be_uint32_t, 0x10> scenario_progress; // Final has 0x30 entries here
/* 00B8 */ PlayerRecordsBattleBE unused_offline_records;
/* 00D0 */ parray<uint8_t, 4> unknown_a4;
/* 00D4 */ uint8_t is_encrypted;
/* 00D5 */ uint8_t basis;
/* 00D6 */ parray<uint8_t, 2> unused;
/* 00D8 */ parray<uint8_t, 1000> card_counts;
/* 04C0 */ parray<be_uint16_t, 50> card_count_checksums;
/* 0524 */ parray<be_uint64_t, 300> rare_tokens;
/* 0E84 */ parray<DeckDefinition, 25> decks;
/* 1B68 */ parray<uint8_t, 0x08> unknown_a8;
/* 1B70 */ be_uint32_t offline_clv_exp;
/* 1B74 */ be_uint32_t online_clv_exp;
/* 1B78 */ parray<PlayerConfig::PlayerReference, 10> recent_human_opponents;
/* 1C90 */ parray<uint8_t, 0x28> unknown_a10;
/* 1CB8 */ be_uint32_t init_timestamp;
/* 1CBC */ be_uint32_t last_online_battle_start_timestamp;
/* 1CC0 */ be_uint32_t unknown_t3;
/* 1CC4 */ parray<uint8_t, 0x94> unknown_a14;
/* 1D58 */
PlayerConfigNTE(const PlayerConfig& config);
operator PlayerConfig() const;
void decrypt();
void encrypt(uint8_t basis);
} __packed_ws__(PlayerConfigNTE, 0x1D58);
enum class HPType : uint8_t {
DEFEAT_PLAYER = 0,
DEFEAT_TEAM = 1,
+21 -4
View File
@@ -3250,7 +3250,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
pending_export->dest_bb_license->username, pending_export->character_index);
} else {
filename = Client::backup_character_filename(
pending_export->dest_account->account_id, pending_export->character_index);
pending_export->dest_account->account_id, pending_export->character_index, is_ep3(c->version()));
}
if (s->player_files_manager->get_character(filename)) {
@@ -3323,7 +3323,8 @@ static void on_30(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
} else {
filename = Client::backup_character_filename(
pending_export->dest_account->account_id,
pending_export->character_index);
pending_export->character_index,
is_ep3(c->version()));
}
auto s = c->require_server_state();
@@ -3332,6 +3333,21 @@ static void on_30(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
return;
}
if (is_ep3(c->version())) {
try {
if (c->version() == Version::GC_EP3_NTE) {
PSOGCEp3CharacterFile::Character ch(check_size_t<PSOGCEp3NTECharacter>(data));
Client::save_ep3_character_file(filename, ch);
} else {
Client::save_ep3_character_file(filename, check_size_t<PSOGCEp3CharacterFile::Character>(data));
}
send_text_message(c, "$C7Character data saved\n(full save file)");
} catch (const exception& e) {
send_text_message_printf(c, "$C6Character data could\nnot be saved:\n%s", e.what());
}
return;
}
shared_ptr<PSOBBCharacterFile> bb_char;
switch (c->version()) {
case Version::DC_V2:
@@ -3346,13 +3362,14 @@ static void on_30(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
case Version::XB_V3:
bb_char = PSOBBCharacterFile::create_from_xb(check_size_t<PSOXBCharacterFileCharacter>(data));
break;
case Version::GC_EP3_NTE:
case Version::GC_EP3:
throw logic_error("Episode 3 case not handled correctly");
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::PC_NTE:
case Version::PC_V2:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
case Version::BB_V4:
default:
throw logic_error("extended player data command not implemented for version");
+101
View File
@@ -257,6 +257,65 @@ Image PSOGCSnapshotFile::decode_image() const {
return ret;
}
PSOGCEp3CharacterFile::Character::Character(const PSOGCEp3NTECharacter& nte)
: inventory(nte.inventory),
disp(nte.disp),
flags(nte.flags),
creation_timestamp(nte.creation_timestamp),
signature(nte.signature),
play_time_seconds(nte.play_time_seconds),
option_flags(nte.option_flags),
save_count(nte.save_count),
ppp_username(nte.ppp_username),
ppp_password(nte.ppp_password),
seq_vars(nte.seq_vars),
death_count(nte.death_count),
bank(nte.bank),
guild_card(nte.guild_card),
symbol_chats(nte.symbol_chats),
chat_shortcuts(nte.chat_shortcuts),
auto_reply(nte.auto_reply),
info_board(nte.info_board),
battle_records(nte.battle_records),
unknown_a10(nte.unknown_a10),
challenge_record_stats(nte.challenge_record_stats),
ep3_config(nte.ep3_config),
unknown_a11(nte.unknown_a11),
unknown_a12(nte.unknown_a12),
unknown_a13(nte.unknown_a13) {
this->ep3_config.backup_visual = this->disp.visual;
}
PSOGCEp3CharacterFile::Character::operator PSOGCEp3NTECharacter() const {
return {
.inventory = this->inventory,
.disp = this->disp,
.flags = this->flags,
.creation_timestamp = this->creation_timestamp,
.signature = this->signature,
.play_time_seconds = this->play_time_seconds,
.option_flags = this->option_flags,
.save_count = this->save_count,
.ppp_username = this->ppp_username,
.ppp_password = this->ppp_password,
.seq_vars = this->seq_vars,
.death_count = this->death_count,
.bank = this->bank,
.guild_card = this->guild_card,
.symbol_chats = this->symbol_chats,
.chat_shortcuts = this->chat_shortcuts,
.auto_reply = this->auto_reply,
.info_board = this->info_board,
.battle_records = this->battle_records,
.unknown_a10 = this->unknown_a10,
.challenge_record_stats = this->challenge_record_stats,
.ep3_config = this->ep3_config,
.unknown_a11 = this->unknown_a11,
.unknown_a12 = this->unknown_a12,
.unknown_a13 = this->unknown_a13,
};
}
void PSOBBGuildCardFile::Entry::clear() {
this->data.clear();
this->unknown_a1.clear(0);
@@ -598,6 +657,48 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_gc(const PSOGCCha
return ret;
}
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_ep3(const PSOGCEp3CharacterFile::Character& ep3) {
auto ret = make_shared<PSOBBCharacterFile>();
ret->inventory = ep3.inventory;
uint8_t language = ret->inventory.language;
ret->disp = ep3.disp.to_bb(language, language);
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++) {
auto& ret_sc = ret->symbol_chats[z];
const auto& gc_sc = ep3.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);
}
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->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();
}
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();
}
ret->offline_battle_records = ep3.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;
+42 -3
View File
@@ -531,6 +531,42 @@ struct PSOGCCharacterFile {
/* 1156C */
} __packed_ws__(PSOGCCharacterFile, 0x1156C);
struct PSOGCEp3NTECharacter {
// This structure is internally split into two by the game. The offsets here
// are relative to the start of this structure (first column), and relative
// to the start of the second internal structure (second column).
/* 0000:---- */ PlayerInventoryBE inventory;
/* 034C:---- */ PlayerDispDataDCPCV3BE disp;
/* 041C:0000 */ be_uint32_t flags = 0;
/* 0420:0004 */ be_uint32_t creation_timestamp = 0;
/* 0424:0008 */ be_uint32_t signature = 0xA205B064;
/* 0428:000C */ be_uint32_t play_time_seconds = 0;
/* 042C:0010 */ be_uint32_t option_flags = 0x00040058;
/* 0430:0014 */ be_uint32_t save_count = 1;
/* 0434:0018 */ pstring<TextEncoding::ASCII, 0x1C> ppp_username;
/* 0450:0034 */ pstring<TextEncoding::ASCII, 0x10> ppp_password;
// seq_vars is an array of 8192 bits, which contain all the Episode 3 quest
// progress flags. This includes things like which maps are unlocked, which
// NPC decks are unlocked, and whether the player has a VIP card or not.
/* 0460:0044 */ parray<uint8_t, 0x400> seq_vars;
/* 0860:0444 */ be_uint32_t death_count = 0;
/* 0864:0448 */ PlayerBank200BE bank;
/* 1B2C:1710 */ GuildCardGCBE guild_card;
/* 1BBC:17A0 */ parray<SaveFileSymbolChatEntryGC, 12> symbol_chats;
/* 1FDC:1BC0 */ parray<SaveFileShortcutEntryGC, 20> chat_shortcuts;
/* 266C:2250 */ pstring<TextEncoding::MARKED, 0xAC> auto_reply;
/* 2718:22FC */ pstring<TextEncoding::MARKED, 0xAC> info_board;
// // In this struct, place_counts[0] is win_count and [1] is loss_count
/* 27C4:23A8 */ PlayerRecordsBattleBE battle_records;
/* 27DC:23C0 */ parray<uint8_t, 4> unknown_a10;
/* 27E0:23C4 */ PlayerRecordsChallengeV3BE::Stats challenge_record_stats;
/* 28B8:249C */ Episode3::PlayerConfigNTE ep3_config;
/* 4610:41F4 */ be_uint32_t unknown_a11 = 0;
/* 4614:41F8 */ be_uint32_t unknown_a12 = 0;
/* 4618:41FC */ be_uint32_t unknown_a13 = 0;
/* 461C:4200 */
} __packed_ws__(PSOGCEp3NTECharacter, 0x461C);
struct PSOGCEp3CharacterFile {
/* 00000 */ be_uint32_t checksum = 0; // crc32 of this field (as 0) through end of struct
struct Character {
@@ -541,10 +577,8 @@ struct PSOGCEp3CharacterFile {
/* 034C:---- */ PlayerDispDataDCPCV3BE disp;
/* 041C:0000 */ be_uint32_t flags = 0;
/* 0420:0004 */ be_uint32_t creation_timestamp = 0;
/* 0424:0008 */ be_uint32_t signature = 0xA204B064;
/* 0424:0008 */ be_uint32_t signature = 0xA205B064;
/* 0428:000C */ be_uint32_t play_time_seconds = 0;
// See the comment in PSOGCCharacterFile::Character about what the bits in
// this field mean.
/* 042C:0010 */ be_uint32_t option_flags = 0x00040058;
/* 0430:0014 */ be_uint32_t save_count = 1;
/* 0434:0018 */ pstring<TextEncoding::ASCII, 0x1C> ppp_username;
@@ -572,6 +606,10 @@ struct PSOGCEp3CharacterFile {
/* 39AC:3590 */ be_uint32_t unknown_a12 = 0;
/* 39B0:3594 */ be_uint32_t unknown_a13 = 0;
/* 39B4:3598 */
Character() = default;
explicit Character(const PSOGCEp3NTECharacter& nte);
operator PSOGCEp3NTECharacter() const;
} __packed_ws__(Character, 0x39B4);
/* 00004 */ parray<Character, 7> characters;
/* 193F0 */ pstring<TextEncoding::ASCII, 0x10> serial_number; // As %08X (not decimal)
@@ -691,6 +729,7 @@ struct PSOBBCharacterFile {
static std::shared_ptr<PSOBBCharacterFile> create_from_dc_v2(const PSODCV2CharacterFile& 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);
PSODCV2CharacterFile to_dc_v2() const;
+2 -3
View File
@@ -2385,21 +2385,20 @@ void send_self_leave_notification(shared_ptr<Client> c) {
}
void send_get_player_info(shared_ptr<Client> c, bool request_extended) {
// TODO: Support extended player info on Ep3 (JP and NTE)
switch (c->version()) {
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
case Version::PC_NTE:
case Version::PC_V2:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
case Version::BB_V4:
request_extended = false;
break;
case Version::DC_V2:
case Version::GC_NTE:
case Version::GC_V3:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
case Version::XB_V3:
break;
default:
@@ -14,3 +14,4 @@ data:
.data 0x805C4D80 # root_protocol (anchor: send_05)
.data 0x803DB138 # free9
.data 0x800787B0 # TProtocol_wait_send_drain
.data 0x00002370 # sizeof(*char_file_part2)
@@ -14,3 +14,4 @@ data:
.data 0x805CBD60 # root_protocol (anchor: send_05)
.data 0x803DB190 # free9
.data 0x800787B0 # TProtocol_wait_send_drain
.data 0x00002370 # sizeof(*char_file_part2)
@@ -14,3 +14,4 @@ data:
.data 0x805D5580 # root_protocol (anchor: send_05)
.data 0x803DE890 # free9
.data 0x8007889C # TProtocol_wait_send_drain
.data 0x00002370 # sizeof(*char_file_part2)
@@ -14,3 +14,4 @@ data:
.data 0x805C4488 # root_protocol (anchor: send_05)
.data 0x803D9E90 # free9
.data 0x8007848C # TProtocol_wait_send_drain
.data 0x00002370 # sizeof(*char_file_part2)
@@ -14,3 +14,4 @@ data:
.data 0x805CEA50 # root_protocol (anchor: send_05)
.data 0x803DC870 # free9
.data 0x800785F0 # TProtocol_wait_send_drain
.data 0x00002370 # sizeof(*char_file_part2)
@@ -14,3 +14,4 @@ data:
.data 0x805D5ED0 # root_protocol (anchor: send_05)
.data 0x803DE710 # free9
.data 0x80078748 # TProtocol_wait_send_drain
.data 0x00002370 # sizeof(*char_file_part2)
@@ -14,3 +14,4 @@ data:
.data 0x805D5C70 # root_protocol (anchor: send_05)
.data 0x803DE4C0 # free9
.data 0x800786A0 # TProtocol_wait_send_drain
.data 0x00002370 # sizeof(*char_file_part2)
@@ -14,3 +14,4 @@ data:
.data 0x805D17C0 # root_protocol (anchor: send_05)
.data 0x803DD380 # free9
.data 0x80078820 # TProtocol_wait_send_drain
.data 0x00002370 # sizeof(*char_file_part2)
@@ -0,0 +1,17 @@
.meta hide_from_patches_menu
.meta name="GetExtendedPlayerInfo"
.meta description=""
entry_ptr:
reloc0:
.offsetof start
start:
.include GetExtendedPlayerInfoGC
data:
.data 0x8038C0EC # malloc9
.data 0x8057A6F0 # char_file_part1
.data 0x8057A6F4 # char_file_part2
.data 0x8057A150 # root_protocol (anchor: send_05)
.data 0x8038C144 # free9
.data 0x80026B88 # TProtocol_wait_send_drain
.data 0x0000358C # sizeof(*char_file_part2)
@@ -0,0 +1,17 @@
.meta hide_from_patches_menu
.meta name="GetExtendedPlayerInfo"
.meta description=""
entry_ptr:
reloc0:
.offsetof start
start:
.include GetExtendedPlayerInfoGC
data:
.data 0x8038B09C # malloc9
.data 0x80579880 # char_file_part1
.data 0x80579884 # char_file_part2
.data 0x805792E0 # root_protocol (anchor: send_05)
.data 0x8038B0F4 # free9
.data 0x80026A04 # TProtocol_wait_send_drain
.data 0x0000358C # sizeof(*char_file_part2)
@@ -0,0 +1,17 @@
.meta hide_from_patches_menu
.meta name="GetExtendedPlayerInfo"
.meta description=""
entry_ptr:
reloc0:
.offsetof start
start:
.include GetExtendedPlayerInfoGC
data:
.data 0x80358094 # malloc9
.data 0x8058B980 # char_file_part1
.data 0x8058B984 # char_file_part2
.data 0x8058B3A0 # root_protocol (anchor: send_05)
.data 0x803580EC # free9
.data 0x80026FE4 # TProtocol_wait_send_drain
.data 0x000041F4 # sizeof(*char_file_part2)
@@ -0,0 +1,17 @@
.meta hide_from_patches_menu
.meta name="GetExtendedPlayerInfo"
.meta description=""
entry_ptr:
reloc0:
.offsetof start
start:
.include GetExtendedPlayerInfoGC
data:
.data 0x8038CF94 # malloc9
.data 0x8057CB10 # char_file_part1
.data 0x8057CB14 # char_file_part2
.data 0x8057C570 # root_protocol (anchor: send_05)
.data 0x8038CFEC # free9
.data 0x80026BB8 # TProtocol_wait_send_drain
.data 0x0000358C # sizeof(*char_file_part2)
@@ -1,25 +1,31 @@
stwu [r1 - 0x20], r1
stwu [r1 - 0x40], r1
mflr r0
stw [r1 + 0x24], r0
stw [r1 + 0x44], r0
stw [r1 + 0x08], r31
stw [r1 + 0x0C], r30
stw [r1 + 0x10], r29
stw [r1 + 0x14], r28
stw [r1 + 0x18], r27
b get_data_ptr
get_data_ptr_ret:
mflr r30
li r3, 0x279C
lwz r27, [r30 + 0x18] # sizeof(part2)
addi r3, r27, 0x42C # sizeof(header) + sizeof(part1) + sizeof(part2) + sizeof(unused fields after part2)
lwz r0, [r30]
mtctr r0
bctrl # malloc9
mr. r31, r3
beq malloc9_failed
lis r0, 0x3000
ori r0, r0, 0x9C27
stw [r31], r0 # header = 30 00 9C 27
li r0, 0x3000
sth [r31], r0
addi r3, r27, 0x42C
stb [r31 + 2], r3
rlwinm r3, r3, 24, 24, 31
stb [r31 + 3], r3 # header = 30 00 SS SS
lwz r4, [r30 + 0x04]
lwz r4, [r4] # r4 = char_file_part1
@@ -30,16 +36,18 @@ get_data_ptr_ret:
lwz r4, [r30 + 0x08]
lwz r4, [r4] # r4 = char_file_part2
addi r3, r31, 0x0420
li r5, 0x2370 # sizeof(part2)
mr r5, r27
bl memcpy
li r0, 0
stw [r31 + 0x2790], r0
stw [r31 + 0x2794], r0
stw [r31 + 0x2798], r0
add r3, r27, r31
addi r3, r3, 0x420 # r3 = pointer to unused fields after part2
stw [r3 + 0], r0
stw [r3 + 4], r0
stw [r3 + 8], r0
mr r28, r31
li r29, 0x279C
addi r29, r27, 0x42C
send_again:
lwz r3, [r30 + 0x0C]
lwz r3, [r3]
@@ -74,12 +82,13 @@ drain_failed:
li r3, 0
malloc9_failed:
lwz r27, [r1 + 0x18]
lwz r28, [r1 + 0x14]
lwz r29, [r1 + 0x10]
lwz r30, [r1 + 0x0C]
lwz r31, [r1 + 0x08]
lwz r0, [r1 + 0x24]
addi r1, r1, 0x20
lwz r0, [r1 + 0x44]
addi r1, r1, 0x40
mtlr r0
blr
@@ -9,4 +9,5 @@ start:
.include SetExtendedPlayerInfoGC
data:
.data 0x805C5758 # character_file
.data 0x00002370 # sizeof(part2)
# Server adds a PSOGCCharacterFile::Character here
@@ -9,4 +9,5 @@ start:
.include SetExtendedPlayerInfoGC
data:
.data 0x805CC738 # character_file
.data 0x00002370 # sizeof(part2)
# Server adds a PSOGCCharacterFile::Character here
@@ -9,4 +9,5 @@ start:
.include SetExtendedPlayerInfoGC
data:
.data 0x805D5F58 # character_file
.data 0x00002370 # sizeof(part2)
# Server adds a PSOGCCharacterFile::Character here
@@ -9,4 +9,5 @@ start:
.include SetExtendedPlayerInfoGC
data:
.data 0x805C4E60 # character_file
.data 0x00002370 # sizeof(part2)
# Server adds a PSOGCCharacterFile::Character here
@@ -9,4 +9,5 @@ start:
.include SetExtendedPlayerInfoGC
data:
.data 0x805CF428 # character_file
.data 0x00002370 # sizeof(part2)
# Server adds a PSOGCCharacterFile::Character here
@@ -9,4 +9,5 @@ start:
.include SetExtendedPlayerInfoGC
data:
.data 0x805D68A8 # character_file
.data 0x00002370 # sizeof(part2)
# Server adds a PSOGCCharacterFile::Character here
@@ -9,4 +9,5 @@ start:
.include SetExtendedPlayerInfoGC
data:
.data 0x805D6648 # character_file
.data 0x00002370 # sizeof(part2)
# Server adds a PSOGCCharacterFile::Character here
@@ -6,44 +6,8 @@ entry_ptr:
reloc0:
.offsetof start
start:
mflr r12
bl get_data_ptr
get_data_ptr_ret:
mflr r11
lwz r10, [r11]
# Copy part1 data into place
lwz r3, [r10 + 0x08]
addi r4, r11, 0x0004
li r5, 0x41C
bl memcpy
# Copy part2 data into place, but retain the values of a few metadata fields
# so the game won't think the file is corrupt
lwz r3, [r10 + 0x0C]
lwz r7, [r3 + 0x04] # creation_timestamp
lwz r8, [r3 + 0x08] # signature
lwz r9, [r3 + 0x14] # save_count
addi r4, r11, 0x0420
li r5, 0x2268
bl memcpy
lwz r3, [r10 + 0x0C]
stw [r3 + 0x04], r7 # creation_timestamp
stw [r3 + 0x08], r8 # signature
stw [r3 + 0x14], r9 # save_count
li r3, 1
mtlr r12
blr
memcpy:
.include CopyDataWords
blr
get_data_ptr:
bl get_data_ptr_ret
.include SetExtendedPlayerInfoGC
data:
.data 0x805CC738 # character_file
# Server adds a PSOGCCharacterFile::Character here
.data 0x00002268 # sizeof(part2)
# Server adds a PSOGCNTECharacterFileCharacter here
@@ -9,4 +9,5 @@ start:
.include SetExtendedPlayerInfoGC
data:
.data 0x805D2198 # character_file
.data 0x00002370 # sizeof(part2)
# Server adds a PSOGCCharacterFile::Character here
@@ -0,0 +1,13 @@
.meta hide_from_patches_menu
.meta name="SetExtendedPlayerInfo"
.meta description=""
entry_ptr:
reloc0:
.offsetof start
start:
.include SetExtendedPlayerInfoGC
data:
.data 0x8057A6E8 # character_file
.data 0x0000358C # sizeof(*char_file_part2)
# Server adds a PSOGCEp3CharacterFile::Character here
@@ -0,0 +1,13 @@
.meta hide_from_patches_menu
.meta name="SetExtendedPlayerInfo"
.meta description=""
entry_ptr:
reloc0:
.offsetof start
start:
.include SetExtendedPlayerInfoGC
data:
.data 0x80579878 # character_file
.data 0x0000358C # sizeof(*char_file_part2)
# Server adds a PSOGCEp3CharacterFile::Character here
@@ -0,0 +1,13 @@
.meta hide_from_patches_menu
.meta name="SetExtendedPlayerInfo"
.meta description=""
entry_ptr:
reloc0:
.offsetof start
start:
.include SetExtendedPlayerInfoGC
data:
.data 0x8058B978 # character_file
.data 0x0000358C # sizeof(*char_file_part2)
# Server adds a PSOGCEp3CharacterFile::Character here
@@ -0,0 +1,13 @@
.meta hide_from_patches_menu
.meta name="SetExtendedPlayerInfo"
.meta description=""
entry_ptr:
reloc0:
.offsetof start
start:
.include SetExtendedPlayerInfoGC
data:
.data 0x8057CB08 # character_file
.data 0x0000358C # sizeof(*char_file_part2)
# Server adds a PSOGCEp3CharacterFile::Character here
@@ -7,7 +7,7 @@ get_data_ptr_ret:
# Copy part1 data into place
lwz r3, [r10 + 0x08]
addi r4, r11, 0x0004
addi r4, r11, 0x0008
li r5, 0x41C
bl memcpy
@@ -17,8 +17,8 @@ get_data_ptr_ret:
lwz r7, [r3 + 0x04] # creation_timestamp
lwz r8, [r3 + 0x08] # signature
lwz r9, [r3 + 0x14] # save_count
addi r4, r11, 0x0420
li r5, 0x2370
addi r4, r11, 0x0424
lwz r5, [r11 + 4]
bl memcpy
lwz r3, [r10 + 0x0C]
stw [r3 + 0x04], r7 # creation_timestamp
+2
View File
@@ -1054,6 +1054,8 @@
// has to wait for the initial server connection, then wait for the quest to
// load, then wait for the client to leave the "game", before even getting to
// the welcome message.
// To enable this feature, set this value to 88500. This is the number of the
// quest in system/quests/hidden that implements the patch.
// This quest is not intended to be localized since it should not contain any
// user-visible text, so the server uses the language field to determine
// which quest to send based on the client's version: