diff --git a/src/Client.hh b/src/Client.hh index 43c8de0d..8783e847 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -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; diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 12ec7a77..44c0008b 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -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 -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(data.data()); -} -template -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(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 struct S_GuildCardSearchResult { @@ -1185,7 +1146,7 @@ struct S_StreamFileIndexEntry_BB_01EB { le_uint32_t checksum; le_uint32_t offset; ptext filename; -} __attribute__((packed)); +}; struct S_StreamFileChunk_BB_02EB { le_uint32_t chunk_index; @@ -1209,7 +1170,7 @@ union C_UpdateAccountData_BB_ED { parray pad_config; // 05ED parray tech_menu; // 06ED parray 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 +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 name; - ptext desc; + ptext name; + ptext desc; + parray unused2; uint8_t reserved1; uint8_t reserved2; uint8_t section_id; uint8_t char_class; }; +struct G_SendGuildCard_PC_6x06 : G_SendGuildCard_PC_GC { }; +struct G_SendGuildCard_GC_6x06 : G_SendGuildCard_PC_GC { }; -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 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) diff --git a/src/Lobby.hh b/src/Lobby.hh index 146c6e69..450b0e63 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -67,7 +67,7 @@ struct Lobby { size_t count_clients() const; bool any_client_loading() const; - void add_client(std::shared_ptr c, bool reverse_indexes = false); + void add_client(std::shared_ptr c, bool reverse_indexes = true); void remove_client(std::shared_ptr c); void move_client_to_lobby(std::shared_ptr dest_lobby, diff --git a/src/Main.cc b/src/Main.cc index 76cad0b1..92d2c7da 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -97,22 +97,26 @@ void populate_state_from_config(shared_ptr s, s->common_item_creator.reset(new CommonItemCreator(enemy_categories, box_categories, unit_types)); - shared_ptr> information_menu(new vector()); + shared_ptr> information_menu_pc(new vector()); + shared_ptr> information_menu_gc(new vector()); shared_ptr> information_contents(new vector()); - 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, diff --git a/src/PSOProtocol.cc b/src/PSOProtocol.cc index 3ad12c26..1d50c6fc 100644 --- a/src/PSOProtocol.cc +++ b/src/PSOProtocol.cc @@ -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)); + } +} diff --git a/src/PSOProtocol.hh b/src/PSOProtocol.hh index 8f5d8c17..7ad2f627 100644 --- a/src/PSOProtocol.hh +++ b/src/PSOProtocol.hh @@ -4,6 +4,7 @@ #include #include +#include #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 +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(data.data()); +} +template +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(data.data()); +} + +void check_size_v(size_t size, size_t min_size, size_t max_size = 0); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 6c182c1c..8736ea8d 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -417,7 +417,7 @@ void process_client_checksum(shared_ptr, shared_ptr c, void process_server_time_request(shared_ptr, shared_ptr 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 s, shared_ptr c, void process_ep3_menu_challenge(shared_ptr, shared_ptr 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, shared_ptr c, void process_ep3_server_data_request(shared_ptr s, shared_ptr 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(data.data()); auto l = s->find_lobby(c->lobby_id); @@ -557,9 +557,10 @@ void process_ep3_tournament_control(shared_ptr, shared_ptr void process_message_box_closed(shared_ptr s, shared_ptr 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 s, shared_ptrinformation_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 s, shared_ptr 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 s, shared_ptr 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 s, shared_ptr c, void process_game_list_request(shared_ptr s, shared_ptr 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 s, shared_ptr 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 s, shared_ptr 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 s, shared_ptr c, void process_quest_list_request(shared_ptr s, shared_ptr 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 s, shared_ptr c, void process_quest_ready(shared_ptr s, shared_ptr 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 s, shared_ptr 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(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(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(data.data())); break; @@ -1108,16 +1122,14 @@ void process_player_data(shared_ptr s, shared_ptr c, void process_game_command(shared_ptr s, shared_ptr 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(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 s, shared_ptr c, void process_key_config_request_bb(shared_ptr, shared_ptr 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, shared_ptr, shared_ptr 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, shared_ptr, shared_ptr 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, shared_ptr 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, shared_ptr c void process_change_arrow_color(shared_ptr s, shared_ptr 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 s, shared_ptr c, void process_info_board_request(shared_ptr s, shared_ptr 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 void process_write_info_board_t(shared_ptr, shared_ptr 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(data.data()), data.size() / sizeof(CharT)); @@ -1433,7 +1445,7 @@ void process_write_info_board_t(shared_ptr, shared_ptr c, template void process_set_auto_reply_t(shared_ptr, shared_ptr 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(data.data()), data.size() / sizeof(CharT)); @@ -1441,7 +1453,7 @@ void process_set_auto_reply_t(shared_ptr, shared_ptr c, void process_disable_auto_reply(shared_ptr, shared_ptr 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 create_game_generic(shared_ptr 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 s, shared_ptr c, void process_lobby_name_request(shared_ptr s, shared_ptr 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 s, shared_ptr c, void process_client_ready(shared_ptr s, shared_ptr 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, shared_ptr c, void process_encryption_ok_patch(shared_ptr, shared_ptr 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, diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 9aee9a3a..303290df 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -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 l, shared_ptr c, - uint8_t command, uint8_t flag, const PSOSubcommand* p, - size_t count) { +template +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(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( + 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(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 l, shared_ptr 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 l, shared_ptr 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 l, shared_ptr 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 l, shared_ptr c, //////////////////////////////////////////////////////////////////////////////// // Chat commands and the like -// client requests to send a guild card static void process_subcommand_send_guild_card(shared_ptr, shared_ptr l, shared_ptr 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(&p[9].byte[0]), 0x58); + if (c->version == GameVersion::PC) { + const auto* cmd = check_size_sc(data); + c->player.guild_card_desc = cmd->desc; + } else if (c->version == GameVersion::GC) { + const auto* cmd = check_size_sc(data); + c->player.guild_card_desc = cmd->desc; + } else if (c->version == GameVersion::BB) { + const auto* cmd = check_size_sc(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, // client sends a symbol chat static void process_subcommand_symbol_chat(shared_ptr, shared_ptr l, shared_ptr 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, shared_ptr l, shared_ptr 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, 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, // need to process changing areas since we keep track of where players are static void process_subcommand_change_area(shared_ptr, shared_ptr l, shared_ptr 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, +// when a player is hit by an enemy, heal them if infinite HP is enabled +static void process_subcommand_hit_by_enemy(shared_ptr, shared_ptr l, shared_ptr 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, // when a player casts a tech, restore TP if infinite TP is enabled static void process_subcommand_use_technique(shared_ptr, shared_ptr l, shared_ptr 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, static void process_subcommand_switch_state_changed(shared_ptr, shared_ptr l, shared_ptr 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, // player drops an item static void process_subcommand_drop_item(shared_ptr, shared_ptr l, shared_ptr 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(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(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, 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, shared_ptr l, shared_ptr 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(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(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, 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, shared_ptr l, shared_ptr 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(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(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, 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, shared_ptr l, shared_ptr 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(data); - auto* cmd = reinterpret_cast(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, } } else { - forward_subcommand(l, c, command, flag, p, count); + forward_subcommand(l, c, command, flag, data); } } static void process_subcommand_use_item(shared_ptr, shared_ptr l, shared_ptr 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(data); - auto* cmd = reinterpret_cast(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, 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 s, shared_ptr l, shared_ptr 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 } static void process_subcommand_open_bank(shared_ptr, - shared_ptr l, shared_ptr c, uint8_t, uint8_t, - const PSOSubcommand*, size_t) { + shared_ptr l, shared_ptr 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, - shared_ptr l, shared_ptr c, uint8_t, uint8_t, - const PSOSubcommand* p, size_t count) { + shared_ptr l, shared_ptr c, uint8_t, uint8_t, const string& data) { if (l->version == GameVersion::BB) { - check_size(count, 4); + const auto* cmd = check_size_sc(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(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, // player sorts the items in their inventory static void process_subcommand_sort_inventory(shared_ptr, shared_ptr l, shared_ptr 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(p); - - if (cmd->size != 31) { - return; - } + const auto* cmd = check_size_sc(data); PlayerInventory sorted; memset(&sorted, 0, sizeof(PlayerInventory)); @@ -526,24 +460,11 @@ static void process_subcommand_sort_inventory(shared_ptr, // enemy killed; leader sends drop item request static void process_subcommand_enemy_drop_item(shared_ptr s, shared_ptr l, shared_ptr 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(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(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 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 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 s, shared_ptr l, shared_ptr 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(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(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 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, +// enemy hit by player +static void process_subcommand_enemy_hit(shared_ptr, shared_ptr l, shared_ptr 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(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(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, 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 s, +// enemy killed by player +static void process_subcommand_enemy_killed(shared_ptr s, shared_ptr l, shared_ptr 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(p); + const auto* cmd = check_size_sc(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 s, // destroy item (sent when there are too many items on the ground) static void process_subcommand_destroy_item(shared_ptr, shared_ptr l, shared_ptr 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(p); - if ((cmd->size != 3) || !l->is_game()) { + const auto* cmd = check_size_sc(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, shared_ptr l, shared_ptr 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(p); - if (!l->is_game() || (cmd->size != 3) || (cmd->client_id != c->lobby_client_id)) { + const auto* cmd = check_size_sc(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, 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, // 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 s, // shared_ptr l, shared_ptr 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(data); // -// auto* cmd = reinterpret_cast(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, // // 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, static void process_subcommand_forward_check_size(shared_ptr, shared_ptr l, shared_ptr 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, shared_ptr l, shared_ptr 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, shared_ptr l, shared_ptr 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, shared_ptr l, shared_ptr 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, shared_ptr l, shared_ptr 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, shared_ptr l, shared_ptr 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, shared_ptr, shared_ptr, 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, shared_ptr, shared_ptr, 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, // for more information on flags. The maximum size is not enforced if it's zero. typedef void (*subcommand_handler_t)(shared_ptr s, shared_ptr l, shared_ptr 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 s, shared_ptr l, - shared_ptr 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 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(data[0]); + subcommand_handlers[which](s, l, c, command, flag, data); } bool subcommand_is_implemented(uint8_t which) { diff --git a/src/ReceiveSubcommands.hh b/src/ReceiveSubcommands.hh index 070ad097..60f19a46 100644 --- a/src/ReceiveSubcommands.hh +++ b/src/ReceiveSubcommands.hh @@ -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 s, std::shared_ptr l, std::shared_ptr 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); diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 4f3ea6d1..a65d2974 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -598,42 +598,45 @@ void send_card_search_result( -void send_guild_card_gc(shared_ptr c, shared_ptr source) { - S_SendGuildCard_GC cmd; +template +void send_guild_card_pc_gc(shared_ptr c, shared_ptr 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 c, shared_ptr 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 c, shared_ptr source) { - if (c->version == GameVersion::GC) { - send_guild_card_gc(c, source); + if (c->version == GameVersion::PC) { + send_guild_card_pc_gc(c, source); + } else if (c->version == GameVersion::GC) { + send_guild_card_pc_gc(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 l, shared_ptr c) { // notifies other players of a dropped item from a box or enemy void send_drop_item(shared_ptr 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 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 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 l, const ItemData& item, // notifies other players that an item was picked up void send_pick_up_item(shared_ptr l, shared_ptr 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 l, shared_ptr c, // creates an item in a player's inventory (used for withdrawing items from the bank) void send_create_inventory_item(shared_ptr l, shared_ptr 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 l, shared_ptr c, // destroys an item void send_destroy_item(shared_ptr l, shared_ptr 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 c) { &c->player.bank.items[c->player.bank.num_items]); uint32_t checksum = random_object(); - 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 c) { // sends the player a shop's contents void send_shop(shared_ptr 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 l, shared_ptr 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 l, shared_ptr 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 c) { } void send_change_event(shared_ptr 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 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 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); + } } diff --git a/src/ServerState.cc b/src/ServerState.cc index ce61fbd7..8156c7f7 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -197,6 +197,15 @@ uint32_t ServerState::connect_address_for_client(std::shared_ptr c) { +shared_ptr> 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& ServerState::proxy_destinations_menu_for_version(GameVersion version) { if (version == GameVersion::PC) { return this->proxy_destinations_menu_pc; diff --git a/src/ServerState.hh b/src/ServerState.hh index ddbac7e1..de797f8d 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -53,7 +53,8 @@ struct ServerState { std::shared_ptr license_manager; std::vector main_menu; - std::shared_ptr> information_menu; + std::shared_ptr> information_menu_pc; + std::shared_ptr> information_menu_gc; std::shared_ptr> information_contents; std::vector proxy_destinations_menu_pc; std::vector proxy_destinations_menu_gc; @@ -98,6 +99,7 @@ struct ServerState { uint32_t connect_address_for_client(std::shared_ptr c); + std::shared_ptr> information_menu_for_version(GameVersion version); const std::vector& proxy_destinations_menu_for_version(GameVersion version); const std::vector>& proxy_destinations_for_version(GameVersion version); diff --git a/src/Text.hh b/src/Text.hh index 9764dff8..86a1c1c8 100644 --- a/src/Text.hh +++ b/src/Text.hh @@ -386,6 +386,7 @@ template void remove_language_marker_inplace(ptext& 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; }