bring subcommand abstraction in line with main command abstraction

This commit is contained in:
Martin Michelsen
2022-04-03 11:00:14 -07:00
parent 9c33c2de46
commit fe9eceed5c
13 changed files with 882 additions and 489 deletions
+1 -1
View File
@@ -120,7 +120,7 @@ struct Client {
bool infinite_hp; // cheats enabled
bool infinite_tp; // cheats enabled
bool switch_assist; // cheats enabled
PSOSubcommand last_switch_enabled_subcommand[3];
std::string last_switch_enabled_subcommand;
bool can_chat;
std::string pending_bb_save_username;
uint8_t pending_bb_save_player_index;
+501 -88
View File
@@ -36,45 +36,6 @@
// This function is used in a lot of places to check received command sizes and
// cast them to the appropriate type
template <typename T>
const T& check_size_t(
const std::string& data,
size_t min_size = sizeof(T),
size_t max_size = sizeof(T)) {
if (data.size() < min_size) {
throw std::runtime_error(string_printf(
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
min_size, data.size()));
}
if (data.size() > max_size) {
throw std::runtime_error(string_printf(
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
max_size, data.size()));
}
return *reinterpret_cast<const T*>(data.data());
}
template <typename T>
T& check_size_t(
std::string& data,
size_t min_size = sizeof(T),
size_t max_size = sizeof(T)) {
if (data.size() < min_size) {
throw std::runtime_error(string_printf(
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
min_size, data.size()));
}
if (data.size() > max_size) {
throw std::runtime_error(string_printf(
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
max_size, data.size()));
}
return *reinterpret_cast<T*>(data.data());
}
// 00: Invalid command
// 01 (S->C): Lobby message box
@@ -357,7 +318,7 @@ struct C_GuildCardSearch_40 {
le_uint32_t player_tag;
le_uint32_t searcher_guild_card_number;
le_uint32_t target_guild_card_number;
} __attribute__((packed));
};
template <typename HeaderT, typename CharT>
struct S_GuildCardSearchResult {
@@ -1185,7 +1146,7 @@ struct S_StreamFileIndexEntry_BB_01EB {
le_uint32_t checksum;
le_uint32_t offset;
ptext<char, 0x40> filename;
} __attribute__((packed));
};
struct S_StreamFileChunk_BB_02EB {
le_uint32_t chunk_index;
@@ -1209,7 +1170,7 @@ union C_UpdateAccountData_BB_ED {
parray<uint8_t, 0x38> pad_config; // 05ED
parray<uint8_t, 0x28> tech_menu; // 06ED
parray<uint8_t, 0xE8> customize; // 07ED
} __attribute__((packed));
};
// EE: Scrolling message (BB)
// Contents are plain text (char16_t)
@@ -1234,25 +1195,62 @@ union C_UpdateAccountData_BB_ED {
// Now, the game subcommands (used in commands 60, 62, 6C, 6D, C9, and CB).
// These are laid out similarly as above. These structs start with G_ to
// indicate that they are (usually) bidirectional, and are (usually) generated
// by clients and consumed by clients.
// This structure is used by many commands (noted below)
struct G_ItemSubcommand {
uint8_t command;
uint8_t size;
uint8_t client_id;
uint8_t unused;
le_uint32_t item_id;
le_uint32_t amount;
};
struct S_SendGuildCard_GC {
// 00: Invalid subcommand
// 01: Unknown
// 02: Unknown
// 03: Unknown
// 04: Unknown
// 05: Switch state changed
// TODO: make last_switch_enabled_subcommand in both Client and Proxy use this
// when it's available
// struct G_SwitchStateChanged_6x05 {
// uint8_t subcommand;
// uint8_t size;
// TODO;
// };
// 06: Send guild card
template <typename CharT>
struct G_SendGuildCard_PC_GC {
uint8_t subcommand;
uint8_t subsize;
uint8_t size;
le_uint16_t unused;
le_uint32_t player_tag;
le_uint32_t guild_card_number;
ptext<char, 0x18> name;
ptext<char, 0x6C> desc;
ptext<CharT, 0x18> name;
ptext<CharT, 0x48> desc;
parray<uint8_t, 0x24> unused2;
uint8_t reserved1;
uint8_t reserved2;
uint8_t section_id;
uint8_t char_class;
};
struct G_SendGuildCard_PC_6x06 : G_SendGuildCard_PC_GC<char16_t> { };
struct G_SendGuildCard_GC_6x06 : G_SendGuildCard_PC_GC<char> { };
struct S_SendGuildCard_BB {
struct G_SendGuildCard_BB_6x06 {
uint8_t subcommand;
uint8_t subsize;
uint8_t size;
le_uint16_t unused;
le_uint32_t guild_card_number;
ptext<char16_t, 0x18> name;
@@ -1264,32 +1262,150 @@ struct S_SendGuildCard_BB {
uint8_t char_class;
};
struct S_DropItem_BB {
uint8_t subcommand;
uint8_t subsize;
le_uint16_t unused;
uint8_t area;
uint8_t dude;
le_uint16_t request_id;
float x;
float y;
le_uint32_t unused2;
ItemData data;
// 07: Symbol chat
// 08: Unknown
// 09: Unknown
// 0A: Enemy hit
struct G_EnemyHitByPlayer_6x0A {
uint8_t command;
uint8_t size;
le_uint16_t enemy_id2;
le_uint16_t enemy_id;
le_uint16_t damage;
le_uint32_t flags;
};
struct S_DropStackedItem_BB {
// 0B: Unknown (supported; game only)
// 0C: Add condition (poison/slow/etc.)
// 0D: Remove condition (poison/slow/etc.)
// 0E: Unknown
// 0F: Unknown
// 10: Unknown
// 11: Unknown
// 12: Unknown (supported; game only)
// 13: Unknown (supported; game only)
// 14: Unknown (supported; game only)
// 15: Unknown (supported; game only)
// 16: Unknown
// 17: Unknown (supported; game only)
// 18: Unknown (supported; game only)
// 19: Unknown (supported; game only)
// 1A: Unknown
// 1B: Unknown
// 1C: Unknown
// 1D: Unknown
// 1E: Unknown
// 1F: Unknown (supported; lobby & game)
// 20: Unknown (supported; lobby & game)
// 21: Inter-level warp
// 22: Set player visibility
// 23: Set player visibility
// 24: Unknown (supported; game only)
// 25: Equip item
// Format is G_ItemSubcommand
// 26: Unequip item
// Format is G_ItemSubcommand
// 27: Use item
// Format is G_ItemSubcommand
// 28: Feed MAG
// 29: Delete item (via bank deposit / sale / feeding MAG)
struct G_DestroyItem_6x29 {
uint8_t subcommand;
uint8_t subsize;
le_uint16_t unused;
le_uint16_t client_id;
le_uint32_t item_id;
le_uint32_t amount;
};
// 2A: Drop item
struct G_PlayerDropItem_6x2A {
uint8_t command;
uint8_t size;
uint8_t client_id;
uint8_t unused;
le_uint16_t unused2; // should be 1
le_uint16_t area;
le_uint16_t unused2;
float x;
float y;
le_uint32_t unused3;
ItemData data;
le_uint32_t item_id;
le_float x;
le_float y;
le_float z;
};
struct S_PickUpItem_BB {
// 2B: Unknown (supported; game only)
// 2C: Talk to NPC
// 2D: Done talking to NPC
// 2E: Unknown
// 2F: Hit by enemy
// 30: Level up
struct G_LevelUp_6x30 {
uint8_t command;
uint8_t size;
uint8_t client_id;
uint8_t unused;
le_uint16_t atp;
le_uint16_t mst;
le_uint16_t evp;
le_uint16_t hp;
le_uint16_t dfp;
le_uint16_t ata;
le_uint32_t level;
};
// 31: Medical center
// 32: Medical center
// 33: Revive player (e.g. with moon atomizer)
// 34: Unknown
// 35: Unknown
// 36: Unknown (supported; game only)
// 37: Photon blast
// 38: Unknown
// 39: Photon blast ready
// 3A: Unknown (supported; game only)
// 3B: Unknown (supported; lobby & game)
// 3C: Unknown
// 3D: Unknown
// 3E: Stop moving
// 3F: Unknown (supported; lobby & game)
// 40: Walk
// 41: Unknown
// 42: Run
// 43: Unknown (supported; lobby & game)
// 44: Unknown (supported; lobby & game)
// 45: Unknown (supported; lobby & game)
// 46: Unknown (supported; lobby & game)
// 47: Unknown (supported; lobby & game)
// 48: Use technique
// 49: Unknown (supported; lobby & game)
// 4A: Unknown (supported; lobby & game)
// 4B: Hit by enemy
// 4C: Hit by enemy
// 4D: Unknown (supported; lobby & game)
// 4E: Unknown (supported; lobby & game)
// 4F: Unknown (supported; lobby & game)
// 50: Unknown (supported; lobby & game)
// 51: Unknown
// 52: Toggle shop/bank interaction
// 53: Unknown (supported; game only)
// 54: Unknown
// 55: Intra-map warp
// 56: Unknown (supported; lobby & game)
// 57: Unknown (supported; lobby & game)
// 58: Unknown (supported; game only)
// 59: Pick up item
struct G_PickUpItem_6x59 {
uint8_t subcommand;
uint8_t subsize;
le_uint16_t client_id;
@@ -1298,35 +1414,171 @@ struct S_PickUpItem_BB {
le_uint32_t item_id;
};
struct S_CreateInventoryItem_BB {
uint8_t subcommand;
uint8_t subsize;
le_uint16_t client_id;
ItemData item;
le_uint32_t unused;
};
// 5A: Request to pick up item
struct S_DestroyItem_BB {
uint8_t subcommand;
uint8_t subsize;
le_uint16_t client_id;
struct G_PickUpItemRequest_6x5A {
uint8_t command;
uint8_t size;
uint8_t client_id;
uint8_t unused;
le_uint32_t item_id;
le_uint32_t amount;
uint8_t area;
uint8_t unused2[3];
};
struct S_BankContentsHeader_BB {
// 5B: Unknown
// 5C: Unknown
// 5D: Drop meseta or stacked item
struct G_DropStackedItem_6x5D {
uint8_t subcommand;
uint8_t unused1;
uint8_t subsize;
le_uint16_t unused;
le_uint16_t area;
le_uint16_t unused2;
le_uint32_t size; // same as size in overall command header
le_uint32_t checksum; // can be random; client won't notice
le_uint32_t numItems;
le_uint32_t meseta;
le_float x;
le_float y;
le_uint32_t unused3;
ItemData data;
};
struct S_ShopContents_BB {
uint8_t subcommand; // B6
uint8_t size; // 2C regardless of the number of items??
// 5E: Buy item at shop
// 5F: Drop item from box/enemy
struct G_DropItem_6x5F {
uint8_t subcommand;
uint8_t subsize;
le_uint16_t unused;
uint8_t area;
uint8_t enemy_instance;
le_uint16_t request_id;
le_float x;
le_float y;
le_uint32_t unused2;
ItemData data;
};
// 60: Request for item drop (handled by the server on BB)
struct G_EnemyDropItemRequest_6x60 {
uint8_t command;
uint8_t size;
le_uint16_t unused;
uint8_t area;
uint8_t enemy_id;
le_uint16_t request_id;
le_float x;
le_float y;
le_uint32_t unknown[2];
};
// 61: Feed MAG
// 62: Unknown
// 63: Destroy an item on the ground (used when too many items have been dropped)
// 64: Unknown
// 65: Unknown
// 66: Unknown
// 67: Create enemy set
// 68: Telepipe/Ryuker
// 69: Unknown
// 6A: Unknown (supported; game only)
// 6B: Unknown (used while loading into game)
// 6C: Unknown (used while loading into game)
// 6D: Unknown (used while loading into game)
// 6E: Unknown (used while loading into game)
// 6F: Unknown (used while loading into game)
// 70: Unknown (used while loading into game)
// 71: Unknown (used while loading into game)
// 72: Unknown (used while loading into game)
// 73: Invalid subcommand
// 74: Word select
// 75: Unknown (supported; game only)
// 76: Enemy killed
// 77: Sync quest data
// 78: Unknown
// 79: Lobby 14/15 soccer game
// 7A: Unknown
// 7B: Unknown
// 7C: Unknown (supported; game only)
// 7D: Unknown (supported; game only)
// 7E: Unknown
// 7F: Unknown
// 80: Trigger trap
// 81: Unknown
// 82: Unknown
// 83: Place trap
// 84: Unknown (supported; game only)
// 85: Unknown (supported; game only)
// 86: Hit destructible wall
// 87: Unknown
// 88: Unknown (supported; game only)
// 89: Unknown (supported; game only)
// 8A: Unknown
// 8B: Unknown
// 8C: Unknown
// 8D: Unknown (supported; lobby & game)
// 8E: Unknown
// 8F: Unknown
// 90: Unknown
// 91: Unknown (supported; game only)
// 92: Unknown
// 93: Timed switch activated
// 94: Warp (the $warp chat command is implemented using this)
// 95: Unknown
// 96: Unknown
// 97: Unknown
// 98: Unknown
// 99: Unknown
// 9A: Update player stat ($infhp/$inftp are implemented using this command)
// 9B: Unknown
// 9C: Unknown (supported; game only)
// 9D: Unknown
// 9E: Unknown
// 9F: Episode 2 boss actions
// A0: Episode 2 boss actions
// A1: Unknown
// A2: Request for item drop from box (handled by server on BB)
struct G_BoxItemDropRequest_6xA2 {
uint8_t command;
uint8_t size;
le_uint16_t unused;
uint8_t area;
uint8_t unused2;
le_uint16_t request_id;
le_float x;
le_float y;
le_uint32_t unknown[6];
};
// A3: Episode 2 boss actions
// A4: Unknown
// A5: Episode 2 boss actions
// A6: Trade proposal
// A7: Unknown
// A8: Episode 2 boss actions
// A9: Episode 2 boss actions
// AA: Episode 2 boss actions
// AB: Create lobby chair
// AC: Unknown
// AD: Unknown
// AE: Unknown (supported; lobby & game)
// AF: Turn in lobby chair
// B0: Move in lobby chair
// B1: Unknown
// B2: Unknown
// B3: Unknown
// B4: Unknown
// B5: BB shop request
// B6: BB shop contents (server->client only)
struct G_ShopContents_BB_6xB6 {
uint8_t subcommand;
uint8_t size; // always 2C regardless of the number of items??
le_uint16_t params; // 037F
uint8_t shop_type;
uint8_t num_items;
@@ -1334,6 +1586,167 @@ struct S_ShopContents_BB {
ItemData entries[20];
};
// B7: BB buy shop item
// B8: Accept tekker result
// Format is G_IdentifyResult
// B9: Provisional tekker result
struct G_IdentifyResult_BB_6xB9 {
uint8_t subcommand;
uint8_t size;
uint8_t client_id;
uint8_t unused;
ItemData item;
};
// BA: Unknown
// BB: BB bank request
// BC: BB bank contents (server->client only)
struct G_BankContentsHeader_BB_6xBC {
uint8_t subcommand;
uint8_t unused1;
le_uint16_t unused2;
le_uint32_t size; // same as size in overall command header
le_uint32_t checksum; // can be random; client won't notice
le_uint32_t numItems;
le_uint32_t meseta;
// Item data follows
};
// BD: BB bank action (take/deposit meseta/item)
struct G_BankAction_BB_6xBD {
uint8_t subcommand;
uint8_t size;
le_uint16_t unused;
le_uint32_t item_id; // 0xFFFFFFFF = meseta; anything else = item
le_uint32_t meseta_amount;
uint8_t action; // 0 = deposit, 1 = take
uint8_t item_amount;
le_uint16_t unused2;
};
// BE: BB create inventory item (server->client only)
struct G_CreateInventoryItem_BB_6xBE {
uint8_t subcommand;
uint8_t subsize;
le_uint16_t client_id;
ItemData item;
le_uint32_t unused;
};
// BF: Ep3 change music; also BB give EXP (BB usage is server->client only)
struct G_GiveExperience_BB_6xBF {
uint8_t subcommand;
uint8_t size;
uint8_t client_id;
uint8_t unused;
le_uint32_t amount;
};
// C0: Unknown
// C1: Unknown
// C2: Unknown
// C3: Split stacked item - not sent if entire stack is dropped
struct G_SplitStackedItem_6xC3 {
uint8_t command;
uint8_t size;
uint8_t client_id;
uint8_t unused;
le_uint16_t area;
le_uint16_t unused2;
le_float x;
le_float y;
le_uint32_t item_id;
le_uint32_t amount;
};
// C4: Sort inventory
struct G_SortInventory_6xC4 {
uint8_t command;
uint8_t size;
le_uint16_t unused;
le_uint32_t item_ids[30];
};
// C5: Unknown
// C6: Unknown
// C7: Unknown
// C8: Enemy killed
struct G_EnemyKilled_6xC8 {
uint8_t command;
uint8_t size;
le_uint16_t enemy_id2;
le_uint16_t enemy_id;
le_uint16_t killer_client_id;
le_uint32_t unused;
};
// C9: Unknown
// CA: Unknown
// CB: Unknown
// CC: Unknown
// CD: Unknown
// CE: Unknown
// CF: Unknown (supported; game only)
// D0: Unknown
// D1: Unknown
// D2: Unknown
// D3: Unknown
// D4: Unknown
// D5: Unknown
// D6: Unknown
// D7: Unknown
// D8: Unknown
// D9: Unknown
// DA: Unknown
// DB: Unknown
// DC: Unknown
// DD: Unknown
// DE: Unknown
// DF: Unknown
// E0: Unknown
// E1: Unknown
// E2: Unknown
// E3: Unknown
// E4: Unknown
// E5: Unknown
// E6: Unknown
// E7: Unknown
// E8: Unknown
// E9: Unknown
// EA: Unknown
// EB: Unknown
// EC: Unknown
// ED: Unknown
// EE: Unknown
// EF: Unknown
// F0: Unknown
// F1: Unknown
// F2: Unknown
// F3: Unknown
// F4: Unknown
// F5: Unknown
// F6: Unknown
// F7: Unknown
// F8: Unknown
// F9: Unknown
// FA: Unknown
// FB: Unknown
// FC: Unknown
// FD: Unknown
// FE: Unknown
// FF: Unknown
#pragma pack(pop)
+1 -1
View File
@@ -67,7 +67,7 @@ struct Lobby {
size_t count_clients() const;
bool any_client_loading() const;
void add_client(std::shared_ptr<Client> c, bool reverse_indexes = false);
void add_client(std::shared_ptr<Client> c, bool reverse_indexes = true);
void remove_client(std::shared_ptr<Client> c);
void move_client_to_lobby(std::shared_ptr<Lobby> dest_lobby,
+8 -4
View File
@@ -97,22 +97,26 @@ void populate_state_from_config(shared_ptr<ServerState> s,
s->common_item_creator.reset(new CommonItemCreator(enemy_categories,
box_categories, unit_types));
shared_ptr<vector<MenuItem>> information_menu(new vector<MenuItem>());
shared_ptr<vector<MenuItem>> information_menu_pc(new vector<MenuItem>());
shared_ptr<vector<MenuItem>> information_menu_gc(new vector<MenuItem>());
shared_ptr<vector<u16string>> information_contents(new vector<u16string>());
information_menu->emplace_back(INFORMATION_MENU_GO_BACK, u"Go back",
information_menu_gc->emplace_back(INFORMATION_MENU_GO_BACK, u"Go back",
u"Return to the\nmain menu", 0);
{
uint32_t item_id = 0;
for (const auto& item : d.at("InformationMenuContents")->as_list()) {
auto& v = item->as_list();
information_menu->emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
information_menu_pc->emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
decode_sjis(v.at(1)->as_string()), 0);
information_menu_gc->emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
decode_sjis(v.at(1)->as_string()), MenuItem::Flag::REQUIRES_MESSAGE_BOXES);
information_contents->emplace_back(decode_sjis(v.at(2)->as_string()));
item_id++;
}
}
s->information_menu = information_menu;
s->information_menu_pc = information_menu_pc;
s->information_menu_gc = information_menu_gc;
s->information_contents = information_contents;
s->proxy_destinations_menu_pc.emplace_back(PROXY_DESTINATIONS_MENU_GO_BACK,
+16
View File
@@ -207,3 +207,19 @@ void print_received_command(
print_data(stderr, w.str());
}
void check_size_v(size_t size, size_t min_size, size_t max_size) {
if (size < min_size) {
throw std::runtime_error(string_printf(
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
min_size, size));
}
if (max_size < min_size) {
max_size = min_size;
}
if (size > max_size) {
throw std::runtime_error(string_printf(
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
max_size, size));
}
}
+40
View File
@@ -4,6 +4,7 @@
#include <event2/bufferevent.h>
#include <functional>
#include <phosg/Strings.hh>
#include "Version.hh"
#include "PSOEncryption.hh"
@@ -64,3 +65,42 @@ void print_received_command(
size_t size,
GameVersion version,
const char* name = nullptr);
// This function is used in a lot of places to check received command sizes and
// cast them to the appropriate type
template <typename T>
const T& check_size_t(
const std::string& data,
size_t min_size = sizeof(T),
size_t max_size = sizeof(T)) {
if (data.size() < min_size) {
throw std::runtime_error(string_printf(
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
min_size, data.size()));
}
if (data.size() > max_size) {
throw std::runtime_error(string_printf(
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
max_size, data.size()));
}
return *reinterpret_cast<const T*>(data.data());
}
template <typename T>
T& check_size_t(
std::string& data,
size_t min_size = sizeof(T),
size_t max_size = sizeof(T)) {
if (data.size() < min_size) {
throw std::runtime_error(string_printf(
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
min_size, data.size()));
}
if (data.size() > max_size) {
throw std::runtime_error(string_printf(
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
max_size, data.size()));
}
return *reinterpret_cast<T*>(data.data());
}
void check_size_v(size_t size, size_t min_size, size_t max_size = 0);
+53 -40
View File
@@ -417,7 +417,7 @@ void process_client_checksum(shared_ptr<ServerState>, shared_ptr<Client> c,
void process_server_time_request(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // B1
check_size(data.size(), 0);
check_size_v(data.size(), 0);
send_server_time(c);
}
@@ -443,7 +443,7 @@ void process_ep3_jukebox(shared_ptr<ServerState> s, shared_ptr<Client> c,
void process_ep3_menu_challenge(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t, uint32_t flag, const string& data) { // DC
check_size(data.size(), 0);
check_size_v(data.size(), 0);
if (flag != 0) {
send_command(c, 0xDC);
}
@@ -451,7 +451,7 @@ void process_ep3_menu_challenge(shared_ptr<ServerState>, shared_ptr<Client> c,
void process_ep3_server_data_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // CA
check_size(data.size(), 8, 0xFFFF);
check_size_v(data.size(), 8, 0xFFFF);
const PSOSubcommand* cmds = reinterpret_cast<const PSOSubcommand*>(data.data());
auto l = s->find_lobby(c->lobby_id);
@@ -557,9 +557,10 @@ void process_ep3_tournament_control(shared_ptr<ServerState>, shared_ptr<Client>
void process_message_box_closed(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // D6
check_size(data.size(), 0);
check_size_v(data.size(), 0);
if (c->flags & Client::Flag::IN_INFORMATION_MENU) {
send_menu(c, u"Information", INFORMATION_MENU_ID, *s->information_menu, false);
send_menu(c, u"Information", INFORMATION_MENU_ID,
*s->information_menu_for_version(c->version), false);
} else if (c->flags & Client::Flag::AT_WELCOME_MESSAGE) {
send_menu(c, s->name.c_str(), MAIN_MENU_ID, s->main_menu, false);
c->flags &= ~Client::Flag::AT_WELCOME_MESSAGE;
@@ -601,7 +602,7 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
} else {
try {
// we use item_id + 1 here because "go back" is the first item
send_ship_info(c, s->information_menu->at(cmd.item_id + 1).description.c_str());
send_ship_info(c, s->information_menu_for_version(c->version)->at(cmd.item_id + 1).description.c_str());
} catch (const out_of_range&) {
send_ship_info(c, u"$C6No such information exists.");
}
@@ -664,7 +665,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
case MAIN_MENU_INFORMATION:
send_menu(c, u"Information", INFORMATION_MENU_ID,
*s->information_menu, false);
*s->information_menu_for_version(c->version), true);
c->flags |= Client::Flag::IN_INFORMATION_MENU;
break;
@@ -874,7 +875,14 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
send_quest_file(l->clients[x], bin_basename, *bin_contents, false, false);
send_quest_file(l->clients[x], dat_basename, *dat_contents, false, false);
l->clients[x]->flags |= Client::Flag::LOADING_QUEST;
// There is no such thing as command AC on PSO PC - quests just start
// immediately when they're done downloading. There are also no chunk
// acknowledgements (C->S 13 commands) like there are on GC. So, for
// PC clients, we can just not set the loading flag, since we never
// need to check/clear it later.
if (l->clients[x]->version != GameVersion::PC) {
l->clients[x]->flags |= Client::Flag::LOADING_QUEST;
}
}
} else {
@@ -918,10 +926,17 @@ void process_change_lobby(shared_ptr<ServerState> s, shared_ptr<Client> c,
void process_game_list_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 08
check_size(data.size(), 0);
check_size_v(data.size(), 0);
send_game_menu(c, s);
}
void process_information_menu_request_pc(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 1F
check_size_v(data.size(), 0);
send_menu(c, u"Information", INFORMATION_MENU_ID,
*s->information_menu_for_version(c->version), true);
}
void process_change_ship(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string&) { // A0
// The client actually sends data in this command... looks like nothing
@@ -950,7 +965,7 @@ void process_change_block(shared_ptr<ServerState> s, shared_ptr<Client> c,
void process_quest_list_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t flag, const string& data) { // A2
check_size(data.size(), 0);
check_size_v(data.size(), 0);
if (!s->quest_index) {
send_lobby_message_box(c, u"$C6Quests are not available.");
@@ -985,7 +1000,7 @@ void process_quest_list_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
void process_quest_ready(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // AC
check_size(data.size(), 0);
check_size_v(data.size(), 0);
auto l = s->find_lobby(c->lobby_id);
if (!l || !l->is_game()) {
@@ -1042,22 +1057,21 @@ void process_player_data(shared_ptr<ServerState> s, shared_ptr<Client> c,
// autoreply text is a variable length
switch (c->version) {
case GameVersion::PC:
check_size(data.size(), sizeof(PSOPlayerDataPC),
sizeof(PSOPlayerDataPC) + sizeof(char16_t) * c->player.auto_reply.size());
check_size_v(data.size(), sizeof(PSOPlayerDataPC), 0xFFFF);
c->player.import(*reinterpret_cast<const PSOPlayerDataPC*>(data.data()));
break;
case GameVersion::GC:
if (flag == 4) { // Episode 3
check_size(data.size(), sizeof(PSOPlayerDataGC) + 0x23FC);
check_size_v(data.size(), sizeof(PSOPlayerDataGC) + 0x23FC);
// TODO: import Episode 3 data somewhere
} else {
check_size(data.size(), sizeof(PSOPlayerDataGC),
check_size_v(data.size(), sizeof(PSOPlayerDataGC),
sizeof(PSOPlayerDataGC) + sizeof(char) * c->player.auto_reply.size());
}
c->player.import(*reinterpret_cast<const PSOPlayerDataGC*>(data.data()));
break;
case GameVersion::BB:
check_size(data.size(), sizeof(PSOPlayerDataBB),
check_size_v(data.size(), sizeof(PSOPlayerDataBB),
sizeof(PSOPlayerDataBB) + sizeof(char16_t) * c->player.auto_reply.size());
c->player.import(*reinterpret_cast<const PSOPlayerDataBB*>(data.data()));
break;
@@ -1108,16 +1122,14 @@ void process_player_data(shared_ptr<ServerState> s, shared_ptr<Client> c,
void process_game_command(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t command, uint32_t flag, const string& data) { // 60 62 6C 6D C9 CB (C9 CB are ep3 only)
check_size(data.size(), 4, 0xFFFF);
const PSOSubcommand* sub = reinterpret_cast<const PSOSubcommand*>(data.data());
check_size_v(data.size(), 4, 0xFFFF);
auto l = s->find_lobby(c->lobby_id);
if (!l) {
return;
}
process_subcommand(s, l, c, command, flag, sub,
data.size() / sizeof(PSOSubcommand));
process_subcommand(s, l, c, command, flag, data);
}
////////////////////////////////////////////////////////////////////////////////
@@ -1180,7 +1192,7 @@ void process_chat_dc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
void process_key_config_request_bb(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) {
check_size(data.size(), 0);
check_size_v(data.size(), 0);
send_team_and_key_config_bb(c);
}
@@ -1217,7 +1229,7 @@ void process_player_preview_request_bb(shared_ptr<ServerState>, shared_ptr<Clien
void process_client_checksum_bb(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t command, uint32_t, const string& data) {
check_size(data.size(), 0);
check_size_v(data.size(), 0);
if (command == 0x01E8) {
send_accept_client_checksum_bb(c);
@@ -1238,7 +1250,7 @@ void process_guild_card_data_request_bb(shared_ptr<ServerState>, shared_ptr<Clie
void process_stream_file_request_bb(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t command, uint32_t, const string& data) {
check_size(data.size(), 0);
check_size_v(data.size(), 0);
if (command == 0x04EB) {
send_stream_file_bb(c);
@@ -1307,31 +1319,31 @@ void process_change_account_data_bb(shared_ptr<ServerState>, shared_ptr<Client>
switch (command) {
case 0x01ED:
check_size(data.size(), sizeof(cmd->option));
check_size_v(data.size(), sizeof(cmd->option));
c->player.option_flags = cmd->option;
break;
case 0x02ED:
check_size(data.size(), sizeof(cmd->symbol_chats));
check_size_v(data.size(), sizeof(cmd->symbol_chats));
c->player.symbol_chats = cmd->symbol_chats;
break;
case 0x03ED:
check_size(data.size(), sizeof(cmd->chat_shortcuts));
check_size_v(data.size(), sizeof(cmd->chat_shortcuts));
c->player.shortcuts = cmd->chat_shortcuts;
break;
case 0x04ED:
check_size(data.size(), sizeof(cmd->key_config));
check_size_v(data.size(), sizeof(cmd->key_config));
c->player.key_config.key_config = cmd->key_config;
break;
case 0x05ED:
check_size(data.size(), sizeof(cmd->pad_config));
check_size_v(data.size(), sizeof(cmd->pad_config));
c->player.key_config.joystick_config = cmd->pad_config;
break;
case 0x06ED:
check_size(data.size(), sizeof(cmd->tech_menu));
check_size_v(data.size(), sizeof(cmd->tech_menu));
c->player.tech_menu_config = cmd->tech_menu;
break;
case 0x07ED:
check_size(data.size(), sizeof(cmd->customize));
check_size_v(data.size(), sizeof(cmd->customize));
c->player.disp.config = cmd->customize;
break;
default:
@@ -1354,7 +1366,7 @@ void process_return_player_data_bb(shared_ptr<ServerState>, shared_ptr<Client> c
void process_change_arrow_color(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t flag, const string& data) { // 89
check_size(data.size(), 0);
check_size_v(data.size(), 0);
c->lobby_arrow_color = flag;
auto l = s->find_lobby(c->lobby_id);
@@ -1417,14 +1429,14 @@ void process_simple_mail(shared_ptr<ServerState> s, shared_ptr<Client> c,
void process_info_board_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // D8
check_size(data.size(), 0);
check_size_v(data.size(), 0);
send_info_board(c, s->find_lobby(c->lobby_id));
}
template <typename CharT>
void process_write_info_board_t(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // D9
check_size(data.size(), 0, c->player.info_board.size() * sizeof(CharT));
check_size_v(data.size(), 0, c->player.info_board.size() * sizeof(CharT));
c->player.info_board.assign(
reinterpret_cast<const CharT*>(data.data()),
data.size() / sizeof(CharT));
@@ -1433,7 +1445,7 @@ void process_write_info_board_t(shared_ptr<ServerState>, shared_ptr<Client> c,
template <typename CharT>
void process_set_auto_reply_t(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // C7
check_size(data.size(), 0, c->player.auto_reply.size() * sizeof(CharT));
check_size_v(data.size(), 0, c->player.auto_reply.size() * sizeof(CharT));
c->player.auto_reply.assign(
reinterpret_cast<const CharT*>(data.data()),
data.size() / sizeof(CharT));
@@ -1441,7 +1453,7 @@ void process_set_auto_reply_t(shared_ptr<ServerState>, shared_ptr<Client> c,
void process_disable_auto_reply(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // C8
check_size(data.size(), 0);
check_size_v(data.size(), 0);
c->player.auto_reply.clear();
}
@@ -1500,7 +1512,8 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
}
uint8_t min_level = ((episode == 0xFF) ? 0 : default_minimum_levels[episode - 1][difficulty]);
if (min_level > c->player.disp.level) {
if (!(c->license->privileges & Privilege::FREE_JOIN_GAMES) &&
(min_level > c->player.disp.level)) {
throw invalid_argument("level too low for difficulty");
}
@@ -1658,7 +1671,7 @@ void process_create_game_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
void process_lobby_name_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 8A
check_size(data.size(), 0);
check_size_v(data.size(), 0);
auto l = s->find_lobby(c->lobby_id);
if (!l) {
throw invalid_argument("client not in any lobby");
@@ -1668,7 +1681,7 @@ void process_lobby_name_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
void process_client_ready(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // 6F
check_size(data.size(), 0);
check_size_v(data.size(), 0);
auto l = s->find_lobby(c->lobby_id);
if (!l || !l->is_game()) {
@@ -1700,7 +1713,7 @@ void process_team_command_bb(shared_ptr<ServerState>, shared_ptr<Client> c,
void process_encryption_ok_patch(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) {
check_size(data.size(), 0);
check_size_v(data.size(), 0);
send_command(c, 0x04); // this requests the user's login information
}
@@ -1879,7 +1892,7 @@ static process_command_t pc_handlers[0x100] = {
process_menu_selection, nullptr, nullptr, process_ignored_command,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, process_ignored_command, nullptr, nullptr,
nullptr, process_ignored_command, nullptr, process_information_menu_request_pc,
// 20
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+194 -312
View File
@@ -15,50 +15,51 @@
using namespace std;
// The functions in this file are called when a BB client sends a game command
// (60, 62, 6C, or 6D) that must be handled by the server.
struct ItemSubcommand {
uint8_t command;
uint8_t size;
uint8_t client_id;
uint8_t unused;
uint32_t item_id;
uint32_t amount;
} __attribute__((packed));
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, received 0x%hX bytes)",
min_size, size));
}
if (max_size == 0) {
max_size = min_size;
}
if (size > max_size) {
throw runtime_error(string_printf(
"command too large (expected at most 0x%hX bytes, received 0x%hX bytes)",
max_size, size));
}
}
// The functions in this file are called when a client sends a game command
// (60, 62, 6C, or 6D).
bool command_is_private(uint8_t command) {
// TODO: are either of the ep3 commands private? looks like not
// TODO: are either of the Ep3 commands (C9/CB) private? Looks like not...
return (command == 0x62) || (command == 0x6D);
}
void forward_subcommand(shared_ptr<Lobby> l, shared_ptr<Client> c,
uint8_t command, uint8_t flag, const PSOSubcommand* p,
size_t count) {
template <typename CmdT = PSOSubcommand>
const CmdT* check_size_sc(
const string& data,
size_t min_size = sizeof(CmdT),
size_t max_size = sizeof(CmdT),
bool check_size_field = true) {
if (max_size < min_size) {
max_size = min_size;
}
const auto* cmd = &check_size_t<CmdT>(data, min_size, max_size);
if (check_size_field && cmd->size) {
throw runtime_error("invalid subcommand size field");
}
return cmd;
}
template <>
const PSOSubcommand* check_size_sc<PSOSubcommand>(
const string& data, size_t min_size, size_t max_size, bool check_size_field) {
if (max_size < min_size) {
max_size = min_size;
}
const auto* ret = &check_size_t<PSOSubcommand>(data, min_size, max_size);
if (check_size_field && (ret[0].byte[1] != data.size() / 4)) {
throw runtime_error("invalid subcommand size field");
}
return ret;
}
static void forward_subcommand(shared_ptr<Lobby> l, shared_ptr<Client> c,
uint8_t command, uint8_t flag, const string& data) {
// if the command is an Ep3-only command, make sure an Ep3 client sent it
bool command_is_ep3 = (command & 0xF0) == 0xC0;
@@ -77,7 +78,7 @@ void forward_subcommand(shared_ptr<Lobby> l, shared_ptr<Client> c,
if (command_is_ep3 && !(target->flags & Client::Flag::EPISODE_3)) {
return;
}
send_command(target, command, flag, p, count * 4);
send_command(target, command, flag, data);
} else {
if (command_is_ep3) {
@@ -85,11 +86,11 @@ void forward_subcommand(shared_ptr<Lobby> l, shared_ptr<Client> c,
if (!target || (target == c) || !(target->flags & Client::Flag::EPISODE_3)) {
continue;
}
send_command(target, command, flag, p, count * 4);
send_command(target, command, flag, data);
}
} else {
send_command_excluding_client(l, c, command, flag, p, count * 4);
send_command_excluding_client(l, c, command, flag, data.data(), data.size());
}
}
}
@@ -99,23 +100,23 @@ void forward_subcommand(shared_ptr<Lobby> l, shared_ptr<Client> c,
////////////////////////////////////////////////////////////////////////////////
// Chat commands and the like
// client requests to send a guild card
static void process_subcommand_send_guild_card(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
check_size(count, 9, 0xFFFF);
if (!command_is_private(command) || !l || flag >= l->max_clients ||
(!l->clients[flag]) || (p->byte[1] != count)) {
const string& data) {
if (!command_is_private(command) || !l || (flag >= l->max_clients) ||
(!l->clients[flag])) {
return;
}
if (c->version == GameVersion::GC) {
if (count < 0x25) {
return;
}
c->player.guild_card_desc = decode_sjis(
reinterpret_cast<const char*>(&p[9].byte[0]), 0x58);
if (c->version == GameVersion::PC) {
const auto* cmd = check_size_sc<G_SendGuildCard_PC_6x06>(data);
c->player.guild_card_desc = cmd->desc;
} else if (c->version == GameVersion::GC) {
const auto* cmd = check_size_sc<G_SendGuildCard_GC_6x06>(data);
c->player.guild_card_desc = cmd->desc;
} else if (c->version == GameVersion::BB) {
const auto* cmd = check_size_sc<G_SendGuildCard_BB_6x06>(data);
c->player.guild_card_desc = cmd->desc;
}
send_guild_card(l->clients[flag], c);
@@ -124,24 +125,22 @@ static void process_subcommand_send_guild_card(shared_ptr<ServerState>,
// client sends a symbol chat
static void process_subcommand_symbol_chat(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
check_size(count, 2, 0xFFFF);
const string& data) {
const auto* p = check_size_sc(data, 0x08, 0xFFFF);
if (!c->can_chat || (p->byte[1] != count) || (p->byte[1] < 2) ||
(p[1].byte[0] != c->lobby_client_id)) {
if (!c->can_chat || (p[1].byte[0] != c->lobby_client_id)) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
// client sends a word select chat
static void process_subcommand_word_select(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
check_size(count, 8, 0xFFFF);
const string& data) {
const auto* p = check_size_sc(data, 0x20, 0xFFFF);
if (!c->can_chat || (p->byte[1] != count) || (p->byte[1] < 8) ||
(p->byte[2] != c->lobby_client_id)) {
if (!c->can_chat || (p[0].byte[2] != c->lobby_client_id)) {
return;
}
@@ -156,7 +155,7 @@ static void process_subcommand_word_select(shared_ptr<ServerState>,
return;
}
}
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
////////////////////////////////////////////////////////////////////////////////
@@ -165,23 +164,24 @@ static void process_subcommand_word_select(shared_ptr<ServerState>,
// need to process changing areas since we keep track of where players are
static void process_subcommand_change_area(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
check_size(count, 2, 0xFFFF);
if (!l->is_game() || (p->byte[1] != count)) {
const string& data) {
const auto* p = check_size_sc(data, 0x08, 0xFFFF);
if (!l->is_game()) {
return;
}
c->area = p[1].dword;
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
// when a player is hit by a monster, heal them if infinite HP is enabled
static void process_subcommand_hit_by_monster(shared_ptr<ServerState>,
// when a player is hit by an enemy, heal them if infinite HP is enabled
static void process_subcommand_hit_by_enemy(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
const string& data) {
const auto* p = check_size_sc(data, 0x04, 0xFFFF);
if (!l->is_game() || (p->byte[2] != c->lobby_client_id)) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
if ((l->flags & Lobby::Flag::CHEATS_ENABLED) && c->infinite_hp) {
send_player_stats_change(l, c, PlayerStatsChange::ADD_HP, 1020);
}
@@ -190,11 +190,12 @@ static void process_subcommand_hit_by_monster(shared_ptr<ServerState>,
// when a player casts a tech, restore TP if infinite TP is enabled
static void process_subcommand_use_technique(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
if (!l->is_game() || (p->byte[1] != count) || (p->byte[2] != c->lobby_client_id)) {
const string& data) {
const auto* p = check_size_sc(data, 0x04, 0xFFFF);
if (!l->is_game() || (p->byte[2] != c->lobby_client_id)) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
if ((l->flags & Lobby::Flag::CHEATS_ENABLED) && c->infinite_tp) {
send_player_stats_change(l, c, PlayerStatsChange::ADD_TP, 255);
}
@@ -202,18 +203,18 @@ static void process_subcommand_use_technique(shared_ptr<ServerState>,
static void process_subcommand_switch_state_changed(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
if (!l->is_game() || (p->byte[1] != count)) {
const string& data) {
const auto* p = check_size_sc(data, 0x0C);
if (!l->is_game()) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
if ((count == 3) && (p[2].byte[3] == 1)) { // If this is a switch enable command
forward_subcommand(l, c, command, flag, data);
if (p[2].byte[3] == 1) { // If this is a switch enable command
if ((l->flags & Lobby::Flag::CHEATS_ENABLED) && c->switch_assist &&
(c->last_switch_enabled_subcommand[0].byte[0] == 0x05)) {
!c->last_switch_enabled_subcommand.empty()) {
log(INFO, "[Switch assist] Replaying previous enable command");
forward_subcommand(l, c, command, flag, c->last_switch_enabled_subcommand, 3);
send_command(c, command, flag, c->last_switch_enabled_subcommand,
sizeof(c->last_switch_enabled_subcommand));
forward_subcommand(l, c, command, flag, c->last_switch_enabled_subcommand);
send_command(c, command, flag, c->last_switch_enabled_subcommand);
}
memcpy(&c->last_switch_enabled_subcommand, p, sizeof(c->last_switch_enabled_subcommand));
}
@@ -225,25 +226,11 @@ static void process_subcommand_switch_state_changed(shared_ptr<ServerState>,
// player drops an item
static void process_subcommand_drop_item(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
const string& data) {
if (l->version == GameVersion::BB) {
check_size(count, 6);
const auto* cmd = check_size_sc<G_PlayerDropItem_6x2A>(data);
struct Cmd {
uint8_t command;
uint8_t size;
uint8_t client_id;
uint8_t unused;
uint16_t unused2; // should be 1
uint16_t area;
uint32_t item_id;
float x;
float y;
float z;
} __attribute__((packed));
auto* cmd = reinterpret_cast<const Cmd*>(p);
if ((cmd->size != 6) || (cmd->client_id != c->lobby_client_id)) {
if ((cmd->client_id != c->lobby_client_id)) {
return;
}
@@ -251,31 +238,17 @@ static void process_subcommand_drop_item(shared_ptr<ServerState>,
c->player.remove_item(cmd->item_id, 0, &item);
l->add_item(item);
}
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
// player splits a stack and drops part of it
static void process_subcommand_drop_stacked_item(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
const string& data) {
if (l->version == GameVersion::BB) {
check_size(count, 6);
const auto* cmd = check_size_sc<G_SplitStackedItem_6xC3>(data);
struct Cmd {
uint8_t command;
uint8_t size;
uint8_t client_id;
uint8_t unused;
uint16_t area;
uint16_t unused2;
float x;
float y;
uint32_t item_id;
uint32_t amount;
} __attribute__((packed));
auto* cmd = reinterpret_cast<const Cmd*>(p);
if (!l->is_game() || (cmd->size != 6) || (cmd->client_id != c->lobby_client_id)) {
if (!l->is_game() || (cmd->client_id != c->lobby_client_id)) {
return;
}
@@ -293,29 +266,18 @@ static void process_subcommand_drop_stacked_item(shared_ptr<ServerState>,
send_drop_stacked_item(l, item.data, cmd->area, cmd->x, cmd->y);
} else {
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
}
// player requests to pick up an item
static void process_subcommand_pick_up_item(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
const string& data) {
if (l->version == GameVersion::BB) {
check_size(count, 3);
auto* cmd = check_size_sc<G_PickUpItemRequest_6x5A>(data);
struct Cmd {
uint8_t command;
uint8_t size;
uint8_t client_id;
uint8_t unused;
uint32_t item_id;
uint8_t area;
uint8_t unused2[3];
} __attribute__((packed));
auto* cmd = reinterpret_cast<const Cmd*>(p);
if (!l->is_game() || (cmd->size != 3) || (cmd->client_id != c->lobby_client_id)) {
if (!l->is_game() || (cmd->client_id != c->lobby_client_id)) {
return;
}
@@ -326,19 +288,18 @@ static void process_subcommand_pick_up_item(shared_ptr<ServerState>,
send_pick_up_item(l, c, item.data.item_id, cmd->area);
} else {
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
}
// player equips an item
static void process_subcommand_equip_unequip_item(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
const string& data) {
if (l->version == GameVersion::BB) {
check_size(count, 3);
const auto* cmd = check_size_sc<G_ItemSubcommand>(data);
auto* cmd = reinterpret_cast<const ItemSubcommand*>(p);
if ((cmd->size != 3) || (cmd->client_id != c->lobby_client_id)) {
if ((cmd->client_id != c->lobby_client_id)) {
return;
}
@@ -350,18 +311,17 @@ static void process_subcommand_equip_unequip_item(shared_ptr<ServerState>,
}
} else {
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
}
static void process_subcommand_use_item(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
const string& data) {
if (l->version == GameVersion::BB) {
check_size(count, 2);
const auto* cmd = check_size_sc<G_ItemSubcommand>(data);
auto* cmd = reinterpret_cast<const ItemSubcommand*>(p);
if ((cmd->size != 2) || (cmd->client_id != c->lobby_client_id)) {
if (cmd->client_id != c->lobby_client_id) {
return;
}
@@ -375,17 +335,18 @@ static void process_subcommand_use_item(shared_ptr<ServerState>,
player_use_item(c, index);
}
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
static void process_subcommand_open_shop_or_ep3_unknown(shared_ptr<ServerState> s,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
const string& data) {
if (l->flags & Lobby::Flag::EPISODE_3_ONLY) {
check_size(count, 2, 0xFFFF);
forward_subcommand(l, c, command, flag, p, count);
check_size_sc(data, 0x08, 0xFFFF);
forward_subcommand(l, c, command, flag, data);
} else {
const auto* p = check_size_sc(data, 0x08);
uint32_t shop_type = p[1].dword;
if ((l->version == GameVersion::BB) && l->is_game()) {
@@ -413,33 +374,18 @@ static void process_subcommand_open_shop_or_ep3_unknown(shared_ptr<ServerState>
}
static void process_subcommand_open_bank(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t, uint8_t,
const PSOSubcommand*, size_t) {
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t, uint8_t, const string&) {
if ((l->version == GameVersion::BB) && l->is_game()) {
send_bank(c);
}
}
// player performs some bank action
static void process_subcommand_bank_action(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t, uint8_t,
const PSOSubcommand* p, size_t count) {
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t, uint8_t, const string& data) {
if (l->version == GameVersion::BB) {
check_size(count, 4);
const auto* cmd = check_size_sc<G_BankAction_BB_6xBD>(data);
struct Cmd {
uint8_t subcommand;
uint8_t size;
uint16_t unused;
uint32_t item_id;
uint32_t meseta_amount;
uint8_t action;
uint8_t item_amount;
uint16_t unused2;
} __attribute__((packed));
auto* cmd = reinterpret_cast<const Cmd*>(p);
if (!l->is_game() || (cmd->size != 4)) {
if (!l->is_game()) {
return;
}
@@ -484,21 +430,9 @@ static void process_subcommand_bank_action(shared_ptr<ServerState>,
// player sorts the items in their inventory
static void process_subcommand_sort_inventory(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t, uint8_t,
const PSOSubcommand* p, size_t count) {
const string& data) {
if (l->version == GameVersion::BB) {
check_size(count, 31);
struct Cmd {
uint8_t command;
uint8_t size;
uint16_t unused;
uint32_t item_ids[30];
} __attribute__((packed));
auto* cmd = reinterpret_cast<const Cmd*>(p);
if (cmd->size != 31) {
return;
}
const auto* cmd = check_size_sc<G_SortInventory_6xC4>(data);
PlayerInventory sorted;
memset(&sorted, 0, sizeof(PlayerInventory));
@@ -526,24 +460,11 @@ static void process_subcommand_sort_inventory(shared_ptr<ServerState>,
// enemy killed; leader sends drop item request
static void process_subcommand_enemy_drop_item(shared_ptr<ServerState> s,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
const string& data) {
if (l->version == GameVersion::BB) {
check_size(count, 6);
const auto* cmd = check_size_sc<G_EnemyDropItemRequest_6x60>(data);
struct Cmd {
uint8_t command;
uint8_t size;
uint16_t unused;
uint8_t area;
uint8_t monster_id;
uint16_t request_id;
float x;
float y;
uint32_t unknown[2];
} __attribute__((packed));
auto* cmd = reinterpret_cast<const Cmd*>(p);
if ((cmd->size != 6) || !l->is_game()) {
if (!l->is_game()) {
return;
}
@@ -556,13 +477,13 @@ static void process_subcommand_enemy_drop_item(shared_ptr<ServerState> s,
l->next_drop_item.data.item_data1d[0] = 0;
} else {
if (l->rare_item_set) {
if (cmd->monster_id <= 0x65) {
is_rare = sample_rare_item(l->rare_item_set->rares[cmd->monster_id].probability);
if (cmd->enemy_id <= 0x65) {
is_rare = sample_rare_item(l->rare_item_set->rares[cmd->enemy_id].probability);
}
}
if (is_rare) {
memcpy(&item.data.item_data1d, l->rare_item_set->rares[cmd->monster_id].item_code, 3);
memcpy(&item.data.item_data1d, l->rare_item_set->rares[cmd->enemy_id].item_code, 3);
//RandPercentages();
if (item.data.item_data1d[0] == 0) {
item.data.item_data1[4] |= 0x80; // make it untekked if it's a weapon
@@ -584,31 +505,18 @@ static void process_subcommand_enemy_drop_item(shared_ptr<ServerState> s,
cmd->request_id);
} else {
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
}
// box broken; leader sends drop item request
static void process_subcommand_box_drop_item(shared_ptr<ServerState> s,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
const string& data) {
if (l->version == GameVersion::BB) {
check_size(count, 10);
const auto* cmd = check_size_sc<G_BoxItemDropRequest_6xA2>(data);
struct Cmd {
uint8_t command;
uint8_t size;
uint16_t unused;
uint8_t area;
uint8_t unused2;
uint16_t request_id;
float x;
float y;
uint32_t unknown[6];
} __attribute__((packed));
auto* cmd = reinterpret_cast<const Cmd*>(p);
if ((cmd->size != 10) || !l->is_game()) {
if (!l->is_game()) {
return;
}
@@ -656,31 +564,20 @@ static void process_subcommand_box_drop_item(shared_ptr<ServerState> s,
cmd->request_id);
} else {
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
}
// monster hit by player
static void process_subcommand_monster_hit(shared_ptr<ServerState>,
// enemy hit by player
static void process_subcommand_enemy_hit(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
const string& data) {
if (l->version == GameVersion::BB) {
check_size(count, 10);
const auto* cmd = check_size_sc<G_EnemyHitByPlayer_6x0A>(data);
struct Cmd {
uint8_t command;
uint8_t size;
uint16_t enemy_id2;
uint16_t enemy_id;
uint16_t damage;
uint32_t flags;
} __attribute__((packed));
auto* cmd = reinterpret_cast<const Cmd*>(p);
if (cmd->size != 10) {
if (!l->is_game()) {
return;
}
if (cmd->enemy_id >= l->enemies.size()) {
return;
}
@@ -692,35 +589,22 @@ static void process_subcommand_monster_hit(shared_ptr<ServerState>,
l->enemies[cmd->enemy_id].last_hit = c->lobby_client_id;
}
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
// monster killed by player
static void process_subcommand_monster_killed(shared_ptr<ServerState> s,
// enemy killed by player
static void process_subcommand_enemy_killed(shared_ptr<ServerState> s,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
if (l->version == GameVersion::BB) {
check_size(count, 3);
}
forward_subcommand(l, c, command, flag, p, count);
const string& data) {
forward_subcommand(l, c, command, flag, data);
if (l->version == GameVersion::BB) {
struct Cmd {
uint8_t command;
uint8_t size;
uint16_t enemy_id2;
uint16_t enemy_id;
uint16_t killer_client_id;
uint32_t unused;
} __attribute__((packed));
auto* cmd = reinterpret_cast<const Cmd*>(p);
const auto* cmd = check_size_sc<G_EnemyKilled_6xC8>(data);
if (!l->is_game() || (cmd->size != 3) || (cmd->enemy_id >= l->enemies.size() ||
if (!l->is_game() || (cmd->enemy_id >= l->enemies.size() ||
(l->enemies[cmd->enemy_id].hit_flags & 0x80))) {
return;
}
if (l->enemies[cmd->enemy_id].experience == 0xFFFFFFFF) {
send_text_message(c, u"$C6Unknown enemy type killed");
return;
@@ -774,29 +658,25 @@ static void process_subcommand_monster_killed(shared_ptr<ServerState> s,
// destroy item (sent when there are too many items on the ground)
static void process_subcommand_destroy_item(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
const string& data) {
if (l->version == GameVersion::BB) {
check_size(count, 3);
auto* cmd = reinterpret_cast<const ItemSubcommand*>(p);
if ((cmd->size != 3) || !l->is_game()) {
const auto* cmd = check_size_sc<G_ItemSubcommand>(data);
if (!l->is_game()) {
return;
}
l->remove_item(cmd->item_id, nullptr);
}
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
// player requests to tekk an item
static void process_subcommand_identify_item(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
const string& data) {
if (l->version == GameVersion::BB) {
check_size(count, 3);
auto* cmd = reinterpret_cast<const ItemSubcommand*>(p);
if (!l->is_game() || (cmd->size != 3) || (cmd->client_id != c->lobby_client_id)) {
const auto* cmd = check_size_sc<G_ItemSubcommand>(data);
if (!l->is_game() || (cmd->client_id != c->lobby_client_id)) {
return;
}
@@ -809,16 +689,17 @@ static void process_subcommand_identify_item(shared_ptr<ServerState>,
c->player.identify_result = c->player.inventory.items[x];
c->player.identify_result.data.item_data1[4] &= 0x7F;
// TODO: move this into a SendCommands.cc
PSOSubcommand sub[6];
sub[0].byte[0] = 0xB9;
sub[0].byte[1] = 0x06;
sub[0].word[1] = c->lobby_client_id;
memcpy(&sub[1], &c->player.identify_result.data, sizeof(ItemData));
send_command(l, 0x60, 0x00, sub, 0x18);
// TODO: move this into a SendCommands.cc function
G_IdentifyResult_BB_6xB9 res;
res.subcommand = 0xB9;
res.size = sizeof(cmd) / 4;
res.client_id = c->lobby_client_id;
res.unused = 0;
res.item = c->player.identify_result.data;
send_command(l, 0x60, 0x00, res);
} else {
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
}
@@ -827,13 +708,12 @@ static void process_subcommand_identify_item(shared_ptr<ServerState>,
// correct though so we can just put it in the table when we figure out the id
// static void process_subcommand_accept_identified_item(shared_ptr<ServerState> s,
// shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
// const PSOSubcommand* p, size_t count) {
// const string& data) {
//
// if (l->version == GameVersion::BB) {
// check_size(count, 3);
// const auto* cmd = check_size_sc<G_ItemSubcommand>(data);
//
// auto* cmd = reinterpret_cast<const ItemSubcommand*>(p);
// if ((cmd->size != 3) || (cmd->client_id != c->lobby_client_id)) {
// if (cmd->client_id != c->lobby_client_id) {
// return;
// }
//
@@ -842,7 +722,7 @@ static void process_subcommand_identify_item(shared_ptr<ServerState>,
// // TODO: what do we send to the other clients? anything?
//
// } else {
// forward_subcommand(l, c, command, flag, p, count);
// forward_subcommand(l, c, command, flag, data);
// }
// }
@@ -850,79 +730,78 @@ static void process_subcommand_identify_item(shared_ptr<ServerState>,
static void process_subcommand_forward_check_size(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
if (p->byte[1] != count) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
const string& data) {
check_size_sc(data, sizeof(PSOSubcommand), 0xFFFF);
forward_subcommand(l, c, command, flag, data);
}
static void process_subcommand_forward_check_game(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
const string& data) {
if (!l->is_game()) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
static void process_subcommand_forward_check_game_loading(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
const string& data) {
if (!l->is_game() || !l->any_client_loading()) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
static void process_subcommand_forward_check_size_client(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
if ((p->byte[1] != count) || (p->byte[2] != c->lobby_client_id)) {
const string& data) {
const auto* p = check_size_sc(data, sizeof(PSOSubcommand), 0xFFFF);
if (p->byte[2] != c->lobby_client_id) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
static void process_subcommand_forward_check_size_game(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
if (!l->is_game() || (p->byte[1] != count)) {
const string& data) {
check_size_sc(data, sizeof(PSOSubcommand), 0xFFFF);
if (!l->is_game()) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
static void process_subcommand_forward_check_size_ep3_lobby(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
if (l->is_game() || !(l->flags & Lobby::Flag::EPISODE_3_ONLY) || (p->byte[1] != count)) {
const string& data) {
check_size_sc(data, sizeof(PSOSubcommand), 0xFFFF);
if (l->is_game() || !(l->flags & Lobby::Flag::EPISODE_3_ONLY)) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
forward_subcommand(l, c, command, flag, data);
}
static void process_subcommand_invalid(shared_ptr<ServerState>,
shared_ptr<Lobby>, shared_ptr<Client>, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
const string& data) {
const auto* p = check_size_sc(data, sizeof(PSOSubcommand), 0xFFFF);
if (command_is_private(command)) {
log(WARNING, "Invalid subcommand: %02hhX (%zu of them) (private to player %hhu)",
p->byte[0], count, flag);
log(WARNING, "Invalid subcommand: %02hhX (private to %hhu)", p->byte[0], flag);
} else {
log(WARNING, "Invalid subcommand: %02hhX (%zu of them) (public)",
p->byte[0], count);
log(WARNING, "Invalid subcommand: %02hhX (public)", p->byte[0]);
}
}
static void process_subcommand_unimplemented(shared_ptr<ServerState>,
shared_ptr<Lobby>, shared_ptr<Client>, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count) {
const string& data) {
const auto* p = check_size_sc(data, sizeof(PSOSubcommand), 0xFFFF);
if (command_is_private(command)) {
log(WARNING, "Unknown subcommand: %02hhX (%zu of them) (private to player %hhu)",
p->byte[0], count, flag);
log(WARNING, "Unknown subcommand: %02hhX (private to %hhu)", p->byte[0], flag);
} else {
log(WARNING, "Unknown subcommand: %02hhX (%zu of them) (public)",
p->byte[0], count);
log(WARNING, "Unknown subcommand: %02hhX (public)", p->byte[0]);
}
}
@@ -933,7 +812,7 @@ static void process_subcommand_unimplemented(shared_ptr<ServerState>,
// for more information on flags. The maximum size is not enforced if it's zero.
typedef void (*subcommand_handler_t)(shared_ptr<ServerState> s,
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* p, size_t count);
const string& data);
subcommand_handler_t subcommand_handlers[0x100] = {
/* 00 */ process_subcommand_invalid,
@@ -946,7 +825,7 @@ subcommand_handler_t subcommand_handlers[0x100] = {
/* 07 */ process_subcommand_symbol_chat,
/* 08 */ process_subcommand_unimplemented,
/* 09 */ process_subcommand_unimplemented,
/* 0A */ process_subcommand_monster_hit,
/* 0A */ process_subcommand_enemy_hit,
/* 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.)
@@ -983,7 +862,7 @@ subcommand_handler_t subcommand_handlers[0x100] = {
/* 2C */ process_subcommand_forward_check_size, // Talk to NPC
/* 2D */ process_subcommand_forward_check_size, // Done talking to NPC
/* 2E */ process_subcommand_unimplemented,
/* 2F */ process_subcommand_hit_by_monster,
/* 2F */ process_subcommand_hit_by_enemy,
/* 30 */ process_subcommand_forward_check_size_game, // Level up
/* 31 */ process_subcommand_forward_check_size_game, // Medical center
/* 32 */ process_subcommand_forward_check_size_game, // Medical center
@@ -1011,8 +890,8 @@ subcommand_handler_t subcommand_handlers[0x100] = {
/* 48 */ process_subcommand_use_technique,
/* 49 */ process_subcommand_forward_check_size_client,
/* 4A */ process_subcommand_forward_check_size_client,
/* 4B */ process_subcommand_hit_by_monster,
/* 4C */ process_subcommand_hit_by_monster,
/* 4B */ process_subcommand_hit_by_enemy,
/* 4C */ process_subcommand_hit_by_enemy,
/* 4D */ process_subcommand_forward_check_size_client,
/* 4E */ process_subcommand_forward_check_size_client,
/* 4F */ process_subcommand_forward_check_size_client,
@@ -1031,7 +910,7 @@ subcommand_handler_t subcommand_handlers[0x100] = {
/* 5C */ process_subcommand_unimplemented,
/* 5D */ process_subcommand_forward_check_size_game, // Drop meseta or stacked item
/* 5E */ process_subcommand_forward_check_size_game, // Buy item at shop
/* 5F */ process_subcommand_forward_check_size_game, // Drop item from box/monster
/* 5F */ process_subcommand_forward_check_size_game, // Drop item from box/enemy
/* 60 */ process_subcommand_enemy_drop_item, // Request for item drop (handled by the server on BB)
/* 61 */ process_subcommand_forward_check_size_game, // Feed mag
/* 62 */ process_subcommand_unimplemented,
@@ -1054,7 +933,7 @@ subcommand_handler_t subcommand_handlers[0x100] = {
/* 73 */ process_subcommand_invalid,
/* 74 */ process_subcommand_word_select,
/* 75 */ process_subcommand_forward_check_size_game,
/* 76 */ process_subcommand_forward_check_size_game, // Monster killed
/* 76 */ process_subcommand_forward_check_size_game, // Enemy killed
/* 77 */ process_subcommand_forward_check_size_game, // Sync quest data
/* 78 */ process_subcommand_unimplemented,
/* 79 */ process_subcommand_forward_check_size, // Lobby 14/15 soccer game
@@ -1136,7 +1015,7 @@ subcommand_handler_t subcommand_handlers[0x100] = {
/* C5 */ process_subcommand_unimplemented,
/* C6 */ process_subcommand_unimplemented,
/* C7 */ process_subcommand_unimplemented,
/* C8 */ process_subcommand_monster_killed,
/* C8 */ process_subcommand_enemy_killed,
/* C9 */ process_subcommand_unimplemented,
/* CA */ process_subcommand_unimplemented,
/* CB */ process_subcommand_unimplemented,
@@ -1195,9 +1074,12 @@ subcommand_handler_t subcommand_handlers[0x100] = {
};
void process_subcommand(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
shared_ptr<Client> c, uint8_t command, uint8_t flag,
const PSOSubcommand* sub, size_t count) {
subcommand_handlers[sub->byte[0]](s, l, c, command, flag, sub, count);
shared_ptr<Client> c, uint8_t command, uint8_t flag, const string& data) {
if (data.empty()) {
throw runtime_error("game command is empty");
}
uint8_t which = static_cast<uint8_t>(data[0]);
subcommand_handlers[which](s, l, c, command, flag, data);
}
bool subcommand_is_implemented(uint8_t which) {
+1 -2
View File
@@ -7,10 +7,9 @@
#include "ServerState.hh"
void check_size(uint16_t size, uint16_t min_size, uint16_t max_size = 0);
void process_subcommand(std::shared_ptr<ServerState> s,
std::shared_ptr<Lobby> l, std::shared_ptr<Client> c, uint8_t command,
uint8_t flag, const PSOSubcommand* sub, size_t count);
uint8_t flag, const std::string& data);
bool subcommand_is_implemented(uint8_t which);
+52 -40
View File
@@ -598,42 +598,45 @@ void send_card_search_result(
void send_guild_card_gc(shared_ptr<Client> c, shared_ptr<Client> source) {
S_SendGuildCard_GC cmd;
template <typename CmdT>
void send_guild_card_pc_gc(shared_ptr<Client> c, shared_ptr<Client> source) {
CmdT cmd;
cmd.subcommand = 0x06;
cmd.subsize = 0x25;
cmd.size = sizeof(CmdT) / 4;
cmd.unused = 0x0000;
cmd.player_tag = 0x00010000;
cmd.reserved1 = 1;
cmd.reserved2 = 1;
cmd.guild_card_number = source->license->serial_number;
cmd.name = source->player.disp.name;
remove_language_marker_inplace(cmd.name);
cmd.desc = source->player.guild_card_desc;
cmd.reserved1 = 1;
cmd.reserved2 = 1;
cmd.section_id = source->player.disp.section_id;
cmd.char_class = source->player.disp.char_class;
send_command(c, 0x62, c->lobby_client_id, cmd);
}
void send_guild_card_bb(shared_ptr<Client> c, shared_ptr<Client> source) {
S_SendGuildCard_BB cmd;
G_SendGuildCard_BB_6x06 cmd;
cmd.subcommand = 0x06;
cmd.subsize = 0x43;
cmd.size = sizeof(cmd) / 4;
cmd.unused = 0x0000;
cmd.reserved1 = 1;
cmd.reserved2 = 1;
cmd.guild_card_number = source->license->serial_number;
cmd.name = remove_language_marker(source->player.disp.name);
cmd.team_name = remove_language_marker(source->player.team_name);
cmd.desc = source->player.guild_card_desc;
cmd.reserved1 = 1;
cmd.reserved2 = 1;
cmd.section_id = source->player.disp.section_id;
cmd.char_class = source->player.disp.char_class;
send_command(c, 0x62, c->lobby_client_id, cmd);
}
void send_guild_card(shared_ptr<Client> c, shared_ptr<Client> source) {
if (c->version == GameVersion::GC) {
send_guild_card_gc(c, source);
if (c->version == GameVersion::PC) {
send_guild_card_pc_gc<G_SendGuildCard_PC_6x06>(c, source);
} else if (c->version == GameVersion::GC) {
send_guild_card_pc_gc<G_SendGuildCard_GC_6x06>(c, source);
} else if (c->version == GameVersion::BB) {
send_guild_card_bb(c, source);
} else {
@@ -1118,7 +1121,7 @@ void send_revive_player(shared_ptr<Lobby> l, shared_ptr<Client> c) {
// notifies other players of a dropped item from a box or enemy
void send_drop_item(shared_ptr<Lobby> l, const ItemData& item,
bool from_enemy, uint8_t area, float x, float y, uint16_t request_id) {
S_DropItem_BB cmd = {
G_DropItem_6x5F cmd = {
0x5F, 0x0A, 0x0000, area, from_enemy, request_id, x, y, 0, item};
send_command(l, 0x60, 0x00, cmd);
}
@@ -1126,7 +1129,7 @@ void send_drop_item(shared_ptr<Lobby> l, const ItemData& item,
// notifies other players that a stack was split and part of it dropped (a new item was created)
void send_drop_stacked_item(shared_ptr<Lobby> l, const ItemData& item,
uint8_t area, float x, float y) {
S_DropStackedItem_BB cmd = {
G_DropStackedItem_6x5D cmd = {
0x5D, 0x09, 0x0000, area, 0, x, y, 0, item};
send_command(l, 0x60, 0x00, cmd);
}
@@ -1134,7 +1137,7 @@ void send_drop_stacked_item(shared_ptr<Lobby> l, const ItemData& item,
// notifies other players that an item was picked up
void send_pick_up_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
uint32_t item_id, uint8_t area) {
S_PickUpItem_BB cmd = {
G_PickUpItem_6x59 cmd = {
0x59, 0x03, c->lobby_client_id, c->lobby_client_id, area, item_id};
send_command(l, 0x60, 0x00, cmd);
}
@@ -1142,7 +1145,7 @@ void send_pick_up_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
// creates an item in a player's inventory (used for withdrawing items from the bank)
void send_create_inventory_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
const ItemData& item) {
S_CreateInventoryItem_BB cmd = {
G_CreateInventoryItem_BB_6xBE cmd = {
0xBE, 0x07, c->lobby_client_id, item, 0};
send_command(l, 0x60, 0x00, cmd);
}
@@ -1150,7 +1153,7 @@ void send_create_inventory_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
// destroys an item
void send_destroy_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
uint32_t item_id, uint32_t amount) {
S_DestroyItem_BB cmd = {
G_DestroyItem_6x29 cmd = {
0x29, 0x03, c->lobby_client_id, item_id, amount};
send_command(l, 0x60, 0x00, cmd);
}
@@ -1161,7 +1164,7 @@ void send_bank(shared_ptr<Client> c) {
&c->player.bank.items[c->player.bank.num_items]);
uint32_t checksum = random_object<uint32_t>();
S_BankContentsHeader_BB cmd = {
G_BankContentsHeader_BB_6xBC cmd = {
0xBC, 0, 0, 0, checksum, c->player.bank.num_items, c->player.bank.meseta};
size_t size = 8 + sizeof(cmd) + items.size() * sizeof(PlayerBankItem);
@@ -1172,7 +1175,7 @@ void send_bank(shared_ptr<Client> c) {
// sends the player a shop's contents
void send_shop(shared_ptr<Client> c, uint8_t shop_type) {
S_ShopContents_BB cmd = {
G_ShopContents_BB_6xB6 cmd = {
0xB6,
0x2C,
0x037F,
@@ -1208,30 +1211,27 @@ void send_level_up(shared_ptr<Lobby> l, shared_ptr<Client> c) {
}
}
// TODO: Make a real struct for this
PSOSubcommand sub[5];
sub[0].byte[0] = 0x30;
sub[0].byte[1] = 0x05;
sub[0].word[1] = c->lobby_client_id;
sub[1].word[0] = stats.atp;
sub[1].word[1] = stats.mst;
sub[2].word[0] = stats.evp;
sub[2].word[1] = stats.hp;
sub[3].word[0] = stats.dfp;
sub[3].word[1] = stats.ata;
sub[4].dword = c->player.disp.level;
send_command(l, 0x60, 0x00, sub, 0x14);
G_LevelUp_6x30 cmd = {
0x30,
sizeof(G_LevelUp_6x30) / 4,
c->lobby_client_id,
0,
stats.atp,
stats.mst,
stats.evp,
stats.hp,
stats.dfp,
stats.ata,
c->player.disp.level};
send_command(l, 0x60, 0x00, cmd);
}
// gives a player EXP
void send_give_experience(shared_ptr<Lobby> l, shared_ptr<Client> c,
uint32_t amount) {
// TODO: Make a real struct for this
PSOSubcommand sub[2];
sub[0].word[0] = 0x02BF;
sub[0].word[1] = c->lobby_client_id;
sub[1].dword = amount;
send_command(l, 0x60, 0x00, sub, 8);
G_GiveExperience_BB_6xBF cmd = {
0xBF, sizeof(G_GiveExperience_BB_6xBF) / 4, c->lobby_client_id, 0, amount};
send_command(l, 0x60, 0x00, cmd);
}
@@ -1368,13 +1368,25 @@ void send_server_time(shared_ptr<Client> c) {
}
void send_change_event(shared_ptr<Client> c, uint8_t new_event) {
send_command(c, 0xDA, new_event);
// THis command isn't supported on versions before GC apparently
if ((c->version == GameVersion::GC) || (c->version == GameVersion::BB)) {
send_command(c, 0xDA, new_event);
}
}
void send_change_event(shared_ptr<Lobby> l, uint8_t new_event) {
send_command(l, 0xDA, new_event);
for (auto& c : l->clients) {
if (!c) {
continue;
}
send_change_event(c, new_event);
}
}
void send_change_event(shared_ptr<ServerState> s, uint8_t new_event) {
send_command(s, 0xDA, new_event);
// TODO: Create a collection of all clients on the server (including those not
// in lobbies) and use that here instead
for (auto& l : s->all_lobbies()) {
send_change_event(l, new_event);
}
}
+9
View File
@@ -197,6 +197,15 @@ uint32_t ServerState::connect_address_for_client(std::shared_ptr<Client> c) {
shared_ptr<const vector<MenuItem>> ServerState::information_menu_for_version(GameVersion version) {
if (version == GameVersion::PC) {
return this->information_menu_pc;
} else if (version == GameVersion::GC) {
return this->information_menu_gc;
}
throw out_of_range("no information menu exists for this version");
}
const vector<MenuItem>& ServerState::proxy_destinations_menu_for_version(GameVersion version) {
if (version == GameVersion::PC) {
return this->proxy_destinations_menu_pc;
+3 -1
View File
@@ -53,7 +53,8 @@ struct ServerState {
std::shared_ptr<LicenseManager> license_manager;
std::vector<MenuItem> main_menu;
std::shared_ptr<std::vector<MenuItem>> information_menu;
std::shared_ptr<std::vector<MenuItem>> information_menu_pc;
std::shared_ptr<std::vector<MenuItem>> information_menu_gc;
std::shared_ptr<std::vector<std::u16string>> information_contents;
std::vector<MenuItem> proxy_destinations_menu_pc;
std::vector<MenuItem> proxy_destinations_menu_gc;
@@ -98,6 +99,7 @@ struct ServerState {
uint32_t connect_address_for_client(std::shared_ptr<Client> c);
std::shared_ptr<const std::vector<MenuItem>> information_menu_for_version(GameVersion version);
const std::vector<MenuItem>& proxy_destinations_menu_for_version(GameVersion version);
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations_for_version(GameVersion version);
+3
View File
@@ -386,6 +386,7 @@ template <typename CharT, size_t Count>
void remove_language_marker_inplace(ptext<CharT, Count>& a) {
if ((a.items[0] == '\t') && (a.items[1] != 'C')) {
text_strnzcpy_t(a.items, Count, &a.items[2], Count);
a.items[text_strlen_t(a.items) + 1] = 0;
}
}
@@ -429,6 +430,8 @@ size_t add_color_inplace(T* a, size_t max_chars) {
a++;
}
*d = 0;
// TODO: we should clear the chars after the null if the new string is shorter
// than the original
return d - orig_d;
}