From 77d5436b157df042f6bed95117c10f340200d812 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sat, 15 Nov 2025 22:36:36 -0800 Subject: [PATCH] implement quest item creation masks --- src/CommandFormats.hh | 34 ++-- src/Quest.cc | 3 + src/QuestMetadata.cc | 85 ++++++++++ src/QuestMetadata.hh | 27 ++++ src/QuestScript.cc | 140 ++++++++++++++-- src/QuestScript.hh | 24 ++- src/ReceiveSubcommands.cc | 151 ++++++++++++------ src/ServerState.cc | 11 -- src/ServerState.hh | 1 - .../MomokaItemExchangeFix.59NL.patch.s | 115 +++++++++++++ system/config.example.json | 10 -- system/quests/events/q201-bb-j.bin | Bin 21037 -> 21042 bytes system/quests/retrieval/q058-gc-e.bin.txt | 12 ++ system/quests/solo-extra/q035-bb-j.bin | Bin 4521 -> 4474 bytes system/quests/solo-extra/q143-bb-j.bin | Bin 21905 -> 21905 bytes tests/config.json | 8 - 16 files changed, 512 insertions(+), 109 deletions(-) create mode 100644 system/client-functions/BlueBurstExclusive/MomokaItemExchangeFix.59NL.patch.s diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 4cab9fbb..e6bf1e4f 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -6280,6 +6280,8 @@ struct G_AdjustPlayerMeseta_BB_6xC9 { } __packed_ws__(G_AdjustPlayerMeseta_BB_6xC9, 8); // 6xCA: Request item reward from quest (BB; handled by server) +// The server should create the item in the player's inventory using 6xBE if it +// matches at least one of the item creation masks in the quest's header. struct G_QuestCreateItem_BB_6xCA { G_UnusedHeader header; @@ -6365,6 +6367,10 @@ struct G_Unknown_BB_6xD4 { // 6xD5: Exchange item in quest (BB; handled by server) // The client sends this when it executes an F953 quest opcode. +// If any item matching find_item.data1[0-2] is present in the player's +// inventory, the server should destroy that item using 6x29, then create +// replace_item in the player's inventory using 6xBE if it matches at least one +// of the item creation masks in the quest's header. struct G_QuestExchangeItem_BB_6xD5 { G_ClientIDHeader header; @@ -6385,6 +6391,8 @@ struct G_WrapItem_BB_6xD6 { // 6xD7: Paganini Photon Drop exchange (BB; handled by server) // The client sends this when it executes an F955 quest opcode. +// The server should create the item in the player's inventory using 6xBE if it +// matches at least one of the item creation masks in the quest's header. struct G_PaganiniPhotonDropExchange_BB_6xD7 { G_ClientIDHeader header; @@ -6409,7 +6417,10 @@ struct G_AddSRankWeaponSpecial_BB_6xD8 { // The client sends this when it executes an F95B quest opcode. The client has // an unfortunate bug where it doesn't set the size field when generating this // command, so the size ends up as an uninitialized value and the client sends -// more (or less!) data than necessary. +// more (or less!) data than necessary. The MomokaItemExchangeFix patch fixes +// this bug. +// The server should create the item in the player's inventory using 6xBE if it +// matches at least one of the item creation masks in the quest's header. struct G_MomokaItemExchange_BB_6xD9 { /* 00 */ G_ClientIDHeader header; @@ -6470,18 +6481,21 @@ struct G_SetEXPMultiplier_BB_6xDD { // 6xDE: Exchange Secret Lottery Ticket (BB; handled by server) // The client sends this when it executes an F95C quest opcode. -// There appears to be a bug in the client here: it sets the subcommand size to -// 2 instead of 3, so the last relevant field (failure_label) is not sent to -// the server. +// There is a bug in the client here: it sets the subcommand size to 2 instead +// of 3, so the last relevant field (failure_label) is not sent to the server. +// This is fixed in the MomokaItemExchangeFix patch. -struct G_ExchangeSecretLotteryTicket_BB_6xDE { +struct G_ExchangeSecretLotteryTicket_Incomplete_BB_6xDE { G_ClientIDHeader header; - uint8_t index = 0; - uint8_t unknown_a1 = 0; + uint8_t index = 0; // 1-8 + uint8_t start_reg_num = 0; le_uint16_t success_label = 0; - // le_uint16_t failure_label = 0; - // parray unused; -} __packed_ws__(G_ExchangeSecretLotteryTicket_BB_6xDE, 8); +} __packed_ws__(G_ExchangeSecretLotteryTicket_Incomplete_BB_6xDE, 8); + +struct G_ExchangeSecretLotteryTicket_BB_6xDE : G_ExchangeSecretLotteryTicket_Incomplete_BB_6xDE { + le_uint16_t failure_label = 0; + parray unused; +} __packed_ws__(G_ExchangeSecretLotteryTicket_BB_6xDE, 0x0C); // 6xDF: Exchange Photon Crystals (BB; handled by server) // The client sends this when it executes an F95D quest opcode. diff --git a/src/Quest.cc b/src/Quest.cc index 538141d8..dba53de2 100644 --- a/src/Quest.cc +++ b/src/Quest.cc @@ -354,6 +354,9 @@ const string& Quest::name_for_language(Language language) const { void Quest::add_version(shared_ptr vq) { this->meta.assert_compatible(vq->meta); + if (this->meta.create_item_mask_entries.empty()) { + this->meta.create_item_mask_entries = vq->meta.create_item_mask_entries; + } this->versions.emplace(this->versions_key(vq->version, vq->language), vq); size_t lang_index = static_cast(vq->language); diff --git a/src/QuestMetadata.cc b/src/QuestMetadata.cc index 3a84be17..beded9ce 100644 --- a/src/QuestMetadata.cc +++ b/src/QuestMetadata.cc @@ -47,6 +47,25 @@ void QuestMetadata::assert_compatible(const QuestMetadata& other) const { if (this->enemy_exp_overrides != other.enemy_exp_overrides) { throw runtime_error("quest version has different enemy EXP overrides"); } + if (!this->create_item_mask_entries.empty() && + !other.create_item_mask_entries.empty() && + this->create_item_mask_entries != other.create_item_mask_entries) { + string this_str, other_str; + for (const auto& item : this->create_item_mask_entries) { + if (!this_str.empty()) { + this_str += ", "; + } + this_str += item.str(); + } + for (const auto& item : other.create_item_mask_entries) { + if (!other_str.empty()) { + other_str += ", "; + } + other_str += item.str(); + } + throw runtime_error(std::format( + "quest version has a different set of create item masks (existing: {}, new: {})", this_str, other_str)); + } if (!this->battle_rules != !other.battle_rules) { throw runtime_error(std::format( "quest version has a different battle rules presence state (existing: {}, new: {})", @@ -151,6 +170,12 @@ phosg::JSON QuestMetadata::json() const { auto key_str = std::format("{}:0x{:02X}:{}", name_for_difficulty(difficulty), floor, phosg::name_for_enum(enemy_type)); enemy_exp_overrides_json.emplace(key_str, exp_override); } + + auto create_item_mask_entries_json = phosg::JSON::dict(); + for (const auto& item : this->create_item_mask_entries) { + create_item_mask_entries_json.emplace_back(item.str()); + } + return phosg::JSON::dict({ {"CategoryID", this->category_id}, {"Number", this->quest_number}, @@ -172,9 +197,69 @@ phosg::JSON QuestMetadata::json() const { {"AllowStartFromChatCommand", this->allow_start_from_chat_command}, {"LockStatusRegister", (this->lock_status_register >= 0) ? this->lock_status_register : phosg::JSON(nullptr)}, {"EnemyEXPOverrides", std::move(enemy_exp_overrides_json)}, + {"CreateItemMasks", std::move(create_item_mask_entries_json)}, }); } +QuestMetadata::CreateItemMask::CreateItemMask(const std::string& s) { + phosg::StringReader r(s); + for (size_t z = 0; z < 12 && !r.eof(); z++) { + auto& range = this->data1_ranges[z]; + char c = r.get_s8(); + if (c == '[') { + c = r.get_s8(); + range.min = (phosg::value_for_hex_char(c) << 4) | phosg::value_for_hex_char(r.get_s8()); + if (r.get_s8() != '-') { + throw std::runtime_error("invalid range spec"); + } + c = r.get_s8(); + range.max = (phosg::value_for_hex_char(c) << 4) | phosg::value_for_hex_char(r.get_s8()); + if (r.get_s8() != ']') { + throw std::runtime_error("invalid range spec"); + } + } else { + range.min = (phosg::value_for_hex_char(c) << 4) | phosg::value_for_hex_char(r.get_s8()); + range.max = range.min; + } + } +} + +std::string QuestMetadata::CreateItemMask::str() const { + std::string ret; + for (size_t z = 0; z < 12; z++) { + const auto& r = this->data1_ranges[z]; + if (r.min == r.max) { + ret += std::format("{:02X}", r.min); + } else { + ret += std::format("[{:02X}-{:02X}]", r.min, r.max); + } + } + return ret; +} + +bool QuestMetadata::CreateItemMask::match(const ItemData& item) const { + for (size_t z = 0; z < 12; z++) { + const auto& r = this->data1_ranges[z]; + uint8_t v = item.data1[z]; + if (v < r.min || v > r.max) { + return false; + } + } + return true; +} + +uint32_t QuestMetadata::CreateItemMask::primary_identifier() const { + uint32_t ret = 0; + for (size_t z = 0; z < 3; z++) { + const auto& r = this->data1_ranges[z]; + if (r.min != r.max) { + throw std::runtime_error("create item mask is ambiguous; cannot compute primary identifier"); + } + ret = (ret << 8) | r.min; + } + return ret << 8; +} + std::unordered_map QuestMetadata::parse_enemy_exp_overrides(const phosg::JSON& json) { try { std::unordered_map ret; diff --git a/src/QuestMetadata.hh b/src/QuestMetadata.hh index b4f44284..3aa2384d 100644 --- a/src/QuestMetadata.hh +++ b/src/QuestMetadata.hh @@ -44,6 +44,33 @@ struct QuestMetadata { int16_t lock_status_register = -1; std::unordered_map enemy_exp_overrides; + // Item create allowances (only used on BB) + struct CreateItemMask { + struct Range { + uint8_t min = 0x00; + uint8_t max = 0x00; + + bool operator==(const Range& other) const = default; + bool operator!=(const Range& other) const = default; + }; + std::array data1_ranges; + + CreateItemMask() = default; + CreateItemMask(const CreateItemMask& other) = default; + CreateItemMask(CreateItemMask&& other) = default; + CreateItemMask& operator=(const CreateItemMask& other) = default; + CreateItemMask& operator=(CreateItemMask&& other) = default; + bool operator==(const CreateItemMask& other) const = default; + bool operator!=(const CreateItemMask& other) const = default; + + explicit CreateItemMask(const std::string& s); // Inverse of str() + std::string str() const; + + bool match(const ItemData& item) const; + uint32_t primary_identifier() const; // Raises if any of data1[0-2] are ambiguous + }; + std::vector create_item_mask_entries; + std::string name; std::string short_description; std::string long_description; diff --git a/src/QuestScript.cc b/src/QuestScript.cc index 9bedca32..e339d76d 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -969,7 +969,9 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { // Creates an item in the player's inventory. If the item is successfully // created, this opcode sends 6x2B on all versions except BB. On BB, this - // opcode sends 6xCA, and the server sends 6xBE to create the item. + // opcode sends 6xCA, and the server sends 6xBE to create the item. The + // requested item must match one of the item creation masks in the quest + // script's header. // regsA[0-2] = item.data1[0-2] // regB = returned item ID, or FFFFFFFF if item can't be created {0xB3, "item_create", nullptr, {{R_REG_SET_FIXED, 3}, W_REG}, F_V0_V4}, @@ -2764,6 +2766,9 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { // 5 = bank // 6 = tekker // 7 = government quest counter + // 8 = Momoka item exchange (this opens a menu displaying the items + // specified in the quest header, which sends 6xD9 when the player + // chooses an item) // valueA is not bounds-checked, so it could be used to write a byte with // the value 1 anywhere in memory. {0xF950, "bb_p2_menu", "BB_p2_menu", {I32}, F_V4 | F_ARGS}, @@ -2781,7 +2786,9 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { // Returns the number of items in the player's inventory. {0xF952, "bb_get_number_in_pack", "BB_get_number_in_pack", {W_REG}, F_V4}, - // Requests an item exchange in the player's inventory. Sends 6xD5. + // Requests an item exchange in the player's inventory. Sends 6xD5. The + // requested item must match one of the item creation masks in the quest + // script's header. // valueA/valueB/valueC = item.data1[0-2] to search for // valueD/valueE/valueF = item.data1[0-2] to replace it with // labelG = label to call on success @@ -2794,11 +2801,13 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { // 2 = item not found) {0xF954, "bb_check_wrap", "BB_check_wrap", {I32, W_REG}, F_V4 | F_ARGS}, - // Requests an item exchange for Photon Drops. Sends 6xD7. + // Requests an item exchange for Photon Drops. Sends 6xD7. The requested + // item must match one of the item creation masks in the quest script's + // header. // valueA/valueB/valueC = item.data1[0-2] for requested item // labelD = label to call on success // labelE = label to call on failure - {0xF955, "bb_exchange_pd_item", "BB_exchange_PD_item", {I32, I32, I32, LABEL16, LABEL16}, F_V4 | F_ARGS}, + {0xF955, "bb_exchange_pd_item", "BB_exchange_PD_item", {I32, I32, I32, SCRIPT16, SCRIPT16}, F_V4 | F_ARGS}, // Requests an S-rank special upgrade in exchange for Photon Drops. Sends // 6xD8. @@ -2807,7 +2816,7 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { // valueE = special type // labelF = label to call on success // labelG = label to call on failure - {0xF956, "bb_exchange_pd_srank", "BB_exchange_PD_srank", {I32, I32, I32, I32, I32, LABEL16, LABEL16}, F_V4 | F_ARGS}, + {0xF956, "bb_exchange_pd_srank", "BB_exchange_PD_srank", {I32, I32, I32, I32, I32, SCRIPT16, SCRIPT16}, F_V4 | F_ARGS}, // Requests a weapon attribute upgrade in exchange for Photon Drops. Sends // 6xDA. @@ -2817,12 +2826,12 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { // valueF = payment count (number of PDs) // labelG = label to call on success // labelH = label to call on failure - {0xF957, "bb_exchange_pd_percent", "BB_exchange_PD_special", {I32, I32, I32, I32, I32, I32, LABEL16, LABEL16}, F_V4 | F_ARGS}, + {0xF957, "bb_exchange_pd_percent", "BB_exchange_PD_special", {I32, I32, I32, I32, I32, I32, SCRIPT16, SCRIPT16}, F_V4 | F_ARGS}, // Requests a weapon attribute upgrade in exchange for Photon Spheres. // Sends 6xDA. Same arguments as bb_exchange_pd_percent, except Photon // Spheres are used instead. - {0xF958, "bb_exchange_ps_percent", "BB_exchange_PS_percent", {I32, I32, I32, I32, I32, I32, LABEL16, LABEL16}, F_V4 | F_ARGS}, + {0xF958, "bb_exchange_ps_percent", "BB_exchange_PS_percent", {I32, I32, I32, I32, I32, I32, SCRIPT16, SCRIPT16}, F_V4 | F_ARGS}, // Determines whether the Episode 4 boss can escape if undefeated after 20 // minutes. @@ -2833,24 +2842,28 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { // (even if the boss has already been defeated). {0xF95A, "bb_is_ep4_boss_dying", nullptr, {W_REG}, F_V4}, - // Requests an item exchange. Sends 6xD9. + // Requests an item exchange. Sends 6xD9. The requested item must match one + // of the item creation masks in the quest script's header. // valueA = find_item.data1[0-2] (low 3 bytes; high byte unused) // valueB = replace_item.data1[0-2] (low 3 bytes; high byte unused) // valueC = token1 (see 6xD9 in CommandFormats.hh) // valueD = token2 (see 6xD9 in CommandFormats.hh) // labelE = label to call on success // labelF = label to call on failure - {0xF95B, "bb_send_6xD9", nullptr, {I32, I32, I32, I32, LABEL16, LABEL16}, F_V4 | F_ARGS}, + {0xF95B, "bb_replace_item", "bb_send_6xD9", {I32, I32, I32, I32, SCRIPT16, SCRIPT16}, F_V4 | F_ARGS}, // Requests an exchange of Secret Lottery Tickets for items. Sends 6xDE. - // See SecretLotteryResultItems in config.json for the item pool used by - // this opcode. + // The pool of items that can be returned by this opcode is determined by + // the quest's header; newserv assembles/disassembles this field as + // .exchange_item directives. The first entry in the header is the currency + // item (which is to be deleted from the inventory); the others are the + // items the server randomly chooses from. // valueA = index // valueB = unknown_a1 // labelC = label to call on success // labelD = label to call on failure (unused because of a client bug; see // 6xDE description in CommandFormats.hh for details) - {0xF95C, "bb_exchange_slt", "BB_exchange_SLT", {I32, I32, LABEL32, LABEL32}, F_V4 | F_ARGS}, + {0xF95C, "bb_exchange_slt", "BB_exchange_SLT", {I32, I32, SCRIPT16, SCRIPT16}, F_V4 | F_ARGS}, // Removes a single Photon Crystal from the player's inventory, and // disables drops for the rest of the quest. Sends 6xDF. @@ -2959,6 +2972,52 @@ void check_opcode_definitions() { } } +CreateItemMaskEntry::CreateItemMaskEntry(const QuestMetadata::CreateItemMask& mask) { + for (size_t z = 0; z < 12; z++) { + auto& r = mask.data1_ranges[z]; + if (r.min == r.max) { + this->data1_fields[z] = r.min; + } else if (r.min == 0x00 && r.max == 0xFF) { + this->data1_fields[z] = -1; + } else { + this->data1_fields[z] = (r.min * 1000000) + (r.max * 1000); + } + } +} + +CreateItemMaskEntry::operator QuestMetadata::CreateItemMask() const { + using Range = QuestMetadata::CreateItemMask::Range; + + QuestMetadata::CreateItemMask ret; + for (size_t z = 0; z < 12; z++) { + int32_t v = this->data1_fields[z]; + + if (v < 0) { + // If v is negative, any value is allowed in this field + ret.data1_ranges[z] = Range{.min = 0x00, .max = 0xFF}; + + } else if (v < 0x100) { + // If v fits in an unsigned byte, this field must match exactly + ret.data1_ranges[z] = Range{.min = static_cast(v), .max = static_cast(v)}; + + } else if (v >= 1000000 && v <= 2000000) { + // Otherwise, the allowed range of values is encoded in decimal as + // 1MMMmmm (m = min, M = max) + uint32_t min = v % 1000; + uint32_t max = (v / 1000) % 1000; + if (min > 0xFF || max > 0xFF | min > max) { + throw std::runtime_error(std::format("invalid range spec {} (0x{:X})", v, v)); + } + ret.data1_ranges[z] = Range{.min = static_cast(min), .max = static_cast(max)}; + + } else { + throw std::runtime_error(std::format("invalid range spec {} (0x{:X})", v, v)); + } + } + + return ret; +} + std::string disassemble_quest_script( const void* data, size_t size, @@ -3047,7 +3106,7 @@ std::string disassemble_quest_script( } case Version::BB_V4: { use_wstrs = true; - const auto& header = r.get(); + const auto& header = r.get(); code_offset = header.code_offset; function_table_offset = header.function_table_offset; if (override_language != Language::UNKNOWN) { @@ -3061,9 +3120,25 @@ std::string disassemble_quest_script( if (header.joinable) { lines.emplace_back(".joinable"); } - lines.emplace_back(".name " + escape_string(header.name.decode(language))); - lines.emplace_back(".short_desc " + escape_string(header.short_description.decode(language))); - lines.emplace_back(".long_desc " + escape_string(header.long_description.decode(language))); + lines.emplace_back(std::format(".name {}", escape_string(header.name.decode(language)))); + lines.emplace_back(std::format(".short_desc {}", escape_string(header.short_description.decode(language)))); + lines.emplace_back(std::format(".long_desc {}", escape_string(header.long_description.decode(language)))); + // Quests saved with Qedit may not have the full header, so only parse + // the full header if the code and function table offsets don't point to + // space within it + if ((header.code_offset >= sizeof(PSOQuestHeaderBB)) && + (header.function_table_offset >= sizeof(PSOQuestHeaderBB))) { + r.go(0); + const auto& header = r.get(); + for (size_t z = 0; z < header.create_item_mask_entries.size(); z++) { + const auto& qh_mask = header.create_item_mask_entries[z]; + if (!qh_mask.is_valid()) { + break; + } + QuestMetadata::CreateItemMask qm_mask = qh_mask; + lines.emplace_back(std::format(".allow_create_item {}", qm_mask.str())); + } + } break; } default: @@ -4010,6 +4085,7 @@ AssembledQuestScript assemble_quest_script( Episode quest_episode = Episode::EP1; uint8_t quest_max_players = 4; bool quest_joinable = false; + std::vector create_item_mask_entries; for (const auto& line : lines) { if (line.text.empty()) { continue; @@ -4028,6 +4104,17 @@ AssembledQuestScript assemble_quest_script( quest_short_desc = phosg::parse_data_string(line.text.substr(12)); } else if (line.text.starts_with(".long_desc ")) { quest_long_desc = phosg::parse_data_string(line.text.substr(11)); + } else if (line.text.starts_with(".allow_create_item ")) { + if (create_item_mask_entries.size() >= 0x40) { + throw std::runtime_error("too many .allow_create_item directives; at most 64 are allowed"); + } + + string args_str = line.text.substr(19); + phosg::strip_whitespace(args_str); + + QuestMetadata::CreateItemMask mask(line.text.substr(19)); + create_item_mask_entries.emplace_back(mask); + } else if (line.text.starts_with(".quest_num ")) { quest_num = stoul(line.text.substr(11), nullptr, 0); } else if (line.text.starts_with(".language ")) { @@ -4646,6 +4733,10 @@ AssembledQuestScript assemble_quest_script( header.name.encode(quest_name, quest_language); header.short_description.encode(quest_short_desc, quest_language); header.long_description.encode(quest_long_desc, quest_language); + header.unknown_a5.clear(0xFF); + for (size_t z = 0; z < create_item_mask_entries.size(); z++) { + header.create_item_mask_entries[z] = create_item_mask_entries[z]; + } w.put(header); break; } @@ -4753,7 +4844,7 @@ void populate_quest_metadata_from_script( break; } case Version::BB_V4: { - const auto& header = r.get(); + const auto& header = r.get(); meta.episode = episode_for_quest_episode_number(header.episode); meta.joinable |= header.joinable; meta.max_players = 4; @@ -4763,6 +4854,21 @@ void populate_quest_metadata_from_script( meta.name = header.name.decode(language); meta.short_description = header.short_description.decode(language); meta.long_description = header.long_description.decode(language); + // Quests saved with Qedit may not have the full header, so only parse + // the full header if the code and function table offsets don't point to + // space within it + if ((header.code_offset >= sizeof(PSOQuestHeaderBB)) && + (header.function_table_offset >= sizeof(PSOQuestHeaderBB))) { + r.go(0); + const auto& header = r.get(); + for (size_t z = 0; z < header.create_item_mask_entries.size(); z++) { + const auto& item = header.create_item_mask_entries[z]; + if (!item.is_valid()) { + break; + } + meta.create_item_mask_entries.emplace_back(item); + } + } code_offset = header.code_offset; function_table_offset = header.function_table_offset; break; diff --git a/src/QuestScript.hh b/src/QuestScript.hh index 54fb22d4..a6611a19 100644 --- a/src/QuestScript.hh +++ b/src/QuestScript.hh @@ -83,7 +83,21 @@ struct PSOQuestHeaderGC { /* 01D4 */ } __packed_ws__(PSOQuestHeaderGC, 0x1D4); -struct PSOQuestHeaderBB { +struct CreateItemMaskEntry { + parray data1_fields; + le_uint32_t present = 0; + le_uint32_t unknown_a3 = 0; + + bool is_valid() const { + return (this->data1_fields[0] || this->data1_fields[1] || this->data1_fields[2]); + } + + CreateItemMaskEntry() = default; + CreateItemMaskEntry(const QuestMetadata::CreateItemMask& mask); + operator QuestMetadata::CreateItemMask() const; +} __packed_ws__(CreateItemMaskEntry, 0x38); + +struct PSOQuestHeaderBBBase { /* 0000 */ le_uint32_t code_offset = 0; /* 0004 */ le_uint32_t function_table_offset = 0; /* 0008 */ le_uint32_t size = 0; @@ -99,7 +113,13 @@ struct PSOQuestHeaderBB { /* 0058 */ pstring short_description; /* 0158 */ pstring long_description; /* 0398 */ -} __packed_ws__(PSOQuestHeaderBB, 0x398); +} __packed_ws__(PSOQuestHeaderBBBase, 0x0398); + +struct PSOQuestHeaderBB : PSOQuestHeaderBBBase { + /* 0398 */ parray unknown_a5; + /* 042C */ parray create_item_mask_entries; + /* 122C */ +} __packed_ws__(PSOQuestHeaderBB, 0x122C); void check_opcode_definitions(); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 037bb26b..c3c17022 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -4182,18 +4182,43 @@ static asio::awaitable on_adjust_player_meseta_bb(shared_ptr c, Su co_return; } -static asio::awaitable on_item_reward_request_bb(shared_ptr c, SubcommandMessage& msg) { - const auto& cmd = msg.check_size_t(); +static void assert_quest_item_create_allowed(shared_ptr l, const ItemData& item) { + // We always enforce these restrictions, even if the client has cheat mode + // enabled or has debug enabled. If the client can cheat, there are much + // easier ways to create items (e.g. the $item chat command) than spoofing + // these quest item creation commands, so they should just do that instead. + + if (!l->quest) { + throw std::runtime_error("cannot create quest reward item with no quest loaded"); + } + for (const auto& mask : l->quest->meta.create_item_mask_entries) { + if (mask.match(item)) { + return; + } + } + l->log.warning_f("Player attempted to create quest item {}, but it does not match any create item mask", item.hex()); + l->log.info_f("Quest has {} create item masks:", l->quest->meta.create_item_mask_entries.size()); + for (const auto& mask : l->quest->meta.create_item_mask_entries) { + l->log.info_f(" {}", mask.str()); + } + throw std::runtime_error("invalid item creation from quest"); +} + +static asio::awaitable on_quest_create_item_bb(shared_ptr c, SubcommandMessage& msg) { + const auto& cmd = msg.check_size_t(); auto s = c->require_server_state(); auto l = c->require_lobby(); const auto& limits = *s->item_stack_limits(c->version()); ItemData item; item = cmd.item_data; + // enforce_stack_size_limits must come after this assert since quests may + // attempt to create stackable items with a count of zero + assert_quest_item_create_allowed(l, item); item.enforce_stack_size_limits(limits); item.id = l->generate_item_id(c->lobby_client_id); - // The logic for the item_create and item_create2 opcodes (B3 and B4) + // The logic for the item_create and item_create2 quest opcodes (B3 and B4) // includes a precondition check to see if the player can actually add the // item to their inventory or not, and the entire command is skipped if not. // However, on BB, the implementation performs this check and sends a 6xCA @@ -4869,21 +4894,21 @@ static asio::awaitable on_quest_exchange_item_bb(shared_ptr c, Sub throw runtime_error("6xD5 command sent during free play"); } - const auto& cmd = msg.check_size_t(); + const auto& cmd = msg.check_size_t(); auto s = c->require_server_state(); try { auto p = c->character_file(); const auto& limits = *s->item_stack_limits(c->version()); + ItemData new_item = cmd.replace_item; + assert_quest_item_create_allowed(l, new_item); + new_item.enforce_stack_size_limits(limits); + size_t found_index = p->inventory.find_item_by_primary_identifier(cmd.find_item.primary_identifier()); auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 1, limits); send_destroy_item_to_lobby(c, found_item.id, 1); - // TODO: We probably should use an allow-list here to prevent the client - // from creating arbitrary items if cheat mode is disabled. - ItemData new_item = cmd.replace_item; - new_item.enforce_stack_size_limits(limits); new_item.id = l->generate_item_id(c->lobby_client_id); p->add_item(new_item, limits); send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item); @@ -4934,14 +4959,14 @@ static asio::awaitable on_photon_drop_exchange_for_item_bb(shared_ptrcharacter_file(); const auto& limits = *s->item_stack_limits(c->version()); + ItemData new_item = cmd.new_item; + assert_quest_item_create_allowed(l, new_item); + new_item.enforce_stack_size_limits(limits); + size_t found_index = p->inventory.find_item_by_primary_identifier(0x03100000); auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 0, limits); send_destroy_item_to_lobby(c, found_item.id, found_item.stack_size(limits)); - // TODO: We probably should use an allow-list here to prevent the client - // from creating arbitrary items if cheat mode is disabled. - ItemData new_item = cmd.new_item; - new_item.enforce_stack_size_limits(limits); new_item.id = l->generate_item_id(c->lobby_client_id); p->add_item(new_item, limits); send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item); @@ -4975,9 +5000,12 @@ static asio::awaitable on_photon_drop_exchange_for_s_rank_special_bb(share uint8_t cost = costs.at(cmd.special_type); size_t payment_item_index = p->inventory.find_item_by_primary_identifier(0x03100000); - // Ensure weapon exists before removing PDs, so inventory state will be - // consistent in case of error - p->inventory.find_item(cmd.item_id); + { + const auto& item = p->inventory.items[p->inventory.find_item(cmd.item_id)]; + if (!item.data.is_s_rank_weapon()) { + throw std::runtime_error("6xD8 cannot be used for non-ES weapons"); + } + } auto payment_item = p->remove_item(p->inventory.items[payment_item_index].data.id, cost, limits); send_destroy_item_to_lobby(c, payment_item.id, cost); @@ -5008,24 +5036,61 @@ static asio::awaitable on_secret_lottery_ticket_exchange_bb(shared_ptrcheck_flag(Lobby::Flag::QUEST_IN_PROGRESS) && !l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)) { throw runtime_error("6xDE command sent during free play"); } - - auto s = c->require_server_state(); - const auto& cmd = msg.check_size_t(); - - if (s->secret_lottery_results.empty()) { - throw runtime_error("no secret lottery results are defined"); + if (!l->quest) { + throw runtime_error("6xDE command sent with no quest loaded"); + } + if (l->quest->meta.create_item_mask_entries.size() < 2) { + throw runtime_error("quest does not have enough create item mask entries"); } + // See notes about 6xDE in CommandFormats.hh about this weirdness + const auto& cmd = msg.check_size_t(0x0C); + uint16_t failure_label; + if (msg.size >= 0x0C) { + const auto& cmd = msg.check_size_t(); + failure_label = cmd.failure_label; + } else { + failure_label = cmd.success_label; + } + + // The last mask entry is the currency item (e.g. Secret Lottery Ticket) + const auto& currency_mask = l->quest->meta.create_item_mask_entries.back(); + uint32_t currency_primary_identifier = currency_mask.primary_identifier(); auto p = c->character_file(); - ssize_t slt_index = -1; + ssize_t currency_index = -1; try { - slt_index = p->inventory.find_item_by_primary_identifier(0x03100300); // Secret Lottery Ticket + currency_index = p->inventory.find_item_by_primary_identifier(currency_primary_identifier); + c->log.info_f("Currency item {:08X} found at index {}", currency_primary_identifier, currency_index); } catch (const out_of_range&) { + c->log.info_f("Currency item {:08X} not found in inventory", currency_primary_identifier); } - if (slt_index >= 0) { + S_ExchangeSecretLotteryTicketResult_BB_24 out_cmd; + out_cmd.start_reg_num = cmd.start_reg_num; + out_cmd.label = (currency_index >= 0) ? cmd.success_label.load() : failure_label; + for (size_t z = 0; z < out_cmd.reg_values.size(); z++) { + out_cmd.reg_values[z] = (l->rand_crypt->next() % (l->quest->meta.create_item_mask_entries.size() - 1)) + 1; + c->log.info_f("Mask index {} is {} ({})", z, out_cmd.reg_values[z] - 1, l->quest->meta.create_item_mask_entries[out_cmd.reg_values[z] - 1].str()); + } + + if (currency_index >= 0) { + size_t mask_index = out_cmd.reg_values[cmd.index - 1] - 1; + const auto& mask = l->quest->meta.create_item_mask_entries[mask_index]; + c->log.info_f("Chose mask {} ({})", mask_index, mask.str()); + + ItemData item; + for (size_t z = 0; z < 12; z++) { + const auto& r = mask.data1_ranges[z]; + if (r.min != r.max) { + throw std::runtime_error("invalid range for bb_exchange_slt"); + } + item.data1[z] = r.min; + } + auto s = c->require_server_state(); const auto& limits = *s->item_stack_limits(c->version()); - uint32_t slt_item_id = p->inventory.items[slt_index].data.id; + item.enforce_stack_size_limits(limits); + + uint32_t slt_item_id = p->inventory.items[currency_index].data.id; G_ExchangeItemInQuest_BB_6xDB exchange_cmd; exchange_cmd.header.subcommand = 0xDB; @@ -5038,28 +5103,12 @@ static asio::awaitable on_secret_lottery_ticket_exchange_bb(shared_ptrremove_item(slt_item_id, 1, limits); - ItemData item = (s->secret_lottery_results.size() == 1) - ? s->secret_lottery_results[0] - : s->secret_lottery_results[l->rand_crypt->next() % s->secret_lottery_results.size()]; - item.enforce_stack_size_limits(limits); item.id = l->generate_item_id(c->lobby_client_id); p->add_item(item, limits); send_create_inventory_item_to_lobby(c, c->lobby_client_id, item); } - S_ExchangeSecretLotteryTicketResult_BB_24 out_cmd; - out_cmd.start_reg_num = cmd.index; - out_cmd.label = cmd.success_label; - if (s->secret_lottery_results.empty()) { - out_cmd.reg_values.clear(0); - } else if (s->secret_lottery_results.size() == 1) { - out_cmd.reg_values.clear(1); - } else { - for (size_t z = 0; z < out_cmd.reg_values.size(); z++) { - out_cmd.reg_values[z] = l->rand_crypt->next() % s->secret_lottery_results.size(); - } - } - send_command_t(c, 0x24, (slt_index >= 0) ? 0 : 1, out_cmd); + send_command_t(c, 0x24, (currency_index >= 0) ? 0 : 1, out_cmd); co_return; } @@ -5284,6 +5333,11 @@ static asio::awaitable on_momoka_item_exchange_bb(shared_ptr c, Su auto p = c->character_file(); try { const auto& limits = *s->item_stack_limits(c->version()); + + ItemData new_item = cmd.replace_item; + assert_quest_item_create_allowed(l, new_item); + new_item.enforce_stack_size_limits(limits); + size_t found_index = p->inventory.find_item_by_primary_identifier(cmd.find_item.primary_identifier()); auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 1, limits); @@ -5292,10 +5346,6 @@ static asio::awaitable on_momoka_item_exchange_bb(shared_ptr c, Su send_destroy_item_to_lobby(c, found_item.id, 1); - // TODO: We probably should use an allow-list here to prevent the client - // from creating arbitrary items if cheat mode is disabled. - ItemData new_item = cmd.replace_item; - new_item.enforce_stack_size_limits(limits); new_item.id = l->generate_item_id(c->lobby_client_id); p->add_item(new_item, limits); send_create_inventory_item_to_lobby(c, c->lobby_client_id, new_item); @@ -5326,6 +5376,9 @@ static asio::awaitable on_upgrade_weapon_attribute_bb(shared_ptr c try { size_t item_index = p->inventory.find_item(cmd.item_id); auto& item = p->inventory.items[item_index].data; + if (item.is_s_rank_weapon()) { + throw std::runtime_error("6xDA command sent for ES weapon"); + } uint32_t payment_primary_identifier = cmd.payment_type ? 0x03100100 : 0x03100000; size_t payment_index = p->inventory.find_item_by_primary_identifier(payment_primary_identifier); @@ -5348,7 +5401,7 @@ static asio::awaitable on_upgrade_weapon_attribute_bb(shared_ptr c } size_t attribute_index = 0; - for (size_t z = 6; z <= 10; z += 2) { + for (size_t z = 6; z <= (item.has_kill_count() ? 10 : 8); z += 2) { if ((item.data1[z] == 0) || (!(item.data1[z] & 0x80) && (item.data1[z] == cmd.attribute))) { attribute_index = z; break; @@ -5588,7 +5641,7 @@ const vector subcommand_definitions{ /* 6xC7 */ {NONE, NONE, 0xC7, on_charge_attack_bb}, /* 6xC8 */ {NONE, NONE, 0xC8, on_enemy_exp_request_bb}, /* 6xC9 */ {NONE, NONE, 0xC9, on_adjust_player_meseta_bb}, - /* 6xCA */ {NONE, NONE, 0xCA, on_item_reward_request_bb}, + /* 6xCA */ {NONE, NONE, 0xCA, on_quest_create_item_bb}, /* 6xCB */ {NONE, NONE, 0xCB, on_transfer_item_via_mail_message_bb}, /* 6xCC */ {NONE, NONE, 0xCC, on_exchange_item_for_team_points_bb}, /* 6xCD */ {NONE, NONE, 0xCD, forward_subcommand_m}, @@ -5682,5 +5735,3 @@ asio::awaitable on_subcommand_multi(shared_ptr c, Channel::Message offset += cmd_size; } } - -// NOCOMMIT: Make BB item creation opcodes use the quests' create masks diff --git a/src/ServerState.cc b/src/ServerState.cc index 05c1ff0e..11a3c253 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -1432,7 +1432,6 @@ void ServerState::load_config_late() { this->quest_F95F_results.clear(); this->quest_F960_success_results.clear(); this->quest_F960_failure_results = QuestF960Result(); - this->secret_lottery_results.clear(); if (this->item_name_index(Version::BB_V4)) { try { for (const auto& type_it : this->config_json->get_list("QuestF95EResultItems")) { @@ -1469,16 +1468,6 @@ void ServerState::load_config_late() { } } catch (const out_of_range&) { } - try { - for (const auto& it : this->config_json->get_list("SecretLotteryResultItems")) { - try { - this->secret_lottery_results.emplace_back(this->parse_item_description(Version::BB_V4, it->as_string())); - } catch (const exception& e) { - config_log.warning_f("Cannot parse item description \"{}\": {} (skipping entry)", it->as_string(), e.what()); - } - } - } catch (const out_of_range&) { - } auto parse_primary_identifier_list = [&](const char* key, Version v) -> unordered_set { unordered_set ret; diff --git a/src/ServerState.hh b/src/ServerState.hh index f6f6546b..d7599d9f 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -239,7 +239,6 @@ struct ServerState : public std::enable_shared_from_this { std::vector> quest_F95F_results; // [(num_photon_tickets, item)] std::vector quest_F960_success_results; QuestF960Result quest_F960_failure_results; - std::vector secret_lottery_results; float bb_global_exp_multiplier = 1.0f; float exp_share_multiplier = 0.5f; float server_global_drop_rate_multiplier = 1.0f; diff --git a/system/client-functions/BlueBurstExclusive/MomokaItemExchangeFix.59NL.patch.s b/system/client-functions/BlueBurstExclusive/MomokaItemExchangeFix.59NL.patch.s new file mode 100644 index 00000000..e0cfae90 --- /dev/null +++ b/system/client-functions/BlueBurstExclusive/MomokaItemExchangeFix.59NL.patch.s @@ -0,0 +1,115 @@ +.meta name="Item exch. fix" +.meta description="Fixes Momoka item exchange\nopcode" + +entry_ptr: +reloc0: + .offsetof start +start: + .include WriteCodeBlocksBB + + + + # Fix 6xDE failure label truncation + .data 0x006B90DE + .data 1 + .binary 03 + + + + # Fix send_6xD9 not setting size field + + .data 0x006CA540 + .deltaof send_6xD9_start, send_6xD9_end + .address 0x006CA540 +send_6xD9_start: # [std](void* this @ ecx) -> void + push ebx + mov ebx, ecx + push 0 # cmd.success_label, cmd.failure_label + mov eax, [0x00A9C4F4] # local_client_id + xor eax, 1 + push eax # cmd.token2 + mov ecx, [ebx + 0x2C] + call 0x00737D90 # [std](void* this @ ecx = *(this + 0x2C)) -> void* @ eax + mov edx, [ebx + 0x3C] + imul eax, eax, 0x14 + add edx, eax + mov eax, [edx + 0x10] + xor eax, [0x00A9C4F4] # local_client_id + push eax # cmd.token1 + push dword [edx + 0x10] # cmd.replace_item.data2d + push dword [edx + 0x0C] # cmd.replace_item.id + push dword [edx + 0x08] # cmd.replace_item.data1[8-11] + push dword [edx + 0x04] # cmd.replace_item.data1[4-7] + push dword [edx] # cmd.replace_item.data1[0-3] + push dword [ebx + 0x50] # cmd.find_item.data2d + push dword [ebx + 0x4C] # cmd.find_item.id + push dword [ebx + 0x48] # cmd.find_item.data1[8-11] + push dword [ebx + 0x44] # cmd.find_item.data1[4-7] + push dword [ebx + 0x40] # cmd.find_item.data1[0-3] + push 0x00000ED9 # cmd.header + + mov ecx, esp + call 0x008003E0 # send_and_handle_60[std](void* cmd @ ecx) -> void + add esp, 0x38 + + mov dword [ebx], 6 + push 0 + call 0x00859D2D # time[std](void* t @ [esp + 4] = nullptr) -> uint32_t @ eax + add esp, 4 + mov [ebx + 0x5C], eax + + pop ebx + ret +send_6xD9_end: + + + + # Same fix as above, but for quest_F95B_send_6xD9 + + .data 0x006B9018 + .deltaof quest_F95B_send_6xD9_start, quest_F95B_send_6xD9_end + .address 0x006B9018 +quest_F95B_send_6xD9_start: # [std]() -> void + mov edx, 0x00A954CC # quest_args_list + mov ax, [edx + 0x14] # quest_args_list[5] (failure_label) + shl eax, 0x10 + mov ax, [edx + 0x10] # quest_args_list[4] (success_label) + push eax # cmd.success_label, cmd.failure_label + mov ecx, [0x00A9C4F4] # local_client_id + mov eax, [edx + 0x0C] # quest_args_list[3] (token2) + xor eax, ecx + push eax # cmd.token2 + mov eax, [edx + 0x08] # quest_args_list[2] (token1) + xor eax, ecx + push eax # cmd.token1 + push 0x00000000 # cmd.replace_item.data2d + push 0xFFFFFFFF # cmd.replace_item.id + push 0x00000000 # cmd.replace_item.data1[8-11] + push 0x00000000 # cmd.replace_item.data1[4-7] + mov eax, [edx + 0x04] # quest_args_list[1] (data1[0-2] in low 3 bytes) + shl eax, 8 + bswap eax + push eax # cmd.replace_item.data1[0-3] + push 0x00000000 # cmd.find_item.data2d + push 0xFFFFFFFF # cmd.find_item.id + push 0x00000000 # cmd.find_item.data1[8-11] + push 0x00000000 # cmd.find_item.data1[4-7] + mov eax, [edx] # quest_args_list[0] (data1[0-2] in low 3 bytes) + shl eax, 8 + bswap eax + push eax # cmd.find_item.data1[0-3] + mov eax, 0xD90E0000 + mov ax, cx + bswap eax + push eax # cmd.header + + mov ecx, esp + call 0x008003E0 # send_and_handle_60[std](void* cmd @ ecx) -> void + add esp, 0x38 + ret +quest_F95B_send_6xD9_end: + + + + .data 0x00000000 + .data 0x00000000 diff --git a/system/config.example.json b/system/config.example.json index 03a1cf1c..126ff726 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -747,16 +747,6 @@ [15, "000A07"], [20, "010157"], ], - // Result item definitions for Secret Lottery Ticket exchange (quest opcode - // F95C, used in the Good Luck quest). - "SecretLotteryResultItems": [ - "000106", "000107", "000206", "000407", "000606", "000807", "000D01", - "001300", "002000", "002700", "002C00", "003400", "003900", "003C00", - "003E00", "004100", "004400", "004500", "004C00", "006A00", "008F07", - "009A00", "01011B", "01011C", "010129", "010129", "010130", "010131", - "010132", "010133", "010221", "010224", "010229", "01022B", "010235", - "031000", - ], // Result items for Coren (quest opcodes F960/F961). Indexed by prize_tier. // When a prize is requested, the server chooses a random number and checks it diff --git a/system/quests/events/q201-bb-j.bin b/system/quests/events/q201-bb-j.bin index 46973afb09658b574eef4bbdc100a5ddbc166e0b..f0c5303cd063dc55f25600fd037c038be6f360a0 100644 GIT binary patch literal 21042 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 ZeEWYnh5sQM{{O+P|0|2-l+1E-`!|yGLuCK} diff --git a/system/quests/retrieval/q058-gc-e.bin.txt b/system/quests/retrieval/q058-gc-e.bin.txt index 200ccd98..80fcfcfc 100644 --- a/system/quests/retrieval/q058-gc-e.bin.txt +++ b/system/quests/retrieval/q058-gc-e.bin.txt @@ -59,6 +59,18 @@ // capability. // .joinable +// On BB, quests that create items via the script must specify which items are +// allowed to be created. To do so, use this directive one or more times, which +// instructs the server to allow creation of that item. These masks can specify +// each byte of the item's data1 as ranges, to allow for parameters in the item +// data. For example, this directive allows the quest to create Trifluids +// (030102) with stack sizes of 1-10: +// .allow_create_item 0301020000[01-0A]000000000000 +// Another example: this directive allows the quest to create any weapon in the +// basic rifle series, not including the rares in that series, with a grind +// value of up to 5 and up to two bonuses between 0 and 30% each: +// .allow_create_item 0007[00-04][00-05]0000[00-05][00-1E][00-05][00-1E]0000 + // The quest script begins after the header directives. A quest script is a // sequence of opcodes, and labels denoting positions within that sequence that // can be jumped to or called like a function. All labels have names, and some diff --git a/system/quests/solo-extra/q035-bb-j.bin b/system/quests/solo-extra/q035-bb-j.bin index a6c434d2b194ce7567370251ef20ebaf29adcbe5..7ee0042ee0e841b36bba801cc42552fdf6457f4b 100644 GIT binary patch delta 4298 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