further improve quest assembler/disassembler matching
This commit is contained in:
+1
-1
@@ -424,7 +424,7 @@ bool Client::can_play_quest(
|
|||||||
if (!q->has_version_any_language(this->version())) {
|
if (!q->has_version_any_language(this->version())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (num_players > q->meta.max_players) {
|
if ((q->meta.max_players > 0) && (num_players > q->meta.max_players)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this->evaluate_quest_availability_expression(
|
return this->evaluate_quest_availability_expression(
|
||||||
|
|||||||
+33
-33
@@ -1576,7 +1576,7 @@ Action a_encode_qst(
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto vq = make_shared<VersionedQuest>();
|
auto vq = make_shared<VersionedQuest>();
|
||||||
vq->version = version;
|
vq->meta.version = version;
|
||||||
vq->bin_contents = bin_data;
|
vq->bin_contents = bin_data;
|
||||||
vq->dat_contents = dat_data;
|
vq->dat_contents = dat_data;
|
||||||
vq->pvr_contents = pvr_data;
|
vq->pvr_contents = pvr_data;
|
||||||
@@ -3152,8 +3152,8 @@ Action a_check_quest_reassembly(
|
|||||||
for (const auto& [_, q] : s->quest_index->quests_by_number) {
|
for (const auto& [_, q] : s->quest_index->quests_by_number) {
|
||||||
for (const auto& [_, vq] : q->versions) {
|
for (const auto& [_, vq] : q->versions) {
|
||||||
auto decompressed_bin = prs_decompress(*vq->bin_contents);
|
auto decompressed_bin = prs_decompress(*vq->bin_contents);
|
||||||
auto disassembled = disassemble_quest_script(decompressed_bin.data(), decompressed_bin.size(), vq->version, vq->language, vq->map_file, false, false);
|
auto disassembled = disassemble_quest_script(decompressed_bin.data(), decompressed_bin.size(), vq->meta.version, vq->meta.language, vq->map_file, false, false);
|
||||||
auto reassembly = disassemble_quest_script(decompressed_bin.data(), decompressed_bin.size(), vq->version, vq->language, vq->map_file, true, false);
|
auto reassembly = disassemble_quest_script(decompressed_bin.data(), decompressed_bin.size(), vq->meta.version, vq->meta.language, vq->map_file, true, false);
|
||||||
string include_dir = phosg::dirname(vq->bin_filename());
|
string include_dir = phosg::dirname(vq->bin_filename());
|
||||||
AssembledQuestScript assembled;
|
AssembledQuestScript assembled;
|
||||||
try {
|
try {
|
||||||
@@ -3162,44 +3162,44 @@ Action a_check_quest_reassembly(
|
|||||||
{"system/quests/includes"},
|
{"system/quests/includes"},
|
||||||
{"system/quests/includes", "system/client-functions/System"},
|
{"system/quests/includes", "system/client-functions/System"},
|
||||||
false);
|
false);
|
||||||
|
if (vq->json_contents) {
|
||||||
|
assembled.meta.apply_json_overrides(*vq->json_contents);
|
||||||
|
}
|
||||||
if (assembled.data != decompressed_bin) {
|
if (assembled.data != decompressed_bin) {
|
||||||
throw std::runtime_error("Reassembled quest script does not match original");
|
throw std::runtime_error("Reassembled quest script does not match original");
|
||||||
}
|
}
|
||||||
if (assembled.quest_number != vq->meta.quest_number) {
|
// Don't check quest number, since we override it based on the filename
|
||||||
throw std::runtime_error(std::format("Reassembled quest number {} does not match original ({})",
|
if (assembled.meta.version != vq->meta.version) {
|
||||||
assembled.quest_number, vq->meta.quest_number));
|
throw std::runtime_error(std::format("Reassembled quest version ({}) does not match original ({})",
|
||||||
|
phosg::name_for_enum(assembled.meta.version), phosg::name_for_enum(vq->meta.version)));
|
||||||
}
|
}
|
||||||
if (assembled.version != vq->version) {
|
if (assembled.meta.language != vq->meta.language) {
|
||||||
throw std::runtime_error(std::format("Reassembled quest version {} does not match original ({})",
|
throw std::runtime_error(std::format("Reassembled quest language ({}) does not match original ({})",
|
||||||
phosg::name_for_enum(assembled.version), phosg::name_for_enum(vq->version)));
|
name_for_language(assembled.meta.language), name_for_language(vq->meta.language)));
|
||||||
}
|
}
|
||||||
if (assembled.language != vq->language) {
|
if (assembled.meta.episode != vq->meta.episode) {
|
||||||
throw std::runtime_error(std::format("Reassembled quest language {} does not match original ({})",
|
throw std::runtime_error(std::format("Reassembled quest episode ({}) does not match original ({})",
|
||||||
name_for_language(assembled.language), name_for_language(vq->language)));
|
name_for_episode(assembled.meta.episode), name_for_episode(vq->meta.episode)));
|
||||||
}
|
}
|
||||||
if (assembled.episode != vq->meta.episode) {
|
if (assembled.meta.joinable != vq->meta.joinable) {
|
||||||
throw std::runtime_error(std::format("Reassembled quest episode {} does not match original ({})",
|
throw std::runtime_error(std::format("Reassembled quest joinable ({}) does not match original ({})",
|
||||||
name_for_episode(assembled.episode), name_for_episode(vq->meta.episode)));
|
assembled.meta.joinable, vq->meta.joinable));
|
||||||
}
|
}
|
||||||
if (assembled.joinable != vq->meta.joinable) {
|
if (assembled.meta.max_players != vq->meta.max_players) {
|
||||||
throw std::runtime_error(std::format("Reassembled quest joinable {} does not match original ({})",
|
throw std::runtime_error(std::format("Reassembled quest max_players ({}) does not match original ({})",
|
||||||
assembled.joinable, vq->meta.joinable));
|
assembled.meta.max_players, vq->meta.max_players));
|
||||||
}
|
}
|
||||||
if (assembled.max_players != vq->meta.max_players) {
|
if (assembled.meta.name != vq->meta.name) {
|
||||||
throw std::runtime_error(std::format("Reassembled quest max_players {} does not match original ({})",
|
throw std::runtime_error(std::format("Reassembled quest name ({}) does not match original ({})",
|
||||||
assembled.max_players, vq->meta.max_players));
|
assembled.meta.name, vq->meta.name));
|
||||||
}
|
}
|
||||||
if (assembled.name != vq->meta.name) {
|
if (assembled.meta.short_description != vq->meta.short_description) {
|
||||||
throw std::runtime_error(std::format("Reassembled quest name \"{}\" does not match original (\"{}\")",
|
throw std::runtime_error(std::format("Reassembled quest short description ({}) does not match original ({})",
|
||||||
assembled.name, vq->meta.name));
|
assembled.meta.short_description, vq->meta.short_description));
|
||||||
}
|
}
|
||||||
if (assembled.short_description != vq->meta.short_description) {
|
if (assembled.meta.long_description != vq->meta.long_description) {
|
||||||
throw std::runtime_error(std::format("Reassembled quest short description \"{}\" does not match original (\"{}\")",
|
throw std::runtime_error(std::format("Reassembled quest long description ({}) does not match original ({})",
|
||||||
assembled.short_description, vq->meta.short_description));
|
assembled.meta.long_description, vq->meta.long_description));
|
||||||
}
|
|
||||||
if (assembled.long_description != vq->meta.long_description) {
|
|
||||||
throw std::runtime_error(std::format("Reassembled quest long description \"{}\" does not match original (\"{}\")",
|
|
||||||
assembled.long_description, vq->meta.long_description));
|
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
phosg::log_error_f("================ DISASSEMBLY:");
|
phosg::log_error_f("================ DISASSEMBLY:");
|
||||||
@@ -3210,10 +3210,10 @@ Action a_check_quest_reassembly(
|
|||||||
phosg::log_error_f("================ BINDIFF:");
|
phosg::log_error_f("================ BINDIFF:");
|
||||||
phosg::print_binary_diff(stderr, decompressed_bin.data(), decompressed_bin.size(), assembled.data.data(), assembled.data.size(), isatty(fileno(stderr)), 3, 0);
|
phosg::print_binary_diff(stderr, decompressed_bin.data(), decompressed_bin.size(), assembled.data.data(), assembled.data.size(), isatty(fileno(stderr)), 3, 0);
|
||||||
}
|
}
|
||||||
phosg::log_info_f("... {} {} {} ({}) FAILED", phosg::name_for_enum(vq->version), name_for_language(vq->language), vq->bin_filename(), vq->meta.name);
|
phosg::log_info_f("... {} {} {} ({}) FAILED", phosg::name_for_enum(vq->meta.version), name_for_language(vq->meta.language), vq->bin_filename(), vq->meta.name);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
phosg::log_info_f("... {} {} {} ({}) OK", phosg::name_for_enum(vq->version), name_for_language(vq->language), vq->bin_filename(), vq->meta.name);
|
phosg::log_info_f("... {} {} {} ({}) OK", phosg::name_for_enum(vq->meta.version), name_for_language(vq->meta.language), vq->bin_filename(), vq->meta.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-2
@@ -4583,7 +4583,7 @@ shared_ptr<SuperMap::Enemy> SuperMap::add_enemy_and_children(
|
|||||||
case 0x00FD: // TObjNpcNgcBase
|
case 0x00FD: // TObjNpcNgcBase
|
||||||
case 0x00FE: // TObjNpcNgcBase
|
case 0x00FE: // TObjNpcNgcBase
|
||||||
case 0x00FF: // TObjNpcNgcBase
|
case 0x00FF: // TObjNpcNgcBase
|
||||||
case 0x0100: // Unknown NPC
|
case 0x0100: // Momoka
|
||||||
// All of these have a default child count of zero
|
// All of these have a default child count of zero
|
||||||
add(EnemyType::NON_ENEMY_NPC);
|
add(EnemyType::NON_ENEMY_NPC);
|
||||||
break;
|
break;
|
||||||
@@ -4923,7 +4923,7 @@ shared_ptr<SuperMap::Enemy> SuperMap::add_enemy_and_children(
|
|||||||
case 0x00C4: // TBoss3VoloptCore or subclass
|
case 0x00C4: // TBoss3VoloptCore or subclass
|
||||||
case 0x00C6: // TBoss3VoloptMonitor
|
case 0x00C6: // TBoss3VoloptMonitor
|
||||||
case 0x00C7: // TBoss3VoloptHiraisin
|
case 0x00C7: // TBoss3VoloptHiraisin
|
||||||
case 0x0118:
|
case 0x0118: // __QUEST_NPC__
|
||||||
add(EnemyType::UNKNOWN);
|
add(EnemyType::UNKNOWN);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
+32
-103
@@ -200,10 +200,10 @@ void VersionedQuest::assert_valid() const {
|
|||||||
if (this->meta.quest_number == 0xFFFFFFFF) {
|
if (this->meta.quest_number == 0xFFFFFFFF) {
|
||||||
throw runtime_error("quest number is not set");
|
throw runtime_error("quest number is not set");
|
||||||
}
|
}
|
||||||
if (this->version == Version::UNKNOWN) {
|
if (this->meta.version == Version::UNKNOWN) {
|
||||||
throw runtime_error("version is not set");
|
throw runtime_error("version is not set");
|
||||||
}
|
}
|
||||||
if (this->language == Language::UNKNOWN) {
|
if (this->meta.language == Language::UNKNOWN) {
|
||||||
throw runtime_error("language is not set");
|
throw runtime_error("language is not set");
|
||||||
}
|
}
|
||||||
switch (this->meta.episode) {
|
switch (this->meta.episode) {
|
||||||
@@ -216,7 +216,7 @@ void VersionedQuest::assert_valid() const {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Episode::EP2:
|
case Episode::EP2:
|
||||||
if (is_v1_or_v2(this->version)) {
|
if (is_v1_or_v2(this->meta.version)) {
|
||||||
throw runtime_error("v1 or v2 quest specifies Episode 2");
|
throw runtime_error("v1 or v2 quest specifies Episode 2");
|
||||||
}
|
}
|
||||||
for (size_t floor = 0; floor < this->meta.area_for_floor.size(); floor++) {
|
for (size_t floor = 0; floor < this->meta.area_for_floor.size(); floor++) {
|
||||||
@@ -227,7 +227,7 @@ void VersionedQuest::assert_valid() const {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Episode::EP3:
|
case Episode::EP3:
|
||||||
if (!is_ep3(this->version)) {
|
if (!is_ep3(this->meta.version)) {
|
||||||
throw runtime_error("non-Ep3 quest specifies Episode 3");
|
throw runtime_error("non-Ep3 quest specifies Episode 3");
|
||||||
}
|
}
|
||||||
for (size_t floor = 0; floor < this->meta.area_for_floor.size(); floor++) {
|
for (size_t floor = 0; floor < this->meta.area_for_floor.size(); floor++) {
|
||||||
@@ -237,7 +237,7 @@ void VersionedQuest::assert_valid() const {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Episode::EP4:
|
case Episode::EP4:
|
||||||
if (!is_v4(this->version)) {
|
if (!is_v4(this->meta.version)) {
|
||||||
throw runtime_error("non-v4 quest specifies Episode 4");
|
throw runtime_error("non-v4 quest specifies Episode 4");
|
||||||
}
|
}
|
||||||
for (size_t floor = 0; floor < this->meta.area_for_floor.size(); floor++) {
|
for (size_t floor = 0; floor < this->meta.area_for_floor.size(); floor++) {
|
||||||
@@ -252,9 +252,6 @@ void VersionedQuest::assert_valid() const {
|
|||||||
default:
|
default:
|
||||||
throw runtime_error("episode is not valid");
|
throw runtime_error("episode is not valid");
|
||||||
}
|
}
|
||||||
if (this->meta.max_players == 0) {
|
|
||||||
throw runtime_error("max players is not set");
|
|
||||||
}
|
|
||||||
if (!this->bin_contents) {
|
if (!this->bin_contents) {
|
||||||
throw runtime_error("bin file is missing");
|
throw runtime_error("bin file is missing");
|
||||||
}
|
}
|
||||||
@@ -264,12 +261,6 @@ void VersionedQuest::assert_valid() const {
|
|||||||
if (!this->map_file) {
|
if (!this->map_file) {
|
||||||
throw runtime_error("parsed map file is missing");
|
throw runtime_error("parsed map file is missing");
|
||||||
}
|
}
|
||||||
if (this->meta.common_item_set_name.empty() != !this->meta.common_item_set) {
|
|
||||||
throw runtime_error("common item set name/pointer mismatch");
|
|
||||||
}
|
|
||||||
if (this->meta.rare_item_set_name.empty() != !this->meta.rare_item_set) {
|
|
||||||
throw runtime_error("rare item set name/pointer mismatch");
|
|
||||||
}
|
|
||||||
if (this->meta.allowed_drop_modes &&
|
if (this->meta.allowed_drop_modes &&
|
||||||
!(this->meta.allowed_drop_modes & (1 << static_cast<size_t>(this->meta.default_drop_mode)))) {
|
!(this->meta.allowed_drop_modes & (1 << static_cast<size_t>(this->meta.default_drop_mode)))) {
|
||||||
throw runtime_error("default drop mode is not allowed");
|
throw runtime_error("default drop mode is not allowed");
|
||||||
@@ -290,7 +281,7 @@ string VersionedQuest::pvr_filename() const {
|
|||||||
|
|
||||||
string VersionedQuest::xb_filename() const {
|
string VersionedQuest::xb_filename() const {
|
||||||
return std::format("quest{}_{}.dat",
|
return std::format("quest{}_{}.dat",
|
||||||
this->meta.quest_number, static_cast<char>(tolower(char_for_language(this->language))));
|
this->meta.quest_number, static_cast<char>(tolower(char_for_language(this->meta.language))));
|
||||||
}
|
}
|
||||||
|
|
||||||
string VersionedQuest::encode_qst() const {
|
string VersionedQuest::encode_qst() const {
|
||||||
@@ -301,8 +292,8 @@ string VersionedQuest::encode_qst() const {
|
|||||||
files.emplace(std::format("quest{}.pvr", this->meta.quest_number), this->pvr_contents);
|
files.emplace(std::format("quest{}.pvr", this->meta.quest_number), this->pvr_contents);
|
||||||
}
|
}
|
||||||
string xb_filename = std::format("quest{}_{}.dat",
|
string xb_filename = std::format("quest{}_{}.dat",
|
||||||
this->meta.quest_number, static_cast<char>(tolower(char_for_language(language))));
|
this->meta.quest_number, static_cast<char>(tolower(char_for_language(this->meta.language))));
|
||||||
return encode_qst_file(files, this->meta.name, this->meta.quest_number, xb_filename, this->version, this->is_dlq_encoded);
|
return encode_qst_file(files, this->meta.name, this->meta.quest_number, xb_filename, this->meta.version, this->is_dlq_encoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
Quest::Quest(shared_ptr<const VersionedQuest> initial_version)
|
Quest::Quest(shared_ptr<const VersionedQuest> initial_version)
|
||||||
@@ -314,8 +305,8 @@ phosg::JSON Quest::json() const {
|
|||||||
auto versions_json = phosg::JSON::list();
|
auto versions_json = phosg::JSON::list();
|
||||||
for (const auto& [_, vq] : this->versions) {
|
for (const auto& [_, vq] : this->versions) {
|
||||||
versions_json.emplace_back(phosg::JSON::dict({
|
versions_json.emplace_back(phosg::JSON::dict({
|
||||||
{"Version", phosg::name_for_enum(vq->version)},
|
{"Version", phosg::name_for_enum(vq->meta.version)},
|
||||||
{"Language", ::name_for_language(vq->language)},
|
{"Language", ::name_for_language(vq->meta.language)},
|
||||||
{"Name", vq->meta.name},
|
{"Name", vq->meta.name},
|
||||||
{"ShortDescription", vq->meta.short_description},
|
{"ShortDescription", vq->meta.short_description},
|
||||||
{"LongDescription", vq->meta.long_description},
|
{"LongDescription", vq->meta.long_description},
|
||||||
@@ -357,9 +348,9 @@ void Quest::add_version(shared_ptr<const VersionedQuest> vq) {
|
|||||||
if (this->meta.create_item_mask_entries.empty()) {
|
if (this->meta.create_item_mask_entries.empty()) {
|
||||||
this->meta.create_item_mask_entries = vq->meta.create_item_mask_entries;
|
this->meta.create_item_mask_entries = vq->meta.create_item_mask_entries;
|
||||||
}
|
}
|
||||||
this->versions.emplace(this->versions_key(vq->version, vq->language), vq);
|
this->versions.emplace(this->versions_key(vq->meta.version, vq->meta.language), vq);
|
||||||
|
|
||||||
size_t lang_index = static_cast<size_t>(vq->language);
|
size_t lang_index = static_cast<size_t>(vq->meta.language);
|
||||||
auto& name_by_language = this->names_by_language.at(lang_index);
|
auto& name_by_language = this->names_by_language.at(lang_index);
|
||||||
if (name_by_language.empty()) {
|
if (name_by_language.empty()) {
|
||||||
name_by_language = vq->meta.name;
|
name_by_language = vq->meta.name;
|
||||||
@@ -435,12 +426,7 @@ shared_ptr<const VersionedQuest> Quest::version(Version v, Language language) co
|
|||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
QuestIndex::QuestIndex(
|
QuestIndex::QuestIndex(const string& directory, shared_ptr<const QuestCategoryIndex> category_index, bool raise_on_any_failure)
|
||||||
const string& directory,
|
|
||||||
shared_ptr<const QuestCategoryIndex> category_index,
|
|
||||||
const unordered_map<string, shared_ptr<const CommonItemSet>>& common_item_sets,
|
|
||||||
const unordered_map<string, shared_ptr<const RareItemSet>>& rare_item_sets,
|
|
||||||
bool raise_on_any_failure)
|
|
||||||
: directory(directory),
|
: directory(directory),
|
||||||
category_index(category_index) {
|
category_index(category_index) {
|
||||||
|
|
||||||
@@ -600,6 +586,7 @@ QuestIndex::QuestIndex(
|
|||||||
// All quests have a bin file (even in Episode 3, though its format is
|
// All quests have a bin file (even in Episode 3, though its format is
|
||||||
// different), so we use bin_files as the primary list of all quests that
|
// different), so we use bin_files as the primary list of all quests that
|
||||||
// should be indexed
|
// should be indexed
|
||||||
|
unordered_map<const FileData*, shared_ptr<const phosg::JSON>> parsed_json_files;
|
||||||
for (auto& [basename, entry] : bin_files) {
|
for (auto& [basename, entry] : bin_files) {
|
||||||
try {
|
try {
|
||||||
auto vq = make_shared<VersionedQuest>();
|
auto vq = make_shared<VersionedQuest>();
|
||||||
@@ -625,9 +612,9 @@ QuestIndex::QuestIndex(
|
|||||||
vq->meta.category_id = categories.at(basename);
|
vq->meta.category_id = categories.at(basename);
|
||||||
|
|
||||||
if (entry.assembled) {
|
if (entry.assembled) {
|
||||||
vq->meta.quest_number = entry.assembled->quest_number;
|
vq->meta.quest_number = entry.assembled->meta.quest_number;
|
||||||
vq->version = entry.assembled->version;
|
vq->meta.version = entry.assembled->meta.version;
|
||||||
vq->language = entry.assembled->language;
|
vq->meta.language = entry.assembled->meta.language;
|
||||||
} else {
|
} else {
|
||||||
// Get the number from the first token
|
// Get the number from the first token
|
||||||
if (quest_number_token.empty()) {
|
if (quest_number_token.empty()) {
|
||||||
@@ -650,30 +637,17 @@ QuestIndex::QuestIndex(
|
|||||||
{"xb", Version::XB_V3},
|
{"xb", Version::XB_V3},
|
||||||
{"bb", Version::BB_V4},
|
{"bb", Version::BB_V4},
|
||||||
});
|
});
|
||||||
vq->version = name_to_version.at(version_token);
|
vq->meta.version = name_to_version.at(version_token);
|
||||||
|
|
||||||
// Get the language from the last token
|
// Get the language from the last token
|
||||||
if (language_token.size() != 1) {
|
if (language_token.size() != 1) {
|
||||||
throw runtime_error("language token is not a single character");
|
throw runtime_error("language token is not a single character");
|
||||||
}
|
}
|
||||||
vq->language = language_for_char(language_token[0]);
|
vq->meta.language = language_for_char(language_token[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto bin_decompressed = prs_decompress(*entry.data);
|
auto bin_decompressed = prs_decompress(*entry.data);
|
||||||
populate_quest_metadata_from_script(vq->meta, bin_decompressed.data(), bin_decompressed.size(), vq->version, vq->language);
|
populate_quest_metadata_from_script(vq->meta, bin_decompressed.data(), bin_decompressed.size(), vq->meta.version, vq->meta.language);
|
||||||
|
|
||||||
// If the quest was assembled (that is, if it came from a .bin.txt file),
|
|
||||||
// the metadata from the source file overrides any automatically-detected
|
|
||||||
// values from above
|
|
||||||
if (entry.assembled) {
|
|
||||||
vq->meta.quest_number = entry.assembled->quest_number;
|
|
||||||
vq->meta.episode = entry.assembled->episode;
|
|
||||||
vq->meta.joinable = entry.assembled->joinable;
|
|
||||||
vq->meta.max_players = entry.assembled->max_players;
|
|
||||||
vq->meta.name = entry.assembled->name;
|
|
||||||
vq->meta.short_description = entry.assembled->short_description;
|
|
||||||
vq->meta.long_description = entry.assembled->long_description;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the corresponding dat and pvr files with the same basename as the
|
// Find the corresponding dat and pvr files with the same basename as the
|
||||||
// bin file; if not found, look for them without the language suffix
|
// bin file; if not found, look for them without the language suffix
|
||||||
@@ -722,57 +696,13 @@ QuestIndex::QuestIndex(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (json_filedata) {
|
if (json_filedata) {
|
||||||
auto metadata_json = phosg::JSON::parse(*json_filedata->data);
|
|
||||||
try {
|
try {
|
||||||
vq->meta.description_flag = metadata_json.at("DescriptionFlag").as_int();
|
vq->json_contents = parsed_json_files.at(json_filedata);
|
||||||
} catch (const out_of_range&) {
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
vq->meta.available_expression = make_shared<IntegralExpression>(metadata_json.get_string("AvailableIf"));
|
|
||||||
} catch (const out_of_range&) {
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
vq->meta.enabled_expression = make_shared<IntegralExpression>(metadata_json.get_string("EnabledIf"));
|
|
||||||
} catch (const out_of_range&) {
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
vq->meta.allow_start_from_chat_command = metadata_json.get_bool("AllowStartFromChatCommand");
|
|
||||||
} catch (const out_of_range&) {
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
vq->meta.joinable = metadata_json.get_bool("Joinable");
|
|
||||||
} catch (const out_of_range&) {
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
vq->meta.lock_status_register = metadata_json.get_int("LockStatusRegister");
|
|
||||||
} catch (const out_of_range&) {
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
vq->meta.enemy_exp_overrides = QuestMetadata::parse_enemy_exp_overrides(metadata_json.at("EnemyEXPOverrides"));
|
|
||||||
} catch (const out_of_range&) {
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
vq->meta.common_item_set_name = metadata_json.at("CommonItemSetName").as_string();
|
|
||||||
} catch (const out_of_range&) {
|
|
||||||
}
|
|
||||||
if (!vq->meta.common_item_set_name.empty()) {
|
|
||||||
vq->meta.common_item_set = common_item_sets.at(vq->meta.common_item_set_name);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
vq->meta.rare_item_set_name = metadata_json.at("RareItemSetName").as_string();
|
|
||||||
} catch (const out_of_range&) {
|
|
||||||
}
|
|
||||||
if (!vq->meta.rare_item_set_name.empty()) {
|
|
||||||
vq->meta.rare_item_set = rare_item_sets.at(vq->meta.rare_item_set_name);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
vq->meta.allowed_drop_modes = metadata_json.at("AllowedDropModes").as_int();
|
|
||||||
} catch (const out_of_range&) {
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
vq->meta.default_drop_mode = phosg::enum_for_name<ServerDropMode>(metadata_json.at("DefaultDropMode").as_string());
|
|
||||||
} catch (const out_of_range&) {
|
} catch (const out_of_range&) {
|
||||||
|
vq->json_contents = make_shared<phosg::JSON>(phosg::JSON::parse(*json_filedata->data));
|
||||||
|
parsed_json_files.emplace(json_filedata, vq->json_contents);
|
||||||
}
|
}
|
||||||
|
vq->meta.apply_json_overrides(*vq->json_contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
vq->assert_valid();
|
vq->assert_valid();
|
||||||
@@ -793,8 +723,8 @@ QuestIndex::QuestIndex(
|
|||||||
q_it->second->add_version(vq);
|
q_it->second->add_version(vq);
|
||||||
static_game_data_log.debug_f("({}) Added {} {} version of quest {} ({})",
|
static_game_data_log.debug_f("({}) Added {} {} version of quest {} ({})",
|
||||||
filenames_str,
|
filenames_str,
|
||||||
phosg::name_for_enum(vq->version),
|
phosg::name_for_enum(vq->meta.version),
|
||||||
char_for_language(vq->language),
|
char_for_language(vq->meta.language),
|
||||||
vq->meta.quest_number,
|
vq->meta.quest_number,
|
||||||
vq->meta.name);
|
vq->meta.name);
|
||||||
} else {
|
} else {
|
||||||
@@ -804,8 +734,8 @@ QuestIndex::QuestIndex(
|
|||||||
this->quests_by_category_id_and_number[q->meta.category_id].emplace(vq->meta.quest_number, q);
|
this->quests_by_category_id_and_number[q->meta.category_id].emplace(vq->meta.quest_number, q);
|
||||||
static_game_data_log.debug_f("({}) Created {} {} quest {} ({}) ({}, {} ({}), {})",
|
static_game_data_log.debug_f("({}) Created {} {} quest {} ({}) ({}, {} ({}), {})",
|
||||||
filenames_str,
|
filenames_str,
|
||||||
phosg::name_for_enum(vq->version),
|
phosg::name_for_enum(vq->meta.version),
|
||||||
char_for_language(vq->language),
|
char_for_language(vq->meta.language),
|
||||||
vq->meta.quest_number,
|
vq->meta.quest_number,
|
||||||
vq->meta.name,
|
vq->meta.name,
|
||||||
name_for_episode(vq->meta.episode),
|
name_for_episode(vq->meta.episode),
|
||||||
@@ -956,13 +886,12 @@ shared_ptr<VersionedQuest> VersionedQuest::create_download_quest(Language overri
|
|||||||
string decompressed_bin = prs_decompress(*this->bin_contents);
|
string decompressed_bin = prs_decompress(*this->bin_contents);
|
||||||
|
|
||||||
void* data_ptr = decompressed_bin.data();
|
void* data_ptr = decompressed_bin.data();
|
||||||
switch (this->version) {
|
switch (this->meta.version) {
|
||||||
case Version::DC_NTE:
|
case Version::DC_NTE:
|
||||||
if (decompressed_bin.size() < sizeof(PSOQuestHeaderDCNTE)) {
|
if (decompressed_bin.size() < sizeof(PSOQuestHeaderDCNTE)) {
|
||||||
throw runtime_error("bin file is too small for header");
|
throw runtime_error("bin file is too small for header");
|
||||||
}
|
}
|
||||||
// There's no known language field in this version, so we don't write
|
// There's no known language field in this version, so we don't write anything here
|
||||||
// anything here
|
|
||||||
break;
|
break;
|
||||||
case Version::DC_11_2000:
|
case Version::DC_11_2000:
|
||||||
case Version::DC_V1:
|
case Version::DC_V1:
|
||||||
@@ -986,11 +915,11 @@ shared_ptr<VersionedQuest> VersionedQuest::create_download_quest(Language overri
|
|||||||
case Version::GC_NTE:
|
case Version::GC_NTE:
|
||||||
case Version::GC_V3:
|
case Version::GC_V3:
|
||||||
case Version::XB_V3:
|
case Version::XB_V3:
|
||||||
if (decompressed_bin.size() < sizeof(PSOQuestHeaderGC)) {
|
if (decompressed_bin.size() < sizeof(PSOQuestHeaderV3)) {
|
||||||
throw runtime_error("bin file is too small for header");
|
throw runtime_error("bin file is too small for header");
|
||||||
}
|
}
|
||||||
if (override_language != Language::UNKNOWN) {
|
if (override_language != Language::UNKNOWN) {
|
||||||
reinterpret_cast<PSOQuestHeaderGC*>(data_ptr)->language = override_language;
|
reinterpret_cast<PSOQuestHeaderV3*>(data_ptr)->language = override_language;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Version::BB_V4:
|
case Version::BB_V4:
|
||||||
|
|||||||
+2
-8
@@ -71,12 +71,11 @@ struct VersionedQuest {
|
|||||||
|
|
||||||
// Most of these default values are intentionally invalid; we use these
|
// Most of these default values are intentionally invalid; we use these
|
||||||
// values to check if each field was parsed during quest indexing.
|
// values to check if each field was parsed during quest indexing.
|
||||||
Version version = Version::UNKNOWN;
|
|
||||||
Language language = Language::UNKNOWN;
|
|
||||||
std::shared_ptr<const std::string> bin_contents;
|
std::shared_ptr<const std::string> bin_contents;
|
||||||
std::shared_ptr<const std::string> dat_contents;
|
std::shared_ptr<const std::string> dat_contents;
|
||||||
std::shared_ptr<const MapFile> map_file;
|
std::shared_ptr<const MapFile> map_file;
|
||||||
std::shared_ptr<const std::string> pvr_contents;
|
std::shared_ptr<const std::string> pvr_contents;
|
||||||
|
std::shared_ptr<const phosg::JSON> json_contents;
|
||||||
bool is_dlq_encoded = false;
|
bool is_dlq_encoded = false;
|
||||||
|
|
||||||
void assert_valid() const;
|
void assert_valid() const;
|
||||||
@@ -132,12 +131,7 @@ struct QuestIndex {
|
|||||||
std::map<std::string, std::shared_ptr<Quest>> quests_by_name;
|
std::map<std::string, std::shared_ptr<Quest>> quests_by_name;
|
||||||
std::map<uint32_t, std::map<uint32_t, std::shared_ptr<Quest>>> quests_by_category_id_and_number;
|
std::map<uint32_t, std::map<uint32_t, std::shared_ptr<Quest>>> quests_by_category_id_and_number;
|
||||||
|
|
||||||
QuestIndex(
|
QuestIndex(const std::string& directory, std::shared_ptr<const QuestCategoryIndex> category_index, bool raise_on_any_failure);
|
||||||
const std::string& directory,
|
|
||||||
std::shared_ptr<const QuestCategoryIndex> category_index,
|
|
||||||
const std::unordered_map<std::string, std::shared_ptr<const CommonItemSet>>& common_item_sets,
|
|
||||||
const std::unordered_map<std::string, std::shared_ptr<const RareItemSet>>& rare_item_sets,
|
|
||||||
bool raise_on_any_failure);
|
|
||||||
phosg::JSON json() const;
|
phosg::JSON json() const;
|
||||||
|
|
||||||
std::shared_ptr<const Quest> get(uint32_t quest_number) const;
|
std::shared_ptr<const Quest> get(uint32_t quest_number) const;
|
||||||
|
|||||||
+52
-10
@@ -2,6 +2,53 @@
|
|||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
void QuestMetadata::apply_json_overrides(const phosg::JSON& json) {
|
||||||
|
try {
|
||||||
|
this->description_flag = json.at("DescriptionFlag").as_int();
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this->available_expression = make_shared<IntegralExpression>(json.get_string("AvailableIf"));
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this->enabled_expression = make_shared<IntegralExpression>(json.get_string("EnabledIf"));
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this->allow_start_from_chat_command = json.get_bool("AllowStartFromChatCommand");
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this->joinable = json.get_bool("Joinable");
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this->lock_status_register = json.get_int("LockStatusRegister");
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this->enemy_exp_overrides = QuestMetadata::parse_enemy_exp_overrides(json.at("EnemyEXPOverrides"));
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this->common_item_set_name = json.at("CommonItemSetName").as_string();
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this->rare_item_set_name = json.at("RareItemSetName").as_string();
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this->allowed_drop_modes = json.at("AllowedDropModes").as_int();
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this->default_drop_mode = phosg::enum_for_name<ServerDropMode>(json.at("DefaultDropMode").as_string());
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void QuestMetadata::assign_default_areas(Version version, Episode episode) {
|
void QuestMetadata::assign_default_areas(Version version, Episode episode) {
|
||||||
for (size_t z = 0; z < 0x12; z++) {
|
for (size_t z = 0; z < 0x12; z++) {
|
||||||
this->area_for_floor[z] = SetDataTableBase::default_area_for_floor(version, episode, z);
|
this->area_for_floor[z] = SetDataTableBase::default_area_for_floor(version, episode, z);
|
||||||
@@ -34,10 +81,11 @@ void QuestMetadata::assert_compatible(const QuestMetadata& other) const {
|
|||||||
"quest version has a different joinability state (existing: {}, new: {})",
|
"quest version has a different joinability state (existing: {}, new: {})",
|
||||||
this->joinable ? "true" : "false", other.joinable ? "true" : "false"));
|
this->joinable ? "true" : "false", other.joinable ? "true" : "false"));
|
||||||
}
|
}
|
||||||
if (this->max_players != other.max_players) {
|
bool this_has_player_limit = (this->max_players != 0) && (this->max_players != 4);
|
||||||
|
bool other_has_player_limit = (other.max_players != 0) && (other.max_players != 4);
|
||||||
|
if ((this_has_player_limit || other_has_player_limit) && (this->max_players != other.max_players)) {
|
||||||
throw runtime_error(std::format(
|
throw runtime_error(std::format(
|
||||||
"quest version has a different maximum player count (existing: {}, new: {})",
|
"quest version has a different maximum player count (existing: {}, new: {})", this->max_players, other.max_players));
|
||||||
this->max_players, other.max_players));
|
|
||||||
}
|
}
|
||||||
if (this->lock_status_register != other.lock_status_register) {
|
if (this->lock_status_register != other.lock_status_register) {
|
||||||
throw runtime_error(std::format(
|
throw runtime_error(std::format(
|
||||||
@@ -136,17 +184,11 @@ void QuestMetadata::assert_compatible(const QuestMetadata& other) const {
|
|||||||
"quest version has different common table name (existing: {}, new: {})",
|
"quest version has different common table name (existing: {}, new: {})",
|
||||||
this->common_item_set_name, other.common_item_set_name));
|
this->common_item_set_name, other.common_item_set_name));
|
||||||
}
|
}
|
||||||
if (this->common_item_set != other.common_item_set) {
|
|
||||||
throw runtime_error("quest version has different common table");
|
|
||||||
}
|
|
||||||
if (this->rare_item_set_name != other.rare_item_set_name) {
|
if (this->rare_item_set_name != other.rare_item_set_name) {
|
||||||
throw runtime_error(std::format(
|
throw runtime_error(std::format(
|
||||||
"quest version has different rare table name (existing: {}, new: {})",
|
"quest version has different rare table name (existing: {}, new: {})",
|
||||||
this->rare_item_set_name, other.rare_item_set_name));
|
this->rare_item_set_name, other.rare_item_set_name));
|
||||||
}
|
}
|
||||||
if (this->rare_item_set != other.rare_item_set) {
|
|
||||||
throw runtime_error("quest version has different rare table");
|
|
||||||
}
|
|
||||||
if (this->allowed_drop_modes != other.allowed_drop_modes) {
|
if (this->allowed_drop_modes != other.allowed_drop_modes) {
|
||||||
throw runtime_error(format("quest version has different allowed drop modes (existing: {:02X}, new: {:02X})",
|
throw runtime_error(format("quest version has different allowed drop modes (existing: {:02X}, new: {:02X})",
|
||||||
this->allowed_drop_modes, other.allowed_drop_modes));
|
this->allowed_drop_modes, other.allowed_drop_modes));
|
||||||
@@ -182,7 +224,7 @@ phosg::JSON QuestMetadata::json() const {
|
|||||||
{"Episode", name_for_episode(this->episode)},
|
{"Episode", name_for_episode(this->episode)},
|
||||||
{"FloorAssignments", std::move(floors_json)},
|
{"FloorAssignments", std::move(floors_json)},
|
||||||
{"Joinable", this->joinable},
|
{"Joinable", this->joinable},
|
||||||
{"MaxPlayers", this->max_players},
|
{"MaxPlayers", this->max_players ? 4 : this->max_players},
|
||||||
{"BattleRules", this->battle_rules ? this->battle_rules->json() : phosg::JSON(nullptr)},
|
{"BattleRules", this->battle_rules ? this->battle_rules->json() : phosg::JSON(nullptr)},
|
||||||
{"ChallengeTemplateIndex", (this->challenge_template_index >= 0) ? this->challenge_template_index : phosg::JSON(nullptr)},
|
{"ChallengeTemplateIndex", (this->challenge_template_index >= 0) ? this->challenge_template_index : phosg::JSON(nullptr)},
|
||||||
{"ChallengeEXPMultiplier", (this->challenge_exp_multiplier >= 0) ? this->challenge_exp_multiplier : phosg::JSON(nullptr)},
|
{"ChallengeEXPMultiplier", (this->challenge_exp_multiplier >= 0) ? this->challenge_exp_multiplier : phosg::JSON(nullptr)},
|
||||||
|
|||||||
+16
-4
@@ -22,6 +22,9 @@ struct QuestMetadata {
|
|||||||
// is used in both the Quest and VersionedQuest structures; in Quest, the
|
// is used in both the Quest and VersionedQuest structures; in Quest, the
|
||||||
// name and description are used only internally.
|
// name and description are used only internally.
|
||||||
|
|
||||||
|
Version version;
|
||||||
|
Language language;
|
||||||
|
|
||||||
// Fields that must match across all quest versions
|
// Fields that must match across all quest versions
|
||||||
uint32_t category_id = 0xFFFFFFFF;
|
uint32_t category_id = 0xFFFFFFFF;
|
||||||
uint32_t quest_number = 0xFFFFFFFF;
|
uint32_t quest_number = 0xFFFFFFFF;
|
||||||
@@ -38,8 +41,6 @@ struct QuestMetadata {
|
|||||||
std::shared_ptr<const IntegralExpression> enabled_expression;
|
std::shared_ptr<const IntegralExpression> enabled_expression;
|
||||||
std::string common_item_set_name; // blank = use default
|
std::string common_item_set_name; // blank = use default
|
||||||
std::string rare_item_set_name; // blank = use default
|
std::string rare_item_set_name; // blank = use default
|
||||||
std::shared_ptr<const CommonItemSet> common_item_set;
|
|
||||||
std::shared_ptr<const RareItemSet> rare_item_set;
|
|
||||||
uint8_t allowed_drop_modes = 0x00; // 0 = use server default
|
uint8_t allowed_drop_modes = 0x00; // 0 = use server default
|
||||||
ServerDropMode default_drop_mode = ServerDropMode::CLIENT; // Ignored if allowed_drop_modes == 0
|
ServerDropMode default_drop_mode = ServerDropMode::CLIENT; // Ignored if allowed_drop_modes == 0
|
||||||
bool allow_start_from_chat_command = false;
|
bool allow_start_from_chat_command = false;
|
||||||
@@ -47,7 +48,6 @@ struct QuestMetadata {
|
|||||||
std::unordered_map<uint32_t, uint32_t> enemy_exp_overrides;
|
std::unordered_map<uint32_t, uint32_t> enemy_exp_overrides;
|
||||||
|
|
||||||
// Extra header fields (only used on BB)
|
// Extra header fields (only used on BB)
|
||||||
std::shared_ptr<parray<uint8_t, 0x94>> bb_unknown_a5;
|
|
||||||
struct CreateItemMask {
|
struct CreateItemMask {
|
||||||
struct Range {
|
struct Range {
|
||||||
uint8_t min = 0x00;
|
uint8_t min = 0x00;
|
||||||
@@ -74,19 +74,31 @@ struct QuestMetadata {
|
|||||||
};
|
};
|
||||||
std::vector<CreateItemMask> create_item_mask_entries;
|
std::vector<CreateItemMask> create_item_mask_entries;
|
||||||
|
|
||||||
|
// Unknown header fields. These are not used by the client, so they are not required to match across quest versions;
|
||||||
|
// however, we still parse them in case we later discover that they had some server-side meaning.
|
||||||
|
uint16_t header_unknown_a1 = 0xFFFF; // All versions
|
||||||
|
uint16_t header_unknown_a2 = 0xFFFF; // All versions
|
||||||
|
uint8_t header_unknown_a3 = 0; // DCv1 - V3
|
||||||
|
uint8_t header_unknown_a4 = 0; // BB only
|
||||||
|
uint16_t header_unknown_a6 = 0; // BB only
|
||||||
|
int16_t header_episode = -1; // -1 = unspecified; BB only; newserv uses script analysis instead
|
||||||
|
int16_t header_language = -1; // -1 = unspecified; DCv1 and later; newserv uses the filename instead
|
||||||
|
std::shared_ptr<parray<uint8_t, 0x94>> header_unknown_a5; // BB only; null for non-BB quests
|
||||||
|
|
||||||
// Fields that may be different across quest versions (and are only used on VersionedQuest, not Quest)
|
// Fields that may be different across quest versions (and are only used on VersionedQuest, not Quest)
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string short_description;
|
std::string short_description;
|
||||||
std::string long_description;
|
std::string long_description;
|
||||||
size_t text_offset;
|
size_t text_offset;
|
||||||
size_t label_table_offset;
|
size_t label_table_offset;
|
||||||
Language language;
|
|
||||||
|
|
||||||
static std::unordered_map<uint32_t, uint32_t> parse_enemy_exp_overrides(const phosg::JSON& json);
|
static std::unordered_map<uint32_t, uint32_t> parse_enemy_exp_overrides(const phosg::JSON& json);
|
||||||
static inline uint32_t exp_override_key(Difficulty difficulty, uint8_t floor, EnemyType enemy_type) {
|
static inline uint32_t exp_override_key(Difficulty difficulty, uint8_t floor, EnemyType enemy_type) {
|
||||||
return (static_cast<uint32_t>(difficulty) << 24) | (static_cast<uint32_t>(floor) << 16) | static_cast<uint32_t>(enemy_type);
|
return (static_cast<uint32_t>(difficulty) << 24) | (static_cast<uint32_t>(floor) << 16) | static_cast<uint32_t>(enemy_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void apply_json_overrides(const phosg::JSON& json);
|
||||||
|
|
||||||
void assign_default_areas(Version version, Episode episode);
|
void assign_default_areas(Version version, Episode episode);
|
||||||
void assert_compatible(const QuestMetadata& other) const;
|
void assert_compatible(const QuestMetadata& other) const;
|
||||||
phosg::JSON json() const;
|
phosg::JSON json() const;
|
||||||
|
|||||||
+192
-163
@@ -2251,7 +2251,7 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = {
|
|||||||
// regsE[0-2] = result point (x, y, z as floats)
|
// regsE[0-2] = result point (x, y, z as floats)
|
||||||
// regsE[3] = result code (0 = failed, 1 = success)
|
// regsE[3] = result code (0 = failed, 1 = success)
|
||||||
// labelF = control point entries (array of valueA VectorXYZTF structures)
|
// labelF = control point entries (array of valueA VectorXYZTF structures)
|
||||||
{0xF8DB, "get_vector_from_path", "unknownF8DB", {I32, FLOAT32, FLOAT32, I32, {W_REG_SET_FIXED, 4}, SCRIPT16}, F_V3_V4 | F_ARGS},
|
{0xF8DB, "get_vector_from_path", "unknownF8DB", {I32, FLOAT32, FLOAT32, I32, {W_REG_SET_FIXED, 4}, {LABEL16, Arg::DataType::VECTOR4F_LIST}}, F_V3_V4 | F_ARGS},
|
||||||
|
|
||||||
// Same as npc_text, but only applies to a specific player slot.
|
// Same as npc_text, but only applies to a specific player slot.
|
||||||
// valueA = client ID
|
// valueA = client ID
|
||||||
@@ -3030,59 +3030,43 @@ std::string disassemble_quest_script(
|
|||||||
|
|
||||||
QuestMetadata meta;
|
QuestMetadata meta;
|
||||||
populate_quest_metadata_from_script(meta, bin_data, bin_size, version, language);
|
populate_quest_metadata_from_script(meta, bin_data, bin_size, version, language);
|
||||||
switch (version) {
|
if (version != Version::DC_NTE) {
|
||||||
case Version::DC_NTE:
|
lines.emplace_back(std::format(".quest_num {}", meta.quest_number));
|
||||||
lines.emplace_back(".name " + escape_string(meta.name));
|
}
|
||||||
break;
|
lines.emplace_back(".name " + escape_string(meta.name));
|
||||||
case Version::DC_11_2000:
|
if (version != Version::DC_NTE) {
|
||||||
case Version::DC_V1:
|
lines.emplace_back(std::format(".short_desc {}", escape_string(meta.short_description)));
|
||||||
case Version::DC_V2:
|
lines.emplace_back(std::format(".long_desc {}", escape_string(meta.long_description)));
|
||||||
lines.emplace_back(std::format(".quest_num {}", meta.quest_number));
|
}
|
||||||
lines.emplace_back(std::format(".language {}", char_for_language(meta.language)));
|
if (!is_v1_or_v2(version) || (version == Version::GC_NTE)) {
|
||||||
lines.emplace_back(".name " + escape_string(meta.name));
|
lines.emplace_back(std::format(".episode {}", token_name_for_episode(meta.episode)));
|
||||||
lines.emplace_back(".short_desc " + escape_string(meta.short_description));
|
lines.emplace_back(std::format(".header_episode 0x{:02X}", meta.header_episode));
|
||||||
lines.emplace_back(".long_desc " + escape_string(meta.long_description));
|
}
|
||||||
break;
|
lines.emplace_back(std::format(".language {}", char_for_language(meta.language)));
|
||||||
case Version::PC_NTE:
|
if (!is_pre_v1(version) && !is_v4(version) && (static_cast<uint8_t>(meta.language) != meta.header_language)) {
|
||||||
case Version::PC_V2:
|
lines.emplace_back(std::format(".header_language 0x{:02X}", meta.header_language));
|
||||||
lines.emplace_back(std::format(".quest_num {}", meta.quest_number));
|
}
|
||||||
lines.emplace_back(std::format(".language {}", char_for_language(meta.language)));
|
if (is_v4(version)) {
|
||||||
lines.emplace_back(".name " + escape_string(meta.name));
|
lines.emplace_back(std::format(".max_players {}", meta.max_players));
|
||||||
lines.emplace_back(".short_desc " + escape_string(meta.short_description));
|
if (meta.joinable) {
|
||||||
lines.emplace_back(".long_desc " + escape_string(meta.long_description));
|
lines.emplace_back(".joinable");
|
||||||
break;
|
}
|
||||||
case Version::GC_NTE:
|
for (const auto& mask : meta.create_item_mask_entries) {
|
||||||
case Version::GC_V3:
|
lines.emplace_back(std::format(".allow_create_item {}", mask.str()));
|
||||||
case Version::GC_EP3_NTE:
|
}
|
||||||
case Version::GC_EP3:
|
}
|
||||||
case Version::XB_V3:
|
lines.emplace_back(std::format(".header_unknown_a1 0x{:04X}", meta.header_unknown_a1));
|
||||||
lines.emplace_back(std::format(".quest_num {}", meta.quest_number));
|
lines.emplace_back(std::format(".header_unknown_a2 0x{:04X}", meta.header_unknown_a2));
|
||||||
lines.emplace_back(std::format(".episode {}", token_name_for_episode(meta.episode)));
|
if (!is_pre_v1(version) && !is_v4(version)) {
|
||||||
lines.emplace_back(std::format(".language {}", char_for_language(meta.language)));
|
lines.emplace_back(std::format(".header_unknown_a3 0x{:02X}", meta.header_unknown_a3));
|
||||||
lines.emplace_back(".name " + escape_string(meta.name));
|
}
|
||||||
lines.emplace_back(".short_desc " + escape_string(meta.short_description));
|
if (is_v4(version)) {
|
||||||
lines.emplace_back(".long_desc " + escape_string(meta.long_description));
|
lines.emplace_back(std::format(".header_unknown_a4 0x{:02X}", meta.header_unknown_a4));
|
||||||
break;
|
if (meta.header_unknown_a5) {
|
||||||
case Version::BB_V4:
|
auto formatted = phosg::format_data_string(meta.header_unknown_a5->data(), meta.header_unknown_a5->size());
|
||||||
lines.emplace_back(std::format(".quest_num {}", meta.quest_number));
|
lines.emplace_back(std::format(".header_unknown_a5 {}", formatted));
|
||||||
lines.emplace_back(std::format(".episode {}", token_name_for_episode(meta.episode)));
|
}
|
||||||
lines.emplace_back(std::format(".language {}", char_for_language(meta.language)));
|
lines.emplace_back(std::format(".header_unknown_a6 0x{:04X}", meta.header_unknown_a6));
|
||||||
lines.emplace_back(std::format(".max_players {}", meta.max_players));
|
|
||||||
if (meta.joinable) {
|
|
||||||
lines.emplace_back(".joinable");
|
|
||||||
}
|
|
||||||
lines.emplace_back(std::format(".name {}", escape_string(meta.name)));
|
|
||||||
lines.emplace_back(std::format(".short_desc {}", escape_string(meta.short_description)));
|
|
||||||
lines.emplace_back(std::format(".long_desc {}", escape_string(meta.long_description)));
|
|
||||||
if (meta.bb_unknown_a5) {
|
|
||||||
lines.emplace_back(std::format(".bb_unknown_a5 {}", phosg::format_data_string(meta.bb_unknown_a5->data(), meta.bb_unknown_a5->size())));
|
|
||||||
}
|
|
||||||
for (const auto& mask : meta.create_item_mask_entries) {
|
|
||||||
lines.emplace_back(std::format(".allow_create_item {}", mask.str()));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw logic_error("invalid quest version");
|
|
||||||
}
|
}
|
||||||
lines.emplace_back();
|
lines.emplace_back();
|
||||||
|
|
||||||
@@ -3218,6 +3202,7 @@ std::string disassemble_quest_script(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fs.enemy_sets) {
|
if (fs.enemy_sets) {
|
||||||
|
uint8_t area = meta.area_for_floor.at(floor);
|
||||||
for (size_t z = 0; z < fs.enemy_set_count; z++) {
|
for (size_t z = 0; z < fs.enemy_set_count; z++) {
|
||||||
// Only NPCs use script labels; no other enemies do
|
// Only NPCs use script labels; no other enemies do
|
||||||
const auto& ene_set = fs.enemy_sets[z];
|
const auto& ene_set = fs.enemy_sets[z];
|
||||||
@@ -3227,9 +3212,9 @@ std::string disassemble_quest_script(
|
|||||||
((ene_set.base_type >= 0x002B) && (ene_set.base_type <= 0x002D)) ||
|
((ene_set.base_type >= 0x002B) && (ene_set.base_type <= 0x002D)) ||
|
||||||
((ene_set.base_type >= 0x0030) && (ene_set.base_type <= 0x0033)) ||
|
((ene_set.base_type >= 0x0030) && (ene_set.base_type <= 0x0033)) ||
|
||||||
((ene_set.base_type >= 0x0045) && (ene_set.base_type <= 0x0047)) ||
|
((ene_set.base_type >= 0x0045) && (ene_set.base_type <= 0x0047)) ||
|
||||||
((ene_set.base_type >= 0x00D0) && (ene_set.base_type <= 0x00D7)) ||
|
((ene_set.base_type >= 0x00D0) && (ene_set.base_type <= 0x00D7) && (area == 0)) ||
|
||||||
((ene_set.base_type >= 0x00F0) && (ene_set.base_type <= 0x0100)) ||
|
((ene_set.base_type >= 0x00F0) && (ene_set.base_type <= 0x0100)) ||
|
||||||
((ene_set.base_type >= 0x0110) && (ene_set.base_type <= 0x0112)) ||
|
((ene_set.base_type >= 0x0110) && (ene_set.base_type <= 0x0112) && (area == 0)) ||
|
||||||
(ene_set.base_type == 0x00A9) ||
|
(ene_set.base_type == 0x00A9) ||
|
||||||
(ene_set.base_type == 0x0118)) &&
|
(ene_set.base_type == 0x0118)) &&
|
||||||
(ene_set.param5 > 0) && (ene_set.param5 < label_table.size())) {
|
(ene_set.param5 > 0) && (ene_set.param5 < label_table.size())) {
|
||||||
@@ -3553,12 +3538,20 @@ std::string disassemble_quest_script(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Type::R_REG:
|
case Type::R_REG:
|
||||||
case Type::W_REG: {
|
case Type::R_REG32:
|
||||||
uint8_t reg = label_r.get_u8();
|
case Type::W_REG:
|
||||||
|
case Type::W_REG32: {
|
||||||
|
uint32_t reg = ((arg.type == Type::R_REG32) || (arg.type == Type::W_REG32))
|
||||||
|
? label_r.get_u32l()
|
||||||
|
: label_r.get_u8();
|
||||||
if (def->flags & F_PUSH_ARG) {
|
if (def->flags & F_PUSH_ARG) {
|
||||||
arg_stack_values.emplace_back((def->opcode == 0x004C) ? ArgStackValue::Type::REG_PTR : ArgStackValue::Type::REG, reg);
|
arg_stack_values.emplace_back((def->opcode == 0x004C) ? ArgStackValue::Type::REG_PTR : ArgStackValue::Type::REG, reg);
|
||||||
}
|
}
|
||||||
dasm_arg = std::format("r{}", reg);
|
if (reg > 0xFF) {
|
||||||
|
dasm_arg = std::format("r{} /* warning: register number out of range */", reg);
|
||||||
|
} else {
|
||||||
|
dasm_arg = std::format("r{}", reg);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Type::R_REG_SET: {
|
case Type::R_REG_SET: {
|
||||||
@@ -4257,17 +4250,7 @@ AssembledQuestScript assemble_quest_script(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Collect metadata directives
|
// Collect metadata directives
|
||||||
Version quest_version = Version::UNKNOWN;
|
AssembledQuestScript ret;
|
||||||
string quest_name;
|
|
||||||
string quest_short_desc;
|
|
||||||
string quest_long_desc;
|
|
||||||
int64_t quest_num = -1;
|
|
||||||
Language quest_language = Language::ENGLISH;
|
|
||||||
Episode quest_episode = Episode::EP1;
|
|
||||||
uint8_t quest_max_players = 4;
|
|
||||||
bool quest_joinable = false;
|
|
||||||
std::shared_ptr<parray<uint8_t, 0x94>> bb_unknown_a5;
|
|
||||||
std::vector<QuestMetadata::CreateItemMask> create_item_mask_entries;
|
|
||||||
for (const auto& line : lines) {
|
for (const auto& line : lines) {
|
||||||
if (line.text.empty()) {
|
if (line.text.empty()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -4279,15 +4262,18 @@ AssembledQuestScript assemble_quest_script(
|
|||||||
} else if (line.text.starts_with(".version ")) {
|
} else if (line.text.starts_with(".version ")) {
|
||||||
string name = line.text.substr(9);
|
string name = line.text.substr(9);
|
||||||
phosg::strip_leading_whitespace(name);
|
phosg::strip_leading_whitespace(name);
|
||||||
quest_version = phosg::enum_for_name<Version>(name);
|
ret.meta.version = phosg::enum_for_name<Version>(name);
|
||||||
|
if ((ret.meta.episode == Episode::NONE) && is_v1_or_v2(ret.meta.version) && (ret.meta.version != Version::GC_NTE)) {
|
||||||
|
ret.meta.episode = Episode::EP1;
|
||||||
|
}
|
||||||
} else if (line.text.starts_with(".name ")) {
|
} else if (line.text.starts_with(".name ")) {
|
||||||
quest_name = phosg::parse_data_string(line.text.substr(6));
|
ret.meta.name = phosg::parse_data_string(line.text.substr(6));
|
||||||
} else if (line.text.starts_with(".short_desc ")) {
|
} else if (line.text.starts_with(".short_desc ")) {
|
||||||
quest_short_desc = phosg::parse_data_string(line.text.substr(12));
|
ret.meta.short_description = phosg::parse_data_string(line.text.substr(12));
|
||||||
} else if (line.text.starts_with(".long_desc ")) {
|
} else if (line.text.starts_with(".long_desc ")) {
|
||||||
quest_long_desc = phosg::parse_data_string(line.text.substr(11));
|
ret.meta.long_description = phosg::parse_data_string(line.text.substr(11));
|
||||||
} else if (line.text.starts_with(".allow_create_item ")) {
|
} else if (line.text.starts_with(".allow_create_item ")) {
|
||||||
if (create_item_mask_entries.size() >= 0x40) {
|
if (ret.meta.create_item_mask_entries.size() >= 0x40) {
|
||||||
throw std::runtime_error("too many .allow_create_item directives; at most 64 are allowed");
|
throw std::runtime_error("too many .allow_create_item directives; at most 64 are allowed");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4295,42 +4281,56 @@ AssembledQuestScript assemble_quest_script(
|
|||||||
phosg::strip_whitespace(args_str);
|
phosg::strip_whitespace(args_str);
|
||||||
|
|
||||||
QuestMetadata::CreateItemMask mask(line.text.substr(19));
|
QuestMetadata::CreateItemMask mask(line.text.substr(19));
|
||||||
create_item_mask_entries.emplace_back(mask);
|
ret.meta.create_item_mask_entries.emplace_back(mask);
|
||||||
|
|
||||||
} else if (line.text.starts_with(".quest_num ")) {
|
} else if (line.text.starts_with(".quest_num ")) {
|
||||||
quest_num = stoul(line.text.substr(11), nullptr, 0);
|
ret.meta.quest_number = stoul(line.text.substr(11), nullptr, 0);
|
||||||
} else if (line.text.starts_with(".language ")) {
|
} else if (line.text.starts_with(".language ")) {
|
||||||
auto code = line.text.substr(10);
|
auto code = line.text.substr(10);
|
||||||
if (code.size() != 1) {
|
if (code.size() != 1) {
|
||||||
throw runtime_error(".language directive argument is invalid");
|
throw runtime_error(".language directive argument is invalid");
|
||||||
}
|
}
|
||||||
quest_language = language_for_char(code[0]);
|
ret.meta.language = language_for_char(code[0]);
|
||||||
} else if (line.text.starts_with(".episode ")) {
|
} else if (line.text.starts_with(".episode ")) {
|
||||||
quest_episode = episode_for_token_name(line.text.substr(9));
|
ret.meta.episode = episode_for_token_name(line.text.substr(9));
|
||||||
} else if (line.text.starts_with(".max_players ")) {
|
} else if (line.text.starts_with(".max_players ")) {
|
||||||
quest_max_players = stoul(line.text.substr(12), nullptr, 0);
|
ret.meta.max_players = stoul(line.text.substr(12), nullptr, 0);
|
||||||
} else if (line.text.starts_with(".joinable ")) {
|
} else if (line.text.starts_with(".joinable ")) {
|
||||||
quest_joinable = true;
|
ret.meta.joinable = true;
|
||||||
} else if (line.text.starts_with(".bb_unknown_a5 ")) {
|
} else if (line.text.starts_with(".header_language ")) {
|
||||||
std::string data = phosg::parse_data_string(line.text.substr(15));
|
ret.meta.header_language = stoul(line.text.substr(17), nullptr, 0);
|
||||||
|
} else if (line.text.starts_with(".header_episode ")) {
|
||||||
|
ret.meta.header_episode = stoul(line.text.substr(16), nullptr, 0);
|
||||||
|
} else if (line.text.starts_with(".header_unknown_a1 ")) {
|
||||||
|
ret.meta.header_unknown_a1 = stoul(line.text.substr(19), nullptr, 0);
|
||||||
|
} else if (line.text.starts_with(".header_unknown_a2 ")) {
|
||||||
|
ret.meta.header_unknown_a2 = stoul(line.text.substr(19), nullptr, 0);
|
||||||
|
} else if (line.text.starts_with(".header_unknown_a3 ")) {
|
||||||
|
ret.meta.header_unknown_a3 = stoul(line.text.substr(19), nullptr, 0);
|
||||||
|
} else if (line.text.starts_with(".header_unknown_a4 ")) {
|
||||||
|
ret.meta.header_unknown_a4 = stoul(line.text.substr(19), nullptr, 0);
|
||||||
|
} else if (line.text.starts_with(".header_unknown_a6 ")) {
|
||||||
|
ret.meta.header_unknown_a6 = stoul(line.text.substr(19), nullptr, 0);
|
||||||
|
} else if (line.text.starts_with(".header_unknown_a5 ")) {
|
||||||
|
std::string data = phosg::parse_data_string(line.text.substr(19));
|
||||||
if (data.size() != 0x94) {
|
if (data.size() != 0x94) {
|
||||||
throw std::runtime_error(".bb_unknown_a5 directive must specify 0x94 bytes of data");
|
throw std::runtime_error(".header_unknown_a5 directive must specify 0x94 bytes of data");
|
||||||
}
|
}
|
||||||
bb_unknown_a5 = std::make_shared<parray<uint8_t, 0x94>>();
|
ret.meta.header_unknown_a5 = std::make_shared<parray<uint8_t, 0x94>>();
|
||||||
for (size_t z = 0; z < 0x94; z++) {
|
for (size_t z = 0; z < 0x94; z++) {
|
||||||
bb_unknown_a5->at(z) = static_cast<uint8_t>(data[z]);
|
ret.meta.header_unknown_a5->at(z) = static_cast<uint8_t>(data[z]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (quest_version == Version::PC_PATCH || quest_version == Version::BB_PATCH || quest_version == Version::UNKNOWN) {
|
if (ret.meta.version == Version::PC_PATCH || ret.meta.version == Version::BB_PATCH || ret.meta.version == Version::UNKNOWN) {
|
||||||
throw runtime_error(".version directive is missing or invalid");
|
throw runtime_error(".version directive is missing or invalid");
|
||||||
}
|
}
|
||||||
if (quest_num < 0) {
|
if (ret.meta.quest_number < 0) {
|
||||||
throw runtime_error(".quest_num directive is missing or invalid");
|
throw runtime_error(".quest_num directive is missing or invalid");
|
||||||
}
|
}
|
||||||
if (quest_name.empty()) {
|
if (ret.meta.name.empty()) {
|
||||||
throw runtime_error(".name directive is missing or invalid");
|
throw runtime_error(".name directive is missing or invalid");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4488,8 +4488,8 @@ AssembledQuestScript assemble_quest_script(
|
|||||||
throw runtime_error("data not found for native include: " + filename);
|
throw runtime_error("data not found for native include: " + filename);
|
||||||
};
|
};
|
||||||
|
|
||||||
bool version_has_args = F_HAS_ARGS & v_flag(quest_version);
|
bool version_has_args = F_HAS_ARGS & v_flag(ret.meta.version);
|
||||||
const auto& opcodes = opcodes_by_name_for_version(quest_version);
|
const auto& opcodes = opcodes_by_name_for_version(ret.meta.version);
|
||||||
phosg::StringWriter code_w;
|
phosg::StringWriter code_w;
|
||||||
for (const auto& line : lines) {
|
for (const auto& line : lines) {
|
||||||
wrap_exceptions_with_line_ref(line, [&]() -> void {
|
wrap_exceptions_with_line_ref(line, [&]() -> void {
|
||||||
@@ -4509,11 +4509,11 @@ AssembledQuestScript assemble_quest_script(
|
|||||||
code_w.write(phosg::parse_data_string(line.text.substr(6)));
|
code_w.write(phosg::parse_data_string(line.text.substr(6)));
|
||||||
} else if (line.text.starts_with(".cstr ")) {
|
} else if (line.text.starts_with(".cstr ")) {
|
||||||
string data = phosg::parse_data_string(line.text.substr(6));
|
string data = phosg::parse_data_string(line.text.substr(6));
|
||||||
if (uses_utf16(quest_version)) {
|
if (uses_utf16(ret.meta.version)) {
|
||||||
code_w.write(tt_utf8_to_utf16(data));
|
code_w.write(tt_utf8_to_utf16(data));
|
||||||
code_w.put_u16l(0);
|
code_w.put_u16l(0);
|
||||||
} else {
|
} else {
|
||||||
code_w.write((quest_language == Language::JAPANESE) ? tt_utf8_to_sega_sjis(data) : tt_utf8_to_8859(data));
|
code_w.write((ret.meta.language == Language::JAPANESE) ? tt_utf8_to_sega_sjis(data) : tt_utf8_to_8859(data));
|
||||||
code_w.put_u8(0);
|
code_w.put_u8(0);
|
||||||
}
|
}
|
||||||
} else if (line.text.starts_with(".zero ")) {
|
} else if (line.text.starts_with(".zero ")) {
|
||||||
@@ -4538,11 +4538,11 @@ AssembledQuestScript assemble_quest_script(
|
|||||||
phosg::strip_whitespace(filename);
|
phosg::strip_whitespace(filename);
|
||||||
string native_text = get_native_include(filename);
|
string native_text = get_native_include(filename);
|
||||||
string code;
|
string code;
|
||||||
if (is_ppc(quest_version)) {
|
if (is_ppc(ret.meta.version)) {
|
||||||
code = std::move(ResourceDASM::PPC32Emulator::assemble(native_text).code);
|
code = std::move(ResourceDASM::PPC32Emulator::assemble(native_text).code);
|
||||||
} else if (is_x86(quest_version)) {
|
} else if (is_x86(ret.meta.version)) {
|
||||||
code = std::move(ResourceDASM::X86Emulator::assemble(native_text).code);
|
code = std::move(ResourceDASM::X86Emulator::assemble(native_text).code);
|
||||||
} else if (is_sh4(quest_version)) {
|
} else if (is_sh4(ret.meta.version)) {
|
||||||
code = std::move(ResourceDASM::SH4Emulator::assemble(native_text).code);
|
code = std::move(ResourceDASM::SH4Emulator::assemble(native_text).code);
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("unknown architecture");
|
throw runtime_error("unknown architecture");
|
||||||
@@ -4603,7 +4603,7 @@ AssembledQuestScript assemble_quest_script(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
auto add_cstr = [&](const string& text, bool bin) -> void {
|
auto add_cstr = [&](const string& text, bool bin) -> void {
|
||||||
switch (quest_version) {
|
switch (ret.meta.version) {
|
||||||
case Version::DC_NTE:
|
case Version::DC_NTE:
|
||||||
code_w.write(bin ? text : tt_utf8_to_sega_sjis(text));
|
code_w.write(bin ? text : tt_utf8_to_sega_sjis(text));
|
||||||
code_w.put_u8(0);
|
code_w.put_u8(0);
|
||||||
@@ -4616,7 +4616,7 @@ AssembledQuestScript assemble_quest_script(
|
|||||||
case Version::GC_EP3_NTE:
|
case Version::GC_EP3_NTE:
|
||||||
case Version::GC_EP3:
|
case Version::GC_EP3:
|
||||||
case Version::XB_V3:
|
case Version::XB_V3:
|
||||||
code_w.write(bin ? text : ((quest_language == Language::JAPANESE) ? tt_utf8_to_sega_sjis(text) : tt_utf8_to_8859(text)));
|
code_w.write(bin ? text : ((ret.meta.language == Language::JAPANESE) ? tt_utf8_to_sega_sjis(text) : tt_utf8_to_8859(text)));
|
||||||
code_w.put_u8(0);
|
code_w.put_u8(0);
|
||||||
break;
|
break;
|
||||||
case Version::PC_NTE:
|
case Version::PC_NTE:
|
||||||
@@ -4715,9 +4715,9 @@ AssembledQuestScript assemble_quest_script(
|
|||||||
size_t label_index;
|
size_t label_index;
|
||||||
auto it = labels_by_name.find(name);
|
auto it = labels_by_name.find(name);
|
||||||
if (it == labels_by_name.end()) {
|
if (it == labels_by_name.end()) {
|
||||||
if (strict) {
|
if (strict || !name.starts_with("label")) {
|
||||||
throw runtime_error("label not defined: " + name);
|
throw runtime_error("label not defined: " + name);
|
||||||
} else if (name.starts_with("label")) {
|
} else {
|
||||||
size_t used_chars;
|
size_t used_chars;
|
||||||
label_index = std::stoul(name.substr(5), &used_chars, 16);
|
label_index = std::stoul(name.substr(5), &used_chars, 16);
|
||||||
if (used_chars != name.size() - 5) {
|
if (used_chars != name.size() - 5) {
|
||||||
@@ -4831,8 +4831,12 @@ AssembledQuestScript assemble_quest_script(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
while (code_w.size() & 3) {
|
|
||||||
code_w.put_u8(0);
|
// Allow label table to be misaligned on x86 architectures in non-strict mode (some official quests do this)
|
||||||
|
if (strict || !is_x86(ret.meta.version)) {
|
||||||
|
while (code_w.size() & 3) {
|
||||||
|
code_w.put_u8(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign all register numbers and patch the code section if needed
|
// Assign all register numbers and patch the code section if needed
|
||||||
@@ -4871,43 +4875,55 @@ AssembledQuestScript assemble_quest_script(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate header
|
// Generate header
|
||||||
|
auto set_basic_header_fields = [&]<typename HeaderT>(HeaderT& header) -> void {
|
||||||
|
ret.meta.text_offset = sizeof(header);
|
||||||
|
ret.meta.label_table_offset = ret.meta.text_offset + code_w.size();
|
||||||
|
header.text_offset = ret.meta.text_offset;
|
||||||
|
header.label_table_offset = ret.meta.label_table_offset;
|
||||||
|
header.size = ret.meta.label_table_offset + label_table.size() * sizeof(label_table[0]);
|
||||||
|
header.unknown_a1 = ret.meta.header_unknown_a1;
|
||||||
|
header.unknown_a2 = ret.meta.header_unknown_a2;
|
||||||
|
};
|
||||||
phosg::StringWriter w;
|
phosg::StringWriter w;
|
||||||
switch (quest_version) {
|
switch (ret.meta.version) {
|
||||||
case Version::DC_NTE: {
|
case Version::DC_NTE: {
|
||||||
PSOQuestHeaderDCNTE header;
|
PSOQuestHeaderDCNTE header;
|
||||||
header.text_offset = sizeof(header);
|
set_basic_header_fields(header);
|
||||||
header.label_table_offset = sizeof(header) + code_w.size();
|
header.name.encode(ret.meta.name, Language::JAPANESE);
|
||||||
header.size = header.label_table_offset + label_table.size() * sizeof(label_table[0]);
|
|
||||||
header.name.encode(quest_name, Language::JAPANESE);
|
|
||||||
w.put(header);
|
w.put(header);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Version::DC_11_2000:
|
case Version::DC_11_2000: {
|
||||||
|
PSOQuestHeaderDC112000 header;
|
||||||
|
set_basic_header_fields(header);
|
||||||
|
header.name.encode(ret.meta.name, ret.meta.language);
|
||||||
|
header.short_description.encode(ret.meta.short_description, ret.meta.language);
|
||||||
|
header.long_description.encode(ret.meta.long_description, ret.meta.language);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case Version::DC_V1:
|
case Version::DC_V1:
|
||||||
case Version::DC_V2: {
|
case Version::DC_V2: {
|
||||||
PSOQuestHeaderDC header;
|
PSOQuestHeaderDC header;
|
||||||
header.text_offset = sizeof(header);
|
set_basic_header_fields(header);
|
||||||
header.label_table_offset = sizeof(header) + code_w.size();
|
header.language = (ret.meta.header_language >= 0) ? static_cast<Language>(ret.meta.header_language) : ret.meta.language;
|
||||||
header.size = header.label_table_offset + label_table.size() * sizeof(label_table[0]);
|
header.unknown_a3 = ret.meta.header_unknown_a3;
|
||||||
header.language = quest_language;
|
header.quest_number = ret.meta.quest_number;
|
||||||
header.quest_number = quest_num;
|
header.name.encode(ret.meta.name, ret.meta.language);
|
||||||
header.name.encode(quest_name, quest_language);
|
header.short_description.encode(ret.meta.short_description, ret.meta.language);
|
||||||
header.short_description.encode(quest_short_desc, quest_language);
|
header.long_description.encode(ret.meta.long_description, ret.meta.language);
|
||||||
header.long_description.encode(quest_long_desc, quest_language);
|
|
||||||
w.put(header);
|
w.put(header);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Version::PC_NTE:
|
case Version::PC_NTE:
|
||||||
case Version::PC_V2: {
|
case Version::PC_V2: {
|
||||||
PSOQuestHeaderPC header;
|
PSOQuestHeaderPC header;
|
||||||
header.text_offset = sizeof(header);
|
set_basic_header_fields(header);
|
||||||
header.label_table_offset = sizeof(header) + code_w.size();
|
header.language = (ret.meta.header_language >= 0) ? static_cast<Language>(ret.meta.header_language) : ret.meta.language;
|
||||||
header.size = header.label_table_offset + label_table.size() * sizeof(label_table[0]);
|
header.unknown_a3 = ret.meta.header_unknown_a3;
|
||||||
header.language = quest_language;
|
header.quest_number = ret.meta.quest_number;
|
||||||
header.quest_number = quest_num;
|
header.name.encode(ret.meta.name, ret.meta.language);
|
||||||
header.name.encode(quest_name, quest_language);
|
header.short_description.encode(ret.meta.short_description, ret.meta.language);
|
||||||
header.short_description.encode(quest_short_desc, quest_language);
|
header.long_description.encode(ret.meta.long_description, ret.meta.language);
|
||||||
header.long_description.encode(quest_long_desc, quest_language);
|
|
||||||
w.put(header);
|
w.put(header);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -4916,43 +4932,44 @@ AssembledQuestScript assemble_quest_script(
|
|||||||
case Version::GC_EP3_NTE:
|
case Version::GC_EP3_NTE:
|
||||||
case Version::GC_EP3:
|
case Version::GC_EP3:
|
||||||
case Version::XB_V3: {
|
case Version::XB_V3: {
|
||||||
PSOQuestHeaderGC header;
|
PSOQuestHeaderV3 header;
|
||||||
header.text_offset = sizeof(header);
|
set_basic_header_fields(header);
|
||||||
header.label_table_offset = sizeof(header) + code_w.size();
|
header.language = (ret.meta.header_language >= 0) ? static_cast<Language>(ret.meta.header_language) : ret.meta.language;
|
||||||
header.size = header.label_table_offset + label_table.size() * sizeof(label_table[0]);
|
header.unknown_a3 = ret.meta.header_unknown_a3;
|
||||||
header.language = quest_language;
|
header.quest_number = ret.meta.quest_number;
|
||||||
header.quest_number = quest_num;
|
header.name.encode(ret.meta.name, ret.meta.language);
|
||||||
header.name.encode(quest_name, quest_language);
|
header.short_description.encode(ret.meta.short_description, ret.meta.language);
|
||||||
header.short_description.encode(quest_short_desc, quest_language);
|
header.long_description.encode(ret.meta.long_description, ret.meta.language);
|
||||||
header.long_description.encode(quest_long_desc, quest_language);
|
|
||||||
w.put(header);
|
w.put(header);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Version::BB_V4: {
|
case Version::BB_V4: {
|
||||||
PSOQuestHeaderBB header;
|
PSOQuestHeaderBB header;
|
||||||
header.text_offset = sizeof(header);
|
set_basic_header_fields(header);
|
||||||
header.label_table_offset = sizeof(header) + code_w.size();
|
header.quest_number = ret.meta.quest_number;
|
||||||
header.size = header.label_table_offset + label_table.size() * sizeof(label_table[0]);
|
header.unknown_a6 = ret.meta.header_unknown_a6;
|
||||||
header.quest_number = quest_num;
|
if (ret.meta.header_episode >= 0) {
|
||||||
if (quest_episode == Episode::EP4) {
|
header.episode = ret.meta.header_episode;
|
||||||
|
} else if (ret.meta.episode == Episode::EP4) {
|
||||||
header.episode = 2;
|
header.episode = 2;
|
||||||
} else if (quest_episode == Episode::EP2) {
|
} else if (ret.meta.episode == Episode::EP2) {
|
||||||
header.episode = 1;
|
header.episode = 1;
|
||||||
} else {
|
} else {
|
||||||
header.episode = 0;
|
header.episode = 0;
|
||||||
}
|
}
|
||||||
header.max_players = (quest_max_players == 4) ? 0 : quest_max_players;
|
header.max_players = ret.meta.max_players;
|
||||||
header.joinable = quest_joinable ? 1 : 0;
|
header.joinable = ret.meta.joinable ? 1 : 0;
|
||||||
header.name.encode(quest_name, quest_language);
|
header.unknown_a4 = ret.meta.header_unknown_a4;
|
||||||
header.short_description.encode(quest_short_desc, quest_language);
|
header.name.encode(ret.meta.name, ret.meta.language);
|
||||||
header.long_description.encode(quest_long_desc, quest_language);
|
header.short_description.encode(ret.meta.short_description, ret.meta.language);
|
||||||
if (bb_unknown_a5) {
|
header.long_description.encode(ret.meta.long_description, ret.meta.language);
|
||||||
header.unknown_a5 = *bb_unknown_a5;
|
if (ret.meta.header_unknown_a5) {
|
||||||
|
header.unknown_a5 = *ret.meta.header_unknown_a5;
|
||||||
} else {
|
} else {
|
||||||
header.unknown_a5.clear(0);
|
header.unknown_a5.clear(0);
|
||||||
}
|
}
|
||||||
for (size_t z = 0; z < create_item_mask_entries.size(); z++) {
|
for (size_t z = 0; z < ret.meta.create_item_mask_entries.size(); z++) {
|
||||||
header.create_item_mask_entries[z] = create_item_mask_entries[z];
|
header.create_item_mask_entries[z] = ret.meta.create_item_mask_entries[z];
|
||||||
}
|
}
|
||||||
w.put(header);
|
w.put(header);
|
||||||
break;
|
break;
|
||||||
@@ -4962,18 +4979,9 @@ AssembledQuestScript assemble_quest_script(
|
|||||||
}
|
}
|
||||||
w.write(code_w.str());
|
w.write(code_w.str());
|
||||||
w.write(label_table.data(), label_table.size() * sizeof(label_table[0]));
|
w.write(label_table.data(), label_table.size() * sizeof(label_table[0]));
|
||||||
return AssembledQuestScript{
|
ret.data = std::move(w.str());
|
||||||
.data = std::move(w.str()),
|
|
||||||
.quest_number = quest_num,
|
return ret;
|
||||||
.version = quest_version,
|
|
||||||
.language = quest_language,
|
|
||||||
.episode = quest_episode,
|
|
||||||
.joinable = quest_joinable,
|
|
||||||
.max_players = quest_max_players,
|
|
||||||
.name = quest_name,
|
|
||||||
.short_description = quest_short_desc,
|
|
||||||
.long_description = quest_long_desc,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void populate_quest_metadata_from_script(
|
void populate_quest_metadata_from_script(
|
||||||
@@ -4984,6 +4992,8 @@ void populate_quest_metadata_from_script(
|
|||||||
switch (version) {
|
switch (version) {
|
||||||
case Version::DC_NTE: {
|
case Version::DC_NTE: {
|
||||||
const auto& header = r.get<PSOQuestHeaderDCNTE>();
|
const auto& header = r.get<PSOQuestHeaderDCNTE>();
|
||||||
|
meta.header_unknown_a1 = header.unknown_a1;
|
||||||
|
meta.header_unknown_a2 = header.unknown_a2;
|
||||||
meta.episode = Episode::EP1;
|
meta.episode = Episode::EP1;
|
||||||
meta.max_players = 4;
|
meta.max_players = 4;
|
||||||
meta.name = header.name.decode(language);
|
meta.name = header.name.decode(language);
|
||||||
@@ -4997,6 +5007,8 @@ void populate_quest_metadata_from_script(
|
|||||||
}
|
}
|
||||||
case Version::DC_11_2000: {
|
case Version::DC_11_2000: {
|
||||||
const auto& header = r.get<PSOQuestHeaderDC112000>();
|
const auto& header = r.get<PSOQuestHeaderDC112000>();
|
||||||
|
meta.header_unknown_a1 = header.unknown_a1;
|
||||||
|
meta.header_unknown_a2 = header.unknown_a2;
|
||||||
meta.episode = Episode::EP1;
|
meta.episode = Episode::EP1;
|
||||||
meta.max_players = 4;
|
meta.max_players = 4;
|
||||||
meta.language = (language == Language::UNKNOWN) ? Language::JAPANESE : language;
|
meta.language = (language == Language::UNKNOWN) ? Language::JAPANESE : language;
|
||||||
@@ -5013,6 +5025,10 @@ void populate_quest_metadata_from_script(
|
|||||||
case Version::DC_V1:
|
case Version::DC_V1:
|
||||||
case Version::DC_V2: {
|
case Version::DC_V2: {
|
||||||
const auto& header = r.get<PSOQuestHeaderDC>();
|
const auto& header = r.get<PSOQuestHeaderDC>();
|
||||||
|
meta.header_language = static_cast<uint8_t>(header.language);
|
||||||
|
meta.header_unknown_a1 = header.unknown_a1;
|
||||||
|
meta.header_unknown_a2 = header.unknown_a2;
|
||||||
|
meta.header_unknown_a3 = header.unknown_a3;
|
||||||
meta.episode = Episode::EP1;
|
meta.episode = Episode::EP1;
|
||||||
meta.max_players = 4;
|
meta.max_players = 4;
|
||||||
meta.language = (language != Language::UNKNOWN) ? language : ((static_cast<size_t>(header.language) < 5) ? header.language : Language::ENGLISH);
|
meta.language = (language != Language::UNKNOWN) ? language : ((static_cast<size_t>(header.language) < 5) ? header.language : Language::ENGLISH);
|
||||||
@@ -5029,6 +5045,10 @@ void populate_quest_metadata_from_script(
|
|||||||
case Version::PC_NTE:
|
case Version::PC_NTE:
|
||||||
case Version::PC_V2: {
|
case Version::PC_V2: {
|
||||||
const auto& header = r.get<PSOQuestHeaderPC>();
|
const auto& header = r.get<PSOQuestHeaderPC>();
|
||||||
|
meta.header_language = static_cast<uint8_t>(header.language);
|
||||||
|
meta.header_unknown_a1 = header.unknown_a1;
|
||||||
|
meta.header_unknown_a2 = header.unknown_a2;
|
||||||
|
meta.header_unknown_a3 = header.unknown_a3;
|
||||||
meta.episode = Episode::EP1;
|
meta.episode = Episode::EP1;
|
||||||
meta.max_players = 4;
|
meta.max_players = 4;
|
||||||
if (meta.quest_number == 0xFFFFFFFF) {
|
if (meta.quest_number == 0xFFFFFFFF) {
|
||||||
@@ -5051,7 +5071,11 @@ void populate_quest_metadata_from_script(
|
|||||||
// same as Episode 3 maps and download quests. Quest scripts (handled
|
// same as Episode 3 maps and download quests. Quest scripts (handled
|
||||||
// here) are only used offline in story mode, but can be disassembled
|
// here) are only used offline in story mode, but can be disassembled
|
||||||
// with disassemble_quest_script, hence we need to be able to parse them.
|
// with disassemble_quest_script, hence we need to be able to parse them.
|
||||||
const auto& header = r.get<PSOQuestHeaderGC>();
|
const auto& header = r.get<PSOQuestHeaderV3>();
|
||||||
|
meta.header_language = static_cast<uint8_t>(header.language);
|
||||||
|
meta.header_unknown_a1 = header.unknown_a1;
|
||||||
|
meta.header_unknown_a2 = header.unknown_a2;
|
||||||
|
meta.header_unknown_a3 = header.unknown_a3;
|
||||||
meta.episode = Episode::EP1;
|
meta.episode = Episode::EP1;
|
||||||
meta.max_players = 4;
|
meta.max_players = 4;
|
||||||
if (meta.quest_number == 0xFFFFFFFF) {
|
if (meta.quest_number == 0xFFFFFFFF) {
|
||||||
@@ -5067,9 +5091,14 @@ void populate_quest_metadata_from_script(
|
|||||||
}
|
}
|
||||||
case Version::BB_V4: {
|
case Version::BB_V4: {
|
||||||
const auto& header = r.get<PSOQuestHeaderBBBase>();
|
const auto& header = r.get<PSOQuestHeaderBBBase>();
|
||||||
|
meta.header_episode = header.episode;
|
||||||
|
meta.header_unknown_a1 = header.unknown_a1;
|
||||||
|
meta.header_unknown_a2 = header.unknown_a2;
|
||||||
|
meta.header_unknown_a4 = header.unknown_a4;
|
||||||
|
meta.header_unknown_a6 = header.unknown_a6;
|
||||||
meta.episode = episode_for_quest_episode_number(header.episode);
|
meta.episode = episode_for_quest_episode_number(header.episode);
|
||||||
meta.joinable |= header.joinable;
|
meta.joinable |= header.joinable;
|
||||||
meta.max_players = header.max_players ? header.max_players : 4;
|
meta.max_players = header.max_players;
|
||||||
if (meta.quest_number == 0xFFFFFFFF) {
|
if (meta.quest_number == 0xFFFFFFFF) {
|
||||||
meta.quest_number = header.quest_number;
|
meta.quest_number = header.quest_number;
|
||||||
}
|
}
|
||||||
@@ -5084,7 +5113,7 @@ void populate_quest_metadata_from_script(
|
|||||||
(header.label_table_offset >= sizeof(PSOQuestHeaderBB))) {
|
(header.label_table_offset >= sizeof(PSOQuestHeaderBB))) {
|
||||||
r.go(0);
|
r.go(0);
|
||||||
const auto& header = r.get<PSOQuestHeaderBB>();
|
const auto& header = r.get<PSOQuestHeaderBB>();
|
||||||
meta.bb_unknown_a5 = std::make_shared<parray<uint8_t, 0x94>>(header.unknown_a5);
|
meta.header_unknown_a5 = std::make_shared<parray<uint8_t, 0x94>>(header.unknown_a5);
|
||||||
for (size_t z = 0; z < header.create_item_mask_entries.size(); z++) {
|
for (size_t z = 0; z < header.create_item_mask_entries.size(); z++) {
|
||||||
const auto& item = header.create_item_mask_entries[z];
|
const auto& item = header.create_item_mask_entries[z];
|
||||||
if (!item.is_valid()) {
|
if (!item.is_valid()) {
|
||||||
|
|||||||
+4
-14
@@ -62,9 +62,7 @@ struct PSOQuestHeaderPC {
|
|||||||
/* 0394 */
|
/* 0394 */
|
||||||
} __packed_ws__(PSOQuestHeaderPC, 0x394);
|
} __packed_ws__(PSOQuestHeaderPC, 0x394);
|
||||||
|
|
||||||
// TODO: Is the XB quest header format the same as on GC? If not, make a
|
struct PSOQuestHeaderV3 {
|
||||||
// separate struct; if so, rename this struct to V3.
|
|
||||||
struct PSOQuestHeaderGC {
|
|
||||||
/* 0000 */ le_uint32_t text_offset = 0;
|
/* 0000 */ le_uint32_t text_offset = 0;
|
||||||
/* 0004 */ le_uint32_t label_table_offset = 0;
|
/* 0004 */ le_uint32_t label_table_offset = 0;
|
||||||
/* 0008 */ le_uint32_t size = 0;
|
/* 0008 */ le_uint32_t size = 0;
|
||||||
@@ -81,7 +79,7 @@ struct PSOQuestHeaderGC {
|
|||||||
/* 0034 */ pstring<TextEncoding::MARKED, 0x80> short_description;
|
/* 0034 */ pstring<TextEncoding::MARKED, 0x80> short_description;
|
||||||
/* 00B4 */ pstring<TextEncoding::MARKED, 0x120> long_description;
|
/* 00B4 */ pstring<TextEncoding::MARKED, 0x120> long_description;
|
||||||
/* 01D4 */
|
/* 01D4 */
|
||||||
} __packed_ws__(PSOQuestHeaderGC, 0x1D4);
|
} __packed_ws__(PSOQuestHeaderV3, 0x1D4);
|
||||||
|
|
||||||
struct CreateItemMaskEntry {
|
struct CreateItemMaskEntry {
|
||||||
parray<le_int32_t, 12> data1_fields;
|
parray<le_int32_t, 12> data1_fields;
|
||||||
@@ -104,7 +102,7 @@ struct PSOQuestHeaderBBBase {
|
|||||||
/* 000C */ le_uint16_t unknown_a1 = 0xFFFF;
|
/* 000C */ le_uint16_t unknown_a1 = 0xFFFF;
|
||||||
/* 000E */ le_uint16_t unknown_a2 = 0xFFFF;
|
/* 000E */ le_uint16_t unknown_a2 = 0xFFFF;
|
||||||
/* 0010 */ le_uint16_t quest_number = 0; // 0xFFFF for challenge quests
|
/* 0010 */ le_uint16_t quest_number = 0; // 0xFFFF for challenge quests
|
||||||
/* 0012 */ le_uint16_t unknown_a3 = 0;
|
/* 0012 */ le_uint16_t unknown_a6 = 0;
|
||||||
/* 0014 */ uint8_t episode = 0; // 0 = Ep1, 1 = Ep2, 2 = Ep4
|
/* 0014 */ uint8_t episode = 0; // 0 = Ep1, 1 = Ep2, 2 = Ep4
|
||||||
/* 0015 */ uint8_t max_players = 0; // 0 means no limit (that is, 4)
|
/* 0015 */ uint8_t max_players = 0; // 0 means no limit (that is, 4)
|
||||||
/* 0016 */ uint8_t joinable = 0;
|
/* 0016 */ uint8_t joinable = 0;
|
||||||
@@ -136,15 +134,7 @@ std::string disassemble_quest_script(
|
|||||||
|
|
||||||
struct AssembledQuestScript {
|
struct AssembledQuestScript {
|
||||||
std::string data;
|
std::string data;
|
||||||
int64_t quest_number = -1;
|
QuestMetadata meta;
|
||||||
Version version = Version::UNKNOWN;
|
|
||||||
Language language = Language::UNKNOWN;
|
|
||||||
Episode episode = Episode::NONE;
|
|
||||||
bool joinable = false;
|
|
||||||
uint8_t max_players = 4;
|
|
||||||
std::string name;
|
|
||||||
std::string short_description;
|
|
||||||
std::string long_description;
|
|
||||||
};
|
};
|
||||||
AssembledQuestScript assemble_quest_script(
|
AssembledQuestScript assemble_quest_script(
|
||||||
const std::string& text,
|
const std::string& text,
|
||||||
|
|||||||
@@ -363,7 +363,7 @@ static asio::awaitable<void> on_login_complete(shared_ptr<Client> c) {
|
|||||||
lobby_data.guild_card_number = c->login->account->account_id;
|
lobby_data.guild_card_number = c->login->account->account_id;
|
||||||
send_command_t(c, 0x64, 0x01, cmd);
|
send_command_t(c, 0x64, 0x01, cmd);
|
||||||
} else {
|
} else {
|
||||||
c->log.info_f("Sending {} version of quest \"{}\"", name_for_language(vq->language), vq->meta.name);
|
c->log.info_f("Sending {} version of quest \"{}\"", name_for_language(vq->meta.language), vq->meta.name);
|
||||||
string bin_filename = vq->bin_filename();
|
string bin_filename = vq->bin_filename();
|
||||||
string dat_filename = vq->dat_filename();
|
string dat_filename = vq->dat_filename();
|
||||||
string xb_filename = vq->xb_filename();
|
string xb_filename = vq->xb_filename();
|
||||||
@@ -2495,7 +2495,7 @@ void set_lobby_quest(shared_ptr<Lobby> l, shared_ptr<const Quest> q, bool substi
|
|||||||
lc->channel->disconnect();
|
lc->channel->disconnect();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
lc->log.info_f("Sending {} version of quest \"{}\"", name_for_language(vq->language), vq->meta.name);
|
lc->log.info_f("Sending {} version of quest \"{}\"", name_for_language(vq->meta.language), vq->meta.name);
|
||||||
|
|
||||||
string bin_filename = vq->bin_filename();
|
string bin_filename = vq->bin_filename();
|
||||||
string dat_filename = vq->dat_filename();
|
string dat_filename = vq->dat_filename();
|
||||||
@@ -4979,7 +4979,7 @@ static asio::awaitable<void> on_6F(shared_ptr<Client> c, Channel::Message& msg)
|
|||||||
if (!vq) {
|
if (!vq) {
|
||||||
throw std::logic_error("cannot find patch enable quest version after it was previously found during login");
|
throw std::logic_error("cannot find patch enable quest version after it was previously found during login");
|
||||||
}
|
}
|
||||||
c->log.info_f("Sending {} version of quest \"{}\"", name_for_language(vq->language), vq->meta.name);
|
c->log.info_f("Sending {} version of quest \"{}\"", name_for_language(vq->meta.language), vq->meta.name);
|
||||||
string bin_filename = vq->bin_filename();
|
string bin_filename = vq->bin_filename();
|
||||||
string dat_filename = vq->dat_filename();
|
string dat_filename = vq->dat_filename();
|
||||||
string xb_filename = vq->xb_filename();
|
string xb_filename = vq->xb_filename();
|
||||||
|
|||||||
+5
-6
@@ -508,8 +508,8 @@ ItemData ServerState::parse_item_description(Version version, const string& desc
|
|||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const CommonItemSet> ServerState::common_item_set(Version logic_version, shared_ptr<const Quest> q) const {
|
shared_ptr<const CommonItemSet> ServerState::common_item_set(Version logic_version, shared_ptr<const Quest> q) const {
|
||||||
if (q && q->meta.common_item_set) {
|
if (q && !q->meta.common_item_set_name.empty()) {
|
||||||
return q->meta.common_item_set;
|
return this->common_item_sets.at(q->meta.common_item_set_name);
|
||||||
} else if (is_v1_or_v2(logic_version) && (logic_version != Version::GC_NTE)) {
|
} else if (is_v1_or_v2(logic_version) && (logic_version != Version::GC_NTE)) {
|
||||||
// TODO: We should probably have a v1 common item set at some point too
|
// TODO: We should probably have a v1 common item set at some point too
|
||||||
return this->common_item_sets.at("common-table-v1-v2");
|
return this->common_item_sets.at("common-table-v1-v2");
|
||||||
@@ -521,8 +521,8 @@ shared_ptr<const CommonItemSet> ServerState::common_item_set(Version logic_versi
|
|||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const RareItemSet> ServerState::rare_item_set(Version logic_version, shared_ptr<const Quest> q) const {
|
shared_ptr<const RareItemSet> ServerState::rare_item_set(Version logic_version, shared_ptr<const Quest> q) const {
|
||||||
if (q && q->meta.rare_item_set) {
|
if (q && !q->meta.rare_item_set_name.empty()) {
|
||||||
return q->meta.rare_item_set;
|
return this->rare_item_sets.at(q->meta.rare_item_set_name);
|
||||||
} else if (is_v1(logic_version)) {
|
} else if (is_v1(logic_version)) {
|
||||||
return this->rare_item_sets.at("rare-table-v1");
|
return this->rare_item_sets.at("rare-table-v1");
|
||||||
} else if (is_v2(logic_version) && (logic_version != Version::GC_NTE)) {
|
} else if (is_v2(logic_version) && (logic_version != Version::GC_NTE)) {
|
||||||
@@ -2150,8 +2150,7 @@ void ServerState::load_ep3_tournament_state() {
|
|||||||
|
|
||||||
void ServerState::load_quest_index(bool raise_on_any_failure) {
|
void ServerState::load_quest_index(bool raise_on_any_failure) {
|
||||||
config_log.info_f("Collecting quests");
|
config_log.info_f("Collecting quests");
|
||||||
this->quest_index = make_shared<QuestIndex>(
|
this->quest_index = make_shared<QuestIndex>("system/quests", this->quest_category_index, raise_on_any_failure);
|
||||||
"system/quests", this->quest_category_index, this->common_item_sets, this->rare_item_sets, raise_on_any_failure);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerState::compile_functions(bool raise_on_any_failure) {
|
void ServerState::compile_functions(bool raise_on_any_failure) {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user