bring subcommand abstraction in line with main command abstraction
This commit is contained in:
+1
-1
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user