handle incorrect mag endianness in PSO GC
This commit is contained in:
+15
-19
@@ -2483,7 +2483,7 @@ struct SC_TradeItems_D0_D3 { // D0 when sent by client, D3 when sent by server
|
||||
// Note: PSO GC sends uninitialized data in the unused entries of this
|
||||
// command. newserv parses and regenerates the item data when sending D3,
|
||||
// which effectively erases the uninitialized data.
|
||||
parray<ItemData, 0x20> items;
|
||||
parray<ItemData, 0x20> item_datas;
|
||||
} __packed__;
|
||||
|
||||
// D1 (S->C): Advance trade state (V3/BB)
|
||||
@@ -3750,11 +3750,10 @@ struct G_DropItem_6x2A {
|
||||
|
||||
struct G_CreateInventoryItem_DC_6x2B {
|
||||
G_ClientIDHeader header;
|
||||
ItemData item;
|
||||
ItemData item_data;
|
||||
} __packed__;
|
||||
|
||||
struct G_CreateInventoryItem_PC_V3_BB_6x2B {
|
||||
G_CreateInventoryItem_DC_6x2B basic_cmd;
|
||||
struct G_CreateInventoryItem_PC_V3_BB_6x2B : G_CreateInventoryItem_DC_6x2B {
|
||||
uint8_t unused1;
|
||||
uint8_t unknown_a2;
|
||||
le_uint16_t unused2;
|
||||
@@ -4123,11 +4122,10 @@ struct G_DropStackedItem_DC_6x5D {
|
||||
le_uint16_t unused2;
|
||||
le_float x;
|
||||
le_float z;
|
||||
ItemData data;
|
||||
ItemData item_data;
|
||||
} __packed__;
|
||||
|
||||
struct G_DropStackedItem_PC_V3_BB_6x5D {
|
||||
G_DropStackedItem_DC_6x5D basic_cmd;
|
||||
struct G_DropStackedItem_PC_V3_BB_6x5D : G_DropStackedItem_DC_6x5D {
|
||||
le_uint32_t unused3;
|
||||
} __packed__;
|
||||
|
||||
@@ -4135,7 +4133,7 @@ struct G_DropStackedItem_PC_V3_BB_6x5D {
|
||||
|
||||
struct G_BuyShopItem_6x5E {
|
||||
G_ClientIDHeader header;
|
||||
ItemData item;
|
||||
ItemData item_data;
|
||||
} __packed__;
|
||||
|
||||
// 6x5F: Drop item from box/enemy
|
||||
@@ -4149,11 +4147,10 @@ struct G_DropItem_DC_6x5F {
|
||||
le_float z;
|
||||
le_uint16_t unknown_a1;
|
||||
le_uint16_t unknown_a2;
|
||||
ItemData data;
|
||||
ItemData item_data;
|
||||
} __packed__;
|
||||
|
||||
struct G_DropItem_PC_V3_BB_6x5F {
|
||||
G_DropItem_DC_6x5F basic_cmd;
|
||||
struct G_DropItem_PC_V3_BB_6x5F : G_DropItem_DC_6x5F {
|
||||
le_uint32_t unused3;
|
||||
} __packed__;
|
||||
|
||||
@@ -4307,10 +4304,9 @@ struct G_SyncItemState_6x6D_Decompressed {
|
||||
// item is dropped. The last item dropped in each area has drop_number equal
|
||||
// to total_items_dropped_per_area[area - 1] - 1.
|
||||
le_uint16_t drop_number;
|
||||
ItemData data;
|
||||
ItemData item_data;
|
||||
} __packed__;
|
||||
// Variable-length field follows:
|
||||
// FloorItem items[sum(floor_item_count_per_area)];
|
||||
FloorItem items[0]; // sum(floor_item_count_per_area) of them, actually
|
||||
} __packed__;
|
||||
|
||||
// 6x6E: Sync flag state (used while loading into game)
|
||||
@@ -4340,7 +4336,7 @@ struct G_Unknown_6x6F {
|
||||
// Annoyingly, they didn't use the same format as the 65/67/68 commands here,
|
||||
// and instead rearranged a bunch of things.
|
||||
|
||||
struct G_SyncPlayerDispAndInventory_6x70 {
|
||||
struct G_SyncPlayerDispAndInventory_V3_6x70 {
|
||||
G_ExtendedHeader<G_UnusedHeader> header;
|
||||
// Offsets in this struct are relative to the overall command header
|
||||
/* 000C */ parray<le_uint16_t, 2> unknown_a1;
|
||||
@@ -5025,7 +5021,7 @@ struct G_ShopContents_BB_6xB6 {
|
||||
uint8_t num_items;
|
||||
le_uint16_t unused;
|
||||
// Note: data2d of these entries should be the price
|
||||
ItemData entries[20];
|
||||
ItemData item_datas[20];
|
||||
} __packed__;
|
||||
|
||||
// 6xB7: Alias for 6xB3 (Episode 3 Trial Edition)
|
||||
@@ -5062,7 +5058,7 @@ struct G_AcceptItemIdentification_BB_6xB8 {
|
||||
|
||||
struct G_IdentifyResult_BB_6xB9 {
|
||||
G_ClientIDHeader header;
|
||||
ItemData item;
|
||||
ItemData item_data;
|
||||
} __packed__;
|
||||
|
||||
// 6xBA: Sync card trade state (Episode 3)
|
||||
@@ -5165,7 +5161,7 @@ struct G_SoundChat_GC_Ep3_6xBE {
|
||||
|
||||
struct G_CreateInventoryItem_BB_6xBE {
|
||||
G_ClientIDHeader header;
|
||||
ItemData item;
|
||||
ItemData item_data;
|
||||
le_uint32_t unused;
|
||||
} __packed__;
|
||||
|
||||
@@ -5344,7 +5340,7 @@ struct G_SetEXPMultiplier_BB_6xDD {
|
||||
|
||||
struct G_Unknown_BB_6xE3 {
|
||||
G_ClientIDHeader header;
|
||||
ItemData unknown_a1;
|
||||
ItemData item_data;
|
||||
} __packed__;
|
||||
|
||||
// 6xE4: Invalid subcommand
|
||||
|
||||
@@ -314,7 +314,7 @@ void BattleRecordPlayer::schedule_events() {
|
||||
|
||||
if (this->event_it == this->record->events.end()) {
|
||||
if (relative_ts >= this->record->battle_end_timestamp) {
|
||||
// If the record is complete and the end timestamp has been reached, so
|
||||
// If the record is complete and the end timestamp has been reached,
|
||||
// send exit commands to all players in the lobby, and don't reschedule
|
||||
// the event (it will be deleted along with the Player when the lobby is
|
||||
// destroyed, when the last client leaves)
|
||||
|
||||
@@ -41,6 +41,12 @@ void ItemData::clear() {
|
||||
this->data2d = 0;
|
||||
}
|
||||
|
||||
void ItemData::bswap_data2_if_mag() {
|
||||
if (this->data1[0] == 0x02) {
|
||||
this->data2d = bswap32(this->data2d);
|
||||
}
|
||||
}
|
||||
|
||||
bool ItemData::empty() const {
|
||||
return (this->data1d[0] == 0) &&
|
||||
(this->data1d[1] == 0) &&
|
||||
|
||||
@@ -78,6 +78,10 @@ struct ItemData { // 0x14 bytes
|
||||
// W = photon blasts
|
||||
// Y = mag synchro
|
||||
// Z = item ID
|
||||
// Note: PSO GC erroneously byteswaps data2 even when the item is a mag. This
|
||||
// makes it incompatible with little-endian versions of PSO (i.e. all other
|
||||
// versions). We manually byteswap data2 upon receipt and immediately before
|
||||
// sending where needed.
|
||||
|
||||
union {
|
||||
parray<uint8_t, 12> data1;
|
||||
@@ -101,6 +105,8 @@ struct ItemData { // 0x14 bytes
|
||||
|
||||
void clear();
|
||||
|
||||
void bswap_data2_if_mag();
|
||||
|
||||
std::string hex() const;
|
||||
std::string name(bool include_color_codes) const;
|
||||
uint32_t primary_identifier() const;
|
||||
|
||||
+6
-1
@@ -522,10 +522,15 @@ void ClientGameData::import_player(const PSOPlayerDataDCPC& pd) {
|
||||
// auto_reply = pd.auto_reply;
|
||||
}
|
||||
|
||||
void ClientGameData::import_player(const PSOPlayerDataV3& gc) {
|
||||
void ClientGameData::import_player(const PSOPlayerDataV3& gc, bool is_gc) {
|
||||
auto account = this->account();
|
||||
auto player = this->player();
|
||||
player->inventory = gc.inventory;
|
||||
if (is_gc) {
|
||||
for (size_t z = 0; z < 30; z++) {
|
||||
player->inventory.items[z].data.bswap_data2_if_mag();
|
||||
}
|
||||
}
|
||||
player->disp = gc.disp.to_bb();
|
||||
player->info_board = gc.info_board;
|
||||
account->blocked_senders = gc.blocked_senders;
|
||||
|
||||
+1
-1
@@ -524,7 +524,7 @@ public:
|
||||
void save_player_data();
|
||||
|
||||
void import_player(const PSOPlayerDataDCPC& pd);
|
||||
void import_player(const PSOPlayerDataV3& pd);
|
||||
void import_player(const PSOPlayerDataV3& pd, bool is_gc);
|
||||
void import_player(const PSOPlayerDataBB& pd);
|
||||
// Note: this function is not const because it can cause player and account
|
||||
// data to be loaded
|
||||
|
||||
@@ -2461,7 +2461,7 @@ static void on_61_98(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
pd = &check_size_t<PSOPlayerDataV3>(data,
|
||||
sizeof(PSOPlayerDataV3) + c->game_data.player()->auto_reply.bytes());
|
||||
}
|
||||
c->game_data.import_player(*pd);
|
||||
c->game_data.import_player(*pd, c->version() == GameVersion::GC);
|
||||
break;
|
||||
}
|
||||
case GameVersion::BB: {
|
||||
@@ -3442,7 +3442,7 @@ static void on_6F(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
|
||||
static void on_D0_V3_BB(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) {
|
||||
auto& cmd = check_size_t<SC_TradeItems_D0_D3>(data);
|
||||
const auto& cmd = check_size_t<SC_TradeItems_D0_D3>(data);
|
||||
|
||||
if (c->game_data.pending_item_trade) {
|
||||
throw runtime_error("player started a trade when one is already pending");
|
||||
@@ -3463,7 +3463,10 @@ static void on_D0_V3_BB(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
c->game_data.pending_item_trade.reset(new PendingItemTrade());
|
||||
c->game_data.pending_item_trade->other_client_id = cmd.target_client_id;
|
||||
for (size_t x = 0; x < cmd.item_count; x++) {
|
||||
c->game_data.pending_item_trade->items.emplace_back(cmd.items[x]);
|
||||
auto& item = c->game_data.pending_item_trade->items.emplace_back(cmd.item_datas[x]);
|
||||
if (c->version() == GameVersion::GC) {
|
||||
item.bswap_data2_if_mag();
|
||||
}
|
||||
}
|
||||
|
||||
// If the other player has a pending trade as well, assume this is the second
|
||||
|
||||
+226
-26
@@ -131,7 +131,7 @@ static void on_forward_check_game(shared_ptr<ServerState>,
|
||||
forward_subcommand(l, c, command, flag, data, size);
|
||||
}
|
||||
|
||||
static void on_forward_sync_game_state(shared_ptr<ServerState>,
|
||||
static void on_forward_sync_joining_player_state(shared_ptr<ServerState>,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const void* data, size_t size) {
|
||||
if (!l->is_game() || !l->any_client_loading()) {
|
||||
@@ -153,6 +153,137 @@ static void on_forward_sync_game_state(shared_ptr<ServerState>,
|
||||
forward_subcommand(l, c, command, flag, data, size);
|
||||
}
|
||||
|
||||
static void on_sync_joining_player_item_state(shared_ptr<ServerState>,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const void* data, size_t size) {
|
||||
if (!l->is_game() || !l->any_client_loading()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// I'm lazy and this should never happen (this command should always be
|
||||
// private to the joining player)
|
||||
if (!command_is_private(command)) {
|
||||
throw runtime_error("6x70 sent via public command");
|
||||
}
|
||||
|
||||
// For non-V3 versions, just forward the data verbatim. For V3, we need to
|
||||
// byteswap mags' data2 fields if exactly one of the sender and recipient are
|
||||
// PSO GC
|
||||
bool sender_is_gc = (c->version() == GameVersion::GC);
|
||||
if (!sender_is_gc && (c->version() != GameVersion::XB)) {
|
||||
forward_subcommand(l, c, command, flag, data, size);
|
||||
|
||||
} else {
|
||||
if (flag >= l->max_clients) {
|
||||
return;
|
||||
}
|
||||
auto target = l->clients[flag];
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
bool target_is_gc = (target->version() == GameVersion::GC);
|
||||
|
||||
if (target_is_gc == sender_is_gc) {
|
||||
send_command(target, command, flag, data, size);
|
||||
|
||||
} else {
|
||||
const auto& cmd = check_size_t<G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E>(data, size, 0xFFFF);
|
||||
if (cmd.compressed_size > size - sizeof(cmd)) {
|
||||
throw runtime_error("compressed end offset is beyond end of command");
|
||||
}
|
||||
|
||||
string decompressed = bc0_decompress(cmd.data, cmd.compressed_size);
|
||||
if (c->options.debug) {
|
||||
c->log.info("Decompressed item sync data (%" PRIX32 " -> %zX bytes; expected %" PRIX32 "):",
|
||||
cmd.compressed_size.load(), decompressed.size(), cmd.decompressed_size.load());
|
||||
print_data(stderr, decompressed);
|
||||
}
|
||||
|
||||
if (decompressed.size() < sizeof(G_SyncItemState_6x6D_Decompressed)) {
|
||||
throw runtime_error(string_printf(
|
||||
"decompressed 6x6D data (0x%zX bytes) is too short for header (0x%zX bytes)",
|
||||
decompressed.size(), sizeof(G_SyncItemState_6x6D_Decompressed)));
|
||||
}
|
||||
auto* decompressed_cmd = reinterpret_cast<G_SyncItemState_6x6D_Decompressed*>(decompressed.data());
|
||||
|
||||
size_t num_floor_items = 0;
|
||||
for (size_t z = 0; z < decompressed_cmd->floor_item_count_per_area.size(); z++) {
|
||||
num_floor_items += decompressed_cmd->floor_item_count_per_area[z];
|
||||
}
|
||||
|
||||
size_t required_size = sizeof(G_SyncItemState_6x6D_Decompressed) + num_floor_items * sizeof(G_SyncItemState_6x6D_Decompressed::FloorItem);
|
||||
if (decompressed.size() < required_size) {
|
||||
throw runtime_error(string_printf(
|
||||
"decompressed 6x6D data (0x%zX bytes) is too short for all items (0x%zX bytes)",
|
||||
decompressed.size(), required_size));
|
||||
}
|
||||
|
||||
for (size_t z = 0; z < num_floor_items; z++) {
|
||||
decompressed_cmd->items[z].item_data.bswap_data2_if_mag();
|
||||
}
|
||||
|
||||
string out_compressed_data = bc0_compress(decompressed);
|
||||
|
||||
G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E out_cmd;
|
||||
out_cmd.header.basic_header.subcommand = 0x6D;
|
||||
out_cmd.header.basic_header.size = 0x00;
|
||||
out_cmd.header.basic_header.unused = 0x0000;
|
||||
out_cmd.header.size = ((out_compressed_data.size() + sizeof(G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E)) + 3) & (~3);
|
||||
out_cmd.decompressed_size = decompressed.size();
|
||||
out_cmd.compressed_size = out_compressed_data.size();
|
||||
|
||||
// TODO: It'd be nice to not copy the data so many times here.
|
||||
StringWriter out_w;
|
||||
out_w.put<G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E>(out_cmd);
|
||||
out_w.write(out_compressed_data);
|
||||
|
||||
send_command(target, command, flag, out_w.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void on_sync_joining_player_disp_and_inventory(shared_ptr<ServerState>,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const void* data, size_t size) {
|
||||
if (!l->is_game() || !l->any_client_loading()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// I'm lazy and this should never happen (this command should always be
|
||||
// private to the joining player)
|
||||
if (!command_is_private(command)) {
|
||||
throw runtime_error("6x70 sent via public command");
|
||||
}
|
||||
|
||||
// For non-V3 versions, just forward the data verbatim. For V3, we need to
|
||||
// byteswap mags' data2 fields if exactly one of the sender and recipient are
|
||||
// PSO GC
|
||||
bool sender_is_gc = (c->version() == GameVersion::GC);
|
||||
if (!sender_is_gc && (c->version() != GameVersion::XB)) {
|
||||
forward_subcommand(l, c, command, flag, data, size);
|
||||
|
||||
} else {
|
||||
if (flag >= l->max_clients) {
|
||||
return;
|
||||
}
|
||||
auto target = l->clients[flag];
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
bool target_is_gc = (target->version() == GameVersion::GC);
|
||||
|
||||
if (target_is_gc == sender_is_gc) {
|
||||
send_command(target, command, flag, data, size);
|
||||
} else {
|
||||
auto out_cmd = check_size_t<G_SyncPlayerDispAndInventory_V3_6x70>(data, size);
|
||||
for (size_t z = 0; z < 30; z++) {
|
||||
out_cmd.inventory.items[z].data.bswap_data2_if_mag();
|
||||
}
|
||||
send_command_t(target, command, flag, out_cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void on_forward_check_game_loading(shared_ptr<ServerState>,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const void* data, size_t size) {
|
||||
@@ -527,11 +658,32 @@ static void on_player_drop_item(shared_ptr<ServerState>,
|
||||
forward_subcommand(l, c, command, flag, data, size);
|
||||
}
|
||||
|
||||
static void on_create_inventory_item(shared_ptr<ServerState>,
|
||||
template <typename CmdT>
|
||||
void forward_subcommand_with_mag_bswap_t(
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag, const CmdT& cmd) {
|
||||
// I'm lazy and this should never happen for item commands (since all players
|
||||
// need to stay in sync)
|
||||
if (command_is_private(command)) {
|
||||
throw runtime_error("6x2B sent via private command");
|
||||
}
|
||||
|
||||
for (auto& other_c : l->clients) {
|
||||
if (!other_c || other_c == c) {
|
||||
continue;
|
||||
}
|
||||
CmdT out_cmd = cmd;
|
||||
if ((c->version() == GameVersion::GC) != (other_c->version() == GameVersion::GC)) {
|
||||
out_cmd.item_data.bswap_data2_if_mag();
|
||||
}
|
||||
send_command_t(other_c, command, flag, out_cmd);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename CmdT>
|
||||
static void on_create_inventory_item_t(shared_ptr<ServerState>,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_CreateInventoryItem_DC_6x2B>(
|
||||
data, size, sizeof(G_CreateInventoryItem_PC_V3_BB_6x2B));
|
||||
const auto& cmd = check_size_t<CmdT>(data, size);
|
||||
|
||||
if ((cmd.header.client_id != c->lobby_client_id)) {
|
||||
return;
|
||||
@@ -546,28 +698,43 @@ static void on_create_inventory_item(shared_ptr<ServerState>,
|
||||
PlayerInventoryItem item;
|
||||
item.present = 1;
|
||||
item.flags = 0;
|
||||
item.data = cmd.item;
|
||||
item.data = cmd.item_data;
|
||||
if (c->version() == GameVersion::GC) {
|
||||
item.data.bswap_data2_if_mag();
|
||||
}
|
||||
c->game_data.player()->add_item(item);
|
||||
|
||||
auto name = item.data.name(false);
|
||||
l->log.info("Player %hu created inventory item %08" PRIX32 " (%s)",
|
||||
cmd.header.client_id.load(), cmd.item.id.load(), name.c_str());
|
||||
cmd.header.client_id.load(), cmd.item_data.id.load(), name.c_str());
|
||||
if (c->options.debug) {
|
||||
string name = item.data.name(true);
|
||||
send_text_message_printf(c, "$C5CREATE %08" PRIX32 "\n%s",
|
||||
cmd.item.id.load(), name.c_str());
|
||||
cmd.item_data.id.load(), name.c_str());
|
||||
}
|
||||
c->game_data.player()->print_inventory(stderr);
|
||||
}
|
||||
|
||||
forward_subcommand(l, c, command, flag, data, size);
|
||||
forward_subcommand_with_mag_bswap_t(l, c, command, flag, cmd);
|
||||
}
|
||||
|
||||
static void on_drop_partial_stack(shared_ptr<ServerState>,
|
||||
static void on_create_inventory_item(shared_ptr<ServerState> s,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_DropStackedItem_DC_6x5D>(
|
||||
data, size, sizeof(G_DropStackedItem_PC_V3_BB_6x5D));
|
||||
if (size == sizeof(G_CreateInventoryItem_PC_V3_BB_6x2B)) {
|
||||
on_create_inventory_item_t<G_CreateInventoryItem_PC_V3_BB_6x2B>(s, l, c, command, flag, data, size);
|
||||
} else if (size == sizeof(G_CreateInventoryItem_DC_6x2B)) {
|
||||
on_create_inventory_item_t<G_CreateInventoryItem_DC_6x2B>(s, l, c, command, flag, data, size);
|
||||
} else {
|
||||
throw runtime_error("invalid size for 6x2B command");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename CmdT>
|
||||
static void on_drop_partial_stack_t(shared_ptr<ServerState>,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<CmdT>(data, size);
|
||||
|
||||
// TODO: Should we check the client ID here too?
|
||||
if (!l->is_game()) {
|
||||
@@ -583,7 +750,10 @@ static void on_drop_partial_stack(shared_ptr<ServerState>,
|
||||
PlayerInventoryItem item;
|
||||
item.present = 1;
|
||||
item.flags = 0;
|
||||
item.data = cmd.data;
|
||||
item.data = cmd.item_data;
|
||||
if (c->version() == GameVersion::GC) {
|
||||
item.data.bswap_data2_if_mag();
|
||||
}
|
||||
l->add_item(item, cmd.area, cmd.x, cmd.z);
|
||||
|
||||
auto name = item.data.name(false);
|
||||
@@ -598,7 +768,19 @@ static void on_drop_partial_stack(shared_ptr<ServerState>,
|
||||
c->game_data.player()->print_inventory(stderr);
|
||||
}
|
||||
|
||||
forward_subcommand(l, c, command, flag, data, size);
|
||||
forward_subcommand_with_mag_bswap_t(l, c, command, flag, cmd);
|
||||
}
|
||||
|
||||
static void on_drop_partial_stack(shared_ptr<ServerState> s,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const void* data, size_t size) {
|
||||
if (size == sizeof(G_DropStackedItem_PC_V3_BB_6x5D)) {
|
||||
on_drop_partial_stack_t<G_DropStackedItem_PC_V3_BB_6x5D>(s, l, c, command, flag, data, size);
|
||||
} else if (size == sizeof(G_DropStackedItem_DC_6x5D)) {
|
||||
on_drop_partial_stack_t<G_DropStackedItem_DC_6x5D>(s, l, c, command, flag, data, size);
|
||||
} else {
|
||||
throw runtime_error("invalid size for 6x5D command");
|
||||
}
|
||||
}
|
||||
|
||||
static void on_drop_partial_stack_bb(shared_ptr<ServerState>,
|
||||
@@ -665,7 +847,10 @@ static void on_buy_shop_item(shared_ptr<ServerState>,
|
||||
PlayerInventoryItem item;
|
||||
item.present = 1;
|
||||
item.flags = 0;
|
||||
item.data = cmd.item;
|
||||
item.data = cmd.item_data;
|
||||
if (c->version() == GameVersion::GC) {
|
||||
item.data.bswap_data2_if_mag();
|
||||
}
|
||||
c->game_data.player()->add_item(item);
|
||||
|
||||
auto name = item.data.name(false);
|
||||
@@ -679,14 +864,14 @@ static void on_buy_shop_item(shared_ptr<ServerState>,
|
||||
c->game_data.player()->print_inventory(stderr);
|
||||
}
|
||||
|
||||
forward_subcommand(l, c, command, flag, data, size);
|
||||
forward_subcommand_with_mag_bswap_t(l, c, command, flag, cmd);
|
||||
}
|
||||
|
||||
static void on_box_or_enemy_item_drop(shared_ptr<ServerState>,
|
||||
template <typename CmdT>
|
||||
static void on_box_or_enemy_item_drop_t(shared_ptr<ServerState>,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_DropItem_DC_6x5F>(
|
||||
data, size, sizeof(G_DropItem_PC_V3_BB_6x5F));
|
||||
const auto& cmd = check_size_t<CmdT>(data, size);
|
||||
|
||||
if (!l->is_game() || (c->lobby_client_id != l->leader_id)) {
|
||||
return;
|
||||
@@ -699,7 +884,10 @@ static void on_box_or_enemy_item_drop(shared_ptr<ServerState>,
|
||||
PlayerInventoryItem item;
|
||||
item.present = 1;
|
||||
item.flags = 0;
|
||||
item.data = cmd.data;
|
||||
item.data = cmd.item_data;
|
||||
if (c->version() == GameVersion::GC) {
|
||||
item.data.bswap_data2_if_mag();
|
||||
}
|
||||
l->add_item(item, cmd.area, cmd.x, cmd.z);
|
||||
|
||||
auto name = item.data.name(false);
|
||||
@@ -712,7 +900,19 @@ static void on_box_or_enemy_item_drop(shared_ptr<ServerState>,
|
||||
}
|
||||
}
|
||||
|
||||
forward_subcommand(l, c, command, flag, data, size);
|
||||
forward_subcommand_with_mag_bswap_t(l, c, command, flag, cmd);
|
||||
}
|
||||
|
||||
static void on_box_or_enemy_item_drop(shared_ptr<ServerState> s,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const void* data, size_t size) {
|
||||
if (size == sizeof(G_DropItem_DC_6x5F)) {
|
||||
on_box_or_enemy_item_drop_t<G_DropItem_DC_6x5F>(s, l, c, command, flag, data, size);
|
||||
} else if (size == sizeof(G_DropItem_PC_V3_BB_6x5F)) {
|
||||
on_box_or_enemy_item_drop_t<G_DropItem_PC_V3_BB_6x5F>(s, l, c, command, flag, data, size);
|
||||
} else {
|
||||
throw runtime_error("invalid size for 6x5F command");
|
||||
}
|
||||
}
|
||||
|
||||
static void on_pick_up_item(shared_ptr<ServerState>,
|
||||
@@ -1375,7 +1575,7 @@ static void on_identify_item_bb(shared_ptr<ServerState>,
|
||||
res.header.subcommand = 0xB9;
|
||||
res.header.size = sizeof(res) / 4;
|
||||
res.header.client_id = c->lobby_client_id;
|
||||
res.item = c->game_data.identify_result.data;
|
||||
res.item_data = c->game_data.identify_result.data;
|
||||
send_command_t(l, 0x60, 0x00, res);
|
||||
|
||||
} else {
|
||||
@@ -1606,12 +1806,12 @@ subcommand_handler_t subcommand_handlers[0x100] = {
|
||||
/* 6x68 */ on_forward_check_size_game,
|
||||
/* 6x69 */ on_forward_check_size_game,
|
||||
/* 6x6A */ on_forward_check_size_game,
|
||||
/* 6x6B */ on_forward_sync_game_state,
|
||||
/* 6x6C */ on_forward_sync_game_state,
|
||||
/* 6x6D */ on_forward_sync_game_state,
|
||||
/* 6x6E */ on_forward_sync_game_state,
|
||||
/* 6x6B */ on_forward_sync_joining_player_state,
|
||||
/* 6x6C */ on_forward_sync_joining_player_state,
|
||||
/* 6x6D */ on_sync_joining_player_item_state,
|
||||
/* 6x6E */ on_forward_sync_joining_player_state,
|
||||
/* 6x6F */ on_forward_check_game_loading,
|
||||
/* 6x70 */ on_forward_check_game_loading,
|
||||
/* 6x70 */ on_sync_joining_player_disp_and_inventory,
|
||||
/* 6x71 */ on_forward_check_game_loading,
|
||||
/* 6x72 */ on_forward_check_game_loading,
|
||||
/* 6x73 */ on_invalid,
|
||||
|
||||
+78
-34
@@ -1374,23 +1374,31 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
|
||||
if (watched_lobby) {
|
||||
// Live spectating
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
if (watched_lobby->clients[z]) {
|
||||
cmd.players[z].lobby_data.player_tag = 0x00010000;
|
||||
cmd.players[z].lobby_data.guild_card = watched_lobby->clients[z]->license->serial_number;
|
||||
cmd.players[z].lobby_data.client_id = watched_lobby->clients[z]->lobby_client_id;
|
||||
cmd.players[z].lobby_data.name = watched_lobby->clients[z]->game_data.player()->disp.name;
|
||||
remove_language_marker_inplace(cmd.players[z].lobby_data.name);
|
||||
cmd.players[z].inventory = watched_lobby->clients[z]->game_data.player()->inventory;
|
||||
cmd.players[z].disp = watched_lobby->clients[z]->game_data.player()->disp.to_dcpcv3();
|
||||
remove_language_marker_inplace(cmd.players[z].disp.name);
|
||||
cmd.entries[z].player_tag = 0x00010000;
|
||||
cmd.entries[z].guild_card_number = watched_lobby->clients[z]->license->serial_number;
|
||||
cmd.entries[z].name = watched_lobby->clients[z]->game_data.player()->disp.name;
|
||||
remove_language_marker_inplace(cmd.entries[z].name);
|
||||
cmd.entries[z].present = 1;
|
||||
cmd.entries[z].level = watched_lobby->clients[z]->game_data.player()->disp.level.load();
|
||||
player_count++;
|
||||
if (!watched_lobby->clients[z]) {
|
||||
continue;
|
||||
}
|
||||
auto& p = cmd.players[z];
|
||||
p.lobby_data.player_tag = 0x00010000;
|
||||
p.lobby_data.guild_card = watched_lobby->clients[z]->license->serial_number;
|
||||
p.lobby_data.client_id = watched_lobby->clients[z]->lobby_client_id;
|
||||
p.lobby_data.name = watched_lobby->clients[z]->game_data.player()->disp.name;
|
||||
remove_language_marker_inplace(p.lobby_data.name);
|
||||
p.inventory = watched_lobby->clients[z]->game_data.player()->inventory;
|
||||
for (size_t y = 0; y < 30; y++) {
|
||||
p.inventory.items[y].data.bswap_data2_if_mag();
|
||||
}
|
||||
p.disp = watched_lobby->clients[z]->game_data.player()->disp.to_dcpcv3();
|
||||
remove_language_marker_inplace(p.disp.name);
|
||||
|
||||
auto& e = cmd.entries[z];
|
||||
e.player_tag = 0x00010000;
|
||||
e.guild_card_number = watched_lobby->clients[z]->license->serial_number;
|
||||
e.name = watched_lobby->clients[z]->game_data.player()->disp.name;
|
||||
remove_language_marker_inplace(e.name);
|
||||
e.present = 1;
|
||||
e.level = watched_lobby->clients[z]->game_data.player()->disp.level.load();
|
||||
|
||||
player_count++;
|
||||
}
|
||||
|
||||
} else if (l->battle_player) {
|
||||
@@ -1410,6 +1418,9 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
|
||||
cmd.players[client_id].lobby_data = entry.lobby_data;
|
||||
remove_language_marker_inplace(cmd.players[client_id].lobby_data.name);
|
||||
cmd.players[client_id].inventory = entry.inventory;
|
||||
for (size_t z = 0; z < 30; z++) {
|
||||
cmd.players[client_id].inventory.items[z].data.bswap_data2_if_mag();
|
||||
}
|
||||
cmd.players[client_id].disp = entry.disp;
|
||||
remove_language_marker_inplace(cmd.players[client_id].disp.name);
|
||||
cmd.entries[client_id].player_tag = 0x00010000;
|
||||
@@ -1481,6 +1492,9 @@ void send_join_game_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
cmd->lobby_data[x].name = l->clients[x]->game_data.player()->disp.name;
|
||||
if (cmd_ep3) {
|
||||
cmd_ep3->players_ep3[x].inventory = l->clients[x]->game_data.player()->inventory;
|
||||
for (size_t z = 0; z < 30; z++) {
|
||||
cmd_ep3->players_ep3[x].inventory.items[z].data.bswap_data2_if_mag();
|
||||
}
|
||||
cmd_ep3->players_ep3[x].disp = convert_player_disp_data<PlayerDispDataDCPCV3>(
|
||||
l->clients[x]->game_data.player()->disp);
|
||||
}
|
||||
@@ -1612,6 +1626,11 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l,
|
||||
e.lobby_data.client_id = lc->lobby_client_id;
|
||||
e.lobby_data.name = lc->game_data.player()->disp.name;
|
||||
e.inventory = lc->game_data.player()->inventory;
|
||||
if (c->version() == GameVersion::GC) {
|
||||
for (size_t z = 0; z < 30; z++) {
|
||||
e.inventory.items[z].data.bswap_data2_if_mag();
|
||||
}
|
||||
}
|
||||
e.disp = convert_player_disp_data<DispDataT>(lc->game_data.player()->disp);
|
||||
if ((c->version() == GameVersion::PC) || (c->version() == GameVersion::DC)) {
|
||||
e.disp.enforce_v2_limits();
|
||||
@@ -1782,13 +1801,16 @@ void send_get_player_info(shared_ptr<Client> c) {
|
||||
void send_execute_item_trade(shared_ptr<Client> c,
|
||||
const vector<ItemData>& items) {
|
||||
SC_TradeItems_D0_D3 cmd;
|
||||
if (items.size() > sizeof(cmd.items) / sizeof(cmd.items[0])) {
|
||||
if (items.size() > cmd.item_datas.size()) {
|
||||
throw logic_error("too many items in execute trade command");
|
||||
}
|
||||
cmd.target_client_id = c->lobby_client_id;
|
||||
cmd.item_count = items.size();
|
||||
for (size_t x = 0; x < items.size(); x++) {
|
||||
cmd.items[x] = items[x];
|
||||
cmd.item_datas[x] = items[x];
|
||||
if (c->version() == GameVersion::GC) {
|
||||
cmd.item_datas[x].bswap_data2_if_mag();
|
||||
}
|
||||
}
|
||||
send_command_t(c, 0xD3, 0x00, cmd);
|
||||
}
|
||||
@@ -1914,31 +1936,43 @@ void send_set_player_visibility(shared_ptr<Lobby> l, shared_ptr<Client> c,
|
||||
// BB game commands
|
||||
|
||||
void send_drop_item(Channel& ch, const ItemData& item,
|
||||
bool from_enemy, uint8_t area, float x, float z, uint16_t request_id) {
|
||||
bool from_enemy, uint8_t area, float x, float z, uint16_t entity_id) {
|
||||
G_DropItem_PC_V3_BB_6x5F cmd = {
|
||||
{{0x5F, 0x0B, 0x0000}, area, from_enemy, request_id, x, z, 0, 0, item}, 0};
|
||||
{{0x5F, 0x0B, 0x0000}, area, from_enemy, entity_id, x, z, 0, 0, item}, 0};
|
||||
if (ch.version == GameVersion::GC) {
|
||||
cmd.item_data.bswap_data2_if_mag();
|
||||
}
|
||||
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
void send_drop_item(shared_ptr<Lobby> l, const ItemData& item,
|
||||
bool from_enemy, uint8_t area, float x, float z, uint16_t request_id) {
|
||||
G_DropItem_PC_V3_BB_6x5F cmd = {
|
||||
{{0x5F, 0x0B, 0x0000}, area, from_enemy, request_id, x, z, 0, 0, item}, 0};
|
||||
send_command_t(l, 0x60, 0x00, cmd);
|
||||
bool from_enemy, uint8_t area, float x, float z, uint16_t entity_id) {
|
||||
for (auto& c : l->clients) {
|
||||
if (!c) {
|
||||
continue;
|
||||
}
|
||||
send_drop_item(c->channel, item, from_enemy, area, x, z, entity_id);
|
||||
}
|
||||
}
|
||||
|
||||
void send_drop_stacked_item(Channel& ch, const ItemData& item,
|
||||
uint8_t area, float x, float z) {
|
||||
G_DropStackedItem_PC_V3_BB_6x5D cmd = {
|
||||
{{0x5D, 0x0A, 0x0000}, area, 0, x, z, item}, 0};
|
||||
if (ch.version == GameVersion::GC) {
|
||||
cmd.item_data.bswap_data2_if_mag();
|
||||
}
|
||||
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
void send_drop_stacked_item(shared_ptr<Lobby> l, const ItemData& item,
|
||||
uint8_t area, float x, float z) {
|
||||
G_DropStackedItem_PC_V3_BB_6x5D cmd = {
|
||||
{{0x5D, 0x0A, 0x0000}, area, 0, x, z, item}, 0};
|
||||
send_command_t(l, 0x60, 0x00, cmd);
|
||||
for (auto& c : l->clients) {
|
||||
if (!c) {
|
||||
continue;
|
||||
}
|
||||
send_drop_stacked_item(c->channel, item, area, x, z);
|
||||
}
|
||||
}
|
||||
|
||||
void send_pick_up_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
|
||||
@@ -1949,16 +1983,16 @@ void send_pick_up_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
|
||||
send_command_t(l, 0x60, 0x00, cmd);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (c->version() != GameVersion::BB) {
|
||||
throw logic_error("6xBE can only be sent to BB clients");
|
||||
}
|
||||
uint16_t client_id = c->lobby_client_id;
|
||||
G_CreateInventoryItem_BB_6xBE cmd = {{0xBE, 0x07, client_id}, item, 0};
|
||||
send_command_t(l, 0x60, 0x00, cmd);
|
||||
}
|
||||
|
||||
// destroys an item
|
||||
void send_destroy_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
|
||||
uint32_t item_id, uint32_t amount) {
|
||||
uint16_t client_id = c->lobby_client_id;
|
||||
@@ -1966,8 +2000,11 @@ void send_destroy_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
|
||||
send_command_t(l, 0x60, 0x00, cmd);
|
||||
}
|
||||
|
||||
// sends the player their bank data
|
||||
void send_bank(shared_ptr<Client> c) {
|
||||
if (c->version() != GameVersion::BB) {
|
||||
throw logic_error("6xBC can only be sent to BB clients");
|
||||
}
|
||||
|
||||
vector<PlayerBankItem> items(c->game_data.player()->bank.items,
|
||||
&c->game_data.player()->bank.items[c->game_data.player()->bank.num_items]);
|
||||
|
||||
@@ -1981,8 +2018,11 @@ void send_bank(shared_ptr<Client> c) {
|
||||
send_command_t_vt(c, 0x6C, 0x00, cmd, items);
|
||||
}
|
||||
|
||||
// sends the player a shop's contents
|
||||
void send_shop(shared_ptr<Client> c, uint8_t shop_type) {
|
||||
if (c->version() != GameVersion::BB) {
|
||||
throw logic_error("6xB6 can only be sent to BB clients");
|
||||
}
|
||||
|
||||
const auto& contents = c->game_data.shop_contents.at(shop_type);
|
||||
|
||||
G_ShopContents_BB_6xB6 cmd = {
|
||||
@@ -1993,10 +2033,10 @@ void send_shop(shared_ptr<Client> c, uint8_t shop_type) {
|
||||
{},
|
||||
};
|
||||
for (size_t x = 0; x < contents.size(); x++) {
|
||||
cmd.entries[x] = contents[x];
|
||||
cmd.item_datas[x] = contents[x];
|
||||
}
|
||||
|
||||
send_command(c, 0x60, 0x00, &cmd, sizeof(cmd) - sizeof(cmd.entries[0]) * (20 - contents.size()));
|
||||
send_command(c, 0x60, 0x00, &cmd, sizeof(cmd) - sizeof(cmd.item_datas[0]) * (20 - contents.size()));
|
||||
}
|
||||
|
||||
// notifies players about a level up
|
||||
@@ -2029,6 +2069,10 @@ void send_level_up(shared_ptr<Lobby> l, shared_ptr<Client> c) {
|
||||
// gives a player EXP
|
||||
void send_give_experience(shared_ptr<Lobby> l, shared_ptr<Client> c,
|
||||
uint32_t amount) {
|
||||
if (c->version() != GameVersion::BB) {
|
||||
throw logic_error("6xBF can only be sent to BB clients");
|
||||
}
|
||||
|
||||
uint16_t client_id = c->lobby_client_id;
|
||||
G_GiveExperience_BB_6xBF cmd = {
|
||||
{0xBF, sizeof(G_GiveExperience_BB_6xBF) / 4, client_id}, amount};
|
||||
|
||||
Reference in New Issue
Block a user