switch to coroutine execution model

This commit is contained in:
Martin Michelsen
2025-04-30 21:43:06 -07:00
parent f65b1f1c14
commit cc99050964
160 changed files with 269127 additions and 227736 deletions
+104 -153
View File
@@ -1,11 +1,10 @@
#pragma once
#include <netinet/in.h>
#include <memory>
#include <stdexcept>
#include "Account.hh"
#include "AsyncUtils.hh"
#include "Channel.hh"
#include "CommandFormats.hh"
#include "Episode3/BattleRecord.hh"
@@ -15,6 +14,7 @@
#include "PSOEncryption.hh"
#include "PSOProtocol.hh"
#include "PatchFileIndex.hh"
#include "ProxySession.hh"
#include "Quest.hh"
#include "QuestScript.hh"
#include "TeamIndex.hh"
@@ -22,21 +22,22 @@
extern const uint64_t CLIENT_CONFIG_MAGIC;
class Server;
class GameServer;
struct Lobby;
class Parsed6x70Data;
struct GetPlayerInfoResult {
// Exactly one of the following two shared_ptrs is not null
std::shared_ptr<PSOBBCharacterFile> character;
std::shared_ptr<PSOGCEp3CharacterFile::Character> ep3_character;
bool is_full_info; // True if the client sent 30; false if it was 61 or 98
};
class Client : public std::enable_shared_from_this<Client> {
public:
enum class Flag : uint64_t {
// clang-format off
// This mask specifies which flags are sent to the client
// TODO: It'd be nice to use a pattern here (e.g. all server-side flags are
// in the high bits) but that would require re-recording or manually
// rewriting all the tests
CLIENT_SIDE_MASK = 0xEF3CFFFF7C0BFFFB,
// Version-related flags
CHECKED_FOR_DC_V1_PROTOTYPE = 0x0000000000000002,
NO_D6_AFTER_LOBBY = 0x0000000000000100,
@@ -61,8 +62,6 @@ public:
SAVE_ENABLED = 0x0000000004000000,
HAS_EP3_CARD_DEFS = 0x0000000008000000,
HAS_EP3_MEDIA_UPDATES = 0x0000000010000000,
USE_OVERRIDE_RANDOM_SEED = 0x0000000020000000,
HAS_GUILD_CARD_NUMBER = 0x0000000040000000,
HAS_AUTO_PATCHES = 0x0000004000000000,
AT_BANK_COUNTER = 0x0000000080000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_ITEM_STATE = 0x0001000000000000, // Server-side only
@@ -86,16 +85,10 @@ public:
PROXY_SAVE_FILES = 0x0000001000000000,
PROXY_CHAT_COMMANDS_ENABLED = 0x0000002000000000,
PROXY_PLAYER_NOTIFICATIONS_ENABLED = 0x0000008000000000,
PROXY_SUPPRESS_CLIENT_PINGS = 0x0000010000000000,
PROXY_SUPPRESS_REMOTE_LOGIN = 0x0000020000000000,
PROXY_ZERO_REMOTE_GUILD_CARD = 0x0000040000000000,
PROXY_EP3_INFINITE_MESETA_ENABLED = 0x0000080000000000,
PROXY_EP3_INFINITE_TIME_ENABLED = 0x0000100000000000,
PROXY_RED_NAME_ENABLED = 0x0000200000000000,
PROXY_BLANK_NAME_ENABLED = 0x0000400000000000,
PROXY_BLOCK_FUNCTION_CALLS = 0x0000800000000000,
PROXY_EP3_UNMASK_WHISPERS = 0x0008000000000000,
PROXY_VIRTUAL_CLIENT = 0x0400000000000000,
// clang-format on
};
enum class ItemDropNotificationMode {
@@ -107,123 +100,70 @@ public:
static constexpr uint64_t DEFAULT_FLAGS = static_cast<uint64_t>(Flag::PROXY_CHAT_COMMANDS_ENABLED);
struct Config {
uint64_t enabled_flags = DEFAULT_FLAGS; // Client::Flag enum
uint32_t specific_version = 0;
int32_t override_random_seed = 0;
uint8_t override_section_id = 0xFF; // FF = no override
uint8_t override_lobby_event = 0xFF; // FF = no override
uint8_t override_lobby_number = 0x80; // 80 = no override
uint32_t proxy_destination_address = 0;
uint16_t proxy_destination_port = 0;
Config() = default;
bool operator==(const Config& other) const = default;
bool operator!=(const Config& other) const = default;
bool should_update_vs(const Config& other) const;
[[nodiscard]] static inline bool check_flag(uint64_t enabled_flags, Flag flag) {
return !!(enabled_flags & static_cast<uint64_t>(flag));
}
[[nodiscard]] inline bool check_flag(Flag flag) const {
return this->check_flag(this->enabled_flags, flag);
}
inline void set_flag(Flag flag) {
this->enabled_flags |= static_cast<uint64_t>(flag);
}
inline void clear_flag(Flag flag) {
this->enabled_flags &= (~static_cast<uint64_t>(flag));
}
inline void toggle_flag(Flag flag) {
this->enabled_flags ^= static_cast<uint64_t>(flag);
}
void set_flags_for_version(Version version, int64_t sub_version);
ItemDropNotificationMode get_drop_notification_mode() const;
void set_drop_notification_mode(ItemDropNotificationMode new_mode);
template <size_t Bytes>
void parse_from(const parray<uint8_t, Bytes>& data) {
phosg::StringReader r(data.data(), data.size());
if (r.get_u32l() != CLIENT_CONFIG_MAGIC) {
throw std::invalid_argument("config signature is incorrect");
}
this->specific_version = r.get_u32l();
this->enabled_flags = r.get_u64l();
this->override_random_seed = r.get_u32l();
this->proxy_destination_address = r.get_u32b();
this->proxy_destination_port = r.get_u16l();
this->override_section_id = r.get_u8();
this->override_lobby_event = r.get_u8();
this->override_lobby_number = r.get_u8();
}
template <size_t Bytes>
void serialize_into(parray<uint8_t, Bytes>& data) const {
phosg::StringWriter w;
w.put_u32l(CLIENT_CONFIG_MAGIC);
w.put_u32l(this->specific_version);
w.put_u64l(this->enabled_flags & static_cast<uint64_t>(Flag::CLIENT_SIDE_MASK));
w.put_u32l(this->override_random_seed);
w.put_u32b(this->proxy_destination_address);
w.put_u16l(this->proxy_destination_port);
w.put_u8(this->override_section_id);
w.put_u8(this->override_lobby_event);
w.put_u8(this->override_lobby_number);
const auto& s = w.str();
for (size_t z = 0; z < s.size(); z++) {
data[z] = s[z];
}
data.clear_after(s.size(), 0xFF);
}
};
std::weak_ptr<Server> server;
std::weak_ptr<GameServer> server;
uint64_t id;
phosg::PrefixedLogger log;
// Account information (not all of these are used; depends on game version)
std::string username;
std::string password;
std::string email_address;
uint64_t hardware_id = 0;
int32_t sub_version = 0;
uint8_t bb_client_code = 0;
uint8_t bb_connection_phase = 0xFF;
ssize_t bb_character_index = -1; // -1 = not set
uint32_t bb_security_token = 0;
parray<uint8_t, 0x28> bb_client_config;
std::string login_character_name;
std::string serial_number;
std::string access_key;
std::string serial_number2;
std::string access_key2;
std::string v1_serial_number;
std::string v1_access_key;
XBNetworkLocation xb_netloc;
parray<le_uint32_t, 3> xb_unknown_a1a;
uint64_t xb_user_id = 0;
uint32_t xb_unknown_a1b = 0;
std::shared_ptr<Login> login;
std::shared_ptr<ProxySession> proxy_session;
// Patch server state (only used for PC_PATCH and BB_PATCH versions)
std::vector<PatchFileChecksumRequest> patch_file_checksum_requests;
// Network
Channel channel;
struct sockaddr_storage next_connection_addr;
std::shared_ptr<Channel> channel;
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> bb_detector_crypt;
ServerBehavior server_behavior;
bool should_disconnect;
bool should_send_to_lobby_server;
bool should_send_to_proxy_server;
std::unordered_map<std::string, std::function<void()>> disconnect_hooks;
std::shared_ptr<XBNetworkLocation> xb_netloc;
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
uint8_t bb_connection_phase;
uint64_t ping_start_time;
uint64_t ping_start_time = 0;
// Lobby/positioning
Config config;
Config synced_config;
// Basic state
uint64_t enabled_flags = DEFAULT_FLAGS; // Client::Flag enum
uint32_t specific_version = 0;
uint8_t override_section_id = 0xFF; // FF = no override
uint8_t override_lobby_event = 0xFF; // FF = no override
uint8_t override_lobby_number = 0x80; // 80 = no override
int64_t override_random_seed = -1;
std::unique_ptr<Variations> override_variations;
int32_t sub_version;
VectorXZF pos;
uint32_t floor;
uint32_t floor = 0x0F;
std::weak_ptr<Lobby> lobby;
uint8_t lobby_client_id;
uint8_t lobby_arrow_color;
int64_t preferred_lobby_id; // <0 = no preference
uint8_t lobby_client_id = 0;
uint8_t lobby_arrow_color = 0;
int64_t preferred_lobby_id = -1; // <0 = no preference
std::unique_ptr<struct event, void (*)(struct event*)> save_game_data_event;
std::unique_ptr<struct event, void (*)(struct event*)> send_ping_event;
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
int16_t card_battle_table_number;
uint16_t card_battle_table_seat_number;
uint16_t card_battle_table_seat_state;
asio::steady_timer save_game_data_timer;
asio::steady_timer send_ping_timer;
asio::steady_timer idle_timeout_timer;
int16_t card_battle_table_number = -1;
uint16_t card_battle_table_seat_number = 0;
uint16_t card_battle_table_seat_state = 0;
std::weak_ptr<Episode3::Tournament::Team> ep3_tournament_team;
std::shared_ptr<const Episode3::BattleRecord> ep3_prev_battle_record;
std::shared_ptr<const Menu> last_menu_sent;
uint32_t last_game_info_requested;
uint32_t last_game_info_requested = 0;
struct JoinCommand {
uint16_t command;
uint32_t flag;
@@ -246,52 +186,72 @@ public:
std::unordered_set<uint32_t> blocked_senders;
std::unique_ptr<PlayerDispDataDCPCV3> v1_v2_last_reported_disp;
std::shared_ptr<Parsed6x70Data> last_reported_6x70;
// These are null unless the client is within the trade sequence (D0-D4 or EE commands)
// These are null unless the client is within the trade sequence (D0-D4 or EE
// commands)
std::unique_ptr<PendingItemTrade> pending_item_trade;
std::unique_ptr<PendingCardTrade> pending_card_trade;
uint32_t telepipe_lobby_id;
uint32_t telepipe_lobby_id = 0;
TelepipeState telepipe_state;
std::shared_ptr<Episode3::PlayerConfig> ep3_config; // Null for non-Ep3
ssize_t bb_character_index; // -1 = not set
ItemData bb_identify_result;
std::array<std::vector<ItemData>, 3> bb_shop_contents;
// Miscellaneous (used by chat commands)
uint32_t next_exp_value; // next EXP value to give
bool can_chat;
struct PendingCharacterExport {
std::shared_ptr<const Account> dest_account;
ssize_t character_index = -1;
std::shared_ptr<const BBLicense> dest_bb_license; // Only used for $bbchar; null for $savechar
};
std::unique_ptr<PendingCharacterExport> pending_character_export;
std::deque<std::function<void(uint32_t, uint32_t)>> function_call_response_queue;
uint32_t next_exp_value = 0; // next EXP value to give
bool can_chat = true;
// NOTE: If you add any new optional promises here, make sure to also add
// them to cancel_pending_promises.
// NOTE: Entries in this queue can be nullptr; that represents a B2 command
// sent by the remote server during a proxy session. We can't just omit those
// from the queue entirely, because if we did, we could end up sending the
// wrong B3 response back.
std::deque<std::shared_ptr<AsyncPromise<C_ExecuteCodeResult_B3>>> function_call_response_queue;
std::shared_ptr<AsyncPromise<GetPlayerInfoResult>> character_data_ready_promise;
std::shared_ptr<AsyncPromise<void>> enable_save_promise;
// File loading state
uint32_t dol_base_addr;
std::shared_ptr<DOLFileIndex::File> loading_dol_file;
std::unordered_map<std::string, std::shared_ptr<const std::string>> sending_files;
Client(
std::shared_ptr<Server> server,
struct bufferevent* bev,
uint64_t virtual_network_id,
Version version,
std::shared_ptr<GameServer> server,
std::shared_ptr<Channel> channel,
ServerBehavior server_behavior);
~Client();
void update_channel_name();
void reschedule_save_game_data_event();
void reschedule_ping_and_timeout_events();
void reschedule_save_game_data_timer();
void reschedule_ping_and_timeout_timers();
inline Version version() const {
return this->channel.version;
return this->channel->version;
}
inline uint8_t language() const {
return this->channel.language;
return this->channel->language;
}
[[nodiscard]] static inline bool check_flag(uint64_t enabled_flags, Flag flag) {
return !!(enabled_flags & static_cast<uint64_t>(flag));
}
[[nodiscard]] inline bool check_flag(Flag flag) const {
return this->check_flag(this->enabled_flags, flag);
}
inline void set_flag(Flag flag) {
this->enabled_flags |= static_cast<uint64_t>(flag);
}
inline void clear_flag(Flag flag) {
this->enabled_flags &= (~static_cast<uint64_t>(flag));
}
inline void toggle_flag(Flag flag) {
this->enabled_flags ^= static_cast<uint64_t>(flag);
}
void set_flags_for_version(Version version, int64_t sub_version);
ItemDropNotificationMode get_drop_notification_mode() const;
void set_drop_notification_mode(ItemDropNotificationMode new_mode);
void convert_account_to_temporary_if_nte();
void sync_config();
@@ -325,15 +285,6 @@ public:
bool can_use_chat_commands() const;
static void dispatch_save_game_data(evutil_socket_t, short, void* ctx);
void save_game_data();
static void dispatch_send_ping(evutil_socket_t, short, void* ctx);
void send_ping();
static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx);
void idle_timeout();
void suspend_timeouts();
void set_login(std::shared_ptr<Login> login);
void create_battle_overlay(std::shared_ptr<const BattleRules> rules, std::shared_ptr<const LevelTable> level_table);
@@ -397,6 +348,8 @@ public:
void print_inventory(FILE* stream) const;
void print_bank(FILE* stream) const;
void cancel_pending_promises();
private:
// The overlay character data is used in battle and challenge modes, when
// character data is temporarily replaced in-game. In other play modes and in
@@ -407,10 +360,8 @@ private:
std::shared_ptr<PSOBBGuildCardFile> guild_card_data;
std::shared_ptr<PlayerBank200> external_bank;
std::shared_ptr<PSOBBCharacterFile> external_bank_character;
ssize_t external_bank_character_index;
uint64_t last_play_time_update;
void save_and_clear_external_bank();
ssize_t external_bank_character_index = -1;
uint64_t last_play_time_update = 0;
void load_all_files();
void update_character_data_after_load(std::shared_ptr<PSOBBCharacterFile> character_data);