diff --git a/src/FunctionCompiler.cc b/src/FunctionCompiler.cc index 068bfcc7..a44774f0 100644 --- a/src/FunctionCompiler.cc +++ b/src/FunctionCompiler.cc @@ -218,7 +218,8 @@ static vector> compile_function_code( const string& function_directory, const string& system_directory, const string& name, - const string& text) { + const string& text, + bool raise_on_any_failure) { unordered_set get_include_stack; function get_include = [&](const string& name) -> string { const char* arch_name_token; @@ -335,6 +336,9 @@ static vector> compile_function_code( } catch (const exception& e) { string version_str = specific_version ? (" (" + str_for_specific_version(specific_version) + ")") : ""; + if (raise_on_any_failure) { + throw; + } function_compiler_log.warning_f("Failed to compile function {}{}: {}", name, version_str, e.what()); } } @@ -342,7 +346,7 @@ static vector> compile_function_code( return ret; } -FunctionCodeIndex::FunctionCodeIndex(const string& directory) { +FunctionCodeIndex::FunctionCodeIndex(const string& directory, bool raise_on_any_failure) { string system_dir_path = directory.ends_with("/") ? (directory + "System") : (directory + "/System"); uint32_t next_menu_item_id = 1; @@ -404,7 +408,7 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) { string path = subdir_path + "/" + filename; string text = phosg::load_file(path); - for (auto code : compile_function_code(arch, subdir_path, system_dir_path, name, text)) { + for (auto code : compile_function_code(arch, subdir_path, system_dir_path, name, text, raise_on_any_failure)) { if (code->specific_version == 0) { code->specific_version = specific_version; } @@ -425,6 +429,9 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) { } } catch (const exception& e) { + if (raise_on_any_failure) { + throw runtime_error(format("({}) {}", filename, e.what())); + } function_compiler_log.warning_f("Failed to compile function {}: {}", filename, e.what()); } }; diff --git a/src/FunctionCompiler.hh b/src/FunctionCompiler.hh index 72242b0c..349dcbd0 100644 --- a/src/FunctionCompiler.hh +++ b/src/FunctionCompiler.hh @@ -54,7 +54,7 @@ const char* name_for_architecture(CompiledFunctionCode::Architecture arch); struct FunctionCodeIndex { FunctionCodeIndex() = default; - explicit FunctionCodeIndex(const std::string& directory); + FunctionCodeIndex(const std::string& directory, bool raise_on_any_failure); std::unordered_map> name_to_function; std::unordered_map> index_to_function; diff --git a/src/Main.cc b/src/Main.cc index d50503b0..623e28ad 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -1720,7 +1720,7 @@ Action a_assemble_all_patches( versions, and one encrypted, for PSO GC JP v1.4, JP Ep3, and Ep3 Trial\n\ Edition). The output files are saved in system/client-functions.\n", +[](phosg::Arguments& args) { - auto fci = make_shared("system/client-functions"); + auto fci = make_shared("system/client-functions", false); bool skip_encrypted = args.get("skip-encrypted"); auto process_code = [&](shared_ptr code, @@ -3038,7 +3038,16 @@ Action a_check_quests( s->load_patch_indexes(); s->load_set_data_tables(); s->load_maps(); - s->load_quest_index(); + s->load_quest_index(true); + phosg::fwrite_fmt(stdout, "All quests indexed\n"); + }); + +Action a_check_client_functions( + "check-client-functions", nullptr, + +[](phosg::Arguments&) { + set_all_log_levels(phosg::LogLevel::L_DEBUG); + FunctionCodeIndex fci("system/client-functions", true); + phosg::fwrite_fmt(stdout, "All client functions compiled\n"); }); Action a_parse_object_graph( diff --git a/src/Quest.cc b/src/Quest.cc index 7acce9cc..b72d5e80 100644 --- a/src/Quest.cc +++ b/src/Quest.cc @@ -436,7 +436,8 @@ QuestIndex::QuestIndex( const string& directory, shared_ptr category_index, const unordered_map>& common_item_sets, - const unordered_map>& rare_item_sets) + const unordered_map>& rare_item_sets, + bool raise_on_any_failure) : directory(directory), category_index(category_index) { @@ -585,6 +586,9 @@ QuestIndex::QuestIndex( } } catch (const exception& e) { + if (raise_on_any_failure) { + throw runtime_error(format("({}) {}", filename, e.what())); + } static_game_data_log.warning_f("({}) Failed to load quest file: ({})", filename, e.what()); } } @@ -803,6 +807,9 @@ QuestIndex::QuestIndex( vq->meta.joinable ? "joinable" : "not joinable"); } } catch (const exception& e) { + if (raise_on_any_failure) { + throw runtime_error(format("({}) {}", basename, e.what())); + } static_game_data_log.warning_f("({}) Failed to index quest file: {}", basename, e.what()); } } diff --git a/src/Quest.hh b/src/Quest.hh index 9e796ddc..2c5634cd 100644 --- a/src/Quest.hh +++ b/src/Quest.hh @@ -136,7 +136,8 @@ struct QuestIndex { const std::string& directory, std::shared_ptr category_index, const std::unordered_map>& common_item_sets, - const std::unordered_map>& rare_item_sets); + const std::unordered_map>& rare_item_sets, + bool raise_on_any_failure); phosg::JSON json() const; std::shared_ptr get(uint32_t quest_number) const; diff --git a/src/QuestScript.cc b/src/QuestScript.cc index e09dd394..c922edc7 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -4019,7 +4019,7 @@ AssembledQuestScript assemble_quest_script( wrap_exceptions_with_line_ref(line, [&]() -> void { if (line.text[0] == '.') { if (line.text.starts_with(".include ")) { - // Nothing to do + // Nothing to do (see above) } else if (line.text.starts_with(".version ")) { string name = line.text.substr(9); phosg::strip_leading_whitespace(name); @@ -4268,7 +4268,12 @@ AssembledQuestScript assemble_quest_script( } auto line_tokens = phosg::split(line.text, ' ', 1); - const auto& opcode_def = opcodes.at(phosg::tolower(line_tokens.at(0))); + const QuestScriptOpcodeDefinition* opcode_def; + try { + opcode_def = opcodes.at(phosg::tolower(line_tokens.at(0))); + } catch (const out_of_range&) { + throw std::runtime_error(std::format("invalid opcode name: {}", line_tokens.at(0))); + } bool use_args = version_has_args && (opcode_def->flags & F_ARGS); if (!use_args) { diff --git a/src/ServerState.cc b/src/ServerState.cc index afcac4b0..a0d68720 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -2161,15 +2161,15 @@ void ServerState::load_ep3_tournament_state() { this->ep3_tournament_index->link_all_clients(this->shared_from_this()); } -void ServerState::load_quest_index() { +void ServerState::load_quest_index(bool raise_on_any_failure) { config_log.info_f("Collecting quests"); this->quest_index = make_shared( - "system/quests", this->quest_category_index, this->common_item_sets, this->rare_item_sets); + "system/quests", this->quest_category_index, this->common_item_sets, this->rare_item_sets, raise_on_any_failure); } -void ServerState::compile_functions() { +void ServerState::compile_functions(bool raise_on_any_failure) { config_log.info_f("Compiling client functions"); - this->function_code_index = make_shared("system/client-functions"); + this->function_code_index = make_shared("system/client-functions", raise_on_any_failure); } void ServerState::load_dol_files() { diff --git a/src/ServerState.hh b/src/ServerState.hh index 4106af63..003bbb5d 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -440,8 +440,8 @@ struct ServerState : public std::enable_shared_from_this { void load_ep3_cards(); void load_ep3_maps(); void load_ep3_tournament_state(); - void load_quest_index(); - void compile_functions(); + void load_quest_index(bool raise_on_any_failure = false); + void compile_functions(bool raise_on_any_failure = false); void load_dol_files(); void load_all(bool enable_thread_pool); diff --git a/system/client-functions/ReticleColors/XBReticleColors.3___.patch.s b/system/client-functions/ReticleColors/XBReticleColors.3___.patch.s index f72ff092..b15f65f3 100644 --- a/system/client-functions/ReticleColors/XBReticleColors.3___.patch.s +++ b/system/client-functions/ReticleColors/XBReticleColors.3___.patch.s @@ -14,7 +14,7 @@ start: .data .data 0x00000004 - li r4, 0xFF00 + li r4, -0x100 .data .data 0x0000000C diff --git a/tests/check-client-functions.test.sh b/tests/check-client-functions.test.sh new file mode 100755 index 00000000..6b1e4a51 --- /dev/null +++ b/tests/check-client-functions.test.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +set -e + +EXECUTABLE="$1" +if [ -z "$EXECUTABLE" ]; then + EXECUTABLE="./newserv" +fi + +$EXECUTABLE --config=tests/config.json check-client-functions diff --git a/tests/check-quests.test.sh b/tests/check-quests.test.sh new file mode 100755 index 00000000..5637b417 --- /dev/null +++ b/tests/check-quests.test.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +set -e + +EXECUTABLE="$1" +if [ -z "$EXECUTABLE" ]; then + EXECUTABLE="./newserv" +fi + +$EXECUTABLE --config=tests/config.json check-quests