add ability to forbid specific quest flag writes
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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&) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user