centralize command formats; fix a few range bugs
This commit is contained in:
@@ -30,7 +30,6 @@ Current known issues / missing features:
|
||||
- The trade window isn't implemented yet.
|
||||
- PSO PC and PSOBB are essentially entirely untested. Only GC is fairly well-tested.
|
||||
- Add all the chat commands that khyller used to have. (Most, but not all, currently exist in newserv.)
|
||||
- The command structures are defined in multiple places. Centralize them.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
+16
-2
@@ -524,6 +524,19 @@ static void command_lobby_type(shared_ptr<ServerState>, shared_ptr<Lobby> l,
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Game commands
|
||||
|
||||
static void command_secid(shared_ptr<ServerState>, shared_ptr<Lobby> l,
|
||||
shared_ptr<Client> c, const char16_t* args) {
|
||||
check_is_game(l, false);
|
||||
|
||||
if (!args[0]) {
|
||||
c->override_section_id = -1;
|
||||
send_text_message(l, u"$C6Override section ID\nremoved");
|
||||
} else {
|
||||
c->override_section_id = section_id_for_name(args);
|
||||
send_text_message(l, u"$C6Override section ID\nset");
|
||||
}
|
||||
}
|
||||
|
||||
static void command_password(shared_ptr<ServerState>, shared_ptr<Lobby> l,
|
||||
shared_ptr<Client> c, const char16_t* args) {
|
||||
check_is_game(l, true);
|
||||
@@ -534,7 +547,7 @@ static void command_password(shared_ptr<ServerState>, shared_ptr<Lobby> l,
|
||||
send_text_message(l, u"$C6Game unlocked");
|
||||
|
||||
} else {
|
||||
char16cpy(l->password, args, 0x10);
|
||||
strncpy_t(l->password, args, countof(l->password));
|
||||
auto encoded = encode_sjis(l->password);
|
||||
send_text_message_printf(l, "$C6Game password:\n%s",
|
||||
encoded.c_str());
|
||||
@@ -578,7 +591,7 @@ static void command_edit(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
|
||||
check_version(c, GameVersion::BB);
|
||||
|
||||
string encoded_args = encode_sjis(args);
|
||||
vector<string> tokens = split(encoded_args, L' ');
|
||||
vector<string> tokens = split(encoded_args, ' ');
|
||||
|
||||
if (tokens.size() < 3) {
|
||||
send_text_message(c, u"$C6Not enough arguments");
|
||||
@@ -909,6 +922,7 @@ static const unordered_map<u16string, ChatCommandDefinition> chat_commands({
|
||||
{u"maxlevel" , {command_max_level , u"Usage:\nmax_level <level>"}},
|
||||
{u"minlevel" , {command_min_level , u"Usage:\nmin_level <level>"}},
|
||||
{u"password" , {command_password , u"Usage:\nlock [password]\nomit password to\nunlock game"}},
|
||||
{u"secid" , {command_secid , u"Usage:\nsecid [section ID]\nomit section ID to\nrevert to normal"}},
|
||||
{u"silence" , {command_silence , u"Usage:\nsilence <name-or-number>"}},
|
||||
{u"song" , {command_song , u"Usage:\nsong <song-number>"}},
|
||||
{u"type" , {command_lobby_type , u"Usage:\ntype <name>"}},
|
||||
|
||||
@@ -38,6 +38,7 @@ Client::Client(
|
||||
lobby_client_id(0),
|
||||
lobby_arrow_color(0),
|
||||
next_exp_value(0),
|
||||
override_section_id(-1),
|
||||
infinite_hp(false),
|
||||
infinite_tp(false),
|
||||
can_chat(true) {
|
||||
|
||||
+4
-3
@@ -61,20 +61,21 @@ struct Client {
|
||||
uint32_t proxy_destination_address;
|
||||
uint16_t proxy_destination_port;
|
||||
|
||||
// timing & menus
|
||||
// Timing & menus
|
||||
uint64_t play_time_begin; // time of connection (used for incrementing play time on BB)
|
||||
uint64_t last_recv_time; // time of last data received
|
||||
uint64_t last_send_time; // time of last data sent
|
||||
|
||||
// lobby/positioning
|
||||
// Lobby/positioning
|
||||
uint32_t area; // which area is the client in?
|
||||
uint32_t lobby_id; // which lobby is this person in?
|
||||
uint8_t lobby_client_id; // which client number is this person?
|
||||
uint8_t lobby_arrow_color; // lobby arrow color ID
|
||||
Player player;
|
||||
|
||||
// miscellaneous (used by chat commands)
|
||||
// Miscellaneous (used by chat commands)
|
||||
uint32_t next_exp_value; // next EXP value to give
|
||||
int16_t override_section_id; // valid if >= 0
|
||||
bool infinite_hp; // cheats enabled
|
||||
bool infinite_tp; // cheats enabled
|
||||
bool can_chat;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -152,7 +152,7 @@ shared_ptr<Client> Lobby::find_client(const char16_t* identifier,
|
||||
(this->clients[x]->license->serial_number == serial_number)) {
|
||||
return this->clients[x];
|
||||
}
|
||||
if (identifier && !char16cmp(this->clients[x]->player.disp.name, identifier, 0x10)) {
|
||||
if (identifier && !char16ncmp(this->clients[x]->player.disp.name, identifier, 0x10)) {
|
||||
return this->clients[x];
|
||||
}
|
||||
}
|
||||
|
||||
+42
-63
@@ -16,11 +16,29 @@ using namespace std;
|
||||
|
||||
// originally there was going to be a language-based header, but then I decided against it.
|
||||
// these strings were already in use for that parser, so I didn't bother changing them.
|
||||
#define PLAYER_FILE_SIGNATURE "newserv player file format; 10 sections present; sequential;"
|
||||
#define ACCOUNT_FILE_SIGNATURE "newserv account file format; 7 sections present; sequential;"
|
||||
#define PLAYER_FILE_SIGNATURE "newserv player file format; 10 sections present; sequential;"
|
||||
#define ACCOUNT_FILE_SIGNATURE "newserv account file format; 7 sections present; sequential;"
|
||||
|
||||
|
||||
|
||||
void PlayerDispDataPCGC::enforce_pc_limits() {
|
||||
// PC has fewer classes, so we'll substitute some here
|
||||
if (this->char_class == 11) {
|
||||
this->char_class = 0; // fomar -> humar
|
||||
} else if (this->char_class == 10) {
|
||||
this->char_class = 1; // ramarl -> hunewearl
|
||||
} else if (this->char_class == 9) {
|
||||
this->char_class = 5; // hucaseal -> racaseal
|
||||
}
|
||||
|
||||
// if the player is still not a valid class, make them appear as the "ninja" NPC
|
||||
if (this->char_class > 8) {
|
||||
this->extra_model = 0;
|
||||
this->v2_flags |= 2;
|
||||
}
|
||||
this->version = 2;
|
||||
}
|
||||
|
||||
// converts PC/GC player data to BB format
|
||||
PlayerDispDataBB PlayerDispDataPCGC::to_bb() const {
|
||||
PlayerDispDataBB bb;
|
||||
@@ -38,7 +56,7 @@ PlayerDispDataBB PlayerDispDataPCGC::to_bb() const {
|
||||
bb.experience = this->experience;
|
||||
bb.meseta = this->meseta;
|
||||
memset(bb.guild_card, 0, sizeof(bb.guild_card));
|
||||
strcpy(bb.guild_card, " 0");
|
||||
strncpy(bb.guild_card, " 0", 0x10);
|
||||
bb.unknown3[0] = this->unknown3[0];
|
||||
bb.unknown3[1] = this->unknown3[1];
|
||||
bb.name_color = this->name_color;
|
||||
@@ -119,7 +137,7 @@ PlayerDispDataBBPreview PlayerDispDataBB::to_preview() const {
|
||||
pre.level = this->level;
|
||||
pre.experience = this->experience;
|
||||
memset(pre.guild_card, 0, sizeof(pre.guild_card));
|
||||
strcpy(pre.guild_card, this->guild_card);
|
||||
strncpy(pre.guild_card, this->guild_card, 0x10);
|
||||
pre.unknown3[0] = this->unknown3[0];
|
||||
pre.unknown3[1] = this->unknown3[1];
|
||||
pre.name_color = this->name_color;
|
||||
@@ -142,7 +160,7 @@ PlayerDispDataBBPreview PlayerDispDataBB::to_preview() const {
|
||||
pre.proportion_x = this->proportion_x;
|
||||
pre.proportion_y = this->proportion_y;
|
||||
memset(pre.name, 0, sizeof(pre.name));
|
||||
char16cpy(pre.name, this->name, 16);
|
||||
char16ncpy(pre.name, this->name, 16);
|
||||
pre.play_time = this->play_time;
|
||||
return pre;
|
||||
}
|
||||
@@ -151,7 +169,7 @@ void PlayerDispDataBB::apply_preview(const PlayerDispDataBBPreview& pre) {
|
||||
this->level = pre.level;
|
||||
this->experience = pre.experience;
|
||||
memset(this->guild_card, 0, sizeof(this->guild_card));
|
||||
strcpy(this->guild_card, pre.guild_card);
|
||||
strncpy(this->guild_card, pre.guild_card, 0x10);
|
||||
this->unknown3[0] = pre.unknown3[0];
|
||||
this->unknown3[1] = pre.unknown3[1];
|
||||
this->name_color = pre.name_color;
|
||||
@@ -174,7 +192,7 @@ void PlayerDispDataBB::apply_preview(const PlayerDispDataBBPreview& pre) {
|
||||
this->proportion_x = pre.proportion_x;
|
||||
this->proportion_y = pre.proportion_y;
|
||||
memset(this->name, 0, sizeof(this->name));
|
||||
char16cpy(this->name, pre.name, 16);
|
||||
char16ncpy(this->name, pre.name, 0x10);
|
||||
this->play_time = 0;
|
||||
}
|
||||
|
||||
@@ -226,55 +244,16 @@ void Player::import(const PSOPlayerDataBB& bb) {
|
||||
// note: we don't copy the inventory and disp here because we already have
|
||||
// it (we sent the player data to the client in the first place)
|
||||
memset(this->info_board, 0, sizeof(this->info_board));
|
||||
char16cpy(this->info_board, bb.info_board, 0xAC);
|
||||
char16ncpy(this->info_board, bb.info_board, 0xAC);
|
||||
memcpy(&this->blocked, bb.blocked, sizeof(uint32_t) * 30);
|
||||
memset(this->auto_reply, 0, sizeof(this->auto_reply));
|
||||
if (bb.auto_reply_enabled) {
|
||||
char16cpy(this->auto_reply, bb.auto_reply, 0xAC);
|
||||
char16ncpy(this->auto_reply, bb.auto_reply, 0xAC);
|
||||
} else {
|
||||
this->auto_reply[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// generates data for 65/67/68 commands (joining games/lobbies)
|
||||
PlayerLobbyJoinDataPCGC Player::export_lobby_data_pc() const {
|
||||
PlayerLobbyJoinDataPCGC pc;
|
||||
pc.inventory = this->inventory;
|
||||
pc.disp = this->disp.to_pcgc();
|
||||
|
||||
// PC has fewer classes, so we'll substitute some here
|
||||
if (pc.disp.char_class == 11) {
|
||||
pc.disp.char_class = 0; // fomar -> humar
|
||||
} else if (pc.disp.char_class == 10) {
|
||||
pc.disp.char_class = 1; // ramarl -> hunewearl
|
||||
} else if (pc.disp.char_class == 9) {
|
||||
pc.disp.char_class = 5; // hucaseal -> racaseal
|
||||
}
|
||||
|
||||
// if the player is still not a valid class, make them appear as the "ninja" NPC
|
||||
if (pc.disp.char_class > 8) {
|
||||
pc.disp.extra_model = 0;
|
||||
pc.disp.v2_flags |= 2;
|
||||
}
|
||||
pc.disp.version = 2;
|
||||
|
||||
return pc;
|
||||
}
|
||||
|
||||
PlayerLobbyJoinDataPCGC Player::export_lobby_data_gc() const {
|
||||
PlayerLobbyJoinDataPCGC gc;
|
||||
gc.inventory = this->inventory;
|
||||
gc.disp = this->disp.to_pcgc();
|
||||
return gc;
|
||||
}
|
||||
|
||||
PlayerLobbyJoinDataBB Player::export_lobby_data_bb() const {
|
||||
PlayerLobbyJoinDataBB bb;
|
||||
bb.inventory = this->inventory;
|
||||
bb.disp = this->disp;
|
||||
return bb;
|
||||
}
|
||||
|
||||
PlayerBB Player::export_bb_player_data() const {
|
||||
PlayerBB bb;
|
||||
bb.inventory = this->inventory;
|
||||
@@ -285,11 +264,11 @@ PlayerBB Player::export_bb_player_data() const {
|
||||
bb.bank = this->bank;
|
||||
bb.serial_number = this->serial_number;
|
||||
memset(bb.name, 0, sizeof(bb.name));
|
||||
char16cpy(bb.name, this->disp.name, 24);
|
||||
char16ncpy(bb.name, this->disp.name, 24);
|
||||
memset(bb.team_name, 0, sizeof(bb.team_name));
|
||||
char16cpy(bb.team_name, this->team_name, 16);
|
||||
char16ncpy(bb.team_name, this->team_name, 16);
|
||||
memset(bb.guild_card_desc, 0, sizeof(bb.guild_card_desc));
|
||||
char16cpy(bb.guild_card_desc, this->guild_card_desc, 0x58);
|
||||
char16ncpy(bb.guild_card_desc, this->guild_card_desc, 0x58);
|
||||
bb.reserved1 = 0;
|
||||
bb.reserved2 = 0;
|
||||
bb.section_id = this->disp.section_id;
|
||||
@@ -298,9 +277,9 @@ PlayerBB Player::export_bb_player_data() const {
|
||||
memcpy(bb.symbol_chats, this->symbol_chats, 0x04E0);
|
||||
memcpy(bb.shortcuts, this->shortcuts, 0x0A40);
|
||||
memset(bb.auto_reply, 0, sizeof(bb.auto_reply));
|
||||
char16cpy(bb.auto_reply, this->auto_reply, 0xAC);
|
||||
char16ncpy(bb.auto_reply, this->auto_reply, 0xAC);
|
||||
memset(bb.info_board, 0, sizeof(bb.info_board));
|
||||
char16cpy(bb.info_board, this->info_board, 0xAC);
|
||||
char16ncpy(bb.info_board, this->info_board, 0xAC);
|
||||
memset(bb.unknown5, 0, 0x1C);
|
||||
memcpy(bb.challenge_data, this->challenge_data, 0x0140);
|
||||
memcpy(bb.tech_menu_config, this->tech_menu_config, 0x0028);
|
||||
@@ -344,13 +323,13 @@ void Player::load_account_data(const string& filename) {
|
||||
memcpy(&this->shortcuts, &account.shortcuts, 0x0A40);
|
||||
memcpy(&this->symbol_chats, &account.symbol_chats, 0x04E0);
|
||||
memset(this->team_name, 0, sizeof(this->team_name));
|
||||
char16cpy(this->team_name, account.team_name, 16);
|
||||
char16ncpy(this->team_name, account.team_name, 16);
|
||||
}
|
||||
|
||||
void Player::save_account_data(const string& filename) const {
|
||||
SavedAccountBB account;
|
||||
|
||||
strcpy(account.signature, ACCOUNT_FILE_SIGNATURE);
|
||||
strncpy(account.signature, ACCOUNT_FILE_SIGNATURE, sizeof(account.signature));
|
||||
memcpy(&account.blocked, &this->blocked, sizeof(uint32_t) * 30);
|
||||
account.guild_cards = this->guild_cards;
|
||||
account.key_config = this->key_config;
|
||||
@@ -358,7 +337,7 @@ void Player::save_account_data(const string& filename) const {
|
||||
memcpy(&account.shortcuts, &this->shortcuts, 0x0A40);
|
||||
memcpy(&account.symbol_chats, &this->symbol_chats, 0x04E0);
|
||||
memset(account.team_name, 0, sizeof(account.team_name));
|
||||
char16cpy(account.team_name, this->team_name, 16);
|
||||
char16ncpy(account.team_name, this->team_name, 16);
|
||||
|
||||
save_file(filename, &account, sizeof(account));
|
||||
}
|
||||
@@ -371,14 +350,14 @@ void Player::load_player_data(const string& filename) {
|
||||
}
|
||||
|
||||
memset(this->auto_reply, 0, sizeof(this->auto_reply));
|
||||
char16cpy(this->auto_reply, player.auto_reply, 0xAC);
|
||||
char16ncpy(this->auto_reply, player.auto_reply, 0xAC);
|
||||
this->bank = player.bank;
|
||||
memcpy(&this->challenge_data, &player.challenge_data, 0x0140);
|
||||
this->disp = player.disp;
|
||||
memset(this->guild_card_desc, 0, sizeof(this->guild_card_desc));
|
||||
char16cpy(this->guild_card_desc, player.guild_card_desc, 0x58);
|
||||
char16ncpy(this->guild_card_desc, player.guild_card_desc, 0x58);
|
||||
memset(this->info_board, 0, sizeof(this->info_board));
|
||||
char16cpy(this->info_board, player.info_board, 0xAC);
|
||||
char16ncpy(this->info_board, player.info_board, 0xAC);
|
||||
this->inventory = player.inventory;
|
||||
memcpy(&this->quest_data1, &player.quest_data1, 0x0208);
|
||||
memcpy(&this->quest_data2, &player.quest_data2, 0x0058);
|
||||
@@ -388,17 +367,17 @@ void Player::load_player_data(const string& filename) {
|
||||
void Player::save_player_data(const string& filename) const {
|
||||
SavedPlayerBB player;
|
||||
|
||||
strcpy(player.signature, PLAYER_FILE_SIGNATURE);
|
||||
strncpy(player.signature, PLAYER_FILE_SIGNATURE, sizeof(player.signature));
|
||||
player.preview = this->disp.to_preview();
|
||||
memset(player.auto_reply, 0, sizeof(player.auto_reply));
|
||||
char16cpy(player.auto_reply, this->auto_reply, 0xAC);
|
||||
char16ncpy(player.auto_reply, this->auto_reply, 0xAC);
|
||||
player.bank = this->bank;
|
||||
memcpy(&player.challenge_data, &this->challenge_data, 0x0140);
|
||||
player.disp = this->disp;
|
||||
memset(player.guild_card_desc, 0, sizeof(player.guild_card_desc));
|
||||
char16cpy(player.guild_card_desc,this->guild_card_desc, 0x58);
|
||||
char16ncpy(player.guild_card_desc,this->guild_card_desc, 0x58);
|
||||
memset(player.info_board, 0, sizeof(player.info_board));
|
||||
char16cpy(player.info_board, this->info_board, 0xAC);
|
||||
char16ncpy(player.info_board, this->info_board, 0xAC);
|
||||
player.inventory = this->inventory;
|
||||
memcpy(&player.quest_data1, &this->quest_data1, 0x0208);
|
||||
memcpy(&player.quest_data2, &this->quest_data2, 0x0058);
|
||||
|
||||
+37
-17
@@ -123,6 +123,7 @@ struct PlayerDispDataPCGC { // 0xD0 in size
|
||||
uint8_t config[0x48];
|
||||
uint8_t technique_levels[0x14];
|
||||
|
||||
void enforce_pc_limits();
|
||||
PlayerDispDataBB to_bb() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
@@ -163,7 +164,7 @@ struct PlayerDispDataBB {
|
||||
uint32_t level;
|
||||
uint32_t experience;
|
||||
uint32_t meseta;
|
||||
char guild_card[16];
|
||||
char guild_card[0x10];
|
||||
uint32_t unknown3[2];
|
||||
uint32_t name_color;
|
||||
uint8_t extra_model;
|
||||
@@ -189,6 +190,7 @@ struct PlayerDispDataBB {
|
||||
uint8_t config[0xE8];
|
||||
uint8_t technique_levels[0x14];
|
||||
|
||||
inline void enforce_pc_limits() { }
|
||||
PlayerDispDataPCGC to_pcgc() const;
|
||||
PlayerDispDataBBPreview to_preview() const;
|
||||
void apply_preview(const PlayerDispDataBBPreview&);
|
||||
@@ -277,7 +279,8 @@ struct PlayerLobbyDataGC {
|
||||
struct PlayerLobbyDataBB {
|
||||
uint32_t player_tag;
|
||||
uint32_t guild_card;
|
||||
uint32_t unknown1[5];
|
||||
be_uint32_t ip_address; // Guess - the official builds didn't use this, but all other versions have it
|
||||
uint32_t unknown1[4];
|
||||
uint32_t client_id;
|
||||
char16_t name[16];
|
||||
uint32_t unknown2;
|
||||
@@ -310,18 +313,6 @@ struct PSOPlayerDataBB { // for command 0x61
|
||||
char16_t auto_reply[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
// PC/GC lobby player data (used in lobby/game join commands)
|
||||
struct PlayerLobbyJoinDataPCGC {
|
||||
PlayerInventory inventory;
|
||||
PlayerDispDataPCGC disp;
|
||||
} __attribute__((packed));
|
||||
|
||||
// BB lobby player data (used in lobby/game join commands)
|
||||
struct PlayerLobbyJoinDataBB {
|
||||
PlayerInventory inventory;
|
||||
PlayerDispDataBB disp;
|
||||
} __attribute__((packed));
|
||||
|
||||
// complete BB player data format (used in E7 command)
|
||||
struct PlayerBB {
|
||||
PlayerInventory inventory; // 0000 // player
|
||||
@@ -416,9 +407,6 @@ struct Player {
|
||||
void import(const PSOPlayerDataPC& pd);
|
||||
void import(const PSOPlayerDataGC& pd);
|
||||
void import(const PSOPlayerDataBB& pd);
|
||||
PlayerLobbyJoinDataPCGC export_lobby_data_pc() const;
|
||||
PlayerLobbyJoinDataPCGC export_lobby_data_gc() const;
|
||||
PlayerLobbyJoinDataBB export_lobby_data_bb() const;
|
||||
PlayerBB export_bb_player_data() const;
|
||||
|
||||
void add_item(const PlayerInventoryItem& item);
|
||||
@@ -434,3 +422,35 @@ std::string filename_for_player_bb(const std::string& username, uint8_t player_i
|
||||
std::string filename_for_bank_bb(const std::string& username, const char* bank_name);
|
||||
std::string filename_for_class_template_bb(uint8_t char_class);
|
||||
std::string filename_for_account_bb(const std::string& username);
|
||||
|
||||
|
||||
|
||||
template <typename DestT, typename SrcT = DestT>
|
||||
DestT convert_player_disp_data(const SrcT&) {
|
||||
static_assert(always_false<DestT, SrcT>::v,
|
||||
"unspecialized strcpy_t should never be called");
|
||||
}
|
||||
|
||||
template <>
|
||||
inline PlayerDispDataPCGC convert_player_disp_data<PlayerDispDataPCGC>(
|
||||
const PlayerDispDataPCGC& src) {
|
||||
return src;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline PlayerDispDataPCGC convert_player_disp_data<PlayerDispDataPCGC, PlayerDispDataBB>(
|
||||
const PlayerDispDataBB& src) {
|
||||
return src.to_pcgc();
|
||||
}
|
||||
|
||||
template <>
|
||||
inline PlayerDispDataBB convert_player_disp_data<PlayerDispDataBB, PlayerDispDataPCGC>(
|
||||
const PlayerDispDataPCGC& src) {
|
||||
return src.to_bb();
|
||||
}
|
||||
|
||||
template <>
|
||||
inline PlayerDispDataBB convert_player_disp_data<PlayerDispDataBB>(
|
||||
const PlayerDispDataBB& src) {
|
||||
return src;
|
||||
}
|
||||
|
||||
+36
-80
@@ -137,9 +137,11 @@ void ProxyServer::on_client_connect(
|
||||
case GameVersion::GC: {
|
||||
uint32_t server_key = random_object<uint32_t>();
|
||||
uint32_t client_key = random_object<uint32_t>();
|
||||
string data = prepare_server_init_contents_dc_pc_gc(false, server_key, client_key);
|
||||
send_command(session->bev.get(), session->version, session->crypt_out.get(), 0x02,
|
||||
0, data.data(), data.size(), "unlinked proxy client");
|
||||
auto cmd = prepare_server_init_contents_dc_pc_gc(
|
||||
false, server_key, client_key);
|
||||
send_command(session->bev.get(), session->version,
|
||||
session->crypt_out.get(), 0x02, 0, &cmd, sizeof(cmd),
|
||||
"unlinked proxy client");
|
||||
session->crypt_out.reset(new PSOGCEncryption(server_key));
|
||||
session->crypt_in.reset(new PSOGCEncryption(client_key));
|
||||
break;
|
||||
@@ -191,11 +193,11 @@ void ProxyServer::UnlinkedSession::on_client_input() {
|
||||
if (command != 0x9E) {
|
||||
log(ERROR, "[ProxyServer] Received unexpected command %02hX", command);
|
||||
should_close_unlinked_session = true;
|
||||
} else if (data.size() < sizeof(LoginCommand_GC_9E) - 0x64) {
|
||||
} else if (data.size() < sizeof(C_Login_PC_GC_9D_9E) - 0x64) {
|
||||
log(ERROR, "[ProxyServer] Login command is too small");
|
||||
should_close_unlinked_session = true;
|
||||
} else {
|
||||
const auto* cmd = reinterpret_cast<const LoginCommand_GC_9E*>(data.data());
|
||||
const auto* cmd = reinterpret_cast<const C_Login_PC_GC_9D_9E*>(data.data());
|
||||
uint32_t serial_number = strtoul(cmd->serial_number, nullptr, 16);
|
||||
try {
|
||||
license = this->server->state->license_manager->verify_gc(
|
||||
@@ -234,9 +236,6 @@ void ProxyServer::UnlinkedSession::on_client_input() {
|
||||
log(ERROR, "[ProxyServer/%08" PRIX32 "] Client configuration is invalid; cannot open session",
|
||||
license->serial_number);
|
||||
} else {
|
||||
// If the client goes back to newserv, we need to set the welcome
|
||||
// message flag so the server will know what to do
|
||||
client_config.flags |= ClientFlag::AT_WELCOME_MESSAGE;
|
||||
session.reset(new LinkedSession(
|
||||
this->server,
|
||||
this->local_port,
|
||||
@@ -506,11 +505,7 @@ void ProxyServer::LinkedSession::on_client_input() {
|
||||
}
|
||||
uint8_t leaving_id = x;
|
||||
uint8_t leader_id = this->lobby_client_id;
|
||||
struct {
|
||||
uint8_t client_id;
|
||||
uint8_t leader_id;
|
||||
uint16_t unused;
|
||||
} __attribute__((packed)) cmd = {leaving_id, leader_id, 0};
|
||||
S_LeaveLobby_66_69 cmd = {leaving_id, leader_id, 0};
|
||||
send_command(this->client_bev.get(), this->version,
|
||||
this->client_output_crypt.get(), 0x69, leaving_id, &cmd,
|
||||
sizeof(cmd), name.c_str());
|
||||
@@ -519,11 +514,7 @@ void ProxyServer::LinkedSession::on_client_input() {
|
||||
// Restore the newserv client config, so the client gets its newserv
|
||||
// guild card number back and the login server knows e.g. not to show
|
||||
// the welcome message (if the appropriate flag is set)
|
||||
struct {
|
||||
uint32_t player_tag;
|
||||
uint32_t serial_number;
|
||||
ClientConfig config;
|
||||
} __attribute__((packed)) update_client_config_cmd = {
|
||||
S_UpdateClientConfig_DC_PC_GC_04 update_client_config_cmd = {
|
||||
0x00010000,
|
||||
this->license->serial_number,
|
||||
this->newserv_client_config,
|
||||
@@ -538,7 +529,7 @@ void ProxyServer::LinkedSession::on_client_input() {
|
||||
const auto& port_name = version_to_port_name.at(static_cast<size_t>(
|
||||
this->version));
|
||||
|
||||
ReconnectCommand_19 reconnect_cmd = {
|
||||
S_Reconnect_19 reconnect_cmd = {
|
||||
0, this->server->state->named_port_configuration.at(port_name).port, 0};
|
||||
|
||||
// If the client is on a virtual connection, we can use any address
|
||||
@@ -607,11 +598,11 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
}
|
||||
// Most servers don't include after_message or have a shorter
|
||||
// after_message than newserv does, so don't require it
|
||||
if (data.size() < offsetof(ServerInitCommand_GC_02_17, after_message)) {
|
||||
if (data.size() < offsetof(S_ServerInit_DC_GC_02_17, after_message)) {
|
||||
throw std::runtime_error("init encryption command is too small");
|
||||
}
|
||||
|
||||
const auto* cmd = reinterpret_cast<const ServerInitCommand_GC_02_17*>(
|
||||
const auto* cmd = reinterpret_cast<const S_ServerInit_DC_GC_02_17*>(
|
||||
data.data());
|
||||
|
||||
// This doesn't get forwarded to the client, so don't recreate the
|
||||
@@ -632,7 +623,7 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
// We don't let the client do this because it believes it already
|
||||
// did (when it was in an unlinked session).
|
||||
if (command == 0x17) {
|
||||
VerifyLicenseCommand_GC_DB cmd;
|
||||
C_VerifyLicense_GC_DB cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
snprintf(cmd.serial_number, sizeof(cmd.serial_number), "%08" PRIX32 "",
|
||||
this->license->serial_number);
|
||||
@@ -654,7 +645,7 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
|
||||
case 0x9A: {
|
||||
should_forward = false;
|
||||
LoginCommand_GC_9E cmd;
|
||||
C_Login_PC_GC_9D_9E cmd;
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
|
||||
if (this->guild_card_number == 0) {
|
||||
@@ -684,25 +675,21 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
0x9E,
|
||||
0x01,
|
||||
&cmd,
|
||||
this->guild_card_number ? (offsetof(LoginCommand_GC_9E, cfg) + 0x20) : sizeof(cmd),
|
||||
this->guild_card_number ? (offsetof(C_Login_PC_GC_9D_9E, cfg) + 0x20) : sizeof(cmd),
|
||||
name.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x04: {
|
||||
struct Contents {
|
||||
uint32_t player_tag;
|
||||
uint32_t guild_card_number;
|
||||
uint8_t client_config[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
if (data.size() < sizeof(Contents)) {
|
||||
// Some servers send a short 04 command if they don't use all of the
|
||||
// 0x20 bytes available. We should be prepared to handle that.
|
||||
if (data.size() < offsetof(S_UpdateClientConfig_DC_PC_GC_04, cfg)) {
|
||||
throw std::runtime_error("set security data command is too small");
|
||||
}
|
||||
|
||||
bool had_guild_card_number = (this->guild_card_number != 0);
|
||||
|
||||
const auto* cmd = reinterpret_cast<const Contents*>(data.data());
|
||||
const auto* cmd = reinterpret_cast<const S_UpdateClientConfig_DC_PC_GC_04*>(data.data());
|
||||
this->guild_card_number = cmd->guild_card_number;
|
||||
log(INFO, "[ProxyServer/%08" PRIX32 "] Guild card number set to %" PRIX32,
|
||||
this->license->serial_number, this->guild_card_number);
|
||||
@@ -720,7 +707,8 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
? "t Lobby Server. Copyright SEGA E"
|
||||
: "t Port Map. Copyright SEGA Enter",
|
||||
0x20);
|
||||
memcpy(this->remote_client_config_data, cmd->client_config, min<size_t>(data.size() - sizeof(Contents), 0x20));
|
||||
memcpy(this->remote_client_config_data, &cmd->cfg,
|
||||
min<size_t>(data.size() - sizeof(S_UpdateClientConfig_DC_PC_GC_04), 0x20));
|
||||
|
||||
// If the guild card number was not set, pretend (to the server)
|
||||
// that this is the first 04 command the client has received. The
|
||||
@@ -739,11 +727,11 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
}
|
||||
|
||||
case 0x19: {
|
||||
if (data.size() < sizeof(ReconnectCommand_19)) {
|
||||
if (data.size() < sizeof(S_Reconnect_19)) {
|
||||
throw std::runtime_error("reconnect command is too small");
|
||||
}
|
||||
|
||||
auto* args = reinterpret_cast<ReconnectCommand_19*>(data.data());
|
||||
auto* args = reinterpret_cast<S_Reconnect_19*>(data.data());
|
||||
memset(&this->next_destination, 0, sizeof(this->next_destination));
|
||||
struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(
|
||||
&this->next_destination);
|
||||
@@ -802,19 +790,12 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
|
||||
bool is_download_quest = (command == 0xA6);
|
||||
|
||||
struct OpenFileCommand {
|
||||
char name[0x20];
|
||||
uint16_t unused;
|
||||
uint16_t flags;
|
||||
char filename[0x10];
|
||||
uint32_t file_size;
|
||||
};
|
||||
if (data.size() < sizeof(OpenFileCommand)) {
|
||||
if (data.size() < sizeof(S_OpenFile_PC_GC_44_A6)) {
|
||||
log(WARNING, "[ProxyServer/%08" PRIX32 "] Open file command is too small; skipping file",
|
||||
this->license->serial_number);
|
||||
break;
|
||||
}
|
||||
const auto* cmd = reinterpret_cast<const OpenFileCommand*>(data.data());
|
||||
const auto* cmd = reinterpret_cast<const S_OpenFile_PC_GC_44_A6*>(data.data());
|
||||
|
||||
string output_filename = string_printf("%s.%s.%" PRIu64,
|
||||
cmd->filename, is_download_quest ? "download" : "online", now());
|
||||
@@ -840,17 +821,12 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
break;
|
||||
}
|
||||
|
||||
struct WriteFileCommand {
|
||||
char filename[0x10];
|
||||
uint8_t data[0x400];
|
||||
uint32_t data_size;
|
||||
};
|
||||
if (data.size() < sizeof(WriteFileCommand)) {
|
||||
if (data.size() < sizeof(S_WriteFile_13_A7)) {
|
||||
log(WARNING, "[ProxyServer/%08" PRIX32 "] Write file command is too small",
|
||||
this->license->serial_number);
|
||||
break;
|
||||
}
|
||||
const auto* cmd = reinterpret_cast<const WriteFileCommand*>(data.data());
|
||||
const auto* cmd = reinterpret_cast<const S_WriteFile_13_A7*>(data.data());
|
||||
|
||||
SavingFile* sf = nullptr;
|
||||
try {
|
||||
@@ -933,27 +909,12 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
|
||||
case 0x65: // other player joined game
|
||||
case 0x68: { // other player joined lobby
|
||||
struct Command {
|
||||
uint8_t client_id;
|
||||
uint8_t leader_id;
|
||||
uint8_t disable_udp;
|
||||
uint8_t lobby_number;
|
||||
uint16_t block_number;
|
||||
uint16_t event;
|
||||
uint32_t unused;
|
||||
struct Entry {
|
||||
PlayerLobbyDataGC lobby_data;
|
||||
PlayerLobbyJoinDataPCGC data;
|
||||
} __attribute__((packed));
|
||||
Entry entries[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
size_t expected_size = sizeof(Command) + sizeof(Command::Entry) * flag;
|
||||
size_t expected_size = offsetof(S_JoinLobby_GC_65_67_68, entries) + sizeof(S_JoinLobby_GC_65_67_68::Entry) * flag;
|
||||
if (data.size() < expected_size) {
|
||||
log(WARNING, "[ProxyServer/%08" PRIX32 "] Lobby join command is incorrect size (expected 0x%zX bytes, received 0x%zX bytes)",
|
||||
this->license->serial_number, expected_size, data.size());
|
||||
} else {
|
||||
const auto* cmd = reinterpret_cast<const Command*>(data.data());
|
||||
const auto* cmd = reinterpret_cast<const S_JoinLobby_GC_65_67_68*>(data.data());
|
||||
|
||||
this->lobby_client_id = cmd->client_id;
|
||||
|
||||
@@ -964,7 +925,7 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
this->license->serial_number, index, x);
|
||||
} else {
|
||||
this->lobby_players[index].guild_card_number = cmd->entries[x].lobby_data.guild_card;
|
||||
this->lobby_players[index].name = cmd->entries[x].data.disp.name;
|
||||
this->lobby_players[index].name = cmd->entries[x].disp.name;
|
||||
log(INFO, "[ProxyServer/%08" PRIX32 "] Added lobby player: (%zu) %" PRIu32 " %s",
|
||||
this->license->serial_number, index,
|
||||
this->lobby_players[index].guild_card_number,
|
||||
@@ -982,20 +943,20 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
log(WARNING, "[ProxyServer/%08" PRIX32 "] Cleared lobby players",
|
||||
this->license->serial_number);
|
||||
|
||||
const size_t expected_size = offsetof(JoinGameCommand_GC_64, player);
|
||||
const size_t ep3_expected_size = sizeof(JoinGameCommand_GC_64);
|
||||
const size_t expected_size = offsetof(S_JoinGame_GC_64, players_ep3);
|
||||
const size_t ep3_expected_size = sizeof(S_JoinGame_GC_64);
|
||||
if (data.size() < expected_size) {
|
||||
log(WARNING, "[ProxyServer/%08" PRIX32 "] Game join command is incorrect size (expected 0x%zX bytes, received 0x%zX bytes)",
|
||||
this->license->serial_number, expected_size, data.size());
|
||||
} else {
|
||||
const auto* cmd = reinterpret_cast<const JoinGameCommand_GC_64*>(data.data());
|
||||
const auto* cmd = reinterpret_cast<const S_JoinGame_GC_64*>(data.data());
|
||||
|
||||
this->lobby_client_id = cmd->client_id;
|
||||
|
||||
for (size_t x = 0; x < flag; x++) {
|
||||
this->lobby_players[x].guild_card_number = cmd->lobby_data[x].guild_card;
|
||||
if (data.size() >= ep3_expected_size) {
|
||||
this->lobby_players[x].name = cmd->player[x].disp.name;
|
||||
this->lobby_players[x].name = cmd->players_ep3[x].disp.name;
|
||||
} else {
|
||||
this->lobby_players[x].name.clear();
|
||||
}
|
||||
@@ -1010,16 +971,11 @@ void ProxyServer::LinkedSession::on_server_input() {
|
||||
|
||||
case 0x66:
|
||||
case 0x69: {
|
||||
struct Command {
|
||||
uint8_t client_id;
|
||||
uint8_t leader_id;
|
||||
uint16_t unused;
|
||||
} __attribute__((packed));
|
||||
if (data.size() < sizeof(Command)) {
|
||||
if (data.size() < sizeof(S_LeaveLobby_66_69)) {
|
||||
log(WARNING, "[ProxyServer/%08" PRIX32 "] Lobby leave command is incorrect size",
|
||||
this->license->serial_number);
|
||||
} else {
|
||||
const auto* cmd = reinterpret_cast<const Command*>(data.data());
|
||||
const auto* cmd = reinterpret_cast<const S_LeaveLobby_66_69*>(data.data());
|
||||
size_t index = cmd->client_id;
|
||||
if (index >= this->lobby_players.size()) {
|
||||
log(WARNING, "[ProxyServer/%08" PRIX32 "] Lobby leave command references missing position",
|
||||
|
||||
+313
-468
File diff suppressed because it is too large
Load Diff
+1
-35
@@ -6,42 +6,8 @@
|
||||
|
||||
|
||||
|
||||
// These commands' structures are defined here because they're used by both the
|
||||
// game server and proxy server
|
||||
|
||||
struct VerifyLicenseCommand_GC_DB {
|
||||
char unused[0x20];
|
||||
char serial_number[0x10];
|
||||
char access_key[0x10];
|
||||
char unused2[0x08];
|
||||
uint32_t sub_version;
|
||||
char serial_number2[0x30];
|
||||
char access_key2[0x30];
|
||||
char password[0x30];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct LoginCommand_GC_9E {
|
||||
uint32_t player_tag; // 00 00 01 00 if guild card is set (via 04)
|
||||
uint32_t guild_card_number; // FF FF FF FF if not set
|
||||
uint32_t unused1[2];
|
||||
uint32_t sub_version;
|
||||
uint8_t unused2[0x24]; // 00 01 00 00 ... (rest is 00)
|
||||
char serial_number[0x10];
|
||||
char access_key[0x10];
|
||||
char serial_number2[0x30];
|
||||
char access_key2[0x30];
|
||||
char name[0x10];
|
||||
// Note: there are 8 bytes at the end of cfg that are technically not
|
||||
// included in the client config on GC, but the field after it is
|
||||
// sufficiently large and unused anyway
|
||||
ClientConfig cfg;
|
||||
uint8_t unused4[0x5C];
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
|
||||
void process_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c);
|
||||
void process_disconnect(std::shared_ptr<ServerState> s,
|
||||
std::shared_ptr<Client> c);
|
||||
void process_command(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
|
||||
uint16_t command, uint32_t flag, uint16_t size, const void* data);
|
||||
uint16_t command, uint32_t flag, const std::string& data);
|
||||
|
||||
@@ -34,7 +34,7 @@ struct ItemSubcommand {
|
||||
void check_size(uint16_t size, uint16_t min_size, uint16_t max_size) {
|
||||
if (size < min_size) {
|
||||
throw runtime_error(string_printf(
|
||||
"command too small (expected at least 0x%hX bytes, got 0x%hX bytes)",
|
||||
"command too small (expected at least 0x%hX bytes, received 0x%hX bytes)",
|
||||
min_size, size));
|
||||
}
|
||||
if (max_size == 0) {
|
||||
@@ -42,7 +42,7 @@ void check_size(uint16_t size, uint16_t min_size, uint16_t max_size) {
|
||||
}
|
||||
if (size > max_size) {
|
||||
throw runtime_error(string_printf(
|
||||
"command too large (expected at most 0x%hX bytes, got 0x%hX bytes)",
|
||||
"command too large (expected at most 0x%hX bytes, received 0x%hX bytes)",
|
||||
max_size, size));
|
||||
}
|
||||
}
|
||||
@@ -928,9 +928,9 @@ subcommand_handler_t subcommand_handlers[0x100] = {
|
||||
/* 08 */ process_subcommand_unimplemented,
|
||||
/* 09 */ process_subcommand_unimplemented,
|
||||
/* 0A */ process_subcommand_monster_hit,
|
||||
/* 0B */ process_subcommand_forward_check_size_game, // Box destroyed
|
||||
/* 0C */ process_subcommand_forward_check_size_game,
|
||||
/* 0D */ process_subcommand_forward_check_size,
|
||||
/* 0B */ process_subcommand_forward_check_size_game,
|
||||
/* 0C */ process_subcommand_forward_check_size_game, // Add condition (poison/slow/etc.)
|
||||
/* 0D */ process_subcommand_forward_check_size_game, // Remove condition (poison/slow/etc.)
|
||||
/* 0E */ process_subcommand_unimplemented,
|
||||
/* 0F */ process_subcommand_unimplemented,
|
||||
/* 10 */ process_subcommand_unimplemented,
|
||||
|
||||
+359
-1181
File diff suppressed because it is too large
Load Diff
+23
-51
@@ -13,6 +13,7 @@
|
||||
#include "Menu.hh"
|
||||
#include "Quest.hh"
|
||||
#include "Text.hh"
|
||||
#include "CommandFormats.hh"
|
||||
|
||||
|
||||
|
||||
@@ -33,54 +34,41 @@ void send_command(std::shared_ptr<Lobby> l, uint16_t command, uint32_t flag = 0,
|
||||
void send_command(std::shared_ptr<ServerState> s, uint16_t command,
|
||||
uint32_t flag = 0, const void* data = nullptr, size_t size = 0);
|
||||
|
||||
template <typename TARGET, typename STRUCT>
|
||||
void send_command(std::shared_ptr<TARGET> c, uint16_t command, uint32_t flag,
|
||||
const STRUCT& data) {
|
||||
template <typename TargetT, typename StructT>
|
||||
static void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag,
|
||||
const StructT& data) {
|
||||
send_command(c, command, flag, &data, sizeof(data));
|
||||
}
|
||||
|
||||
template <typename TARGET>
|
||||
void send_command(std::shared_ptr<TARGET> c, uint16_t command, uint32_t flag,
|
||||
template <typename TargetT>
|
||||
static void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag,
|
||||
const std::string& data) {
|
||||
send_command(c, command, flag, data.data(), data.size());
|
||||
}
|
||||
|
||||
template <typename TARGET, typename STRUCT>
|
||||
void send_command(std::shared_ptr<TARGET> c, uint16_t command, uint32_t flag,
|
||||
const std::vector<STRUCT>& data) {
|
||||
send_command(c, command, flag, data.data(), data.size() * sizeof(STRUCT));
|
||||
template <typename TargetT, typename StructT>
|
||||
void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag,
|
||||
const std::vector<StructT>& data) {
|
||||
send_command(c, command, flag, data.data(), data.size() * sizeof(StructT));
|
||||
}
|
||||
|
||||
template <typename TARGET, typename STRUCT, typename ENTRY>
|
||||
void send_command(std::shared_ptr<TARGET> c, uint16_t command, uint32_t flag,
|
||||
const STRUCT& data, const std::vector<ENTRY>& array_data) {
|
||||
std::string all_data(reinterpret_cast<const char*>(&data), sizeof(STRUCT));
|
||||
template <typename TargetT, typename StructT, typename EntryT>
|
||||
void send_command(std::shared_ptr<TargetT> c, uint16_t command, uint32_t flag,
|
||||
const StructT& data, const std::vector<EntryT>& array_data) {
|
||||
std::string all_data(reinterpret_cast<const char*>(&data), sizeof(StructT));
|
||||
all_data.append(reinterpret_cast<const char*>(array_data.data()),
|
||||
array_data.size() * sizeof(ENTRY));
|
||||
array_data.size() * sizeof(EntryT));
|
||||
send_command(c, command, flag, all_data.data(), all_data.size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct ServerInitCommand_GC_02_17 {
|
||||
char copyright[0x40];
|
||||
uint32_t server_key;
|
||||
uint32_t client_key;
|
||||
char after_message[200];
|
||||
} __attribute__((packed));
|
||||
|
||||
std::string prepare_server_init_contents_dc_pc_gc(
|
||||
S_ServerInit_DC_GC_02_17 prepare_server_init_contents_dc_pc_gc(
|
||||
bool initial_connection, uint32_t server_key, uint32_t client_key);
|
||||
void send_server_init(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
|
||||
bool initial_connection);
|
||||
void send_update_client_config(std::shared_ptr<Client> c);
|
||||
|
||||
struct ReconnectCommand_19 {
|
||||
be_uint32_t address;
|
||||
uint16_t port;
|
||||
uint16_t unused;
|
||||
} __attribute__((packed));
|
||||
|
||||
void send_reconnect(std::shared_ptr<Client> c, uint32_t address, uint16_t port);
|
||||
void send_pc_gc_split_reconnect(std::shared_ptr<Client> c, uint32_t address,
|
||||
uint16_t pc_port, uint16_t gc_port);
|
||||
@@ -111,9 +99,9 @@ void send_chat_message(std::shared_ptr<Client> c, uint32_t from_serial_number,
|
||||
void send_simple_mail(std::shared_ptr<Client> c, uint32_t from_serial_number,
|
||||
const char16_t* from_name, const char16_t* text);
|
||||
|
||||
template <typename TARGET>
|
||||
template <typename TargetT>
|
||||
__attribute__((format(printf, 2, 3))) void send_text_message_printf(
|
||||
std::shared_ptr<TARGET> t, const char* format, ...) {
|
||||
std::shared_ptr<TargetT> t, const char* format, ...) {
|
||||
va_list va;
|
||||
va_start(va, format);
|
||||
std::string buf = string_vprintf(format, va);
|
||||
@@ -124,8 +112,11 @@ __attribute__((format(printf, 2, 3))) void send_text_message_printf(
|
||||
|
||||
void send_info_board(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l);
|
||||
|
||||
void send_card_search_result(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
|
||||
std::shared_ptr<Client> result, std::shared_ptr<Lobby> result_lobby);
|
||||
void send_card_search_result(
|
||||
std::shared_ptr<ServerState> s,
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<Client> result,
|
||||
std::shared_ptr<Lobby> result_lobby);
|
||||
|
||||
void send_guild_card(std::shared_ptr<Client> c, std::shared_ptr<Client> source);
|
||||
void send_menu(std::shared_ptr<Client> c, const char16_t* menu_name,
|
||||
@@ -137,25 +128,6 @@ void send_quest_menu(std::shared_ptr<Client> c, uint32_t menu_id,
|
||||
const std::vector<MenuItem>& items, bool is_download_menu);
|
||||
void send_lobby_list(std::shared_ptr<Client> c, std::shared_ptr<ServerState> s);
|
||||
|
||||
struct JoinGameCommand_GC_64 {
|
||||
uint32_t variations[0x20];
|
||||
PlayerLobbyDataGC lobby_data[4];
|
||||
uint8_t client_id;
|
||||
uint8_t leader_id;
|
||||
uint8_t disable_udp; // guess; putting 0 here causes no movement messages to be sent
|
||||
uint8_t difficulty;
|
||||
uint8_t battle_mode;
|
||||
uint8_t event;
|
||||
uint8_t section_id;
|
||||
uint8_t challenge_mode;
|
||||
uint32_t rare_seed;
|
||||
uint32_t episode; // for PSOPC, this must be 0x00000100
|
||||
struct {
|
||||
PlayerInventory inventory;
|
||||
PlayerDispDataPCGC disp;
|
||||
} __attribute__((packed)) player[4]; // only used on ep3
|
||||
} __attribute__((packed));
|
||||
|
||||
void send_join_lobby(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l);
|
||||
void send_player_join_notification(std::shared_ptr<Client> c,
|
||||
std::shared_ptr<Lobby> l, std::shared_ptr<Client> joining_client);
|
||||
|
||||
+1
-1
@@ -211,7 +211,7 @@ void Server::receive_and_process_commands(shared_ptr<Client> c) {
|
||||
for_each_received_command(c->bev, c->version, c->crypt_in.get(),
|
||||
[this, c](uint16_t command, uint16_t flag, const std::string& data) {
|
||||
try {
|
||||
process_command(this->state, c, command, flag, data.size(), data.data());
|
||||
process_command(this->state, c, command, flag, data);
|
||||
} catch (const exception& e) {
|
||||
log(INFO, "[Server] Error in client stream: %s", e.what());
|
||||
c->should_disconnect = true;
|
||||
|
||||
+30
-11
@@ -22,37 +22,56 @@ ServerState::ServerState()
|
||||
ep3_menu_song(-1) {
|
||||
memset(&this->default_key_file, 0, sizeof(this->default_key_file));
|
||||
|
||||
vector<shared_ptr<Lobby>> ep3_only_lobbies;
|
||||
|
||||
for (size_t x = 0; x < 20; x++) {
|
||||
auto lobby_name = decode_sjis(string_printf("LOBBY%zu", x + 1));
|
||||
bool is_ep3_only = (x > 14);
|
||||
|
||||
shared_ptr<Lobby> l(new Lobby());
|
||||
l->flags |= LobbyFlag::PUBLIC | LobbyFlag::DEFAULT | LobbyFlag::PERSISTENT |
|
||||
((x > 14) ? LobbyFlag::EPISODE_3 : 0);
|
||||
(is_ep3_only ? LobbyFlag::EPISODE_3 : 0);
|
||||
l->block = x + 1;
|
||||
l->type = x;
|
||||
char16cpy(l->name, lobby_name.c_str(), 0x24);
|
||||
char16ncpy(l->name, lobby_name.c_str(), 0x24);
|
||||
l->max_clients = 12;
|
||||
this->add_lobby(l);
|
||||
|
||||
if (!is_ep3_only) {
|
||||
this->public_lobby_search_order.emplace_back(l);
|
||||
} else {
|
||||
ep3_only_lobbies.emplace_back(l);
|
||||
}
|
||||
}
|
||||
|
||||
this->public_lobby_search_order_ep3 = this->public_lobby_search_order;
|
||||
this->public_lobby_search_order_ep3.insert(
|
||||
this->public_lobby_search_order_ep3.begin(),
|
||||
ep3_only_lobbies.begin(),
|
||||
ep3_only_lobbies.end());
|
||||
}
|
||||
|
||||
void ServerState::add_client_to_available_lobby(shared_ptr<Client> c) {
|
||||
auto it = this->id_to_lobby.lower_bound(0);
|
||||
for (; it != this->id_to_lobby.end(); it++) {
|
||||
if (!(it->second->flags & LobbyFlag::PUBLIC)) {
|
||||
continue;
|
||||
}
|
||||
const auto& search_order = (c->flags & ClientFlag::EPISODE_3_GAMES)
|
||||
? this->public_lobby_search_order_ep3
|
||||
: this->public_lobby_search_order;
|
||||
|
||||
shared_ptr<Lobby> added_to_lobby;
|
||||
for (const auto& l : search_order) {
|
||||
try {
|
||||
it->second->add_client(c);
|
||||
l->add_client(c);
|
||||
added_to_lobby = l;
|
||||
break;
|
||||
} catch (const out_of_range&) { }
|
||||
}
|
||||
|
||||
if (it == this->id_to_lobby.end()) {
|
||||
if (!added_to_lobby) {
|
||||
// TODO: Add the user to a dynamically-created private lobby instead
|
||||
throw out_of_range("all lobbies full");
|
||||
}
|
||||
|
||||
// send a join message to the joining player, and notifications to all others
|
||||
this->send_lobby_join_notifications(it->second, c);
|
||||
// Send a join message to the joining player, and notifications to all others
|
||||
this->send_lobby_join_notifications(added_to_lobby, c);
|
||||
}
|
||||
|
||||
void ServerState::remove_client_from_lobby(shared_ptr<Client> c) {
|
||||
|
||||
@@ -59,6 +59,8 @@ struct ServerState {
|
||||
std::u16string welcome_message;
|
||||
|
||||
std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby;
|
||||
std::vector<std::shared_ptr<Lobby>> public_lobby_search_order;
|
||||
std::vector<std::shared_ptr<Lobby>> public_lobby_search_order_ep3;
|
||||
std::atomic<int32_t> next_lobby_id;
|
||||
uint8_t pre_lobby_event;
|
||||
int32_t ep3_menu_song;
|
||||
|
||||
+24
-13
@@ -13,7 +13,7 @@ using namespace std;
|
||||
|
||||
|
||||
|
||||
int char16cmp(const char16_t* s1, const char16_t* s2, size_t count) {
|
||||
int char16ncmp(const char16_t* s1, const char16_t* s2, size_t count) {
|
||||
size_t x;
|
||||
for (x = 0; x < count && s1[x] != 0 && s2[x] != 0; x++) {
|
||||
if (s1[x] < s2[x]) {
|
||||
@@ -30,7 +30,7 @@ int char16cmp(const char16_t* s1, const char16_t* s2, size_t count) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void char16cpy(char16_t* dest, const char16_t* src, size_t count) {
|
||||
void char16ncpy(char16_t* dest, const char16_t* src, size_t count) {
|
||||
size_t x;
|
||||
for (x = 0; x < count && src[x] != 0; x++) {
|
||||
dest[x] = src[x];
|
||||
@@ -85,6 +85,9 @@ static const vector<char16_t>& unicode_to_sjis_table() {
|
||||
return unicode_to_sjis_table_data;
|
||||
}
|
||||
|
||||
// TODO: It looks like these functions are probably wrong. Specifically, we
|
||||
// don't write the high byte when encoding non-ASCII chars, do we?
|
||||
|
||||
void encode_sjis(char* dest, const char16_t* source, size_t max) {
|
||||
const auto& table = unicode_to_sjis_table();
|
||||
while (*source && (--max)) {
|
||||
@@ -108,25 +111,30 @@ void decode_sjis(char16_t* dest, const char* source, size_t max) {
|
||||
*dest = 0;
|
||||
}
|
||||
|
||||
std::string encode_sjis(const char16_t* source) {
|
||||
std::string encode_sjis(const char16_t* src, size_t src_count) {
|
||||
const auto& table = unicode_to_sjis_table();
|
||||
string ret;
|
||||
while (*source) {
|
||||
ret.push_back(table[*(source++)]);
|
||||
for (; *src && (src_count > 0); src_count--) {
|
||||
ret.push_back(table[*(src++)]);
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::u16string decode_sjis(const char* source) {
|
||||
std::u16string decode_sjis(const char* src, size_t src_count) {
|
||||
const auto& table = sjis_to_unicode_table();
|
||||
u16string ret;
|
||||
while (*source) {
|
||||
char16_t src_char = *(source++);
|
||||
while (*src && (src_count > 0)) {
|
||||
char16_t src_char = *(src++);
|
||||
src_count--;
|
||||
if (src_char & 0x80) {
|
||||
src_char = (src_char << 8) | *(source++);
|
||||
if (src_count == 0) {
|
||||
return ret;
|
||||
}
|
||||
src_char = (src_char << 8) | *(src++);
|
||||
if ((src_char & 0xFF) == 0) {
|
||||
return ret;
|
||||
}
|
||||
src_count--;
|
||||
}
|
||||
ret.push_back(table[src_char]);
|
||||
};
|
||||
@@ -145,10 +153,13 @@ std::string encode_sjis(const std::u16string& source) {
|
||||
std::u16string decode_sjis(const std::string& source) {
|
||||
const auto& table = sjis_to_unicode_table();
|
||||
u16string ret;
|
||||
for (size_t x = 0; x < source.size(); x++) {
|
||||
char16_t src_char = source[x];
|
||||
for (size_t x = 0; x < source.size();) {
|
||||
char16_t src_char = source[x++];
|
||||
if (src_char & 0x80) {
|
||||
src_char = (src_char << 8) | source[++x];
|
||||
if (x == source.size()) {
|
||||
return ret;
|
||||
}
|
||||
src_char = (src_char << 8) | source[x++];
|
||||
if ((src_char & 0xFF) == 0) {
|
||||
return ret;
|
||||
}
|
||||
@@ -198,7 +209,7 @@ void remove_language_marker_inplace(char* a) {
|
||||
|
||||
void remove_language_marker_inplace(char16_t* a) {
|
||||
if ((a[0] == '\t') && (a[1] != 'C')) {
|
||||
char16cpy(a, &a[2], char16len(a) - 2);
|
||||
char16ncpy(a, &a[2], char16len(a) - 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+72
-5
@@ -4,21 +4,56 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#include <string>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
|
||||
int char16cmp(const char16_t* s1, const char16_t* s2, size_t count);
|
||||
void char16cpy(char16_t* dest, const char16_t* src, size_t count);
|
||||
|
||||
#define countof(F) (sizeof(F) / sizeof(F[0]))
|
||||
|
||||
|
||||
|
||||
int char16ncmp(const char16_t* s1, const char16_t* s2, size_t count);
|
||||
void char16ncpy(char16_t* dest, const char16_t* src, size_t count);
|
||||
size_t char16len(const char16_t* s);
|
||||
|
||||
|
||||
void encode_sjis(char* dest, const char16_t* source, size_t dest_count);
|
||||
void decode_sjis(char16_t* dest, const char* source, size_t dest_count);
|
||||
std::string encode_sjis(const char16_t* source);
|
||||
std::u16string decode_sjis(const char* source);
|
||||
std::string encode_sjis(const char16_t* source, size_t src_count);
|
||||
std::u16string decode_sjis(const char* source, size_t src_count);
|
||||
std::string encode_sjis(const std::u16string& source);
|
||||
std::u16string decode_sjis(const std::string& source);
|
||||
|
||||
|
||||
|
||||
template <typename DestT, typename SrcT = DestT>
|
||||
void strncpy_t(DestT*, const SrcT*, size_t) {
|
||||
static_assert(always_false<DestT, SrcT>::v,
|
||||
"unspecialized strcpy_t should never be called");
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void strncpy_t<char>(char* dest, const char* src, size_t count) {
|
||||
strncpy(dest, src, count);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void strncpy_t<char, char16_t>(char* dest, const char16_t* src, size_t count) {
|
||||
encode_sjis(dest, src, count);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void strncpy_t<char16_t, char>(char16_t* dest, const char* src, size_t count) {
|
||||
decode_sjis(dest, src, count);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void strncpy_t<char16_t>(char16_t* dest, const char16_t* src, size_t count) {
|
||||
char16ncpy(dest, src, count);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void add_language_marker_inplace(char* s, char marker, size_t dest_count);
|
||||
void add_language_marker_inplace(char16_t* s, char16_t marker, size_t dest_count);
|
||||
void remove_language_marker_inplace(char* s);
|
||||
@@ -29,6 +64,7 @@ std::string remove_language_marker(const std::string& s);
|
||||
std::u16string remove_language_marker(const std::u16string& s);
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
void replace_char_inplace(T* a, T f, T r) {
|
||||
while (*a) {
|
||||
@@ -58,6 +94,8 @@ size_t add_color_inplace(T* a, size_t max_chars) {
|
||||
*(d++) = '%';
|
||||
} else if (*a == 'n') {
|
||||
*(d++) = '#';
|
||||
} else if (*a == '\0') {
|
||||
break;
|
||||
} else {
|
||||
*(d++) = *a;
|
||||
}
|
||||
@@ -70,3 +108,32 @@ size_t add_color_inplace(T* a, size_t max_chars) {
|
||||
|
||||
return d - orig_d;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void add_color(StringWriter& w, const T* src, size_t max_input_chars = SIZE_T_MAX) {
|
||||
for (size_t x = 0; (x < max_input_chars) && *src; x++) {
|
||||
if (*src == '$') {
|
||||
w.put<T>('\t');
|
||||
} else if (*src == '#') {
|
||||
w.put<T>('\n');
|
||||
} else if (*src == '%') {
|
||||
src++;
|
||||
x++;
|
||||
if (*src == 's') {
|
||||
w.put<T>('$');
|
||||
} else if (*src == '%') {
|
||||
w.put<T>('%');
|
||||
} else if (*src == 'n') {
|
||||
w.put<T>('#');
|
||||
} else if (*src == '\0') {
|
||||
break;
|
||||
} else {
|
||||
w.put<T>(*src);
|
||||
}
|
||||
} else {
|
||||
w.put<T>(*src);
|
||||
}
|
||||
src++;
|
||||
}
|
||||
w.put<T>(0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user