Files
psopeeps-newserv/ReceiveSubcommands.cc
T
Martin Michelsen 15fb00f7df fix ubuntu build
2019-10-16 17:49:57 -07:00

1152 lines
38 KiB
C++

#include "ReceiveSubcommands.hh"
#include <string.h>
#include <memory>
#include <phosg/Strings.hh>
#include "Client.hh"
#include "Lobby.hh"
#include "Player.hh"
#include "PSOProtocol.hh"
#include "SendCommands.hh"
#include "Text.hh"
#include "Items.hh"
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;
};
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 %zX bytes, got %zX 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 %zX bytes, got %zX bytes)",
max_size, size));
}
}
bool command_is_private(uint8_t command) {
// TODO: are either of the ep3 commands 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) {
if (command_is_private(command)) {
if (flag >= l->max_clients) {
return;
}
auto target = l->clients[flag];
if (!target) {
return;
}
send_command(target, command, flag, p, count * 4);
} else {
// TODO: don't send the command back to the client it originated from
send_command(l, command, flag, p, count * 4);
}
}
////////////////////////////////////////////////////////////////////////////////
// Chat commands and the like
// client requests to send a guild card
static void process_subcommand_send_guild_card(shared_ptr<ServerState> s,
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)) {
return;
}
if (c->version == GameVersion::GC) {
if (count < 0x25) {
return;
}
decode_sjis(c->player.guild_card_desc,
reinterpret_cast<const char*>(&p[9].byte[0]), 0x58);
}
send_guild_card(l->clients[flag], c);
}
// client sends a symbol chat
static void process_subcommand_symbol_chat(shared_ptr<ServerState> s,
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 (!c->can_chat || (p->byte[1] != count) || (p->byte[1] < 2) ||
(p[1].byte[0] != c->lobby_client_id)) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
}
// client sends a word select chat
static void process_subcommand_word_select(shared_ptr<ServerState> s,
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);
if (!c->can_chat || (p->byte[1] != count) || (p->byte[1] < 8) ||
(p->byte[2] != c->lobby_client_id)) {
return;
}
// TODO: bring this back if it turns out to be important; I suspect it's not
//p->byte[2] = p->byte[3] = p->byte[(c->version == GameVersion::BB) ? 2 : 3];
for (size_t x = 1; x < 8; x++) {
if ((p[x].word[0] > 0x1863) && (p[x].word[0] != 0xFFFF)) {
return;
}
if ((p[x].word[1] > 0x1863) && (p[x].word[1] != 0xFFFF)) {
return;
}
}
forward_subcommand(l, c, command, flag, p, count);
}
////////////////////////////////////////////////////////////////////////////////
// Game commands used by cheat mechanisms
// need to process changing areas since we keep track of where players are
static void process_subcommand_change_area(shared_ptr<ServerState> s,
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)) {
return;
}
c->area = p[1].dword;
forward_subcommand(l, c, command, flag, p, count);
}
// 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> s,
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[2] != c->lobby_client_id)) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
if ((l->flags & LobbyFlag::CheatsEnabled) && c->infinite_hp) {
send_player_stats_change(l, c, PlayerStatsChange::AddHP, 1020);
}
}
// when a player casts a tech, restore TP if infinite TP is enabled
static void process_subcommand_use_technique(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->is_game() || (p->byte[1] != count) || (p->byte[2] != c->lobby_client_id)) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
if ((l->flags & LobbyFlag::CheatsEnabled) && c->infinite_hp) {
send_player_stats_change(l, c, PlayerStatsChange::AddTP, 255);
}
}
////////////////////////////////////////////////////////////////////////////////
// BB Item commands
// player drops an item
static void process_subcommand_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) {
if (l->version == GameVersion::BB) {
check_size(count, 6);
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;
};
auto* cmd = reinterpret_cast<const Cmd*>(p);
if ((cmd->size != 6) || (cmd->client_id != c->lobby_client_id)) {
return;
}
PlayerInventoryItem item;
{
rw_guard g(c->lock, true);
c->player.remove_item(cmd->item_id, 0, &item);
}
// note: this locks the lobby itself; we don't need to manually do it
l->add_item(item);
}
forward_subcommand(l, c, command, flag, p, count);
}
// player splits a stack and drops part of it
static void process_subcommand_drop_stacked_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) {
if (l->version == GameVersion::BB) {
check_size(count, 6);
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;
};
auto* cmd = reinterpret_cast<const Cmd*>(p);
if (!l->is_game() || (cmd->size != 6) || (cmd->client_id != c->lobby_client_id)) {
return;
}
PlayerInventoryItem item;
{
rw_guard g(c->lock, true);
c->player.remove_item(cmd->item_id, cmd->amount, &item);
}
// if a stack was split, the original item still exists, so the dropped item
// needs a new ID. remove_item signals this by returning an item with id=-1
if (item.data.item_id == 0xFFFFFFFF) {
item.data.item_id = l->generate_item_id(c->lobby_client_id);
}
// note: this locks the lobby itself; we don't need to manually do it
l->add_item(item);
send_drop_stacked_item(l, c, item.data, cmd->area, cmd->x, cmd->y);
} else {
forward_subcommand(l, c, command, flag, p, count);
}
}
// player requests to pick up an item
static void process_subcommand_pick_up_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) {
if (l->version == GameVersion::BB) {
check_size(count, 3);
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];
};
auto* cmd = reinterpret_cast<const Cmd*>(p);
if (!l->is_game() || (cmd->size != 3) || (cmd->client_id != c->lobby_client_id)) {
return;
}
PlayerInventoryItem item;
l->remove_item(cmd->item_id, &item);
{
rw_guard g(c->lock, true);
c->player.add_item(item);
}
send_pick_up_item(l, c, item.data.item_id, cmd->area);
} else {
forward_subcommand(l, c, command, flag, p, count);
}
}
// player equips an item
static void process_subcommand_equip_unequip_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) {
if (l->version == GameVersion::BB) {
check_size(count, 3);
auto* cmd = reinterpret_cast<const ItemSubcommand*>(p);
if ((cmd->size != 3) || (cmd->client_id != c->lobby_client_id)) {
return;
}
rw_guard g(c->lock, true);
size_t index = c->player.inventory.find_item(cmd->item_id);
if (cmd->command == 0x25) {
c->player.inventory.items[index].game_flags |= 0x00000008; // equip
} else {
c->player.inventory.items[index].game_flags &= 0xFFFFFFF7; // unequip
}
} else {
forward_subcommand(l, c, command, flag, p, count);
}
}
static void process_subcommand_use_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) {
if (l->version == GameVersion::BB) {
check_size(count, 2);
auto* cmd = reinterpret_cast<const ItemSubcommand*>(p);
if ((cmd->size != 2) || (cmd->client_id != c->lobby_client_id)) {
return;
}
rw_guard g(c->lock, true);
size_t index = c->player.inventory.find_item(cmd->item_id);
if (cmd->command == 0x25) {
c->player.inventory.items[index].game_flags |= 0x00000008; // equip
} else {
c->player.inventory.items[index].game_flags &= 0xFFFFFFF7; // unequip
}
player_use_item_locked(l, c, index);
}
forward_subcommand(l, c, command, flag, p, count);
}
static void process_subcommand_open_bank(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) && l->is_game()) {
send_bank(c);
}
}
// player performs some bank action
static void process_subcommand_bank_action(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, 4);
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;
};
auto* cmd = reinterpret_cast<const Cmd*>(p);
if (!l->is_game() || (cmd->size != 4)) {
return;
}
rw_guard g(c->lock, true);
if (cmd->action == 0) { // deposit
if (cmd->item_id == 0xFFFFFFFF) { // meseta
if (cmd->meseta_amount > c->player.disp.meseta) {
return;
}
if ((c->player.bank.meseta + cmd->meseta_amount) > 999999) {
return;
}
c->player.bank.meseta += cmd->meseta_amount;
c->player.disp.meseta -= cmd->meseta_amount;
} else { // item
PlayerInventoryItem item;
c->player.remove_item(cmd->item_id, cmd->item_amount, &item);
c->player.bank.add_item(item.to_bank_item());
send_destroy_item(l, c, cmd->item_id, cmd->item_amount);
}
} else if (cmd->action == 1) { // take
if (cmd->item_id == 0xFFFFFFFF) { // meseta
if (cmd->meseta_amount > c->player.bank.meseta) {
return;
}
if ((c->player.disp.meseta + cmd->meseta_amount) > 999999) {
return;
}
c->player.bank.meseta -= cmd->meseta_amount;
c->player.disp.meseta += cmd->meseta_amount;
} else { // item
PlayerBankItem bank_item;
c->player.bank.remove_item(cmd->item_id, cmd->item_amount, &bank_item);
PlayerInventoryItem item = bank_item.to_inventory_item();
item.data.item_id = l->generate_item_id(0xFFFFFFFF);
c->player.add_item(item);
send_create_inventory_item(l, c, item.data);
}
}
}
}
// player sorts the items in their inventory
static void process_subcommand_sort_inventory(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, 31);
struct Cmd {
uint8_t command;
uint8_t size;
uint16_t unused;
uint32_t item_ids[30];
};
auto* cmd = reinterpret_cast<const Cmd*>(p);
if (cmd->size != 31) {
return;
}
PlayerInventory sorted;
memset(&sorted, 0, sizeof(PlayerInventory));
rw_guard g(c->lock, true);
for (size_t x = 0; x < 30; x++) {
if (cmd->item_ids[x] == 0xFFFFFFFF) {
sorted.items[x].data.item_id = 0xFFFFFFFF;
} else {
size_t index = c->player.inventory.find_item(cmd->item_ids[x]);
sorted.items[x] = c->player.inventory.items[index];
}
}
sorted.num_items = c->player.inventory.num_items;
sorted.hp_materials_used = c->player.inventory.hp_materials_used;
sorted.tp_materials_used = c->player.inventory.tp_materials_used;
sorted.language = c->player.inventory.language;
c->player.inventory = sorted;
}
}
////////////////////////////////////////////////////////////////////////////////
// BB EXP/Drop Item commands
// 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) {
if (l->version == GameVersion::BB) {
check_size(count, 6);
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];
};
auto* cmd = reinterpret_cast<const Cmd*>(p);
if ((cmd->size != 6) || !l->is_game()) {
return;
}
PlayerInventoryItem item;
memset(&item, 0, sizeof(PlayerInventoryItem));
bool is_rare = false;
if (l->next_drop_item.data.item_data1d[0]) {
item = l->next_drop_item;
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 (is_rare) {
memcpy(&item.data.item_data1d, l->rare_item_set->rares[cmd->monster_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
}
} else {
try {
item.data = s->common_item_creator->create_item(false, l->episode,
l->difficulty, cmd->area, l->section_id);
} catch (const out_of_range&) {
// create_common_item throws this when it doesn't want to make an item
return;
}
}
}
item.data.item_id = l->generate_item_id(0xFFFFFFFF);
l->add_item(item);
send_drop_item(l, item.data, false, cmd->area, cmd->x, cmd->y,
cmd->request_id);
}
}
// 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) {
if (l->version == GameVersion::BB) {
check_size(count, 10);
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];
};
auto* cmd = reinterpret_cast<const Cmd*>(p);
if ((cmd->size != 10) || !l->is_game()) {
return;
}
PlayerInventoryItem item;
memset(&item, 0, sizeof(PlayerInventoryItem));
bool is_rare = false;
if (l->next_drop_item.data.item_data1d[0]) {
item = l->next_drop_item;
l->next_drop_item.data.item_data1d[0] = 0;
} else {
size_t index;
if (l->rare_item_set) {
for (index = 0; index < 30; index++) {
if (l->rare_item_set->box_areas[index] != cmd->area) {
continue;
}
if (sample_rare_item(l->rare_item_set->box_rares[index].probability)) {
is_rare = true;
break;
}
}
}
if (is_rare) {
memcpy(item.data.item_data1, l->rare_item_set->box_rares[index].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
}
} else {
try {
item.data = s->common_item_creator->create_item(true, l->episode,
l->difficulty, cmd->area, l->section_id);
} catch (const out_of_range&) {
// create_common_item throws this when it doesn't want to make an item
return;
}
}
}
item.data.item_id = l->generate_item_id(0xFFFFFFFF);
l->add_item(item);
send_drop_item(l, item.data, false, cmd->area, cmd->x, cmd->y,
cmd->request_id);
}
}
// monster hit by player
static void process_subcommand_monster_hit(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, 10);
struct Cmd {
uint8_t command;
uint8_t size;
uint16_t enemy_id2;
uint16_t enemy_id;
uint16_t damage;
uint32_t flags;
};
auto* cmd = reinterpret_cast<const Cmd*>(p);
if (cmd->size != 10) {
return;
}
if (cmd->enemy_id >= l->enemies.size()) {
return;
}
rw_guard g(l->lock, true);
if (l->enemies[cmd->enemy_id].hit_flags & 0x80) {
return;
}
l->enemies[cmd->enemy_id].hit_flags |= (1 << c->lobby_client_id);
l->enemies[cmd->enemy_id].last_hit = c->lobby_client_id;
}
forward_subcommand(l, c, command, flag, p, count);
}
// monster killed by player
static void process_subcommand_monster_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);
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;
};
auto* cmd = reinterpret_cast<const Cmd*>(p);
if (!l->is_game() || (cmd->size != 3) || (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;
}
rw_guard g(l->lock, true);
auto& enemy = l->enemies[cmd->enemy_id];
enemy.hit_flags |= 0x80;
for (size_t x = 0; x < l->max_clients; x++) {
if (!((enemy.hit_flags >> x) & 1)) {
continue; // player did not hit this enemy
}
auto other_c = l->clients[x];
if (!other_c) {
continue; // no player
}
if (other_c->player.disp.level >= 199) {
continue; // player is level 200 or higher
}
// killer gets full experience, others get 77%
uint32_t exp;
if (enemy.last_hit == other_c->lobby_client_id) {
exp = enemy.experience;
} else {
exp = ((enemy.experience * 77) / 100);
}
rw_guard g(other_c->lock, true);
other_c->player.disp.experience += exp;
send_give_experience(l, other_c, exp);
bool leveled_up = false;
do {
const auto& level = s->level_table->stats_for_level(
other_c->player.disp.char_class, other_c->player.disp.level + 1);
if (other_c->player.disp.experience >= level.experience) {
leveled_up = true;
level.apply(other_c->player.disp.stats);
other_c->player.disp.level++;
} else {
break;
}
} while (other_c->player.disp.level < 199);
if (leveled_up) {
send_level_up(l, other_c);
}
}
}
}
// destroy item (sent when there are too many items on the ground)
static void process_subcommand_destroy_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) {
if (l->version == GameVersion::BB) {
check_size(count, 3);
auto* cmd = reinterpret_cast<const ItemSubcommand*>(p);
if ((cmd->size != 3) || !l->is_game()) {
return;
}
l->remove_item(cmd->item_id, NULL);
}
forward_subcommand(l, c, command, flag, p, count);
}
// player requests to tekk an item
static void process_subcommand_identify_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) {
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)) {
return;
}
rw_guard g(c->lock, true);
size_t x = c->player.inventory.find_item(cmd->item_id);
if (c->player.inventory.items[x].data.item_data1[0] != 0) {
return; // only weapons can be identified
}
c->player.disp.meseta -= 100;
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);
} else {
forward_subcommand(l, c, command, flag, p, count);
}
}
// player accepts the tekk
// TODO: I don't know which subcommand id this is; the function should be
// 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) {
//
// if (l->version == GameVersion::BB) {
// check_size(count, 3);
//
// auto* cmd = reinterpret_cast<const ItemSubcommand*>(p);
// if ((cmd->size != 3) || (cmd->client_id != c->lobby_client_id)) {
// return;
// }
//
// size_t x = c->player.inventory.find_item(cmd->item_id);
// c->player.inventory.items[x] = c->player.identify_result;
// // TODO: what do we send to the other clients? anything?
//
// } else {
// forward_subcommand(l, c, command, flag, p, count);
// }
// }
////////////////////////////////////////////////////////////////////////////////
static void process_subcommand_forward_check_size(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 (p->byte[1] != count) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
}
static void process_subcommand_forward_check_game(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->is_game()) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
}
static void process_subcommand_forward_check_game_loading(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->is_game() || !l->any_client_loading()) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
}
static void process_subcommand_forward_check_size_client(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 ((p->byte[1] != count) || (p->byte[2] != c->lobby_client_id)) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
}
static void process_subcommand_forward_check_size_game(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->is_game() || (p->byte[1] != count)) {
return;
}
forward_subcommand(l, c, command, flag, p, count);
}
// used for invalid commands. normally, clients should be disconnected - to restore this behavior, change the return value back to SUBCOMMAND_ERROR_INVALID_COMMAND.
static void process_subcommand_invalid(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 (command_is_private(command)) {
log(WARNING, "invalid subcommand: %02X (%d of them) (private to player %d)",
p->byte[0], count, flag);
} else {
log(WARNING, "invalid subcommand: %02X (%d of them) (public)",
p->byte[0], count);
}
}
// used when an error occurs (unknown commands, etc). normally, clients should be disconnected - to restore this behavior, change the return value back to SUBCOMMAND_ERROR_INVALID_COMMAND.
static void process_subcommand_unimplemented(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 (command_is_private(command)) {
log(WARNING, "unknown subcommand: %02X (%d of them) (private to player %d)",
p->byte[0], count, flag);
} else {
log(WARNING, "unknown subcommand: %02X (%d of them) (public)",
p->byte[0], count);
}
}
////////////////////////////////////////////////////////////////////////////////
// Subcommands are described by four fields: the minimum size and maximum size (in DWORDs),
// the handler function, and flags that tell when to allow the command. See command-input-subs.h
// 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);
subcommand_handler_t subcommand_handlers[0x100] = {
// 00
process_subcommand_invalid,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_forward_check_size_game,
process_subcommand_send_guild_card,
process_subcommand_symbol_chat,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_monster_hit,
process_subcommand_forward_check_size_game,
process_subcommand_forward_check_size_game,
process_subcommand_forward_check_size,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
// 10
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_forward_check_size_game,
process_subcommand_forward_check_size_game,
process_subcommand_forward_check_size_game,
process_subcommand_forward_check_size_game,
process_subcommand_unimplemented,
process_subcommand_forward_check_size_game,
process_subcommand_forward_check_size_game,
process_subcommand_forward_check_size_game,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_forward_check_size,
// 20
process_subcommand_forward_check_size,
process_subcommand_change_area,
process_subcommand_forward_check_size_client,
process_subcommand_forward_check_size_client,
process_subcommand_forward_check_size_game,
process_subcommand_equip_unequip_item,
process_subcommand_equip_unequip_item,
process_subcommand_use_item,
process_subcommand_unimplemented,
process_subcommand_forward_check_size_game,
process_subcommand_drop_item,
process_subcommand_unimplemented,
process_subcommand_forward_check_size,
process_subcommand_forward_check_size_game,
process_subcommand_unimplemented,
process_subcommand_hit_by_monster,
// 30
process_subcommand_unimplemented,
process_subcommand_forward_check_size_game,
process_subcommand_forward_check_size_game,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_forward_check_game,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_forward_check_size_game,
process_subcommand_forward_check_size,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_forward_check_size,
process_subcommand_forward_check_size,
// 40
process_subcommand_forward_check_size,
process_subcommand_unimplemented,
process_subcommand_forward_check_size,
process_subcommand_forward_check_size_client,
process_subcommand_forward_check_size_client,
process_subcommand_forward_check_size_client,
process_subcommand_forward_check_size_client,
process_subcommand_forward_check_size_client,
process_subcommand_use_technique,
process_subcommand_forward_check_size_client,
process_subcommand_forward_check_size_client,
process_subcommand_hit_by_monster,
process_subcommand_hit_by_monster,
process_subcommand_forward_check_size_client,
process_subcommand_forward_check_size_client,
process_subcommand_forward_check_size_client,
// 50
process_subcommand_forward_check_size_client,
process_subcommand_unimplemented,
process_subcommand_forward_check_size,
process_subcommand_forward_check_size_game,
process_subcommand_unimplemented,
process_subcommand_forward_check_size_client,
process_subcommand_forward_check_size_client,
process_subcommand_forward_check_size_client,
process_subcommand_unimplemented,
process_subcommand_forward_check_size_game,
process_subcommand_pick_up_item,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
// 60
process_subcommand_enemy_drop_item,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_destroy_item,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_forward_check_size_game,
process_subcommand_forward_check_size_game,
process_subcommand_unimplemented,
process_subcommand_forward_check_size_game,
process_subcommand_forward_check_game_loading,
process_subcommand_forward_check_game_loading,
process_subcommand_forward_check_game_loading,
process_subcommand_forward_check_game_loading,
process_subcommand_unimplemented,
// 70
process_subcommand_unimplemented,
process_subcommand_forward_check_game_loading,
process_subcommand_unimplemented,
process_subcommand_invalid,
process_subcommand_word_select,
process_subcommand_forward_check_size_game,
process_subcommand_forward_check_size_game,
process_subcommand_forward_check_size,
process_subcommand_unimplemented,
process_subcommand_forward_check_size,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_forward_check_size_game,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
// 80
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_forward_check_size_game,
process_subcommand_forward_check_size_game,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_forward_check_size_game,
process_subcommand_forward_check_size_game,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_forward_check_size_client,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
// 90
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
// A0
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_box_drop_item,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_forward_check_size_client,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_forward_check_size_client,
process_subcommand_forward_check_size_client,
// B0
process_subcommand_forward_check_size_client,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_identify_item,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_open_bank,
process_subcommand_unimplemented,
process_subcommand_bank_action,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
// C0
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_drop_stacked_item,
process_subcommand_sort_inventory,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_monster_killed,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_forward_check_size_game,
// D0
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
// E0
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
// F0
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
process_subcommand_unimplemented,
};
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);
}