add $savechar and $loadchar commands

This commit is contained in:
Martin Michelsen
2023-12-06 23:54:53 -08:00
parent 7db761f181
commit 072ebe81bf
7 changed files with 114 additions and 44 deletions
+61 -29
View File
@@ -52,12 +52,6 @@ static void check_version(shared_ptr<Client> c, Version version) {
}
}
static void check_not_version(shared_ptr<Client> c, Version version) {
if (c->version() == version) {
throw precondition_failed("$C6This command cannot\nbe used for your\nversion of PSO.");
}
}
static void check_is_game(shared_ptr<Lobby> l, bool is_game) {
if (l->is_game() != is_game) {
throw precondition_failed(is_game ? "$C6This command cannot\nbe used in lobbies." : "$C6This command cannot\nbe used in games.");
@@ -1024,38 +1018,74 @@ static void server_command_change_bank(shared_ptr<Client> c, const std::string&
send_text_message_printf(c, "%" PRIu32 " items\n%" PRIu32 " Meseta", bank.num_items.load(), bank.meseta.load());
}
// TODO: This can be implemented on the proxy server too.
static void server_command_convert_char_to_bb(shared_ptr<Client> c, const std::string& args) {
static void server_command_bbchar_savechar(shared_ptr<Client> c, const std::string& args, bool is_bb_conversion) {
auto s = c->require_server_state();
auto l = c->require_lobby();
check_is_game(l, false);
check_not_version(c, Version::BB_V4);
vector<string> tokens = split(args, ' ');
if (tokens.size() != 3) {
send_text_message(c, "$C6Incorrect argument count");
return;
}
// username/password are tokens[0] and [1]
c->pending_bb_save_character_index = stoul(tokens[2]) - 1;
if (c->pending_bb_save_character_index > 3) {
send_text_message(c, "$C6Player index must be 1-4");
return;
}
try {
c->pending_bb_save_license = s->license_index->verify_bb(tokens[0].c_str(), tokens[1].c_str());
} catch (const exception& e) {
send_text_message_printf(c, "$C6Login failed: %s", e.what());
return;
auto pending_export = make_unique<Client::PendingCharacterExport>();
pending_export->is_bb_conversion = is_bb_conversion;
if (is_bb_conversion) {
vector<string> tokens = split(args, ' ');
if (tokens.size() != 3) {
send_text_message(c, "$C6Incorrect argument count");
return;
}
// username/password are tokens[0] and [1]
pending_export->character_index = stoll(tokens[2]) - 1;
if ((pending_export->character_index > 3) || (pending_export->character_index < 0)) {
send_text_message(c, "$C6Player index must\nbe in range 1-4");
return;
}
try {
c->pending_character_export->license = s->license_index->verify_bb(tokens[0].c_str(), tokens[1].c_str());
} catch (const exception& e) {
send_text_message_printf(c, "$C6Login failed: %s", e.what());
return;
}
} else {
pending_export->character_index = stoll(args) - 1;
if ((pending_export->character_index > 3) || (pending_export->character_index < 0)) {
send_text_message(c, "$C6Player index must\nbe in range 1-4");
return;
}
pending_export->license = c->license;
}
c->pending_character_export = std::move(pending_export);
// Request the player data. The client will respond with a 61, and the handler
// for that command will execute the conversion
send_get_player_info(c);
}
static void server_command_bbchar(shared_ptr<Client> c, const std::string& args) {
server_command_bbchar_savechar(c, args, true);
}
static void server_command_savechar(shared_ptr<Client> c, const std::string& args) {
server_command_bbchar_savechar(c, args, false);
}
static void server_command_loadchar(shared_ptr<Client> c, const std::string& args) {
if (!is_v1_or_v2(c->version())) {
send_text_message(c, "$C7This command can only\nbe used on v1 or v2");
return;
}
auto l = c->require_lobby();
check_is_game(l, false);
size_t index = stoull(args, nullptr, 0);
c->game_data.load_backup_character(c->license->serial_number, index);
auto s = c->require_server_state();
send_player_leave_notification(l, c->lobby_client_id);
s->send_lobby_join_notifications(l, c);
}
static void server_command_save(shared_ptr<Client> c, const std::string&) {
check_version(c, Version::BB_V4);
try {
@@ -1702,12 +1732,13 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
{"$ax", {server_command_ax, nullptr}},
{"$ban", {server_command_ban, nullptr}},
{"$bank", {server_command_change_bank, nullptr}},
{"$bbchar", {server_command_convert_char_to_bb, nullptr}},
{"$bbchar", {server_command_bbchar, nullptr}},
{"$cheat", {server_command_cheat, nullptr}},
{"$debug", {server_command_debug, nullptr}},
{"$defrange", {server_command_ep3_set_def_dice_range, nullptr}},
{"$drop", {server_command_drop, nullptr}},
{"$edit", {server_command_edit, nullptr}},
{"$ep3battledebug", {server_command_enable_ep3_battle_debug_menu, nullptr}},
{"$event", {server_command_lobby_event, proxy_command_lobby_event}},
{"$exit", {server_command_exit, proxy_command_exit}},
{"$gc", {server_command_get_self_card, proxy_command_get_player_card}},
@@ -1720,7 +1751,7 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
{"$kick", {server_command_kick, nullptr}},
{"$li", {server_command_lobby_info, proxy_command_lobby_info}},
{"$ln", {server_command_lobby_type, proxy_command_lobby_type}},
{"$ep3battledebug", {server_command_enable_ep3_battle_debug_menu, nullptr}},
{"$loadchar", {server_command_loadchar, nullptr}},
{"$matcount", {server_command_show_material_counts, nullptr}},
{"$maxlevel", {server_command_max_level, nullptr}},
{"$meseta", {server_command_meseta, nullptr}},
@@ -1739,6 +1770,7 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
{"$quest", {server_command_quest, nullptr}},
{"$rand", {server_command_rand, proxy_command_rand}},
{"$save", {server_command_save, nullptr}},
{"$savechar", {server_command_savechar, nullptr}},
{"$saverec", {server_command_saverec, nullptr}},
{"$sc", {server_command_send_client, proxy_command_send_client}},
{"$secid", {server_command_secid, proxy_command_secid}},
-1
View File
@@ -174,7 +174,6 @@ Client::Client(
card_battle_table_seat_state(0),
next_exp_value(0),
can_chat(true),
pending_bb_save_character_index(0),
dol_base_addr(0) {
this->config.set_flags_for_version(version, -1);
+6 -2
View File
@@ -202,8 +202,12 @@ struct Client : public std::enable_shared_from_this<Client> {
uint32_t next_exp_value; // next EXP value to give
G_SwitchStateChanged_6x05 last_switch_enabled_command;
bool can_chat;
std::shared_ptr<License> pending_bb_save_license;
uint8_t pending_bb_save_character_index;
struct PendingCharacterExport {
std::shared_ptr<const License> license;
ssize_t character_index = -1;
bool is_bb_conversion = false;
};
std::unique_ptr<PendingCharacterExport> pending_character_export;
std::deque<std::function<void(uint32_t, uint32_t)>> function_call_response_queue;
// File loading state
+21
View File
@@ -312,6 +312,10 @@ string ClientGameData::character_filename(const std::string& bb_username, int8_t
return string_printf("system/players/player_%s_%hhd.psochar", bb_username.c_str(), index);
}
string ClientGameData::backup_character_filename(uint32_t serial_number, size_t index) {
return string_printf("system/players/backup_player_%" PRIu32 "_%zu.psochar", serial_number, index);
}
string ClientGameData::character_filename(int8_t index) const {
if (this->bb_username.empty()) {
throw logic_error("non-BB players do not have character data");
@@ -613,6 +617,23 @@ void ClientGameData::save_guild_card_file() const {
player_data_log.info("Saved Guild Card file %s", filename.c_str());
}
void ClientGameData::load_backup_character(uint32_t serial_number, size_t index) {
string filename = this->backup_character_filename(serial_number, index);
auto f = fopen_unique(filename, "rb");
auto header = 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");
}
this->character_data = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
this->last_reported_disp_v1_v2.reset();
}
PlayerBank& ClientGameData::current_bank() {
if (this->external_bank) {
return *this->external_bank;
+3
View File
@@ -114,6 +114,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 serial_number, size_t index);
std::string character_filename(int8_t index = -1) const;
std::string guild_card_filename() const;
std::string shared_bank_filename() const;
@@ -131,6 +132,8 @@ public:
void save_character_file();
void save_guild_card_file() const;
void load_backup_character(uint32_t serial_number, size_t index);
PlayerBank& current_bank();
std::shared_ptr<PSOBBCharacterFile> current_bank_character();
bool use_shared_bank(); // Returns true if the bank exists; false if it was created
+17 -10
View File
@@ -2981,17 +2981,27 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
s->remove_client_from_lobby(c);
} else if (command == 0x61) {
if (c->pending_bb_save_license) {
shared_ptr<License> dest_license = c->pending_bb_save_license;
c->pending_bb_save_license.reset();
if (c->pending_character_export) {
unique_ptr<Client::PendingCharacterExport> pending_export = std::move(c->pending_character_export);
c->pending_character_export.reset();
string filename;
if (pending_export->is_bb_conversion) {
filename = ClientGameData::character_filename(
pending_export->license->bb_username,
pending_export->character_index);
} else {
filename = ClientGameData::backup_character_filename(
pending_export->license->serial_number,
pending_export->character_index);
}
string filename = ClientGameData::character_filename(dest_license->bb_username, c->pending_bb_save_character_index);
if (s->player_files_manager->get_character(filename)) {
send_text_message(c, "$C6The target player\nis currently loaded.\nSign off in Blue\nBurst and try again.");
} else {
auto bb_player = PSOBBCharacterFile::create_from_config(
dest_license->serial_number,
pending_export->license->serial_number,
c->language(),
player->disp.visual,
player->disp.name.decode(c->language()),
@@ -3016,12 +3026,9 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
bb_player->choice_search_config = player->choice_search_config;
try {
ClientGameData::save_character_file(filename, c->game_data.system(), bb_player);
send_text_message_printf(c,
"$C6BB player data saved\nas player %hhu for user\n%s",
static_cast<uint8_t>(c->pending_bb_save_character_index + 1),
dest_license->bb_username.c_str());
send_text_message(c, "$C6Character data saved");
} catch (const exception& e) {
send_text_message_printf(c, "$C6PSOBB player data could\nnot be saved:\n%s", e.what());
send_text_message_printf(c, "$C6Character data could\nnot be saved:\n%s", e.what());
}
}
}