implement $dropmode on proxy server

This commit is contained in:
Martin Michelsen
2024-02-18 21:33:59 -08:00
parent f16b8ef983
commit db2c2a4774
11 changed files with 509 additions and 173 deletions
+47 -2
View File
@@ -359,7 +359,7 @@ static void proxy_command_qset_qclear(shared_ptr<ProxyServer::LinkedSession> ses
ses->client_channel.send(0x60, 0x00, &cmd, sizeof(cmd));
ses->server_channel.send(0x60, 0x00, &cmd, sizeof(cmd));
} else {
G_UpdateQuestFlag_V3_BB_6x75 cmd = {{{0x75, 0x03, 0x0000}, flag_num, should_set ? 0 : 1}, ses->difficulty, 0x0000};
G_UpdateQuestFlag_V3_BB_6x75 cmd = {{{0x75, 0x03, 0x0000}, flag_num, should_set ? 0 : 1}, ses->lobby_difficulty, 0x0000};
ses->client_channel.send(0x60, 0x00, &cmd, sizeof(cmd));
ses->server_channel.send(0x60, 0x00, &cmd, sizeof(cmd));
}
@@ -1705,6 +1705,51 @@ static void server_command_dropmode(shared_ptr<Client> c, const std::string& arg
}
}
static void proxy_command_dropmode(shared_ptr<ProxyServer::LinkedSession> ses, const std::string& args) {
check_cheats_allowed(ses->require_server_state(), ses);
using DropMode = ProxyServer::LinkedSession::DropMode;
if (args.empty()) {
switch (ses->drop_mode) {
case DropMode::DISABLED:
send_text_message(ses->client_channel, "Drop mode: disabled");
break;
case DropMode::PASSTHROUGH:
send_text_message(ses->client_channel, "Drop mode: default");
break;
case DropMode::INTERCEPT:
send_text_message(ses->client_channel, "Drop mode: proxy");
break;
}
} else {
DropMode new_mode;
if ((args == "none") || (args == "disabled")) {
new_mode = DropMode::DISABLED;
} else if ((args == "default") || (args == "passthrough")) {
new_mode = DropMode::PASSTHROUGH;
} else if ((args == "proxy") || (args == "intercept")) {
new_mode = DropMode::INTERCEPT;
} else {
send_text_message(ses->client_channel, "Invalid drop mode");
return;
}
ses->set_drop_mode(new_mode);
switch (ses->drop_mode) {
case DropMode::DISABLED:
send_text_message(ses->client_channel, "Item drops disabled");
break;
case DropMode::PASSTHROUGH:
send_text_message(ses->client_channel, "Item drops changed\nto default mode");
break;
case DropMode::INTERCEPT:
send_text_message(ses->client_channel, "Item drops changed\nto proxy mode");
break;
}
}
}
static void server_command_item(shared_ptr<Client> c, const std::string& args) {
auto s = c->require_server_state();
auto l = c->require_lobby();
@@ -2092,7 +2137,7 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
{"$cheat", {server_command_cheat, nullptr}},
{"$debug", {server_command_debug, nullptr}},
{"$dicerange", {server_command_ep3_set_dice_range, nullptr}},
{"$dropmode", {server_command_dropmode, nullptr}},
{"$dropmode", {server_command_dropmode, proxy_command_dropmode}},
{"$edit", {server_command_edit, nullptr}},
{"$ep3battledebug", {server_command_enable_ep3_battle_debug_menu, nullptr}},
{"$event", {server_command_lobby_event, proxy_command_lobby_event}},
+7 -7
View File
@@ -269,17 +269,14 @@ shared_ptr<Map> Lobby::load_maps(
uint32_t lobby_id,
shared_ptr<const Map::RareEnemyRates> rare_rates,
shared_ptr<PSOLFGEncryption> random_crypt,
shared_ptr<const VersionedQuest> vq) {
if (!vq->dat_contents_decompressed) {
throw runtime_error("quest does not have DAT data");
}
shared_ptr<const string> quest_dat_contents_decompressed) {
auto map = make_shared<Map>(version, lobby_id, random_crypt);
map->add_enemies_and_objects_from_quest_data(
episode,
difficulty,
event,
vq->dat_contents_decompressed->data(),
vq->dat_contents_decompressed->size(),
quest_dat_contents_decompressed->data(),
quest_dat_contents_decompressed->size(),
rare_rates);
return map;
}
@@ -377,6 +374,9 @@ void Lobby::load_maps() {
}
auto vq = this->quest->version(this->base_version, leader_c->language());
if (!vq->dat_contents_decompressed) {
throw runtime_error("quest does not have DAT data");
}
this->map = this->load_maps(
this->base_version,
this->episode,
@@ -385,7 +385,7 @@ void Lobby::load_maps() {
this->lobby_id,
rare_rates,
this->random_crypt,
vq);
vq->dat_contents_decompressed);
} else if (this->mode != GameMode::CHALLENGE) {
auto s = this->require_server_state();
+1 -1
View File
@@ -203,7 +203,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
uint32_t lobby_id,
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
std::shared_ptr<PSOLFGEncryption> random_crypt,
std::shared_ptr<const VersionedQuest> vq);
std::shared_ptr<const std::string> quest_dat_contents_decompressed);
static std::shared_ptr<Map> load_maps(
Version version,
Episode episode,
+4 -1
View File
@@ -2128,7 +2128,10 @@ Action a_find_rare_enemy_seeds(
shared_ptr<Map> map;
if (vq) {
map = Lobby::load_maps(version, episode, difficulty, 0, 0, rare_rates, random_crypt, vq);
if (!vq->dat_contents_decompressed) {
throw runtime_error("quest does not have DAT data");
}
map = Lobby::load_maps(version, episode, difficulty, 0, 0, rare_rates, random_crypt, vq->dat_contents_decompressed);
} else {
generate_variations_deprecated(variations, random_crypt, version, episode, (mode == GameMode::SOLO));
+246 -87
View File
@@ -76,18 +76,6 @@ static void forward_command(shared_ptr<ProxyServer::LinkedSession> ses, bool to_
}
}
static void check_implemented_subcommand(
shared_ptr<ProxyServer::LinkedSession> ses, const string& data) {
if (data.size() < 4) {
ses->log.warning("Received broadcast/target command with no contents");
} else {
if (!subcommand_is_implemented(data[0])) {
ses->log.warning("Received subcommand %02hhX which is not implemented on the server",
data[0]);
}
}
}
// Command handlers. These are called to preprocess or react to specific
// commands in either direction. The functions have abbreviated names in order
// to make the massive table more readable. The functions' names are, in
@@ -954,6 +942,73 @@ static HandlerResult S_V3_BB_DA(shared_ptr<ProxyServer::LinkedSession> ses, uint
}
}
static HandlerResult SC_6x60_6xA2(shared_ptr<ProxyServer::LinkedSession> ses, const string& data) {
if (!ses->is_in_game) {
return HandlerResult::Type::FORWARD;
}
if (ses->next_drop_item.data1d[0]) {
G_SpecializableItemDropRequest_6xA2 cmd = normalize_drop_request(data.data(), data.size());
auto s = ses->require_server_state();
ses->next_drop_item.id = ses->next_item_id++;
bool is_box = (cmd.rt_index == 0x30);
send_drop_item_to_channel(s, ses->server_channel, ses->next_drop_item, !is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
send_drop_item_to_channel(s, ses->client_channel, ses->next_drop_item, !is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
ses->next_drop_item.clear();
return HandlerResult::Type::SUPPRESS;
}
using DropMode = ProxyServer::LinkedSession::DropMode;
switch (ses->drop_mode) {
case DropMode::DISABLED:
return HandlerResult::Type::SUPPRESS;
case DropMode::PASSTHROUGH:
return HandlerResult::Type::FORWARD;
case DropMode::INTERCEPT:
if (!ses->item_creator) {
ses->log.warning("Received item drop request in intercept mode, but item creator is missing");
return HandlerResult::Type::FORWARD;
}
break;
default:
throw logic_error("invalid drop mode");
}
G_SpecializableItemDropRequest_6xA2 cmd = normalize_drop_request(data.data(), data.size());
auto rec = reconcile_drop_request_with_map(
ses->log, ses->client_channel, cmd, ses->version(), ses->lobby_episode, ses->config, ses->map, false);
ItemCreator::DropResult res;
if (rec.is_box) {
if (rec.ignore_def) {
ses->log.info("Creating item from box %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area);
res = ses->item_creator->on_box_item_drop(cmd.effective_area);
} else {
ses->log.info("Creating item from box %04hX (area %02hX; specialized with %g %08" PRIX32 " %08" PRIX32 " %08" PRIX32 ")",
cmd.entity_id.load(), cmd.effective_area, cmd.param3.load(), cmd.param4.load(), cmd.param5.load(), cmd.param6.load());
res = ses->item_creator->on_specialized_box_item_drop(cmd.effective_area, cmd.param3, cmd.param4, cmd.param5, cmd.param6);
}
} else {
ses->log.info("Creating item from enemy %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area);
res = ses->item_creator->on_monster_item_drop(rec.effective_rt_index, cmd.effective_area);
}
if (res.item.empty()) {
ses->log.info("No item was created");
} else {
auto s = ses->require_server_state();
string name = s->describe_item(ses->version(), res.item, false);
ses->log.info("Entity %04hX (area %02hX) created item %s", cmd.entity_id.load(), cmd.effective_area, name.c_str());
res.item.id = ses->next_item_id++;
ses->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for all clients",
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load());
send_drop_item_to_channel(s, ses->client_channel, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
send_drop_item_to_channel(s, ses->server_channel, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
send_item_notification_if_needed(s, ses->client_channel, ses->config, res.item, res.is_from_rare_table);
}
return HandlerResult::Type::SUPPRESS;
}
static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
auto s = ses->require_server_state();
@@ -1061,32 +1116,15 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
const auto& cmd = check_size_t<G_DropItem_DC_6x5F>(data, sizeof(G_DropItem_PC_V3_BB_6x5F));
send_item_notification_if_needed(ses->require_server_state(), ses->client_channel, ses->config, cmd.item.item, true);
} else if ((data[0] == 0x60) && ses->next_drop_item.data1d[0] && !is_v4(ses->version())) {
const auto& cmd = check_size_t<G_StandardDropItemRequest_DC_6x60>(
data, sizeof(G_StandardDropItemRequest_PC_V3_BB_6x60));
ses->next_drop_item.id = ses->next_item_id++;
send_drop_item_to_channel(s, ses->server_channel, ses->next_drop_item, true, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
send_drop_item_to_channel(s, ses->client_channel, ses->next_drop_item, true, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
ses->next_drop_item.clear();
return HandlerResult::Type::SUPPRESS;
// Note: This static_cast is required to make compilers not complain that
// the comparison is always false (which even happens in some environments
// if we use -0x5E... apparently char is unsigned on some systems, or
// std::string's char_type isn't char??)
} else if ((static_cast<uint8_t>(data[0]) == 0xA2) && ses->next_drop_item.data1d[0] && !is_v4(ses->version())) {
const auto& cmd = check_size_t<G_SpecializableItemDropRequest_6xA2>(data);
ses->next_drop_item.id = ses->next_item_id++;
send_drop_item_to_channel(s, ses->server_channel, ses->next_drop_item, false, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
send_drop_item_to_channel(s, ses->client_channel, ses->next_drop_item, false, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
ses->next_drop_item.clear();
return HandlerResult::Type::SUPPRESS;
} else if ((data[0] == 0x60) || (static_cast<uint8_t>(data[0]) == 0xA2)) {
return SC_6x60_6xA2(ses, data);
} else if ((static_cast<uint8_t>(data[0]) == 0xB5) && is_ep3(ses->version()) && (data.size() > 4)) {
if (data[4] == 0x1A) {
return HandlerResult::Type::SUPPRESS;
} else if (data[4] == 0x36) {
const auto& cmd = check_size_t<G_RecreatePlayer_Ep3_6xB5x36>(data);
auto& cmd = check_size_t<G_RecreatePlayer_Ep3_6xB5x36>(data);
set_mask_for_ep3_game_command(&cmd, sizeof(cmd), 0);
if (ses->is_in_game && (cmd.client_id >= 4)) {
return HandlerResult::Type::SUPPRESS;
}
@@ -1299,6 +1337,20 @@ static HandlerResult S_13_A7(shared_ptr<ProxyServer::LinkedSession> ses, uint16_
} else {
ses->log.info("Download complete for file %s", sf->basename.c_str());
}
if (!sf->is_download && ends_with(sf->basename, ".dat")) {
auto quest_dat_data = make_shared<std::string>(join(sf->blocks));
ses->map = Lobby::load_maps(
ses->version(),
ses->lobby_episode,
ses->lobby_difficulty,
ses->lobby_event,
ses->id,
Map::DEFAULT_RARE_ENEMIES,
make_shared<PSOV2Encryption>(ses->lobby_random_seed),
quest_dat_data);
}
ses->saving_files.erase(cmd.filename.decode());
}
@@ -1430,7 +1482,13 @@ static HandlerResult S_65_67_68_EB(shared_ptr<ProxyServer::LinkedSession> ses, u
ses->is_in_game = false;
ses->is_in_quest = false;
ses->floor = 0x0F;
ses->difficulty = 0;
ses->lobby_difficulty = 0;
ses->lobby_section_id = 0;
ses->lobby_mode = GameMode::NORMAL;
ses->lobby_episode = Episode::EP1;
ses->lobby_random_seed = 0;
ses->item_creator.reset();
ses->map.reset();
// This command can cause the client to no longer send D6 responses when
// 1A/D5 large message boxes are closed. newserv keeps track of this
@@ -1448,6 +1506,7 @@ static HandlerResult S_65_67_68_EB(shared_ptr<ProxyServer::LinkedSession> ses, u
size_t num_replacements = 0;
ses->lobby_client_id = cmd.lobby_flags.client_id;
ses->lobby_event = cmd.lobby_flags.event;
update_leader_id(ses, cmd.lobby_flags.leader_id);
for (size_t x = 0; x < flag; x++) {
auto& entry = cmd.entries[x];
@@ -1496,6 +1555,50 @@ constexpr on_command_t S_P_65_67_68 = &S_65_67_68_EB<S_JoinLobby_PC_65_67_68>;
constexpr on_command_t S_X_65_67_68 = &S_65_67_68_EB<S_JoinLobby_XB_65_67_68>;
constexpr on_command_t S_B_65_67_68 = &S_65_67_68_EB<S_JoinLobby_BB_65_67_68>;
template <typename CmdT>
Episode get_episode(const CmdT&) {
return Episode::EP1;
}
template <>
Episode get_episode<S_JoinGame_GC_64>(const S_JoinGame_GC_64& cmd) {
switch (cmd.episode) {
case 1:
return Episode::EP1;
case 2:
return Episode::EP2;
default:
return Episode::NONE;
}
}
template <>
Episode get_episode<S_JoinGame_XB_64>(const S_JoinGame_XB_64& cmd) {
switch (cmd.episode) {
case 1:
return Episode::EP1;
case 2:
return Episode::EP2;
default:
return Episode::NONE;
}
}
template <>
Episode get_episode<S_JoinGame_BB_64>(const S_JoinGame_BB_64& cmd) {
switch (cmd.episode) {
case 1:
return Episode::EP1;
case 2:
return Episode::EP2;
case 3:
return Episode::EP4;
default:
return Episode::NONE;
}
}
template <>
Episode get_episode<S_JoinGame_Ep3_64>(const S_JoinGame_Ep3_64&) {
return Episode::EP3;
}
template <typename CmdT>
static HandlerResult S_64(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t flag, string& data) {
CmdT* cmd;
@@ -1515,7 +1618,41 @@ static HandlerResult S_64(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
ses->floor = 0;
ses->is_in_game = true;
ses->is_in_quest = false;
ses->difficulty = cmd->difficulty;
ses->lobby_event = cmd->event;
ses->lobby_difficulty = cmd->difficulty;
ses->lobby_section_id = cmd->section_id;
// We only need the game mode for overriding drops, and SOLO behaves the same
// as NORMAL in that regard, so we can conveniently ignore SOLO here
if (cmd->battle_mode) {
ses->lobby_mode = GameMode::BATTLE;
} else if (cmd->challenge_mode) {
ses->lobby_mode = GameMode::CHALLENGE;
} else {
ses->lobby_mode = GameMode::NORMAL;
}
ses->lobby_random_seed = cmd->rare_seed;
if (cmd_ep3) {
ses->lobby_episode = Episode::EP3;
} else {
ses->lobby_episode = get_episode(*cmd);
}
// Recreate the item creator if needed, and load maps
auto s = ses->require_server_state();
ses->set_drop_mode(ses->drop_mode);
ses->map = Lobby::load_maps(
ses->version(),
ses->lobby_episode,
ses->lobby_mode,
ses->lobby_difficulty,
ses->lobby_event,
ses->id,
s->set_data_table(ses->version(), ses->lobby_episode, ses->lobby_mode, ses->lobby_difficulty),
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
Map::DEFAULT_RARE_ENEMIES,
make_shared<PSOV2Encryption>(ses->lobby_random_seed),
cmd->variations,
&ses->log);
bool modified = false;
@@ -1569,7 +1706,14 @@ static HandlerResult S_E8(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
ses->floor = 0;
ses->is_in_game = true;
ses->is_in_quest = false;
ses->difficulty = 0;
ses->lobby_event = cmd.event;
ses->lobby_difficulty = 0;
ses->lobby_section_id = cmd.section_id;
ses->lobby_mode = GameMode::NORMAL;
ses->lobby_random_seed = 0;
ses->lobby_episode = Episode::EP3;
ses->item_creator.reset();
ses->map.reset();
bool modified = false;
@@ -1649,7 +1793,15 @@ static HandlerResult C_98(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t c
ses->floor = 0x0F;
ses->is_in_game = false;
ses->is_in_quest = false;
ses->difficulty = 0;
ses->lobby_event = 0;
ses->lobby_difficulty = 0;
ses->lobby_section_id = 0;
ses->lobby_episode = Episode::EP1;
ses->lobby_mode = GameMode::NORMAL;
ses->lobby_random_seed = 0;
ses->item_creator.reset();
ses->map.reset();
if (is_v3(ses->version()) || is_v4(ses->version())) {
return C_GXB_61(ses, command, flag, data);
} else {
@@ -1765,44 +1917,6 @@ static HandlerResult C_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t c
}
}
}
if (!data.empty()) {
if (data[0] == 0x21) {
const auto& cmd = check_size_t<G_InterLevelWarp_6x21>(data);
ses->floor = cmd.floor;
} else if (data[0] == 0x0C) {
if (is_v1_or_v2(ses->version()) && ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) {
send_remove_conditions(ses->client_channel, ses->lobby_client_id);
send_remove_conditions(ses->server_channel, ses->lobby_client_id);
}
} else if (data[0] == 0x2F || data[0] == 0x4B || data[0] == 0x4C) {
if (ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) {
send_player_stats_change(ses->client_channel,
ses->lobby_client_id, PlayerStatsChange::ADD_HP, 2550);
send_player_stats_change(ses->server_channel,
ses->lobby_client_id, PlayerStatsChange::ADD_HP, 2550);
}
} else if (data[0] == 0x3E) {
C_6x_movement<G_StopAtPosition_6x3E>(ses, data);
} else if (data[0] == 0x3F) {
C_6x_movement<G_SetPosition_6x3F>(ses, data);
} else if (data[0] == 0x40) {
C_6x_movement<G_WalkToPosition_6x40>(ses, data);
} else if (data[0] == 0x42) {
C_6x_movement<G_RunToPosition_6x42>(ses, data);
} else if (data[0] == 0x48) {
if (ses->config.check_flag(Client::Flag::INFINITE_TP_ENABLED)) {
send_player_stats_change(ses->client_channel,
ses->lobby_client_id, PlayerStatsChange::ADD_TP, 255);
send_player_stats_change(ses->server_channel,
ses->lobby_client_id, PlayerStatsChange::ADD_TP, 255);
}
} else if (data[0] == 0x5F) {
const auto& cmd = check_size_t<G_DropItem_DC_6x5F>(data, sizeof(G_DropItem_PC_V3_BB_6x5F));
send_item_notification_if_needed(ses->require_server_state(), ses->client_channel, ses->config, cmd.item.item, true);
}
}
return C_6x<void>(ses, command, flag, data);
}
@@ -1814,19 +1928,64 @@ constexpr on_command_t C_B_6x = &C_6x<G_SendGuildCard_BB_6x06>;
template <>
HandlerResult C_6x<void>(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
check_implemented_subcommand(ses, data);
if (!data.empty() && (data[0] == 0x05) && ses->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED)) {
auto& cmd = check_size_t<G_SwitchStateChanged_6x05>(data);
if (cmd.flags && cmd.header.object_id != 0xFFFF) {
if (ses->last_switch_enabled_command.header.subcommand == 0x05) {
ses->log.info("Switch assist: replaying previous enable command");
ses->server_channel.send(0x60, 0x00, &ses->last_switch_enabled_command,
sizeof(ses->last_switch_enabled_command));
ses->client_channel.send(0x60, 0x00, &ses->last_switch_enabled_command,
sizeof(ses->last_switch_enabled_command));
if (!data.empty()) {
if ((data[0] == 0x05) && ses->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED)) {
auto& cmd = check_size_t<G_SwitchStateChanged_6x05>(data);
if (cmd.flags && cmd.header.object_id != 0xFFFF) {
if (ses->last_switch_enabled_command.header.subcommand == 0x05) {
ses->log.info("Switch assist: replaying previous enable command");
ses->server_channel.send(0x60, 0x00, &ses->last_switch_enabled_command,
sizeof(ses->last_switch_enabled_command));
ses->client_channel.send(0x60, 0x00, &ses->last_switch_enabled_command,
sizeof(ses->last_switch_enabled_command));
}
ses->last_switch_enabled_command = cmd;
}
ses->last_switch_enabled_command = cmd;
} else if (data[0] == 0x21) {
const auto& cmd = check_size_t<G_InterLevelWarp_6x21>(data);
ses->floor = cmd.floor;
} else if (data[0] == 0x0C) {
if (is_v1_or_v2(ses->version()) && ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) {
send_remove_conditions(ses->client_channel, ses->lobby_client_id);
send_remove_conditions(ses->server_channel, ses->lobby_client_id);
}
} else if (data[0] == 0x2F || data[0] == 0x4B || data[0] == 0x4C) {
if (ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) {
send_player_stats_change(ses->client_channel,
ses->lobby_client_id, PlayerStatsChange::ADD_HP, 2550);
send_player_stats_change(ses->server_channel,
ses->lobby_client_id, PlayerStatsChange::ADD_HP, 2550);
}
} else if (data[0] == 0x3E) {
C_6x_movement<G_StopAtPosition_6x3E>(ses, data);
} else if (data[0] == 0x3F) {
C_6x_movement<G_SetPosition_6x3F>(ses, data);
} else if (data[0] == 0x40) {
C_6x_movement<G_WalkToPosition_6x40>(ses, data);
} else if (data[0] == 0x42) {
C_6x_movement<G_RunToPosition_6x42>(ses, data);
} else if (data[0] == 0x48) {
if (ses->config.check_flag(Client::Flag::INFINITE_TP_ENABLED)) {
send_player_stats_change(ses->client_channel,
ses->lobby_client_id, PlayerStatsChange::ADD_TP, 255);
send_player_stats_change(ses->server_channel,
ses->lobby_client_id, PlayerStatsChange::ADD_TP, 255);
}
} else if (data[0] == 0x5F) {
const auto& cmd = check_size_t<G_DropItem_DC_6x5F>(data, sizeof(G_DropItem_PC_V3_BB_6x5F));
send_item_notification_if_needed(ses->require_server_state(), ses->client_channel, ses->config, cmd.item.item, true);
} else if (data[0] == 0x60 || static_cast<uint8_t>(data[0]) == 0xA2) {
return SC_6x60_6xA2(ses, data);
}
}
+72 -1
View File
@@ -522,6 +522,7 @@ ProxyServer::LinkedSession::LinkedSession(
sub_version(0), // This is set during resume()
remote_guild_card_number(-1),
next_item_id(0x0F000000),
drop_mode(DropMode::PASSTHROUGH),
lobby_players(12),
lobby_client_id(0),
leader_client_id(0),
@@ -529,7 +530,13 @@ ProxyServer::LinkedSession::LinkedSession(
x(0.0),
z(0.0),
is_in_game(false),
is_in_quest(false) {
is_in_quest(false),
lobby_event(0),
lobby_difficulty(0),
lobby_section_id(0),
lobby_mode(GameMode::NORMAL),
lobby_episode(Episode::EP1),
lobby_random_seed(0) {
this->last_switch_enabled_command.header.subcommand = 0;
memset(this->prev_server_command_bytes, 0, sizeof(this->prev_server_command_bytes));
}
@@ -724,6 +731,70 @@ void ProxyServer::LinkedSession::clear_lobby_players(size_t num_slots) {
this->log.info("Cleared lobby players");
}
void ProxyServer::LinkedSession::set_drop_mode(DropMode new_mode) {
this->drop_mode = new_mode;
if (this->drop_mode == DropMode::INTERCEPT) {
auto s = this->require_server_state();
auto version = this->version();
shared_ptr<const RareItemSet> rare_item_set;
shared_ptr<const CommonItemSet> common_item_set;
switch (version) {
case Version::PC_PATCH:
case Version::BB_PATCH:
case Version::GC_EP3_NTE:
case Version::GC_EP3:
throw runtime_error("cannot create item creator for this base version");
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
case Version::DC_V1:
// TODO: We should probably have a v1 common item set at some point too
common_item_set = s->common_item_set_v2;
rare_item_set = s->rare_item_sets.at("rare-table-v1");
break;
case Version::DC_V2:
case Version::PC_NTE:
case Version::PC_V2:
common_item_set = s->common_item_set_v2;
rare_item_set = s->rare_item_sets.at("rare-table-v2");
break;
case Version::GC_NTE:
case Version::GC_V3:
case Version::XB_V3:
common_item_set = s->common_item_set_v3_v4;
rare_item_set = s->rare_item_sets.at("rare-table-v3");
break;
case Version::BB_V4:
common_item_set = s->common_item_set_v3_v4;
rare_item_set = s->rare_item_sets.at("rare-table-v4");
break;
default:
throw logic_error("invalid lobby base version");
}
uint32_t random_seed = this->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)
? this->config.override_random_seed
: this->lobby_random_seed;
this->item_creator = make_shared<ItemCreator>(
common_item_set,
rare_item_set,
s->armor_random_set,
s->tool_random_set,
s->weapon_random_sets.at(this->lobby_difficulty),
s->tekker_adjustment_set,
s->item_parameter_table(version),
version,
this->lobby_episode,
(this->lobby_mode == GameMode::SOLO) ? GameMode::NORMAL : this->lobby_mode,
this->lobby_difficulty,
this->lobby_section_id,
random_seed,
// TODO: Can we get battle rules here somehow?
nullptr);
} else {
this->item_creator.reset();
}
}
void ProxyServer::LinkedSession::send_to_game_server(const char* error_message) {
// If there is no license, do nothing - we can't return to the game server
// from unlicensed sessions
+16 -1
View File
@@ -74,6 +74,16 @@ public:
ItemData next_drop_item;
uint32_t next_item_id;
enum class DropMode {
DISABLED = 0,
PASSTHROUGH,
INTERCEPT,
};
DropMode drop_mode;
std::shared_ptr<std::string> quest_dat_data;
std::shared_ptr<ItemCreator> item_creator;
std::shared_ptr<Map> map;
struct LobbyPlayer {
uint32_t guild_card_number = 0;
uint64_t xb_user_id = 0;
@@ -90,7 +100,12 @@ public:
float z;
bool is_in_game;
bool is_in_quest;
uint8_t difficulty;
uint8_t lobby_event;
uint8_t lobby_difficulty;
uint8_t lobby_section_id;
GameMode lobby_mode;
Episode lobby_episode;
uint32_t lobby_random_seed;
uint64_t client_ping_start_time = 0;
uint64_t server_ping_start_time = 0;
+1 -1
View File
@@ -49,7 +49,7 @@ static shared_ptr<const Menu> proxy_options_menu_for_client(shared_ptr<const Cli
"Chat commands", "Enable chat\ncommands");
add_flag_option(ProxyOptionsMenuItemID::PLAYER_NOTIFICATIONS, Client::Flag::PROXY_PLAYER_NOTIFICATIONS_ENABLED,
"Player notifs", "Show a message\nwhen other players\njoin or leave");
static const char* item_drop_notifs_description = "Enable item drop\nnotifications:\n- No = no notifs\n- Rare = rares only\n- Item = all items\nbut not Meseta\n- Every = everything";
static const char* item_drop_notifs_description = "Enable item drop\nnotifications:\n- No: no notifs\n- Rare: rares only\n- Item: all items\nbut not Meseta\n- Every: everything";
if (!is_ep3(c->version())) {
switch (c->config.get_drop_notification_mode()) {
case Client::ItemDropNotificationMode::NOTHING:
+94 -72
View File
@@ -2148,27 +2148,7 @@ static void on_sort_inventory_bb(shared_ptr<Client> c, uint8_t, uint8_t, void* d
////////////////////////////////////////////////////////////////////////////////
// EXP/Drop Item commands
static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
auto s = c->require_server_state();
auto l = c->require_lobby();
if (!l->is_game()) {
return;
}
switch (l->drop_mode) {
case Lobby::DropMode::CLIENT:
forward_subcommand(c, command, flag, data, size);
return;
case Lobby::DropMode::DISABLED:
return;
case Lobby::DropMode::SERVER_SHARED:
case Lobby::DropMode::SERVER_DUPLICATE:
case Lobby::DropMode::SERVER_PRIVATE:
break;
default:
throw logic_error("invalid drop mode");
}
G_SpecializableItemDropRequest_6xA2 normalize_drop_request(const void* data, size_t size) {
G_SpecializableItemDropRequest_6xA2 cmd;
if (size == sizeof(G_SpecializableItemDropRequest_6xA2)) {
cmd = check_size_t<G_SpecializableItemDropRequest_6xA2>(data, size);
@@ -2200,21 +2180,35 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
cmd.ignore_def = in_cmd.ignore_def;
cmd.effective_area = in_cmd.floor;
}
return cmd;
}
DropReconcileResult reconcile_drop_request_with_map(
PrefixedLogger& log,
Channel& client_channel,
G_SpecializableItemDropRequest_6xA2& cmd,
Version version,
Episode episode,
const Client::Config& config,
shared_ptr<Map> map,
bool mark_drop) {
DropReconcileResult res;
res.effective_rt_index = 0xFF;
res.is_box = (cmd.rt_index == 0x30);
res.should_drop = true;
res.ignore_def = (cmd.ignore_def != 0);
Map::Object* map_object = nullptr;
Map::Enemy* map_enemy = nullptr;
bool ignore_def = (cmd.ignore_def != 0);
uint8_t effective_rt_index = 0xFF;
bool is_box = (cmd.rt_index == 0x30);
if (is_box) {
if (l->map) {
map_object = &l->map->objects.at(cmd.entity_id);
l->log.info("Drop check for K-%hX %c %s",
map_object->object_id, ignore_def ? 'G' : 'S', Map::name_for_object_type(map_object->base_type));
if (res.is_box) {
if (map) {
map_object = &map->objects.at(cmd.entity_id);
log.info("Drop check for K-%hX %c %s",
map_object->object_id, res.ignore_def ? 'G' : 'S', Map::name_for_object_type(map_object->base_type));
if (cmd.floor != map_object->floor) {
l->log.warning("Floor %02hhX from command does not match object\'s expected floor %02hhX", cmd.floor, map_object->floor);
log.warning("Floor %02hhX from command does not match object\'s expected floor %02hhX", cmd.floor, map_object->floor);
}
if (is_v1_or_v2(l->base_version) && (l->base_version != Version::GC_NTE)) {
if (is_v1_or_v2(version) && (version != Version::GC_NTE)) {
// V1 and V2 don't have 6xA2, so we can't get ignore_def or the object
// parameters from the client on those versions
cmd.param3 = map_object->param3;
@@ -2223,62 +2217,90 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
cmd.param6 = map_object->param6;
}
bool object_ignore_def = (map_object->param1 > 0.0);
if (ignore_def != object_ignore_def) {
l->log.warning("ignore_def value %s from command does not match object\'s expected ignore_def %s (from p1=%g)",
ignore_def ? "true" : "false", object_ignore_def ? "true" : "false", map_object->param1);
if (res.ignore_def != object_ignore_def) {
log.warning("ignore_def value %s from command does not match object\'s expected ignore_def %s (from p1=%g)",
res.ignore_def ? "true" : "false", object_ignore_def ? "true" : "false", map_object->param1);
}
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
send_text_message_printf(c, "$C5K-%hX %c %s",
map_object->object_id, ignore_def ? 'G' : 'S', Map::name_for_object_type(map_object->base_type));
if (config.check_flag(Client::Flag::DEBUG_ENABLED)) {
send_text_message_printf(client_channel, "$C5K-%hX %c %s",
map_object->object_id, res.ignore_def ? 'G' : 'S', Map::name_for_object_type(map_object->base_type));
}
}
} else {
if (l->map) {
map_enemy = &l->map->enemies.at(cmd.entity_id);
l->log.info("Drop check for E-%hX %s", map_enemy->enemy_id, name_for_enum(map_enemy->type));
effective_rt_index = rare_table_index_for_enemy_type(map_enemy->type);
if (map) {
map_enemy = &map->enemies.at(cmd.entity_id);
log.info("Drop check for E-%hX %s", map_enemy->enemy_id, name_for_enum(map_enemy->type));
res.effective_rt_index = rare_table_index_for_enemy_type(map_enemy->type);
// rt_indexes in Episode 4 don't match those sent in the command; we just
// ignore what the client sends.
if ((l->episode != Episode::EP4) && (cmd.rt_index != effective_rt_index)) {
l->log.warning("rt_index %02hhX from command does not match entity\'s expected index %02" PRIX32,
cmd.rt_index, effective_rt_index);
if (!is_v4(l->base_version)) {
effective_rt_index = cmd.rt_index;
if ((episode != Episode::EP4) && (cmd.rt_index != res.effective_rt_index)) {
log.warning("rt_index %02hhX from command does not match entity\'s expected index %02" PRIX32,
cmd.rt_index, res.effective_rt_index);
if (!is_v4(version)) {
res.effective_rt_index = cmd.rt_index;
}
}
if (cmd.floor != map_enemy->floor) {
l->log.warning("Floor %02hhX from command does not match entity\'s expected floor %02hhX",
log.warning("Floor %02hhX from command does not match entity\'s expected floor %02hhX",
cmd.floor, map_enemy->floor);
}
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
send_text_message_printf(c, "$C5E-%hX %s", map_enemy->enemy_id, name_for_enum(map_enemy->type));
if (config.check_flag(Client::Flag::DEBUG_ENABLED)) {
send_text_message_printf(client_channel, "$C5E-%hX %s", map_enemy->enemy_id, name_for_enum(map_enemy->type));
}
}
}
bool should_drop = true;
if (map_object) {
if (map_object->item_drop_checked) {
l->log.info("Drop check has already occurred for K-%04hX; skipping it", map_object->object_id);
should_drop = false;
} else {
map_object->item_drop_checked = true;
if (mark_drop) {
if (map_object) {
if (map_object->item_drop_checked) {
log.info("Drop check has already occurred for K-%04hX; skipping it", map_object->object_id);
res.should_drop = false;
} else {
map_object->item_drop_checked = true;
}
}
}
if (map_enemy) {
if (map_enemy->state_flags & Map::Enemy::Flag::ITEM_DROPPED) {
l->log.info("Drop check has already occurred for E-%04hX; skipping it", map_enemy->enemy_id);
should_drop = false;
} else {
map_enemy->state_flags |= Map::Enemy::Flag::ITEM_DROPPED;
if (map_enemy) {
if (map_enemy->state_flags & Map::Enemy::Flag::ITEM_DROPPED) {
log.info("Drop check has already occurred for E-%04hX; skipping it", map_enemy->enemy_id);
res.should_drop = false;
} else {
map_enemy->state_flags |= Map::Enemy::Flag::ITEM_DROPPED;
}
}
}
if (should_drop) {
return res;
}
static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, uint8_t flag, void* data, size_t size) {
auto s = c->require_server_state();
auto l = c->require_lobby();
if (!l->is_game()) {
return;
}
switch (l->drop_mode) {
case Lobby::DropMode::CLIENT:
forward_subcommand(c, command, flag, data, size);
return;
case Lobby::DropMode::DISABLED:
return;
case Lobby::DropMode::SERVER_SHARED:
case Lobby::DropMode::SERVER_DUPLICATE:
case Lobby::DropMode::SERVER_PRIVATE:
break;
default:
throw logic_error("invalid drop mode");
}
G_SpecializableItemDropRequest_6xA2 cmd = normalize_drop_request(data, size);
auto rec = reconcile_drop_request_with_map(c->log, c->channel, cmd, c->version(), l->episode, c->config, l->map, true);
if (rec.should_drop) {
auto generate_item = [&]() -> ItemCreator::DropResult {
if (is_box) {
if (ignore_def) {
if (rec.is_box) {
if (rec.ignore_def) {
l->log.info("Creating item from box %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area);
return l->item_creator->on_box_item_drop(cmd.effective_area);
} else {
@@ -2288,7 +2310,7 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
}
} else {
l->log.info("Creating item from enemy %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area);
return l->item_creator->on_monster_item_drop(effective_rt_index, cmd.effective_area);
return l->item_creator->on_monster_item_drop(rec.effective_rt_index, cmd.effective_area);
}
};
@@ -2308,12 +2330,12 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
l->log.info("Entity %04hX (area %02hX) created item %s", cmd.entity_id.load(), cmd.effective_area, name.c_str());
if (l->drop_mode == Lobby::DropMode::SERVER_DUPLICATE) {
for (const auto& lc : l->clients) {
if (lc && (is_box || (lc->floor == cmd.floor))) {
if (lc && (rec.is_box || (lc->floor == cmd.floor))) {
res.item.id = l->generate_item_id(0xFF);
l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for %s",
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load(), lc->channel.name.c_str());
l->add_item(cmd.floor, res.item, cmd.x, cmd.z, (1 << lc->lobby_client_id));
send_drop_item_to_channel(s, lc->channel, res.item, !is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
send_drop_item_to_channel(s, lc->channel, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
send_item_notification_if_needed(s, lc->channel, lc->config, res.item, res.is_from_rare_table);
}
}
@@ -2323,7 +2345,7 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for all clients",
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load());
l->add_item(cmd.floor, res.item, cmd.x, cmd.z, 0x00F);
send_drop_item_to_lobby(l, res.item, !is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
send_drop_item_to_lobby(l, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
for (auto lc : l->clients) {
if (lc) {
send_item_notification_if_needed(s, lc->channel, lc->config, res.item, res.is_from_rare_table);
@@ -2335,7 +2357,7 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
}
case Lobby::DropMode::SERVER_PRIVATE: {
for (const auto& lc : l->clients) {
if (lc && (is_box || (lc->floor == cmd.floor))) {
if (lc && (rec.is_box || (lc->floor == cmd.floor))) {
auto res = generate_item();
if (res.item.empty()) {
l->log.info("No item was created for %s", lc->channel.name.c_str());
@@ -2346,7 +2368,7 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for %s",
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load(), lc->channel.name.c_str());
l->add_item(cmd.floor, res.item, cmd.x, cmd.z, (1 << lc->lobby_client_id));
send_drop_item_to_channel(s, lc->channel, res.item, !is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
send_drop_item_to_channel(s, lc->channel, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
send_item_notification_if_needed(s, lc->channel, lc->config, res.item, res.is_from_rare_table);
}
}
+20
View File
@@ -3,6 +3,7 @@
#include <stdint.h>
#include "Client.hh"
#include "CommandFormats.hh"
#include "Lobby.hh"
#include "PSOProtocol.hh"
#include "ServerState.hh"
@@ -16,3 +17,22 @@ void send_item_notification_if_needed(
const Client::Config& config,
const ItemData& item,
bool is_from_rare_table);
G_SpecializableItemDropRequest_6xA2 normalize_drop_request(const void* data, size_t size);
struct DropReconcileResult {
uint8_t effective_rt_index;
bool is_box;
bool should_drop;
bool ignore_def;
};
DropReconcileResult reconcile_drop_request_with_map(
PrefixedLogger& log,
Channel& client_channel,
G_SpecializableItemDropRequest_6xA2& cmd,
Version version,
Episode episode,
const Client::Config& config,
std::shared_ptr<Map> map,
bool mark_drop);