implement $savechar on Episode 3
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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");
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user