use QuestScriptVersion to index quests

This commit is contained in:
Martin Michelsen
2023-07-15 22:29:18 -07:00
parent 64e637dbfb
commit 4858ccd812
8 changed files with 156 additions and 91 deletions
+1 -1
View File
@@ -134,7 +134,7 @@ newserv automatically finds quests in the system/quests/ directory. To install y
Standard quest files should be named like `q###-CATEGORY-VERSION.EXT`, battle quests should be named like `b###-VERSION.EXT`, challenge quests should be named like `c###-VERSION.EXT` for Episode 1 or `d###-VERSION.EXT` for Episode 2, and Episode 3 download quests should be named like `e###-gc3.EXT`. The fields in each filename are: Standard quest files should be named like `q###-CATEGORY-VERSION.EXT`, battle quests should be named like `b###-VERSION.EXT`, challenge quests should be named like `c###-VERSION.EXT` for Episode 1 or `d###-VERSION.EXT` for Episode 2, and Episode 3 download quests should be named like `e###-gc3.EXT`. The fields in each filename are:
- `###`: quest number (this doesn't really matter; it should just be unique across the PSO version) - `###`: quest number (this doesn't really matter; it should just be unique across the PSO version)
- `CATEGORY`: ret = Retrieval, ext = Extermination, evt = Events, shp = Shops, vr = VR, twr = Tower, gv1/gv2/gv4 = Government (BB only), dl = Download (these don't appear during online play), 1p = Solo (BB only) - `CATEGORY`: ret = Retrieval, ext = Extermination, evt = Events, shp = Shops, vr = VR, twr = Tower, gv1/gv2/gv4 = Government (BB only), dl = Download (these don't appear during online play), 1p = Solo (BB only)
- `VERSION`: d1 = Dreamcast v1, dc = Dreamcast v2, pc = PC, gc = GameCube Episodes 1 & 2, gc3 = Episode 3, bb = Blue Burst - `VERSION`: dn = Dreamcast NTE, d1 = Dreamcast v1, dc = Dreamcast v2, pc = PC, gcn = GameCube Trial Edition, gc = GameCube Episodes 1 & 2, gc3 = Episode 3, xb = Xbox, bb = Blue Burst
- `EXT`: file extension (see table below) - `EXT`: file extension (see table below)
For example, the GameCube version of Lost HEAT SWORD is in two files named `q058-ret-gc.bin` and `q058-ret-gc.dat`. newserv knows these files are quests because they're in the system/quests/ directory, it knows they're for PSO GC because the filenames contain `-gc`, and it puts them in the Retrieval category because the filenames contain `-ret`. For example, the GameCube version of Lost HEAT SWORD is in two files named `q058-ret-gc.bin` and `q058-ret-gc.dat`. newserv knows these files are quests because they're in the system/quests/ directory, it knows they're for PSO GC because the filenames contain `-gc`, and it puts them in the Retrieval category because the filenames contain `-ret`.
+29
View File
@@ -98,6 +98,35 @@ Client::~Client() {
this->log.info("Deleted"); this->log.info("Deleted");
} }
QuestScriptVersion Client::quest_version() const {
switch (this->version()) {
case GameVersion::DC:
if (this->flags & Flag::IS_TRIAL_EDITION) {
return QuestScriptVersion::DC_NTE;
} else if (this->flags & Flag::IS_DC_V1) {
return QuestScriptVersion::DC_V1;
} else {
return QuestScriptVersion::DC_V2;
}
case GameVersion::PC:
return QuestScriptVersion::PC_V2;
case GameVersion::GC:
if (this->flags & Flag::IS_TRIAL_EDITION) {
return QuestScriptVersion::GC_NTE;
} else if (this->flags & Flag::IS_EPISODE_3) {
return QuestScriptVersion::GC_EP3;
} else {
return QuestScriptVersion::GC_V3;
}
case GameVersion::XB:
return QuestScriptVersion::XB_V3;
case GameVersion::BB:
return QuestScriptVersion::BB_V4;
default:
throw logic_error("client\'s game version does not have a quest version");
}
}
void Client::set_license(shared_ptr<const License> l) { void Client::set_license(shared_ptr<const License> l) {
this->license = l; this->license = l;
this->game_data.guild_card_number = this->license->serial_number; this->game_data.guild_card_number = this->license->serial_number;
+2
View File
@@ -15,6 +15,7 @@
#include "PSOProtocol.hh" #include "PSOProtocol.hh"
#include "PatchFileIndex.hh" #include "PatchFileIndex.hh"
#include "Player.hh" #include "Player.hh"
#include "QuestScript.hh"
#include "Text.hh" #include "Text.hh"
extern const uint64_t CLIENT_CONFIG_MAGIC; extern const uint64_t CLIENT_CONFIG_MAGIC;
@@ -171,6 +172,7 @@ struct Client {
inline GameVersion version() const { inline GameVersion version() const {
return this->channel.version; return this->channel.version;
} }
QuestScriptVersion quest_version() const;
void set_license(std::shared_ptr<const License> l); void set_license(std::shared_ptr<const License> l);
+83 -76
View File
@@ -236,7 +236,6 @@ Quest::Quest(const string& bin_filename, shared_ptr<const QuestCategoryIndex> ca
menu_item_id(0), menu_item_id(0),
category_id(0), category_id(0),
episode(Episode::NONE), episode(Episode::NONE),
is_dcv1(false),
joinable(false), joinable(false),
file_format(FileFormat::BIN_DAT), file_format(FileFormat::BIN_DAT),
has_mnm_extension(false), has_mnm_extension(false),
@@ -278,12 +277,6 @@ Quest::Quest(const string& bin_filename, shared_ptr<const QuestCategoryIndex> ca
} }
} }
// Quest filenames are like:
// b###-VV.bin for battle mode
// c###-VV.bin for challenge mode
// e###-gc3.mnm (or .bin) for episode 3
// q###-CAT-VV.bin for normal quests
if (basename.empty()) { if (basename.empty()) {
throw invalid_argument("empty filename"); throw invalid_argument("empty filename");
} }
@@ -305,14 +298,16 @@ Quest::Quest(const string& bin_filename, shared_ptr<const QuestCategoryIndex> ca
this->internal_id = strtoull(tokens[0].c_str() + 1, nullptr, 10); this->internal_id = strtoull(tokens[0].c_str() + 1, nullptr, 10);
// Get the version from the second (or previously third) token // Get the version from the second (or previously third) token
static const unordered_map<string, GameVersion> name_to_version({ static const unordered_map<string, QuestScriptVersion> name_to_version({
{"d1", GameVersion::DC}, {"dn", QuestScriptVersion::DC_NTE},
{"dc", GameVersion::DC}, {"d1", QuestScriptVersion::DC_V1},
{"pc", GameVersion::PC}, {"dc", QuestScriptVersion::DC_V2},
{"gc", GameVersion::GC}, {"pc", QuestScriptVersion::PC_V2},
{"gc3", GameVersion::GC}, {"gcn", QuestScriptVersion::GC_NTE},
{"xb", GameVersion::XB}, {"gc", QuestScriptVersion::GC_V3},
{"bb", GameVersion::BB}, {"gc3", QuestScriptVersion::GC_EP3},
{"xb", QuestScriptVersion::XB_V3},
{"bb", QuestScriptVersion::BB_V4},
}); });
this->version = name_to_version.at(tokens[1]); this->version = name_to_version.at(tokens[1]);
@@ -323,11 +318,9 @@ Quest::Quest(const string& bin_filename, shared_ptr<const QuestCategoryIndex> ca
auto bin_decompressed = prs_decompress(*bin_compressed); auto bin_decompressed = prs_decompress(*bin_compressed);
switch (this->version) { switch (this->version) {
case GameVersion::PATCH: case QuestScriptVersion::DC_NTE:
throw invalid_argument("patch server quests are not valid"); case QuestScriptVersion::DC_V1:
break; case QuestScriptVersion::DC_V2: {
case GameVersion::DC: {
if (bin_decompressed.size() < sizeof(PSOQuestHeaderDC)) { if (bin_decompressed.size() < sizeof(PSOQuestHeaderDC)) {
throw invalid_argument("file is too small for header"); throw invalid_argument("file is too small for header");
} }
@@ -337,11 +330,10 @@ Quest::Quest(const string& bin_filename, shared_ptr<const QuestCategoryIndex> ca
this->name = decode_sjis(header->name); this->name = decode_sjis(header->name);
this->short_description = decode_sjis(header->short_description); this->short_description = decode_sjis(header->short_description);
this->long_description = decode_sjis(header->long_description); this->long_description = decode_sjis(header->long_description);
this->is_dcv1 = (tokens[1] == "d1");
break; break;
} }
case GameVersion::PC: { case QuestScriptVersion::PC_V2: {
if (bin_decompressed.size() < sizeof(PSOQuestHeaderPC)) { if (bin_decompressed.size() < sizeof(PSOQuestHeaderPC)) {
throw invalid_argument("file is too small for header"); throw invalid_argument("file is too small for header");
} }
@@ -354,33 +346,42 @@ Quest::Quest(const string& bin_filename, shared_ptr<const QuestCategoryIndex> ca
break; break;
} }
case GameVersion::XB: case QuestScriptVersion::GC_EP3: {
case GameVersion::GC: { // Note: This codepath handles Episode 3 download quests, which are not
if (category.flags & QuestCategoryIndex::Category::Flag::EP3_DOWNLOAD) { // the same as Episode 3 quest scripts. The latter are only used offline
if (bin_decompressed.size() != sizeof(Episode3::MapDefinition)) { // in story mode, but can be disassembled with disassemble_quest_script.
throw invalid_argument("file is incorrect size"); // It's unfortunate that the QuestScriptVersion::GC_EP3 value is used
} // here for Episode 3 download quests (maps) and there for offline story
auto* header = reinterpret_cast<const Episode3::MapDefinition*>(bin_decompressed.data()); // mode scripts, but it's probably not worth refactoring this logic, at
this->joinable = false; // least right now.
this->episode = Episode::EP3; if (bin_decompressed.size() != sizeof(Episode3::MapDefinition)) {
this->name = decode_sjis(header->name); throw invalid_argument("file is incorrect size");
this->short_description = decode_sjis(header->quest_name);
this->long_description = decode_sjis(header->description);
} else {
if (bin_decompressed.size() < sizeof(PSOQuestHeaderGC)) {
throw invalid_argument("file is too small for header");
}
auto* header = reinterpret_cast<const PSOQuestHeaderGC*>(bin_decompressed.data());
this->joinable = false;
this->episode = (header->episode == 1) ? Episode::EP2 : Episode::EP1;
this->name = decode_sjis(header->name);
this->short_description = decode_sjis(header->short_description);
this->long_description = decode_sjis(header->long_description);
} }
auto* header = reinterpret_cast<const Episode3::MapDefinition*>(bin_decompressed.data());
this->joinable = false;
this->episode = Episode::EP3;
this->name = decode_sjis(header->name);
this->short_description = decode_sjis(header->quest_name);
this->long_description = decode_sjis(header->description);
break; break;
} }
case GameVersion::BB: { case QuestScriptVersion::XB_V3:
case QuestScriptVersion::GC_NTE:
case QuestScriptVersion::GC_V3: {
if (bin_decompressed.size() < sizeof(PSOQuestHeaderGC)) {
throw invalid_argument("file is too small for header");
}
auto* header = reinterpret_cast<const PSOQuestHeaderGC*>(bin_decompressed.data());
this->joinable = false;
this->episode = (header->episode == 1) ? Episode::EP2 : Episode::EP1;
this->name = decode_sjis(header->name);
this->short_description = decode_sjis(header->short_description);
this->long_description = decode_sjis(header->long_description);
break;
}
case QuestScriptVersion::BB_V4: {
if (bin_decompressed.size() < sizeof(PSOQuestHeaderBB)) { if (bin_decompressed.size() < sizeof(PSOQuestHeaderBB)) {
throw invalid_argument("file is too small for header"); throw invalid_argument("file is too small for header");
} }
@@ -827,7 +828,7 @@ void add_write_file_commands(
} }
} }
string Quest::export_qst(GameVersion version) const { string Quest::export_qst() const {
bool is_ep3 = this->episode == Episode::EP3; bool is_ep3 = this->episode == Episode::EP3;
if (is_ep3 && !this->is_dlq_encoded) { if (is_ep3 && !this->is_dlq_encoded) {
throw runtime_error("Episode 3 quests can only be encoded in download QST format"); throw runtime_error("Episode 3 quests can only be encoded in download QST format");
@@ -837,8 +838,10 @@ string Quest::export_qst(GameVersion version) const {
// Some tools expect both open file commands at the beginning, hence this // Some tools expect both open file commands at the beginning, hence this
// unfortunate abstraction-breaking. // unfortunate abstraction-breaking.
switch (version) { switch (this->version) {
case GameVersion::DC: case QuestScriptVersion::DC_NTE:
case QuestScriptVersion::DC_V1:
case QuestScriptVersion::DC_V2:
add_open_file_command<PSOCommandHeaderDCV3, S_OpenFile_DC_44_A6>(w, *this, true); add_open_file_command<PSOCommandHeaderDCV3, S_OpenFile_DC_44_A6>(w, *this, true);
add_open_file_command<PSOCommandHeaderDCV3, S_OpenFile_DC_44_A6>(w, *this, false); add_open_file_command<PSOCommandHeaderDCV3, S_OpenFile_DC_44_A6>(w, *this, false);
add_write_file_commands<PSOCommandHeaderDCV3>( add_write_file_commands<PSOCommandHeaderDCV3>(
@@ -846,7 +849,7 @@ string Quest::export_qst(GameVersion version) const {
add_write_file_commands<PSOCommandHeaderDCV3>( add_write_file_commands<PSOCommandHeaderDCV3>(
w, this->file_basename + ".dat", *this->dat_contents(), this->is_dlq_encoded); w, this->file_basename + ".dat", *this->dat_contents(), this->is_dlq_encoded);
break; break;
case GameVersion::PC: case QuestScriptVersion::PC_V2:
add_open_file_command<PSOCommandHeaderPC, S_OpenFile_PC_V3_44_A6>(w, *this, true); add_open_file_command<PSOCommandHeaderPC, S_OpenFile_PC_V3_44_A6>(w, *this, true);
add_open_file_command<PSOCommandHeaderPC, S_OpenFile_PC_V3_44_A6>(w, *this, false); add_open_file_command<PSOCommandHeaderPC, S_OpenFile_PC_V3_44_A6>(w, *this, false);
add_write_file_commands<PSOCommandHeaderPC>( add_write_file_commands<PSOCommandHeaderPC>(
@@ -854,20 +857,22 @@ string Quest::export_qst(GameVersion version) const {
add_write_file_commands<PSOCommandHeaderPC>( add_write_file_commands<PSOCommandHeaderPC>(
w, this->file_basename + ".dat", *this->dat_contents(), this->is_dlq_encoded); w, this->file_basename + ".dat", *this->dat_contents(), this->is_dlq_encoded);
break; break;
case GameVersion::GC: case QuestScriptVersion::GC_NTE:
case GameVersion::XB: case QuestScriptVersion::GC_V3:
case QuestScriptVersion::XB_V3:
add_open_file_command<PSOCommandHeaderDCV3, S_OpenFile_PC_V3_44_A6>(w, *this, true); add_open_file_command<PSOCommandHeaderDCV3, S_OpenFile_PC_V3_44_A6>(w, *this, true);
if (!is_ep3) { add_open_file_command<PSOCommandHeaderDCV3, S_OpenFile_PC_V3_44_A6>(w, *this, false);
add_open_file_command<PSOCommandHeaderDCV3, S_OpenFile_PC_V3_44_A6>(w, *this, false);
}
add_write_file_commands<PSOCommandHeaderDCV3>( add_write_file_commands<PSOCommandHeaderDCV3>(
w, this->file_basename + ".bin", *this->bin_contents(), this->is_dlq_encoded); w, this->file_basename + ".bin", *this->bin_contents(), this->is_dlq_encoded);
if (!is_ep3) { add_write_file_commands<PSOCommandHeaderDCV3>(
add_write_file_commands<PSOCommandHeaderDCV3>( w, this->file_basename + ".dat", *this->dat_contents(), this->is_dlq_encoded);
w, this->file_basename + ".dat", *this->dat_contents(), this->is_dlq_encoded);
}
break; break;
case GameVersion::BB: case QuestScriptVersion::GC_EP3:
add_open_file_command<PSOCommandHeaderDCV3, S_OpenFile_PC_V3_44_A6>(w, *this, true);
add_write_file_commands<PSOCommandHeaderDCV3>(
w, this->file_basename + ".bin", *this->bin_contents(), this->is_dlq_encoded);
break;
case QuestScriptVersion::BB_V4:
add_open_file_command<PSOCommandHeaderBB, S_OpenFile_BB_44_A6>(w, *this, true); add_open_file_command<PSOCommandHeaderBB, S_OpenFile_BB_44_A6>(w, *this, true);
add_open_file_command<PSOCommandHeaderBB, S_OpenFile_BB_44_A6>(w, *this, false); add_open_file_command<PSOCommandHeaderBB, S_OpenFile_BB_44_A6>(w, *this, false);
add_write_file_commands<PSOCommandHeaderBB>( add_write_file_commands<PSOCommandHeaderBB>(
@@ -916,17 +921,16 @@ QuestIndex::QuestIndex(
throw logic_error("duplicate quest menu item id"); throw logic_error("duplicate quest menu item id");
} }
auto category_name = encode_sjis(this->category_index->at(q->category_id).name); auto category_name = encode_sjis(this->category_index->at(q->category_id).name);
static_game_data_log.info("Indexed quest %s (%s => %s-%" PRId64 " (%" PRIu32 "), %s, %s (%" PRIu32 "), joinable=%s, dcv1=%s)", static_game_data_log.info("Indexed quest %s (%s => %s-%" PRId64 " (%" PRIu32 "), %s, %s (%" PRIu32 "), joinable=%s)",
ascii_name.c_str(), ascii_name.c_str(),
filename.c_str(), filename.c_str(),
name_for_version(q->version), name_for_enum(q->version),
q->internal_id, q->internal_id,
q->menu_item_id, q->menu_item_id,
name_for_episode(q->episode), name_for_episode(q->episode),
category_name.c_str(), category_name.c_str(),
q->category_id, q->category_id,
q->joinable ? "true" : "false", q->joinable ? "true" : "false");
q->is_dcv1 ? "true" : "false");
} catch (const exception& e) { } catch (const exception& e) {
static_game_data_log.warning("Failed to index quest file %s (%s)", filename.c_str(), e.what()); static_game_data_log.warning("Failed to index quest file %s (%s)", filename.c_str(), e.what());
} }
@@ -934,8 +938,8 @@ QuestIndex::QuestIndex(
} }
} }
shared_ptr<const Quest> QuestIndex::get(GameVersion version, shared_ptr<const Quest> QuestIndex::get(
uint32_t menu_item_id) const { QuestScriptVersion version, uint32_t menu_item_id) const {
return this->version_menu_item_id_to_quest.at(make_pair(version, menu_item_id)); return this->version_menu_item_id_to_quest.at(make_pair(version, menu_item_id));
} }
@@ -944,17 +948,15 @@ shared_ptr<const string> QuestIndex::get_gba(const string& name) const {
} }
vector<shared_ptr<const Quest>> QuestIndex::filter( vector<shared_ptr<const Quest>> QuestIndex::filter(
GameVersion version, bool is_dcv1, uint32_t category_id) const { QuestScriptVersion version, uint32_t category_id) const {
auto it = this->version_menu_item_id_to_quest.lower_bound(make_pair(version, 0)); auto it = this->version_menu_item_id_to_quest.lower_bound(make_pair(version, 0));
auto end_it = this->version_menu_item_id_to_quest.upper_bound(make_pair(version, 0xFFFFFFFF)); auto end_it = this->version_menu_item_id_to_quest.upper_bound(make_pair(version, 0xFFFFFFFF));
vector<shared_ptr<const Quest>> ret; vector<shared_ptr<const Quest>> ret;
for (; it != end_it; it++) { for (; it != end_it; it++) {
shared_ptr<const Quest> q = it->second; if (it->second->category_id == category_id) {
if ((q->is_dcv1 != is_dcv1) || (q->category_id != category_id)) { ret.emplace_back(it->second);
continue;
} }
ret.emplace_back(q);
} }
return ret; return ret;
@@ -997,7 +999,7 @@ shared_ptr<Quest> Quest::create_download_quest() const {
// This function should not be used for Episode 3 quests (they should be sent // This function should not be used for Episode 3 quests (they should be sent
// to the client as-is, without any encryption or other preprocessing) // to the client as-is, without any encryption or other preprocessing)
if (this->episode == Episode::EP3) { if (this->episode == Episode::EP3 || this->version == QuestScriptVersion::GC_EP3) {
throw logic_error("Episode 3 quests cannot be converted to download quests"); throw logic_error("Episode 3 quests cannot be converted to download quests");
} }
@@ -1005,27 +1007,32 @@ shared_ptr<Quest> Quest::create_download_quest() const {
void* data_ptr = decompressed_bin.data(); void* data_ptr = decompressed_bin.data();
switch (this->version) { switch (this->version) {
case GameVersion::DC: case QuestScriptVersion::DC_NTE:
case QuestScriptVersion::DC_V1:
case QuestScriptVersion::DC_V2:
if (decompressed_bin.size() < sizeof(PSOQuestHeaderDC)) { if (decompressed_bin.size() < sizeof(PSOQuestHeaderDC)) {
throw runtime_error("bin file is too small for header"); throw runtime_error("bin file is too small for header");
} }
reinterpret_cast<PSOQuestHeaderDC*>(data_ptr)->is_download = 0x01; reinterpret_cast<PSOQuestHeaderDC*>(data_ptr)->is_download = 0x01;
break; break;
case GameVersion::PC: case QuestScriptVersion::PC_V2:
if (decompressed_bin.size() < sizeof(PSOQuestHeaderPC)) { if (decompressed_bin.size() < sizeof(PSOQuestHeaderPC)) {
throw runtime_error("bin file is too small for header"); throw runtime_error("bin file is too small for header");
} }
reinterpret_cast<PSOQuestHeaderPC*>(data_ptr)->is_download = 0x01; reinterpret_cast<PSOQuestHeaderPC*>(data_ptr)->is_download = 0x01;
break; break;
case GameVersion::XB: case QuestScriptVersion::GC_NTE:
case GameVersion::GC: case QuestScriptVersion::GC_V3:
case QuestScriptVersion::XB_V3:
if (decompressed_bin.size() < sizeof(PSOQuestHeaderGC)) { if (decompressed_bin.size() < sizeof(PSOQuestHeaderGC)) {
throw runtime_error("bin file is too small for header"); throw runtime_error("bin file is too small for header");
} }
reinterpret_cast<PSOQuestHeaderGC*>(data_ptr)->is_download = 0x01; reinterpret_cast<PSOQuestHeaderGC*>(data_ptr)->is_download = 0x01;
break; break;
case GameVersion::BB: case QuestScriptVersion::BB_V4:
throw invalid_argument("PSOBB does not support download quests"); throw invalid_argument("PSOBB does not support download quests");
case QuestScriptVersion::GC_EP3:
throw logic_error("Episode 3 quests cannot be converted to download quests");
default: default:
throw invalid_argument("unknown game version"); throw invalid_argument("unknown game version");
} }
+7 -8
View File
@@ -7,8 +7,8 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "QuestScript.hh"
#include "StaticGameData.hh" #include "StaticGameData.hh"
#include "Version.hh"
struct QuestCategoryIndex { struct QuestCategoryIndex {
struct Category { struct Category {
@@ -64,9 +64,8 @@ public:
uint32_t menu_item_id; uint32_t menu_item_id;
uint32_t category_id; uint32_t category_id;
Episode episode; Episode episode;
bool is_dcv1;
bool joinable; bool joinable;
GameVersion version; QuestScriptVersion version;
std::string file_basename; // we append -<version>.<bin/dat> when reading std::string file_basename; // we append -<version>.<bin/dat> when reading
FileFormat file_format; FileFormat file_format;
bool has_mnm_extension; bool has_mnm_extension;
@@ -101,7 +100,7 @@ public:
static std::string decode_dlq_data(const std::string& filename); static std::string decode_dlq_data(const std::string& filename);
static std::pair<std::string, std::string> decode_qst_file(const std::string& filename); static std::pair<std::string, std::string> decode_qst_file(const std::string& filename);
std::string export_qst(GameVersion version) const; std::string export_qst() const;
private: private:
// these are populated when requested // these are populated when requested
@@ -113,7 +112,7 @@ struct QuestIndex {
std::string directory; std::string directory;
std::shared_ptr<const QuestCategoryIndex> category_index; std::shared_ptr<const QuestCategoryIndex> category_index;
std::map<std::pair<GameVersion, uint64_t>, std::shared_ptr<Quest>> version_menu_item_id_to_quest; std::map<std::pair<QuestScriptVersion, uint64_t>, std::shared_ptr<Quest>> version_menu_item_id_to_quest;
std::map<std::string, std::vector<std::shared_ptr<Quest>>> category_to_quests; std::map<std::string, std::vector<std::shared_ptr<Quest>>> category_to_quests;
@@ -121,8 +120,8 @@ struct QuestIndex {
QuestIndex(const std::string& directory, std::shared_ptr<const QuestCategoryIndex> category_index); QuestIndex(const std::string& directory, std::shared_ptr<const QuestCategoryIndex> category_index);
std::shared_ptr<const Quest> get(GameVersion version, uint32_t id) const; std::shared_ptr<const Quest> get(QuestScriptVersion version, uint32_t id) const;
std::shared_ptr<const std::string> get_gba(const std::string& name) const; std::shared_ptr<const std::string> get_gba(const std::string& name) const;
std::vector<std::shared_ptr<const Quest>> filter(GameVersion version, std::vector<std::shared_ptr<const Quest>> filter(
bool is_dcv1, uint32_t category_id) const; QuestScriptVersion version, uint32_t category_id) const;
}; };
+26
View File
@@ -18,6 +18,32 @@
using namespace std; using namespace std;
template <>
const char* name_for_enum<QuestScriptVersion>(QuestScriptVersion v) {
switch (v) {
case QuestScriptVersion::DC_NTE:
return "DC_NTE";
case QuestScriptVersion::DC_V1:
return "DC_V1";
case QuestScriptVersion::DC_V2:
return "DC_V2";
case QuestScriptVersion::PC_V2:
return "PC_V2";
case QuestScriptVersion::GC_NTE:
return "GC_NTE";
case QuestScriptVersion::GC_V3:
return "GC_V3";
case QuestScriptVersion::XB_V3:
return "XB_V3";
case QuestScriptVersion::GC_EP3:
return "GC_EP3";
case QuestScriptVersion::BB_V4:
return "BB_V4";
default:
return "__UNKNOWN__";
}
}
// bit_cast isn't in the standard place on macOS (it is apparently implicitly // bit_cast isn't in the standard place on macOS (it is apparently implicitly
// included by resource_dasm, but newserv can be built without resource_dasm) // included by resource_dasm, but newserv can be built without resource_dasm)
// and I'm too lazy to go find the right header to include // and I'm too lazy to go find the right header to include
+4
View File
@@ -3,6 +3,7 @@
#include <stdint.h> #include <stdint.h>
#include <phosg/Encoding.hh> #include <phosg/Encoding.hh>
#include <phosg/Tools.hh>
#include "Text.hh" #include "Text.hh"
#include "Version.hh" #include "Version.hh"
@@ -19,6 +20,9 @@ enum class QuestScriptVersion {
BB_V4 = 8, BB_V4 = 8,
}; };
template <>
const char* name_for_enum<QuestScriptVersion>(QuestScriptVersion v);
struct PSOQuestHeaderDC { // Same format for DC v1 and v2 struct PSOQuestHeaderDC { // Same format for DC v1 and v2
le_uint32_t code_offset; le_uint32_t code_offset;
le_uint32_t function_table_offset; le_uint32_t function_table_offset;
+4 -6
View File
@@ -1444,7 +1444,7 @@ static void on_09(shared_ptr<ServerState> s, shared_ptr<Client> c,
if (!s->quest_index) { if (!s->quest_index) {
send_quest_info(c, u"$C6Quests are not available.", !c->lobby_id); send_quest_info(c, u"$C6Quests are not available.", !c->lobby_id);
} else { } else {
auto q = s->quest_index->get(c->version(), cmd.item_id); auto q = s->quest_index->get(c->quest_version(), cmd.item_id);
if (!q) { if (!q) {
send_quest_info(c, u"$C4Quest does not\nexist.", !c->lobby_id); send_quest_info(c, u"$C4Quest does not\nexist.", !c->lobby_id);
} else { } else {
@@ -1674,8 +1674,7 @@ static void on_10(shared_ptr<ServerState> s, shared_ptr<Client> c,
vector<shared_ptr<const Quest>> quests; vector<shared_ptr<const Quest>> quests;
for (const auto& category : s->quest_category_index->categories) { for (const auto& category : s->quest_category_index->categories) {
if (category.flags & QuestCategoryIndex::Category::Flag::EP3_DOWNLOAD) { if (category.flags & QuestCategoryIndex::Category::Flag::EP3_DOWNLOAD) {
quests = s->quest_index->filter( quests = s->quest_index->filter(c->quest_version(), category.category_id);
c->version(), c->flags & Client::Flag::IS_DC_V1, category.category_id);
break; break;
} }
} }
@@ -1916,8 +1915,7 @@ static void on_10(shared_ptr<ServerState> s, shared_ptr<Client> c,
break; break;
} }
shared_ptr<Lobby> l = c->lobby_id ? s->find_lobby(c->lobby_id) : nullptr; shared_ptr<Lobby> l = c->lobby_id ? s->find_lobby(c->lobby_id) : nullptr;
auto quests = s->quest_index->filter( auto quests = s->quest_index->filter(c->quest_version(), item_id);
c->version(), c->flags & Client::Flag::IS_DC_V1, item_id);
// Hack: Assume the menu to be sent is the download quest menu if the // Hack: Assume the menu to be sent is the download quest menu if the
// client is not in any lobby // client is not in any lobby
@@ -1930,7 +1928,7 @@ static void on_10(shared_ptr<ServerState> s, shared_ptr<Client> c,
send_lobby_message_box(c, u"$C6Quests are not available."); send_lobby_message_box(c, u"$C6Quests are not available.");
break; break;
} }
auto q = s->quest_index->get(c->version(), item_id); auto q = s->quest_index->get(c->quest_version(), item_id);
if (!q) { if (!q) {
send_lobby_message_box(c, u"$C6Quest does not exist."); send_lobby_message_box(c, u"$C6Quest does not exist.");
break; break;