add all GC 1&2 subcommand formats

This commit is contained in:
Martin Michelsen
2022-10-09 01:35:22 -07:00
parent 48905bfa10
commit f088454c25
12 changed files with 1852 additions and 798 deletions
+1 -1
View File
@@ -67,7 +67,7 @@ Client::Client(
proxy_suppress_remote_login(false),
proxy_zero_remote_guild_card(false),
dol_base_addr(0) {
this->last_switch_enabled_command.subcommand = 0;
this->last_switch_enabled_command.header.subcommand = 0;
memset(&this->next_connection_addr, 0, sizeof(this->next_connection_addr));
if (this->version() == GameVersion::BB) {
+1374 -370
View File
File diff suppressed because it is too large Load Diff
+11 -7
View File
@@ -156,9 +156,9 @@ static int16_t get_u8_or_eof(StringReader& r) {
return r.eof() ? -1 : r.get_u8();
}
string prs_decompress(const string& data, size_t max_size) {
string prs_decompress(const void* data, size_t size, size_t max_output_size) {
string output;
StringReader r(data.data(), data.size());
StringReader r(data, size);
int32_t r3, r5;
int bitpos = 9;
@@ -189,7 +189,7 @@ string prs_decompress(const string& data, size_t max_size) {
return output;
}
output += static_cast<char>(ch);
if (max_size && (output.size() > max_size)) {
if (max_output_size && (output.size() > max_output_size)) {
throw runtime_error("maximum output size exceeded");
}
continue;
@@ -258,14 +258,18 @@ string prs_decompress(const string& data, size_t max_size) {
t = r3;
for (x = 0; x < t; x++) {
output += output.at(output.size() + r5);
if (max_size && (output.size() > max_size)) {
if (max_output_size && (output.size() > max_output_size)) {
throw runtime_error("maximum output size exceeded");
}
}
}
}
size_t prs_decompress_size(const string& data, size_t max_size) {
string prs_decompress(const string& data, size_t max_output_size) {
return prs_decompress(data.data(), data.size(), max_output_size);
}
size_t prs_decompress_size(const string& data, size_t max_output_size) {
size_t output_size = 0;
StringReader r(data.data(), data.size());
@@ -298,7 +302,7 @@ size_t prs_decompress_size(const string& data, size_t max_size) {
return output_size;
}
output_size++;
if (max_size && (output_size > max_size)) {
if (max_output_size && (output_size > max_output_size)) {
throw runtime_error("maximum output size exceeded");
}
continue;
@@ -363,7 +367,7 @@ size_t prs_decompress_size(const string& data, size_t max_size) {
continue;
}
output_size += r3;
if (max_size && (output_size > max_size)) {
if (max_output_size && (output_size > max_output_size)) {
throw runtime_error("maximum output size exceeded");
}
}
+3 -2
View File
@@ -9,7 +9,8 @@
std::string prs_compress(const void* vdata, size_t size);
std::string prs_compress(const std::string& data);
std::string prs_decompress(const std::string& data, size_t max_size = 0);
size_t prs_decompress_size(const std::string& data, size_t max_size = 0);
std::string prs_decompress(const void* data, size_t size, size_t max_output_size = 0);
std::string prs_decompress(const std::string& data, size_t max_output_size = 0);
size_t prs_decompress_size(const std::string& data, size_t max_output_size = 0);
std::string bc0_decompress(const std::string& data);
-6
View File
@@ -47,12 +47,6 @@ union PSOCommandHeader {
PSOCommandHeader();
} __attribute__((packed));
union PSOSubcommand {
uint8_t byte[4];
le_uint16_t word[2];
le_uint32_t dword;
} __attribute__((packed));
// This function is used in a lot of places to check received command sizes and
// cast them to the appropriate type
template <typename T>
+49 -31
View File
@@ -836,13 +836,24 @@ static HandlerResult S_6x(shared_ptr<ServerState>,
if (session.save_files) {
if ((session.version == GameVersion::GC) && (data.size() >= 0x14)) {
PSOSubcommand* subs = &check_size_t<PSOSubcommand>(data, 0x14, 0xFFFF);
if (subs[0].dword == 0x000000B6 && subs[2].dword == 0x00000041) {
string filename = string_printf("map%08" PRIX32 ".%" PRIu64 ".mnmd",
subs[3].dword.load(), now());
string map_data = prs_decompress(data.substr(0x14));
save_file(filename, map_data);
session.log.warning("Wrote %zu bytes to %s", map_data.size(), filename.c_str());
if (static_cast<uint8_t>(data[0]) == 0xB6) {
const auto& header = check_size_t<G_MapSubsubcommand_GC_Ep3_6xB6>(
data, sizeof(G_MapSubsubcommand_GC_Ep3_6xB6), 0xFFFF);
if (header.subsubcommand == 0x00000041) {
const auto& cmd = check_size_t<G_MapData_GC_Ep3_6xB6x41>(
data, sizeof(G_MapData_GC_Ep3_6xB6x41), 0xFFFF);
string filename = string_printf("map%08" PRIX32 ".%" PRIu64 ".mnmd",
cmd.map_number.load(), now());
string map_data = prs_decompress(
data.data() + sizeof(cmd), data.size() - sizeof(cmd));
save_file(filename, map_data);
if (map_data.size() != sizeof(Ep3Map)) {
session.log.warning("Wrote %zu bytes to %s (expected %zu bytes; the file may be invalid)",
map_data.size(), filename.c_str(), sizeof(Ep3Map));
} else {
session.log.info("Wrote %zu bytes to %s", map_data.size(), filename.c_str());
}
}
}
}
}
@@ -850,7 +861,31 @@ static HandlerResult S_6x(shared_ptr<ServerState>,
if (!data.empty() &&
session.next_drop_item.data.data1d[0] &&
(session.version != GameVersion::BB)) {
if (data[0] == 0x60) {
if (data[0] == 0x46) {
const auto& cmd = check_size_t<G_AttackFinished_6x46>(data,
offsetof(G_AttackFinished_6x46, entries),
sizeof(G_AttackFinished_6x46));
if ((cmd.count > 11) || (cmd.count > cmd.header.size - 2)) {
session.log.warning("Blocking subcommand 6x46 with invalid count");
return HandlerResult::Type::SUPPRESS;
}
} else if (data[0] == 0x47) {
const auto& cmd = check_size_t<G_CastTechnique_6x47>(data,
offsetof(G_CastTechnique_6x47, targets),
sizeof(G_CastTechnique_6x47));
if ((cmd.target_count > 10) || (cmd.target_count > cmd.header.size - 2)) {
session.log.warning("Blocking subcommand 6x47 with invalid count");
return HandlerResult::Type::SUPPRESS;
}
} else if (data[0] == 0x49) {
const auto& cmd = check_size_t<G_SubtractPBEnergy_6x49>(data,
offsetof(G_SubtractPBEnergy_6x49, entries),
sizeof(G_SubtractPBEnergy_6x49));
if ((cmd.entry_count > 14) || (cmd.entry_count > cmd.header.size - 2)) {
session.log.warning("Blocking subcommand 6x49 with invalid count");
return HandlerResult::Type::SUPPRESS;
}
} else if (data[0] == 0x60) {
const auto& cmd = check_size_t<G_EnemyDropItemRequest_DC_6x60>(data,
sizeof(G_EnemyDropItemRequest_DC_6x60),
sizeof(G_EnemyDropItemRequest_PC_V3_BB_6x60));
@@ -1242,30 +1277,13 @@ static HandlerResult C_6x(shared_ptr<ServerState> s,
if (!data.empty()) {
if (data[0] == 0x2F || data[0] == 0x4B || data[0] == 0x4C) {
if (session.infinite_hp) {
vector<PSOSubcommand> subs;
for (size_t amount = 1020; amount > 0;) {
auto& sub1 = subs.emplace_back();
sub1.word[0] = 0x029A;
sub1.byte[2] = session.lobby_client_id;
sub1.byte[3] = 0x00;
auto& sub2 = subs.emplace_back();
sub2.word[0] = 0x0000;
sub2.byte[2] = PlayerStatsChange::ADD_HP;
sub2.byte[3] = (amount > 0xFF) ? 0xFF : amount;
amount -= sub2.byte[3];
}
session.client_channel.send(0x60, 0x00, subs.data(), subs.size() * sizeof(PSOSubcommand));
send_player_stats_change(session.client_channel,
session.lobby_client_id, PlayerStatsChange::ADD_HP, 2550);
}
} else if (data[0] == 0x48) {
if (session.infinite_tp) {
PSOSubcommand subs[2];
subs[0].word[0] = 0x029A;
subs[0].byte[2] = session.lobby_client_id;
subs[0].byte[3] = 0x00;
subs[1].word[0] = 0x0000;
subs[1].byte[2] = PlayerStatsChange::ADD_TP;
subs[1].byte[3] = 0xFF;
session.client_channel.send(0x60, 0x00, &subs[0], sizeof(subs));
send_player_stats_change(session.client_channel,
session.lobby_client_id, PlayerStatsChange::ADD_TP, 255);
}
}
}
@@ -1284,8 +1302,8 @@ HandlerResult C_6x<void>(shared_ptr<ServerState>,
if (!data.empty() && (data[0] == 0x05) && session.switch_assist) {
auto& cmd = check_size_t<G_SwitchStateChanged_6x05>(data);
if (cmd.enabled && cmd.switch_id != 0xFFFF) {
if (session.last_switch_enabled_command.subcommand == 0x05) {
if (cmd.flags && cmd.header.object_id != 0xFFFF) {
if (session.last_switch_enabled_command.header.subcommand == 0x05) {
session.log.info("Switch assist: replaying previous enable command");
session.server_channel.send(0x60, 0x00, &session.last_switch_enabled_command,
sizeof(session.last_switch_enabled_command));
+9 -3
View File
@@ -215,10 +215,16 @@ void ProxyServer::on_client_connect(
auto cmd = prepare_server_init_contents_bb(server_key, client_key, 0);
session->channel.send(0x03, 0x00, &cmd, sizeof(cmd));
session->detector_crypt.reset(new PSOBBMultiKeyDetectorEncryption(
this->state->bb_private_keys, bb_crypt_initial_client_commands, cmd.client_key.data(), sizeof(cmd.client_key)));
this->state->bb_private_keys,
bb_crypt_initial_client_commands,
cmd.basic_cmd.client_key.data(),
sizeof(cmd.basic_cmd.client_key)));
session->channel.crypt_in = session->detector_crypt;
session->channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption(
session->detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), true));
session->detector_crypt,
cmd.basic_cmd.server_key.data(),
sizeof(cmd.basic_cmd.server_key),
true));
break;
}
default:
@@ -491,7 +497,7 @@ ProxyServer::LinkedSession::LinkedSession(
lobby_client_id(0),
leader_client_id(0),
is_in_game(false) {
this->last_switch_enabled_command.subcommand = 0;
this->last_switch_enabled_command.header.subcommand = 0;
memset(this->prev_server_command_bytes, 0, sizeof(this->prev_server_command_bytes));
}
+24 -16
View File
@@ -815,28 +815,35 @@ static void on_ep3_counter_state(shared_ptr<ServerState>, shared_ptr<Client> c,
static void on_ep3_server_data_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // CA
check_size_v(data.size(), 8, 0xFFFF);
const PSOSubcommand* cmds = reinterpret_cast<const PSOSubcommand*>(data.data());
auto l = s->find_lobby(c->lobby_id);
if (!l || !(l->flags & Lobby::Flag::EPISODE_3_ONLY) || !l->is_game()) {
c->should_disconnect = true;
return;
throw runtime_error("Episode 3 server data request sent in lobby or non-Episode 3 game");
}
if (cmds[0].byte[0] != 0xB3) {
c->should_disconnect = true;
return;
const auto& header = check_size_t<G_CardBattleCommandHeader_GC_Ep3_6xB3_6xB4_6xB5>(
data, sizeof(G_CardBattleCommandHeader_GC_Ep3_6xB3_6xB4_6xB5), 0xFFFF);
// TODO: We can support this since set_mask_for_ep3_game_command already
// exists; I just don't want to make a copy of the data string
if (header.mask_key != 0) {
throw runtime_error("Episode 3 server data request has nonzero mask key");
}
switch (cmds[1].byte[0]) {
if (header.basic_header.subcommand != 0xB3) {
throw runtime_error("unknown Episode 3 server data request");
}
switch (header.subsubcommand) {
// Phase 1: map select
case 0x40:
check_size_t<G_MapListRequest_GC_Ep3_6xB3x40_CAx40>(data);
send_ep3_map_list(s, l);
break;
case 0x41:
send_ep3_map_data(s, l, cmds[4].dword);
case 0x41: {
const auto& cmd = check_size_t<G_MapDataRequest_GC_Ep3_6xB3x41_CAx41>(data);
send_ep3_map_data(s, l, cmd.map_number);
break;
}
/* What follows is some raw code that has survived since the days of khyller
* (approx. 2004). Much more research and engineering is needed to get
@@ -902,7 +909,8 @@ static void on_ep3_server_data_request(shared_ptr<ServerState> s, shared_ptr<Cli
break; */
default:
c->log.error("Unknown Episode III server data request: %02X", cmds[1].byte[0]);
c->log.error("Unknown Episode III server data request: %02X",
header.subsubcommand);
}
}
@@ -1161,13 +1169,13 @@ static void on_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
if (uses_unicode) {
const auto& cmd = check_size_t<C_MenuSelection_PC_BB_10_Flag02>(data);
password = cmd.password;
menu_id = cmd.menu_id;
item_id = cmd.item_id;
menu_id = cmd.basic_cmd.menu_id;
item_id = cmd.basic_cmd.item_id;
} else {
const auto& cmd = check_size_t<C_MenuSelection_DC_V3_10_Flag02>(data);
password = decode_sjis(cmd.password);
menu_id = cmd.menu_id;
item_id = cmd.item_id;
menu_id = cmd.basic_cmd.menu_id;
item_id = cmd.basic_cmd.item_id;
}
} else {
const auto& cmd = check_size_t<C_MenuSelection_10_Flag00>(data);
+301 -257
View File
File diff suppressed because it is too large Load Diff
+68 -97
View File
@@ -121,10 +121,10 @@ prepare_server_init_contents_console(
uint32_t server_key, uint32_t client_key, uint8_t flags) {
bool initial_connection = (flags & SendServerInitFlag::IS_INITIAL_CONNECTION);
S_ServerInitWithAfterMessage_DC_PC_V3_02_17_91_9B<0xB4> cmd;
cmd.copyright = initial_connection
cmd.basic_cmd.copyright = initial_connection
? dc_port_map_copyright : dc_lobby_server_copyright;
cmd.server_key = server_key;
cmd.client_key = client_key;
cmd.basic_cmd.server_key = server_key;
cmd.basic_cmd.client_key = client_key;
cmd.after_message = anti_copyright;
return cmd;
}
@@ -165,9 +165,9 @@ prepare_server_init_contents_bb(
uint8_t flags) {
bool use_secondary_message = (flags & SendServerInitFlag::USE_SECONDARY_MESSAGE);
S_ServerInitWithAfterMessage_BB_03_9B<0xB4> cmd;
cmd.copyright = use_secondary_message ? bb_pm_server_copyright : bb_game_server_copyright;
cmd.server_key = server_key;
cmd.client_key = client_key;
cmd.basic_cmd.copyright = use_secondary_message ? bb_pm_server_copyright : bb_game_server_copyright;
cmd.basic_cmd.server_key = server_key;
cmd.basic_cmd.client_key = client_key;
cmd.after_message = anti_copyright;
return cmd;
}
@@ -187,11 +187,12 @@ void send_server_init_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt(new PSOBBMultiKeyDetectorEncryption(
s->bb_private_keys,
bb_crypt_initial_client_commands,
cmd.client_key.data(),
sizeof(cmd.client_key)));
cmd.basic_cmd.client_key.data(),
sizeof(cmd.basic_cmd.client_key)));
c->channel.crypt_in = detector_crypt;
c->channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption(
detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), true));
detector_crypt, cmd.basic_cmd.server_key.data(),
sizeof(cmd.basic_cmd.server_key), true));
}
void send_server_init_patch(shared_ptr<Client> c) {
@@ -778,9 +779,9 @@ void send_guild_card_dc_pc_v3_t(
uint8_t section_id,
uint8_t char_class) {
CmdT cmd;
cmd.subcommand = 0x06;
cmd.size = sizeof(CmdT) / 4;
cmd.unused = 0x0000;
cmd.header.subcommand = 0x06;
cmd.header.size = sizeof(CmdT) / 4;
cmd.header.unused = 0x0000;
cmd.player_tag = 0x00010000;
cmd.guild_card_number = guild_card_number;
cmd.name = name;
@@ -802,9 +803,9 @@ static void send_guild_card_bb(
uint8_t section_id,
uint8_t char_class) {
G_SendGuildCard_BB_6x06 cmd;
cmd.subcommand = 0x06;
cmd.size = sizeof(cmd) / 4;
cmd.unused = 0x0000;
cmd.header.subcommand = 0x06;
cmd.header.size = sizeof(cmd) / 4;
cmd.header.unused = 0x0000;
cmd.guild_card_number = guild_card_number;
cmd.name = remove_language_marker(name);
cmd.team_name = remove_language_marker(team_name);
@@ -1378,84 +1379,54 @@ void send_resume_game(shared_ptr<Lobby> l, shared_ptr<Client> ready_client) {
////////////////////////////////////////////////////////////////////////////////
// Game/cheat commands
// sends an HP/TP/Meseta modifying command (see flag definitions in command-functions.h)
static vector<G_UpdatePlayerStat_6x9A> generate_stats_change_subcommands(
uint16_t client_id, PlayerStatsChange stat, uint32_t amount) {
if (amount > (0x7BF8 * 0xFF) / sizeof(G_UpdatePlayerStat_6x9A)) {
throw runtime_error("stats change command is too large");
}
uint8_t stat_ch = static_cast<uint8_t>(stat);
vector<G_UpdatePlayerStat_6x9A> subs;
while (amount > 0) {
uint8_t sub_amount = min<size_t>(amount, 0xFF);
subs.emplace_back(G_UpdatePlayerStat_6x9A{{0x9A, 0x02, client_id}, 0, stat_ch, sub_amount});
amount -= sub_amount;
}
return subs;
}
void send_player_stats_change(shared_ptr<Lobby> l, shared_ptr<Client> c,
PlayerStatsChange stat, uint32_t amount) {
auto subs = generate_stats_change_subcommands(c->lobby_client_id, stat, amount);
send_command_vt(l, (subs.size() > 0x400 / sizeof(G_UpdatePlayerStat_6x9A)) ? 0x6C : 0x60, 0x00, subs);
}
if (amount > 2550) {
throw invalid_argument("amount cannot be larger than 2550");
}
vector<PSOSubcommand> subs;
while (amount > 0) {
{
auto& sub = subs.emplace_back();
sub.byte[0] = 0x9A;
sub.byte[1] = 0x02;
sub.byte[2] = c->lobby_client_id;
sub.byte[3] = 0x00;
}
{
auto& sub = subs.emplace_back();
sub.byte[0] = 0x00;
sub.byte[1] = 0x00;
sub.byte[2] = stat;
sub.byte[3] = (amount > 0xFF) ? 0xFF : amount;
amount -= sub.byte[3];
}
}
send_command_vt(l, 0x60, 0x00, subs);
void send_player_stats_change(Channel& ch, uint16_t client_id, PlayerStatsChange stat, uint32_t amount) {
auto subs = generate_stats_change_subcommands(client_id, stat, amount);
send_command_vt(ch, (subs.size() > 0x400 / sizeof(G_UpdatePlayerStat_6x9A)) ? 0x6C : 0x60, 0x00, subs);
}
void send_warp(Channel& ch, uint8_t client_id, uint32_t area) {
PSOSubcommand cmds[2];
cmds[0].byte[0] = 0x94;
cmds[0].byte[1] = 0x02;
cmds[0].byte[2] = client_id;
cmds[0].byte[3] = 0x00;
cmds[1].dword = area;
ch.send(0x62, client_id, cmds, 8);
G_InterLevelWarp_6x94 cmd = {{0x94, 0x02, 0}, area, {}};
ch.send(0x62, client_id, &cmd, sizeof(cmd));
}
void send_warp(shared_ptr<Client> c, uint32_t area) {
PSOSubcommand cmds[2];
cmds[0].byte[0] = 0x94;
cmds[0].byte[1] = 0x02;
cmds[0].byte[2] = c->lobby_client_id;
cmds[0].byte[3] = 0x00;
cmds[1].dword = area;
send_command(c, 0x62, c->lobby_client_id, cmds, 8);
send_warp(c->channel, c->lobby_client_id, area);
c->area = area;
}
void send_ep3_change_music(shared_ptr<Client> c, uint32_t song) {
PSOSubcommand cmds[2];
cmds[0].byte[0] = 0xBF;
cmds[0].byte[1] = 0x02;
cmds[0].byte[2] = c->lobby_client_id;
cmds[0].byte[3] = 0x00;
cmds[1].dword = song;
send_command(c, 0x60, 0x00, cmds, 8);
G_ChangeLobbyMusic_GC_Ep3_6xBF cmd = {{0xBF, 0x02, 0}, song};
send_command_t(c, 0x60, 0x00, cmd);
}
void send_set_player_visibility(shared_ptr<Lobby> l, shared_ptr<Client> c,
bool visible) {
PSOSubcommand cmd;
cmd.byte[0] = visible ? 0x23 : 0x22;
cmd.byte[1] = 0x01;
cmd.byte[2] = c->lobby_client_id;
cmd.byte[3] = 0x00;
send_command(l, 0x60, 0x00, &cmd, 4);
}
void send_revive_player(shared_ptr<Lobby> l, shared_ptr<Client> c) {
PSOSubcommand cmd;
cmd.byte[0] = 0x31;
cmd.byte[1] = 0x01;
cmd.byte[2] = c->lobby_client_id;
cmd.byte[3] = 0x00;
send_command(l, 0x60, 0x00, &cmd, 4);
uint8_t subcmd = visible ? 0x23 : 0x22;
uint16_t client_id = c->lobby_client_id;
G_SetPlayerVisibility_6x22_6x23 cmd = {{subcmd, 0x01, client_id}};
send_command_t(l, 0x60, 0x00, cmd);
}
@@ -1466,14 +1437,14 @@ void send_revive_player(shared_ptr<Lobby> l, shared_ptr<Client> c) {
void send_drop_item(Channel& ch, 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, item}, 0};
{{0x5F, 0x0B, 0x0000}, area, from_enemy, request_id, x, z, 0, 0, item}, 0};
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, item}, 0};
{{0x5F, 0x0B, 0x0000}, area, from_enemy, request_id, x, z, 0, 0, item}, 0};
send_command_t(l, 0x60, 0x00, cmd);
}
@@ -1484,14 +1455,15 @@ void send_drop_stacked_item(shared_ptr<Lobby> l, const ItemData& item,
// TODO: Is this order correct? The original code sent {item, 0}, but it seems
// GC sends {0, item} (the last two fields in the struct are switched).
G_DropStackedItem_PC_V3_BB_6x5D cmd = {
{0x5D, 0x0A, 0x0000, area, 0, x, z, item}, 0};
{{0x5D, 0x0A, 0x0000}, area, 0, x, z, item}, 0};
send_command_t(l, 0x60, 0x00, cmd);
}
void send_pick_up_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
uint32_t item_id, uint8_t area) {
uint16_t client_id = c->lobby_client_id;
G_PickUpItem_6x59 cmd = {
0x59, 0x03, c->lobby_client_id, c->lobby_client_id, area, item_id};
{0x59, 0x03, client_id}, client_id, area, item_id};
send_command_t(l, 0x60, 0x00, cmd);
}
@@ -1499,16 +1471,16 @@ void send_pick_up_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
// bank)
void send_create_inventory_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
const ItemData& item) {
G_CreateInventoryItem_BB_6xBE cmd = {
0xBE, 0x07, c->lobby_client_id, item, 0};
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) {
G_ItemSubcommand cmd = {
0x29, 0x03, c->lobby_client_id, item_id, amount};
uint16_t client_id = c->lobby_client_id;
G_DeleteInventoryItem_6x29 cmd = {{0x29, 0x03, client_id}, item_id, amount};
send_command_t(l, 0x60, 0x00, cmd);
}
@@ -1519,10 +1491,10 @@ void send_bank(shared_ptr<Client> c) {
uint32_t checksum = random_object<uint32_t>();
G_BankContentsHeader_BB_6xBC cmd = {
0xBC, 0, 0, 0, checksum, c->game_data.player()->bank.num_items, c->game_data.player()->bank.meseta};
{{0xBC, 0, 0}, 0}, checksum, c->game_data.player()->bank.num_items, c->game_data.player()->bank.meseta};
size_t size = 8 + sizeof(cmd) + items.size() * sizeof(PlayerBankItem);
cmd.size = size;
cmd.header.size = size;
send_command_t_vt(c, 0x6C, 0x00, cmd, items);
}
@@ -1530,9 +1502,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) {
G_ShopContents_BB_6xB6 cmd = {
0xB6,
0x2C,
0x037F,
{0xB6, 0x2C, 0x037F},
shop_type,
static_cast<uint8_t>(c->game_data.shop_contents.size()),
0,
@@ -1566,24 +1536,24 @@ void send_level_up(shared_ptr<Lobby> l, shared_ptr<Client> c) {
}
G_LevelUp_6x30 cmd = {
0x30,
sizeof(G_LevelUp_6x30) / 4,
c->lobby_client_id,
{0x30, sizeof(G_LevelUp_6x30) / 4, c->lobby_client_id},
stats.atp,
stats.mst,
stats.evp,
stats.hp,
stats.dfp,
stats.ata,
c->game_data.player()->disp.level};
c->game_data.player()->disp.level.load(),
0};
send_command_t(l, 0x60, 0x00, cmd);
}
// gives a player EXP
void send_give_experience(shared_ptr<Lobby> l, shared_ptr<Client> c,
uint32_t amount) {
uint16_t client_id = c->lobby_client_id;
G_GiveExperience_BB_6xBF cmd = {
0xBF, sizeof(G_GiveExperience_BB_6xBF) / 4, c->lobby_client_id, amount};
{0xBF, sizeof(G_GiveExperience_BB_6xBF) / 4, client_id}, amount};
send_command_t(l, 0x60, 0x00, cmd);
}
@@ -1633,7 +1603,8 @@ void send_ep3_map_list(shared_ptr<ServerState> s, shared_ptr<Lobby> l) {
StringWriter w;
uint32_t subcommand_size = (data.size() + sizeof(G_MapList_GC_Ep3_6xB6x40) + 3) & (~3);
w.put<G_MapList_GC_Ep3_6xB6x40>({{0xB6, 0, 0, subcommand_size, 0x40}, data.size()});
w.put<G_MapList_GC_Ep3_6xB6x40>(
G_MapList_GC_Ep3_6xB6x40{{{{0xB6, 0, 0}, subcommand_size}, 0x40, {}}, data.size(), 0});
w.write(data);
send_command(l, 0x6C, 0x00, w.str());
}
@@ -1644,7 +1615,7 @@ void send_ep3_map_data(shared_ptr<ServerState> s, shared_ptr<Lobby> l, uint32_t
StringWriter w;
uint32_t subcommand_size = (compressed.size() + sizeof(G_MapData_GC_Ep3_6xB6x41) + 3) & (~3);
w.put<G_MapData_GC_Ep3_6xB6x41>({{0xB6, 0, 0, subcommand_size, 0x41}, entry->map.map_number.load(), 0, compressed.size()});
w.put<G_MapData_GC_Ep3_6xB6x41>({{{{0xB6, 0, 0}, subcommand_size}, 0x41, {}}, entry->map.map_number.load(), compressed.size(), 0});
w.write(compressed);
send_command(l, 0x6C, 0x00, w.str());
}
@@ -1687,7 +1658,7 @@ void set_mask_for_ep3_game_command(void* vdata, size_t size, uint8_t mask_key) {
}
auto* header = reinterpret_cast<G_CardBattleCommandHeader_GC_Ep3_6xB3_6xB4_6xB5*>(vdata);
size_t command_bytes = header->size * 4;
size_t command_bytes = header->basic_header.size * 4;
if (command_bytes != size) {
throw runtime_error("command size field does not match actual size");
}
+9 -1
View File
@@ -84,6 +84,12 @@ void send_command_vt(std::shared_ptr<TargetT> c, uint16_t command,
send_command(c, command, flag, data.data(), data.size() * sizeof(StructT));
}
template <typename StructT>
void send_command_vt(Channel& ch, uint16_t command, uint32_t flag,
const std::vector<StructT>& data) {
ch.send(command, flag, data.data(), data.size() * sizeof(StructT));
}
template <typename TargetT, typename StructT, typename EntryT>
void send_command_t_vt(std::shared_ptr<TargetT> c, uint16_t command,
uint32_t flag, const StructT& data, const std::vector<EntryT>& array_data) {
@@ -226,7 +232,9 @@ enum PlayerStatsChange {
};
void send_player_stats_change(std::shared_ptr<Lobby> l, std::shared_ptr<Client> c,
PlayerStatsChange which, uint32_t amount);
PlayerStatsChange stat, uint32_t amount);
void send_player_stats_change(
Channel& ch, uint16_t client_id, PlayerStatsChange stat, uint32_t amount);
void send_warp(Channel& ch, uint8_t client_id, uint32_t area);
void send_warp(std::shared_ptr<Client> c, uint32_t area);
+3 -7
View File
@@ -327,13 +327,9 @@ Proxy commands (these will only work when exactly one client is connected):\n\
} else if (command_name == "warp") {
auto session = this->get_proxy_session();
PSOSubcommand cmds[2];
cmds[0].word[0] = 0x0294;
cmds[0].word[1] = session->lobby_client_id;
cmds[1].dword = stoul(command_args);
session->client_channel.send(0x60, 0x00, &cmds, sizeof(cmds));
session->server_channel.send(0x60, 0x00, &cmds, sizeof(cmds));
uint8_t area = stoul(command_args);
send_warp(session->client_channel, session->lobby_client_id, area);
send_warp(session->server_channel, session->lobby_client_id, area);
} else if ((command_name == "info-board") || (command_name == "info-board-data")) {
auto session = this->get_proxy_session();