add ability to forbid specific quest flag writes

This commit is contained in:
Martin Michelsen
2023-12-18 21:58:39 -08:00
parent e3315822de
commit b7604eb643
11 changed files with 88 additions and 8 deletions
+1
View File
@@ -57,6 +57,7 @@ public:
HAS_GUILD_CARD_NUMBER = 0x0000000040000000,
AT_BANK_COUNTER = 0x0000000080000000,
SHOULD_SEND_ARTIFICIAL_ITEM_STATE = 0x0001000000000000,
SHOULD_SEND_ARTIFICIAL_FLAG_STATE = 0x0002000000000000,
// Cheat mode flags
SWITCH_ASSIST_ENABLED = 0x0000000100000000,
+5
View File
@@ -485,6 +485,11 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
// received. For this reason, we consume item IDs here only if the client is
// NTE or 11/2000.
this->assign_inventory_and_bank_item_ids(c, is_pre_v1(c->version()));
// On BB, we send artificial flag state to fix an Episode 2 bug where the
// CCA door lock state is overwritten by quests.
if (c->version() == Version::BB_V4) {
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
}
}
// If the lobby is recording a battle record, add the player join event
+15
View File
@@ -508,6 +508,13 @@ struct QuestFlagsForDifficulty {
uint8_t mask = 0x80 >> (flag_index & 7);
this->data[byte_index] &= (~mask);
}
inline void update_all(bool set) {
if (set) {
this->data.clear(0xFF);
} else {
this->data.clear(0x00);
}
}
} __attribute__((packed));
struct QuestFlags {
@@ -522,6 +529,14 @@ struct QuestFlags {
inline void clear(uint8_t difficulty, uint16_t flag_index) {
this->data[difficulty].clear(flag_index);
}
inline void update_all(uint8_t difficulty, bool set) {
this->data[difficulty].update_all(set);
}
inline void update_all(bool set) {
for (size_t z = 0; z < 4; z++) {
this->update_all(z, set);
}
}
} __attribute__((packed));
struct BattleRules {
+4 -1
View File
@@ -345,11 +345,14 @@ static void on_1D(shared_ptr<Client> c, uint16_t, uint32_t, string&) {
if (c->config.check_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_ITEM_STATE)) {
c->config.clear_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_ITEM_STATE);
auto l = c->require_lobby();
if (!is_ep3(c->version())) {
send_game_item_state(c);
}
}
if (c->config.check_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE)) {
c->config.clear_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
send_game_flag_state(c);
}
}
static void on_05_XB(shared_ptr<Client> c, uint16_t, uint32_t, string&) {
+14 -7
View File
@@ -1724,19 +1724,26 @@ static void on_set_quest_flag(shared_ptr<Client> c, uint8_t command, uint8_t fla
difficulty = cmd.difficulty;
}
if (flag_index >= 0x400) {
if ((flag_index >= 0x400) || (difficulty > 3) || (action > 1)) {
return;
}
// TODO: Should we allow overlays here?
auto p = c->character(true, false);
// The client explicitly checks for both 0 and 1 - any other value means no
// operation is performed.
if (action == 0) {
p->quest_flags.set(difficulty, flag_index);
} else if (action == 1) {
p->quest_flags.clear(difficulty, flag_index);
auto s = c->require_server_state();
if (s->quest_flag_persist_mask.get(flag_index)) {
// The client explicitly checks for both 0 and 1 - any other value means no
// operation is performed.
if (action == 0) {
c->log.info("Setting quest flag %s:%03hX", name_for_difficulty(difficulty), flag_index);
p->quest_flags.set(difficulty, flag_index);
} else if (action == 1) {
c->log.info("Clearing quest flag %s:%03hX", name_for_difficulty(difficulty), flag_index);
p->quest_flags.clear(difficulty, flag_index);
}
} else {
c->log.info("Quest flag %s:%03hX cannot be modified", name_for_difficulty(difficulty), flag_index);
}
forward_subcommand(c, command, flag, data, size);
+25
View File
@@ -2395,6 +2395,31 @@ void send_game_item_state(shared_ptr<Client> c) {
}
}
void send_game_flag_state(shared_ptr<Client> c) {
auto l = c->require_lobby();
G_SetQuestFlags_6x6F cmd;
cmd.header.subcommand = 0x6F;
cmd.header.size = sizeof(G_SetQuestFlags_6x6F) >> 2;
cmd.header.unused = 0x0000;
cmd.quest_flags = c->character()->quest_flags;
for (const auto& lc : l->clients) {
if (!lc) {
continue;
}
if (lc->game_join_command_queue) {
lc->log.info("Client not ready to receive join commands; adding to queue");
auto& cmd = lc->game_join_command_queue->emplace_back();
cmd.command = 0x0060;
cmd.flag = 0x00000000;
cmd.data.assign(reinterpret_cast<const char*>(&cmd), sizeof(cmd));
} else {
send_command_t(lc, 0x60, 0x00, cmd);
}
}
}
void send_drop_item_to_channel(shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
bool from_enemy, uint8_t floor, float x, float z, uint16_t entity_id) {
uint8_t subcommand = get_pre_v1_subcommand(ch.version, 0x51, 0x58, 0x5F);
+1
View File
@@ -298,6 +298,7 @@ void send_ep3_change_music(Channel& ch, uint32_t song);
void send_revive_player(std::shared_ptr<Client> c);
void send_game_item_state(std::shared_ptr<Client> c);
void send_game_flag_state(std::shared_ptr<Client> c);
void send_drop_item_to_channel(std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
void send_drop_item_to_lobby(std::shared_ptr<Lobby> l, const ItemData& item,
+9
View File
@@ -676,6 +676,15 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
(this->allowed_drop_modes_v4_battle & (1 << static_cast<size_t>(Lobby::DropMode::CLIENT))) || (this->allowed_drop_modes_v4_challenge & (1 << static_cast<size_t>(Lobby::DropMode::CLIENT)))) {
throw runtime_error("CLIENT drop mode cannot be allowed in V4");
}
this->quest_flag_persist_mask.update_all(true);
try {
for (const auto& flag_id_json : json.get_list("PreventPersistQuestFlags")) {
this->quest_flag_persist_mask.clear(flag_id_json->as_int());
}
} catch (const out_of_range&) {
}
this->persistent_game_idle_timeout_usecs = json.get_int("PersistentGameIdleTimeout", this->persistent_game_idle_timeout_usecs);
this->cheat_mode_behavior = parse_behavior_switch("CheatModeBehavior", this->cheat_mode_behavior);
this->ep3_send_function_call_enabled = json.get_bool("EnableEpisode3SendFunctionCall", this->ep3_send_function_call_enabled);
+1
View File
@@ -94,6 +94,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
Lobby::DropMode default_drop_mode_v4_normal;
Lobby::DropMode default_drop_mode_v4_battle;
Lobby::DropMode default_drop_mode_v4_challenge;
QuestFlagsForDifficulty quest_flag_persist_mask;
uint64_t persistent_game_idle_timeout_usecs;
bool ep3_send_function_call_enabled;
bool catch_handler_exceptions;
+6
View File
@@ -781,6 +781,12 @@
"Episode2": [1, 30, 60, 100],
"Episode4": [1, 40, 70, 110],
},
// Some quest flags should not be written to the character file when they're
// updated during a quest. Specify the flag IDs here to prevent those flags
// from being updated in saved BB character data. The default value here
// prevents the door locks in CCA from being deactivated in free-roam by
// loading a quest.
"PreventPersistQuestFlags": [0x0046, 0x0047, 0x0048],
// Whether to enable certain exception handling. Disabling this causes
// newserv to abort when any client causes an exception, which is generally
+7
View File
@@ -300,4 +300,11 @@
"RewardItem": "031903",
},
],
"BBMinimumLevels": {
"Episode1": [1, 20, 50, 90],
"Episode2": [1, 30, 60, 100],
"Episode4": [1, 40, 70, 110],
},
"PreventPersistQuestFlags": [0x0046, 0x0047, 0x0048],
}