From 2649fd17ec5414697298a1f2e765bc973096722b Mon Sep 17 00:00:00 2001 From: ManDude <7569514+ManDude@users.noreply.github.com> Date: Thu, 30 Jun 2022 00:43:23 +0100 Subject: [PATCH] subtitle editor fixes + other smaller fixes (#1572) * [extractor] validate files when extracted as folder * jp text fixes * move game text version to the text file and fix subtitle editor escape chars * make bad subtitles not crash the game * fix texscroll in lag * fix mood, fix decomp of other versions, fix text decomp * clang * fix tests * oops dammit * new fixes * shut up codacy * fix nonexistant subtitles crashing the game * fix text hacks and extractor re-use on folders --- .../deserialization/subtitles/subtitles.cpp | 17 +- common/serialization/subtitles/subtitles.cpp | 174 +++++++++++++----- common/serialization/subtitles/subtitles.h | 42 ++--- common/util/FontUtils.cpp | 72 ++++++++ common/util/FontUtils.h | 8 + decompiler/config/jak1_jp.jsonc | 3 + decompiler/config/jak1_ntsc_black_label.jsonc | 4 +- decompiler/config/jak1_pal.jsonc | 3 + decompiler/config/jak1_us2.jsonc | 3 + decompiler/data/game_text.cpp | 2 + decompiler/extractor/main.cpp | 119 ++++++++++++ game/assets/game_subtitle.gp | 4 +- game/assets/game_text.gp | 8 +- game/assets/jak1/subtitle/game_subtitle_en.gd | 3 +- game/assets/jak1/subtitle/game_subtitle_es.gd | 1 + game/assets/jak1/text/game_text_en.gs | 3 +- game/assets/jak1/text/game_text_ja.gs | 11 +- game/assets/jak1/text/text_patch_ja.gs | 1 + game/graphics/pipelines/opengl.cpp | 2 +- game/tools/subtitles/subtitle_editor.cpp | 53 +++--- goal_src/engine/ambient/mood.gc | 12 +- goal_src/engine/anim/bones.gc | 2 +- goal_src/pc/pckernel.gc | 4 +- goal_src/pc/subtitle.gc | 6 +- .../compiler/compilation/CompilerControl.cpp | 14 +- goalc/data_compiler/game_text_common.cpp | 12 +- goalc/data_compiler/game_text_common.h | 12 +- goalc/make/Tools.cpp | 28 +-- test/test_data/test_game_text.gp | 2 +- test/test_data/test_game_text.txt | 1 + 30 files changed, 439 insertions(+), 187 deletions(-) diff --git a/common/deserialization/subtitles/subtitles.cpp b/common/deserialization/subtitles/subtitles.cpp index a32a19cfde..32095b06d3 100644 --- a/common/deserialization/subtitles/subtitles.cpp +++ b/common/deserialization/subtitles/subtitles.cpp @@ -3,6 +3,7 @@ #include #include +#include "common/serialization/subtitles/subtitles.h" #include "common/util/FileUtil.h" #include "third-party/fmt/core.h" @@ -31,6 +32,9 @@ bool write_subtitle_db_to_files(const GameSubtitleDB& db) { std::string file_contents = ""; file_contents += fmt::format("(language-id {})\n", fmt::join(banks, " ")); + auto file_ver = parse_text_only_version(bank->file_path); + auto font = get_font_bank(file_ver); + file_contents += fmt::format("(text-version {})\n", get_text_version_name(file_ver)); for (const auto& group_name : db.m_subtitle_groups->m_group_order) { file_contents += @@ -46,20 +50,19 @@ bool write_subtitle_db_to_files(const GameSubtitleDB& db) { file_contents += fmt::format(" :hint #x{0:x}", scene_info.m_id); } file_contents += "\n"; - for (const auto& line : scene_info.m_lines) { + for (auto& line : scene_info.lines()) { // Clear screen entries - if (line.line_utf8.empty()) { + if (line.line.empty()) { file_contents += fmt::format(" ({})\n", line.frame); } else { file_contents += fmt::format(" ({}", line.frame); if (line.offscreen && scene_info.m_kind == SubtitleSceneKind::Movie) { file_contents += " :offscreen"; } - file_contents += fmt::format(" \"{}\"", line.speaker_utf8); - // escape quotes - std::string temp = line.line_utf8; - temp = std::regex_replace(temp, std::regex("\""), "\\\""); - file_contents += fmt::format(" \"{}\")\n", temp); + file_contents += + fmt::format(" \"{}\"", font->convert_game_to_utf8(line.speaker.c_str())); + file_contents += + fmt::format(" \"{}\")\n", font->convert_game_to_utf8(line.line.c_str())); } } file_contents += " )\n"; diff --git a/common/serialization/subtitles/subtitles.cpp b/common/serialization/subtitles/subtitles.cpp index 83858d9b11..f1fcf658e7 100644 --- a/common/serialization/subtitles/subtitles.cpp +++ b/common/serialization/subtitles/subtitles.cpp @@ -7,37 +7,6 @@ #include "third-party/fmt/core.h" -static const std::unordered_map s_text_ver_enum_map = { - {"jak1-v1", GameTextVersion::JAK1_V1}, - {"jak1-v2", GameTextVersion::JAK1_V2}}; - -// TODO - why not just return the inputs instead of passing in an empty one? -void open_text_project(const std::string& kind, - const std::string& filename, - std::unordered_map>& inputs) { - goos::Reader reader; - auto& proj = reader.read_from_file({filename}).as_pair()->cdr.as_pair()->car; - if (!proj.is_pair() || !proj.as_pair()->car.is_symbol() || - proj.as_pair()->car.as_symbol()->name != kind) { - throw std::runtime_error(fmt::format("invalid {} project", kind)); - } - - goos::for_each_in_list(proj.as_pair()->cdr, [&](const goos::Object& o) { - if (!o.is_pair()) { - throw std::runtime_error(fmt::format("invalid entry in {} project", kind)); - } - - auto& ver = o.as_pair()->car.as_symbol()->name; - auto& in = o.as_pair()->cdr.as_pair()->car.as_string()->data; - - if (s_text_ver_enum_map.count(ver) == 0) { - throw std::runtime_error(fmt::format("unknown text version {}", ver)); - } - - inputs[s_text_ver_enum_map.at(ver)].push_back(in); - }); -} - int64_t get_int(const goos::Object& obj) { if (obj.is_int()) { return obj.integer_obj.value; @@ -76,8 +45,8 @@ std::string get_string(const goos::Object& x) { * Each entry should be (id "line for 1st language" "line for 2nd language" ...) * This adds the text line to each of the specified languages. */ -void parse_text(const goos::Object& data, GameTextVersion text_ver, GameTextDB& db) { - auto font = get_font_bank(text_ver); +void parse_text(const goos::Object& data, GameTextDB& db) { + const GameTextFontBank* font = nullptr; std::vector> banks; std::string possible_group_name; @@ -170,9 +139,29 @@ void parse_text(const goos::Object& data, GameTextVersion text_ver, GameTextDB& throw std::runtime_error(fmt::format("Non-string value in text id #x{:x}", id)); } }); + } else if (head.is_symbol("text-version")) { + if (font) { + throw std::runtime_error("text version is already set"); + } + + const auto& ver_name = car(cdr(obj)); + if (!ver_name.is_symbol()) { + throw std::runtime_error("invalid text version entry"); + } + + if (auto it = sTextVerEnumMap.find(ver_name.as_symbol()->name); + it == sTextVerEnumMap.end()) { + throw std::runtime_error( + fmt::format("unknown text version {}", ver_name.as_symbol()->name)); + } else { + font = get_font_bank(it->second); + } } else if (head.is_int()) { + if (!font) { + throw std::runtime_error("Text version must be set before defining entries."); + } if (banks.size() == 0) { throw std::runtime_error("At least one language must be set before defining entries."); } @@ -214,11 +203,8 @@ void parse_text(const goos::Object& data, GameTextVersion text_ver, GameTextDB& * Each scene should be (scene-name ... ) * This adds the subtitle to each of the specified languages. */ -void parse_subtitle(const goos::Object& data, - GameTextVersion text_ver, - GameSubtitleDB& db, - const std::string& file_path) { - auto font = get_font_bank(text_ver); +void parse_subtitle(const goos::Object& data, GameSubtitleDB& db, const std::string& file_path) { + const GameTextFontBank* font = nullptr; std::map> banks; for_each_in_list(data.as_pair()->cdr, [&](const goos::Object& obj) { @@ -244,9 +230,29 @@ void parse_subtitle(const goos::Object& data, banks[lang]->file_path = file_path; } }); + } else if (head.is_symbol("text-version")) { + if (font) { + throw std::runtime_error("text version is already set"); + } + + const auto& ver_name = car(cdr(obj)); + if (!ver_name.is_symbol()) { + throw std::runtime_error("invalid text version entry"); + } + + if (auto it = sTextVerEnumMap.find(ver_name.as_symbol()->name); + it == sTextVerEnumMap.end()) { + throw std::runtime_error( + fmt::format("unknown text version {}", ver_name.as_symbol()->name)); + } else { + font = get_font_bank(it->second); + } } else if (head.is_string() || head.is_int()) { + if (!font) { + throw std::runtime_error("Text version must be set before defining entries."); + } if (banks.size() == 0) { throw std::runtime_error("At least one language must be set before defining scenes."); } @@ -319,11 +325,9 @@ void parse_subtitle(const goos::Object& data, } } }); - auto line_utf8 = line ? line->data : ""; - auto line_str = font->convert_utf8_to_game(line_utf8); - auto speaker_utf8 = speaker ? speaker->data : ""; - auto speaker_str = font->convert_utf8_to_game(speaker_utf8); - scene.add_line(time, line_str, line_utf8, speaker_str, speaker_utf8, offscreen); + auto line_str = font->convert_utf8_to_game(line ? line->data : ""); + auto speaker_str = font->convert_utf8_to_game(speaker ? speaker->data : ""); + scene.add_line(time, line_str, speaker_str, offscreen); } else { throw std::runtime_error( fmt::format("{} | Each entry must be a non-empty list", scene.name())); @@ -349,6 +353,45 @@ void parse_subtitle(const goos::Object& data, } } +GameTextVersion parse_text_only_version(const std::string& filename) { + goos::Reader reader; + return parse_text_only_version(reader.read_from_file({filename})); +} + +GameTextVersion parse_text_only_version(const goos::Object& data) { + const GameTextFontBank* font = nullptr; + + for_each_in_list(data.as_pair()->cdr, [&](const goos::Object& obj) { + if (obj.is_pair()) { + auto& head = car(obj); + if (head.is_symbol("text-version")) { + if (font) { + throw std::runtime_error("text version is already set"); + } + + const auto& ver_name = car(cdr(obj)); + if (!ver_name.is_symbol()) { + throw std::runtime_error("invalid text version entry"); + } + + if (auto it = sTextVerEnumMap.find(ver_name.as_symbol()->name); + it == sTextVerEnumMap.end()) { + throw std::runtime_error( + fmt::format("unknown text version {}", ver_name.as_symbol()->name)); + } else { + font = get_font_bank(it->second); + } + } + } + }); + + if (!font) { + throw std::runtime_error("text version not found"); + } + + return font->version(); +} + void GameSubtitleGroups::hydrate_from_asset_file() { std::string file_path = (file_util::get_jak_project_dir() / "game" / "assets" / "jak1" / "subtitle" / "subtitle-groups.json") @@ -416,21 +459,50 @@ void GameSubtitleGroups::add_scene(const std::string& group_name, const std::str } } +// TODO - why not just return the inputs instead of passing in an empty one? +void open_text_project(const std::string& kind, + const std::string& filename, + std::vector& inputs) { + goos::Reader reader; + auto& proj = reader.read_from_file({filename}).as_pair()->cdr.as_pair()->car; + if (!proj.is_pair() || !proj.as_pair()->car.is_symbol() || + proj.as_pair()->car.as_symbol()->name != kind) { + throw std::runtime_error(fmt::format("invalid {} project", kind)); + } + + goos::for_each_in_list(proj.as_pair()->cdr, [&](const goos::Object& o) { + if (o.is_pair() && o.as_pair()->cdr.is_pair()) { + auto& action = o.as_pair()->car.as_symbol()->name; + + if (action == "file") { + auto& in = o.as_pair()->cdr.as_pair()->car.as_string()->data; + inputs.push_back(in); + } else { + throw std::runtime_error(fmt::format("unknown action {} in {} project", action, kind)); + } + } else { + throw std::runtime_error(fmt::format("invalid entry in {} project", kind)); + } + }); +} + GameSubtitleDB load_subtitle_project() { // Load the subtitle files GameSubtitleDB db; db.m_subtitle_groups = std::make_unique(); db.m_subtitle_groups->hydrate_from_asset_file(); - goos::Reader reader; - std::unordered_map> inputs; - std::string subtitle_project = - (file_util::get_jak_project_dir() / "game" / "assets" / "game_subtitle.gp").string(); - open_text_project("subtitle", subtitle_project, inputs); - for (auto& [ver, in] : inputs) { - for (auto& filename : in) { + try { + goos::Reader reader; + std::vector inputs; + std::string subtitle_project = + (file_util::get_jak_project_dir() / "game" / "assets" / "game_subtitle.gp").string(); + open_text_project("subtitle", subtitle_project, inputs); + for (auto& filename : inputs) { auto code = reader.read_from_file({filename}); - parse_subtitle(code, ver, db, filename); + parse_subtitle(code, db, filename); } + } catch (std::runtime_error& e) { + lg::error("error loading subtitle project: {}", e.what()); } return db; } diff --git a/common/serialization/subtitles/subtitles.h b/common/serialization/subtitles/subtitles.h index 91d3815b2e..ec1c85b891 100644 --- a/common/serialization/subtitles/subtitles.h +++ b/common/serialization/subtitles/subtitles.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "common/goos/Object.h" #include "common/log/log.h" @@ -74,30 +75,17 @@ enum class SubtitleSceneKind { Invalid = -1, Movie = 0, Hint = 1, HintNamed = 2 class GameSubtitleSceneInfo { public: struct SubtitleLine { - SubtitleLine(int frame, - std::string line, - std::string line_utf8, - std::string speaker, - std::string speaker_utf8, - bool offscreen) - : frame(frame), - line(line), - line_utf8(line_utf8), - speaker(speaker), - speaker_utf8(speaker_utf8), - offscreen(offscreen) {} + SubtitleLine(int frame, std::string line, std::string speaker, bool offscreen) + : frame(frame), line(line), speaker(speaker), offscreen(offscreen) {} int frame; std::string line; - std::string line_utf8; std::string speaker; - std::string speaker_utf8; bool offscreen; bool operator<(const SubtitleLine& other) const { return (frame < other.frame); } }; - GameSubtitleSceneInfo() {} GameSubtitleSceneInfo(SubtitleSceneKind kind) : m_kind(kind) {} const std::string& name() const { return m_name; } @@ -115,13 +103,8 @@ class GameSubtitleSceneInfo { m_id = scene.id(); } - void add_line(int frame, - std::string line, - std::string line_utf8, - std::string speaker, - std::string speaker_utf8, - bool offscreen) { - m_lines.emplace_back(SubtitleLine(frame, line, line_utf8, speaker, speaker_utf8, offscreen)); + void add_line(int frame, std::string line, std::string speaker, bool offscreen) { + m_lines.emplace_back(SubtitleLine(frame, line, speaker, offscreen)); std::sort(m_lines.begin(), m_lines.end()); } @@ -147,7 +130,7 @@ class GameSubtitleBank { GameSubtitleSceneInfo& scene_by_name(const std::string& name) { return m_scenes.at(name); } void add_scene(GameSubtitleSceneInfo& scene) { ASSERT(!scene_exists(scene.name())); - m_scenes[scene.name()] = scene; + m_scenes.insert({scene.name(), scene}); } int m_lang_id; @@ -199,10 +182,13 @@ class GameSubtitleDB { // TODO add docstrings -void parse_text(const goos::Object& data, GameTextVersion text_ver, GameTextDB& db); -void parse_subtitle(const goos::Object& data, - GameTextVersion text_ver, - GameSubtitleDB& db, - const std::string& file_path); +void parse_text(const goos::Object& data, GameTextDB& db); +void parse_subtitle(const goos::Object& data, GameSubtitleDB& db, const std::string& file_path); +GameTextVersion parse_text_only_version(const std::string& filename); +GameTextVersion parse_text_only_version(const goos::Object& data); + +void open_text_project(const std::string& kind, + const std::string& filename, + std::vector& inputs); GameSubtitleDB load_subtitle_project(); diff --git a/common/util/FontUtils.cpp b/common/util/FontUtils.cpp index b48f4e14e3..cdbd8ddd98 100644 --- a/common/util/FontUtils.cpp +++ b/common/util/FontUtils.cpp @@ -10,9 +10,36 @@ #include "FontUtils.h" #include +#include + +#include "common/util/Assert.h" #include "third-party/fmt/core.h" +namespace { + +/*! + * Is this a valid character for a hex number? + */ +bool hex_char(char c) { + return !((c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F')); +} + +} // namespace + +const std::unordered_map sTextVerEnumMap = { + {"jak1-v1", GameTextVersion::JAK1_V1}, + {"jak1-v2", GameTextVersion::JAK1_V2}}; + +const std::string& get_text_version_name(GameTextVersion version) { + for (auto& [name, ver] : sTextVerEnumMap) { + if (ver == version) { + return name; + } + } + throw std::runtime_error(fmt::format("invalid text version {}", version)); +} + GameTextFontBank::GameTextFontBank(GameTextVersion version, std::vector* encode_info, std::vector* replace_info, @@ -130,6 +157,51 @@ std::string GameTextFontBank::convert_utf8_to_game(std::string str) const { return str; } +/*! + * Turn a normal readable string into a string readable in the in-game font encoding and converts + * \cXX escape sequences + */ +std::string GameTextFontBank::convert_utf8_to_game_with_escape(const std::string& str) const { + std::string newstr; + + for (int i = 0; i < str.size(); ++i) { + auto c = str.at(i); + if (c == '\\') { + if (i + 1 >= str.size()) { + throw std::runtime_error("incomplete string escape code"); + } + auto p = str.at(i + 1); + if (p == 'c') { + if (i + 3 >= str.size()) { + throw std::runtime_error("incomplete string escape code"); + } + auto first = str.at(i + 2); + auto second = str.at(i + 3); + if (!hex_char(first) || !hex_char(second)) { + throw std::runtime_error("invalid character escape hex number"); + } + char hex_num[3] = {first, second, '\0'}; + std::size_t end = 0; + auto value = std::stoul(hex_num, &end, 16); + if (end != 2) { + throw std::runtime_error("invalid character escape"); + } + ASSERT(value < 256); + newstr.push_back(char(value)); + i += 3; + } else { + throw std::runtime_error("unknown string escape code"); + } + } else { + newstr.push_back(c); + } + } + + replace_to_game(newstr); + encode_utf8_to_game(newstr); + return newstr; +} + /*! * Convert a string from the game-text font encoding to something normal. * Unprintable characters become escape sequences, including tab and newline. diff --git a/common/util/FontUtils.h b/common/util/FontUtils.h index 4bfa31d2b8..f140f1707d 100644 --- a/common/util/FontUtils.h +++ b/common/util/FontUtils.h @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -26,6 +27,10 @@ enum class GameTextVersion { JAKX = 40 // jak x }; +extern const std::unordered_map sTextVerEnumMap; + +const std::string& get_text_version_name(GameTextVersion version); + /*! * What bytes a set of characters (UTF-8) correspond to. You can convert to and fro. */ @@ -68,6 +73,9 @@ class GameTextFontBank { const std::vector* replace_info() const { return m_replace_info; } const std::unordered_set* passthrus() const { return m_passthrus; } + GameTextVersion version() const { return m_version; } + + std::string convert_utf8_to_game_with_escape(const std::string& str) const; std::string convert_utf8_to_game(std::string str) const; std::string convert_game_to_utf8(const char* in) const; }; diff --git a/decompiler/config/jak1_jp.jsonc b/decompiler/config/jak1_jp.jsonc index 4432c38b04..9e7215eb8e 100644 --- a/decompiler/config/jak1_jp.jsonc +++ b/decompiler/config/jak1_jp.jsonc @@ -51,6 +51,9 @@ // this is a guess at where each symbol is first defined/used. "generate_symbol_definition_map": false, + // genreate the all-types file + "generate_all_types" : false, + // debug option for instruction decoder "write_hex_near_instructions": false, diff --git a/decompiler/config/jak1_ntsc_black_label.jsonc b/decompiler/config/jak1_ntsc_black_label.jsonc index 3b47568866..df3b944222 100644 --- a/decompiler/config/jak1_ntsc_black_label.jsonc +++ b/decompiler/config/jak1_ntsc_black_label.jsonc @@ -1,6 +1,6 @@ { "game_version": 1, - "text_version": 10, + "text_version": 11, // patched to 11 "game_name": "jak1", "expected_elf_name": "SCUS_971.24", @@ -113,7 +113,7 @@ "write_patches": false, // set to true to apply patch files "apply_patches": true, - // what to patch an object to and the patch file is + // what to patch an object to and what the patch file is "object_patches": { "0COMMON": { "crc32": "DD2CD7E2", diff --git a/decompiler/config/jak1_pal.jsonc b/decompiler/config/jak1_pal.jsonc index 9621d7d2bc..efb450929c 100644 --- a/decompiler/config/jak1_pal.jsonc +++ b/decompiler/config/jak1_pal.jsonc @@ -51,6 +51,9 @@ // this is a guess at where each symbol is first defined/used. "generate_symbol_definition_map": false, + // genreate the all-types file + "generate_all_types" : false, + // debug option for instruction decoder "write_hex_near_instructions": false, diff --git a/decompiler/config/jak1_us2.jsonc b/decompiler/config/jak1_us2.jsonc index 9713a8fe6f..a3570b22ce 100644 --- a/decompiler/config/jak1_us2.jsonc +++ b/decompiler/config/jak1_us2.jsonc @@ -51,6 +51,9 @@ // this is a guess at where each symbol is first defined/used. "generate_symbol_definition_map": false, + // genreate the all-types file + "generate_all_types" : false, + // debug option for instruction decoder "write_hex_near_instructions": false, diff --git a/decompiler/data/game_text.cpp b/decompiler/data/game_text.cpp index f7f67af96d..c34b0785e6 100644 --- a/decompiler/data/game_text.cpp +++ b/decompiler/data/game_text.cpp @@ -7,6 +7,7 @@ #include "common/goos/Reader.h" #include "common/util/BitUtils.h" +#include "common/util/FontUtils.h" #include "decompiler/ObjectFile/ObjectFileDB.h" @@ -187,6 +188,7 @@ std::string write_game_text( result += fmt::format(" {}", lang); } result += ")\n"; + result += fmt::format("(text-version {})\n\n", get_text_version_name(version)); for (auto& x : text_by_id) { result += fmt::format("(#x{:04x}\n ", x.first); for (auto& y : x.second) { diff --git a/decompiler/extractor/main.cpp b/decompiler/extractor/main.cpp index 473eb17696..94680ab19d 100644 --- a/decompiler/extractor/main.cpp +++ b/decompiler/extractor/main.cpp @@ -105,6 +105,119 @@ std::pair, std::optional> findElfFile( return {serial, elf_hash}; } +std::pair> validate( + const std::filesystem::path& extracted_iso_path) { + if (!std::filesystem::exists(extracted_iso_path / "DGO")) { + fmt::print(stderr, "ERROR: input folder doesn't have a DGO folder. Is this the right input?\n"); + return {ExtractorErrorCode::VALIDATION_BAD_EXTRACTION, std::nullopt}; + } + + std::optional error_code; + std::optional serial = std::nullopt; + std::optional elf_hash = std::nullopt; + std::tie(serial, elf_hash) = findElfFile(extracted_iso_path); + + // - XOR all hashes together and hash the result. This makes the ordering of the hashes (aka + // files) irrelevant + xxh::hash64_t combined_hash = 0; + int filec = 0; + for (auto const& dir_entry : std::filesystem::recursive_directory_iterator(extracted_iso_path)) { + if (dir_entry.is_regular_file()) { + auto buffer = file_util::read_binary_file(dir_entry.path().string()); + auto hash = xxh::xxhash<64>(buffer); + combined_hash ^= hash; + filec++; + } + } + xxh::hash64_t contents_hash = xxh::xxhash<64>({combined_hash}); + + if (!serial || !elf_hash) { + fmt::print(stderr, "ERROR: Unable to locate a Serial/ELF file!\n"); + if (!error_code.has_value()) { + error_code = std::make_optional(ExtractorErrorCode::VALIDATION_CANT_LOCATE_ELF); + } + // No point in continuing here + return {*error_code, std::nullopt}; + } + + // Find the game in our tracking database + std::optional meta_res = std::nullopt; + if (auto dbEntry = isoDatabase.find(serial.value()); dbEntry == isoDatabase.end()) { + fmt::print(stderr, "ERROR: Serial '{}' not found in the validation database\n", serial.value()); + if (!error_code.has_value()) { + error_code = std::make_optional(ExtractorErrorCode::VALIDATION_SERIAL_MISSING_FROM_DB); + } + } else { + auto& metaMap = dbEntry->second; + auto meta_entry = metaMap.find(elf_hash.value()); + if (meta_entry == metaMap.end()) { + fmt::print(stderr, + "ERROR: ELF Hash '{}' not found in the validation database, is this a new or " + "modified version of the same game?\n", + elf_hash.value()); + if (!error_code.has_value()) { + error_code = std::make_optional(ExtractorErrorCode::VALIDATION_ELF_MISSING_FROM_DB); + } + } else { + meta_res = std::make_optional(meta_entry->second); + const auto& meta = *meta_res; + // Print out some information + fmt::print("Detected Game Metadata:\n"); + fmt::print("\tDetected - {}\n", meta.canonical_name); + fmt::print("\tRegion - {}\n", meta.region); + fmt::print("\tSerial - {}\n", dbEntry->first); + fmt::print("\tUses Decompiler Config - {}\n", meta.decomp_config); + + // - Number of Files + if (meta.num_files != filec) { + fmt::print(stderr, + "ERROR: Extracted an unexpected number of files. Expected '{}', Actual '{}'\n", + meta.num_files, filec); + if (!error_code.has_value()) { + error_code = + std::make_optional(ExtractorErrorCode::VALIDATION_INCORRECT_EXTRACTION_COUNT); + } + } + // Check the ISO Hash + if (meta.contents_hash != contents_hash) { + fmt::print(stderr, + "ERROR: Overall ISO content's hash does not match. Expected '{}', Actual '{}'\n", + meta.contents_hash, contents_hash); + } + } + } + + // Finally, return the result + if (error_code.has_value()) { + // Generate the map entry to make things simple, just convienance + if (error_code.value() == ExtractorErrorCode::VALIDATION_SERIAL_MISSING_FROM_DB) { + fmt::print( + "If this is a new release or version that should be supported, consider adding the " + "following serial entry to the database:\n"); + fmt::print( + "\t'{{\"{}\", {{{{{}U, {{\"GAME_TITLE\", \"NTSC-U/PAL/NTSC-J\", {}, {}U, " + "\"DECOMP_CONFIF_FILENAME_NO_EXTENSION\"}}}}}}}}'\n", + serial.value(), elf_hash.value(), filec, contents_hash); + } else if (error_code.value() == ExtractorErrorCode::VALIDATION_ELF_MISSING_FROM_DB) { + fmt::print( + "If this is a new release or version that should be supported, consider adding the " + "following ELF entry to the database under the '{}' serial:\n", + serial.value()); + fmt::print( + "\t'{{{}, {{\"GAME_TITLE\", \"NTSC-U/PAL/NTSC-J\", {}, {}U, " + "\"DECOMP_CONFIF_FILENAME_NO_EXTENSION\"}}}}'\n", + elf_hash.value(), filec, contents_hash); + } else { + fmt::print(stderr, + "Validation has failed to match with expected values, see the above errors for " + "specifics. This may be an error in the validation database!\n"); + } + return {*error_code, std::nullopt}; + } + + return {ExtractorErrorCode::SUCCESS, meta_res}; +} + std::pair> validate( const IsoFile& iso_file, const std::filesystem::path& extracted_iso_path) { @@ -439,6 +552,7 @@ int main(int argc, char** argv) { std::filesystem::file_size(data_dir_path)); return static_cast(ExtractorErrorCode::EXTRACTION_ISO_UNEXPECTED_SIZE); } + auto iso_file = extract_files(data_dir_path, path_to_iso_files); auto validation_res = validate(iso_file, path_to_iso_files); flags = validation_res.second->flags; @@ -455,6 +569,11 @@ int main(int argc, char** argv) { return static_cast(ExtractorErrorCode::VALIDATION_BAD_ISO_CONTENTS); } path_to_iso_files = data_dir_path; + if (std::filesystem::exists(path_to_iso_files / "buildinfo.json")) { + std::filesystem::remove(path_to_iso_files / "buildinfo.json"); + } + auto validation_res = validate(path_to_iso_files); + flags = validation_res.second->flags; } // write out a json file with some metadata for the game diff --git a/game/assets/game_subtitle.gp b/game/assets/game_subtitle.gp index e1961a8f16..82ef36aca5 100644 --- a/game/assets/game_subtitle.gp +++ b/game/assets/game_subtitle.gp @@ -4,8 +4,8 @@ ;; you can find the game-text-version parsing in .cpp and an enum in goal-lib.gc (subtitle - (jak1-v1 "game/assets/jak1/subtitle/game_subtitle_en.gd") - (jak1-v1 "game/assets/jak1/subtitle/game_subtitle_es.gd") + (file "game/assets/jak1/subtitle/game_subtitle_en.gd") + (file "game/assets/jak1/subtitle/game_subtitle_es.gd") ) diff --git a/game/assets/game_text.gp b/game/assets/game_text.gp index ec8a8531f8..cc045f1fa1 100644 --- a/game/assets/game_text.gp +++ b/game/assets/game_text.gp @@ -5,12 +5,12 @@ (text ;; NOTE : we compile using the fixed v2 encoding because it's what we use. - (jak1-v2 "assets/game_text.txt") ;; this is the decompiler-generated file! + (file "assets/game_text.txt") ;; this is the decompiler-generated file! ;; "patch" files so we can fix some errors and perhaps maintain consistency - (jak1-v2 "game/assets/jak1/text/text_patch_ja.gs") + (file "game/assets/jak1/text/text_patch_ja.gs") ;; add custom files down here - (jak1-v2 "game/assets/jak1/text/game_text_en.gs") - (jak1-v2 "game/assets/jak1/text/game_text_ja.gs") + (file "game/assets/jak1/text/game_text_en.gs") + (file "game/assets/jak1/text/game_text_ja.gs") ) diff --git a/game/assets/jak1/subtitle/game_subtitle_en.gd b/game/assets/jak1/subtitle/game_subtitle_en.gd index ec33753d33..6cf53938ea 100644 --- a/game/assets/jak1/subtitle/game_subtitle_en.gd +++ b/game/assets/jak1/subtitle/game_subtitle_en.gd @@ -1,4 +1,5 @@ (language-id 0 6) +(text-version jak1-v2) ;; ----------------- ;; intro @@ -2522,7 +2523,7 @@ ) ("MTA-AM08" :hint #x0 - (0 "WILLARD" "EH HEH... BIRDIE IS MY BEST FRIEND") + (0 "WILLARD" "EH HEH... BIRDIE IS MY BEST FRIEND") ) ("MTA-AM09" :hint #x0 diff --git a/game/assets/jak1/subtitle/game_subtitle_es.gd b/game/assets/jak1/subtitle/game_subtitle_es.gd index 12c55657eb..c052bc6284 100644 --- a/game/assets/jak1/subtitle/game_subtitle_es.gd +++ b/game/assets/jak1/subtitle/game_subtitle_es.gd @@ -1,4 +1,5 @@ (language-id 3) +(text-version jak1-v2) ;; ----------------- ;; intro diff --git a/game/assets/jak1/text/game_text_en.gs b/game/assets/jak1/text/game_text_en.gs index f98fed4df0..7c10a25ee1 100644 --- a/game/assets/jak1/text/game_text_en.gs +++ b/game/assets/jak1/text/game_text_en.gs @@ -1,5 +1,6 @@ (group-name "common") -(language-id 0 6) +(language-id 0 6) ;; english and uk-english +(text-version jak1-v2) ;; ----------------- ;; progress menu (insanity) diff --git a/game/assets/jak1/text/game_text_ja.gs b/game/assets/jak1/text/game_text_ja.gs index 6ee71dd962..3615aca19a 100644 --- a/game/assets/jak1/text/game_text_ja.gs +++ b/game/assets/jak1/text/game_text_ja.gs @@ -1,5 +1,6 @@ (group-name "common") (language-id 5) +(text-version jak1-v2) ;; ----------------- ;; progress menu (insanity) @@ -20,13 +21,13 @@ (#x1088 "サカナとりゲームのテーマ") (#x1089 "チャレンジーのテーマ") -(#x1090 "無限の青のエコ") -(#x1091 "無限の赤のエコ") -(#x1092 "無限の緑のエコ") -(#x1093 "無限の黄のエコ") +(#x1090 "むげんの青のエコ") +(#x1091 "むげんの赤のエコ") +(#x1092 "むげんの緑のエコ") +(#x1093 "むげんの黄のエコ") (#x1094 "へんかのダックスター") (#x1095 "インヴィンシブル") -(#x1096 "全てBGMへんかを エネーブルする") +(#x1096 "すべてBGMのへんかを エネーブルする") (#x10c0 "BGMプレイヤー") diff --git a/game/assets/jak1/text/text_patch_ja.gs b/game/assets/jak1/text/text_patch_ja.gs index 3fafe0fb97..f09aae93ff 100644 --- a/game/assets/jak1/text/text_patch_ja.gs +++ b/game/assets/jak1/text/text_patch_ja.gs @@ -1,5 +1,6 @@ (group-name "common") (language-id 5) +(text-version jak1-v2) ;; ----------------- ;; fixes diff --git a/game/graphics/pipelines/opengl.cpp b/game/graphics/pipelines/opengl.cpp index 9e350b1507..128eec1080 100644 --- a/game/graphics/pipelines/opengl.cpp +++ b/game/graphics/pipelines/opengl.cpp @@ -416,7 +416,7 @@ GfxDisplayMode GLDisplay::get_fullscreen() { int GLDisplay::get_screen_vmode_count() { int count = 0; - auto vmodes = glfwGetVideoModes(glfwGetPrimaryMonitor(), &count); + glfwGetVideoModes(glfwGetPrimaryMonitor(), &count); return count; } diff --git a/game/tools/subtitles/subtitle_editor.cpp b/game/tools/subtitles/subtitle_editor.cpp index 7b906e7a1e..b40498d2b9 100644 --- a/game/tools/subtitles/subtitle_editor.cpp +++ b/game/tools/subtitles/subtitle_editor.cpp @@ -44,7 +44,7 @@ std::string SubtitleEditor::repl_get_process_string(const std::string_view& enti void SubtitleEditor::repl_play_hint(const std::string_view& hint_name) { repl_reset_game(); - repl_set_continue_point("village1-hut"); + // repl_set_continue_point("village1-hut"); // TODO - move into water fountain m_repl.eval( fmt::format("(level-hint-spawn (game-text-id zero) \"{}\" (the-as entity #f) *entity-pool* " @@ -102,7 +102,8 @@ void SubtitleEditor::repl_execute_cutscene_code(const SubtitleEditorDB::Entry& e void SubtitleEditor::repl_rebuild_text() { m_repl.eval("(make-text)"); - // NOTE - still no clue how this doesn't switch languages lol + // increment the language id of the in-memory text file so that it won't match the current + // language and the game will want to reload it asap m_repl.eval("(1+! (-> *subtitle-text* lang))"); } @@ -176,16 +177,15 @@ void SubtitleEditor::draw_window() { if (!is_scene_in_current_lang(m_new_scene_name) && !m_new_scene_name.empty() && !m_new_scene_group.empty()) { if (ImGui::Button("Add Scene")) { - GameSubtitleSceneInfo newScene; + GameSubtitleSceneInfo newScene(SubtitleSceneKind::Movie); newScene.m_name = m_new_scene_name; - newScene.m_kind = SubtitleSceneKind::Movie; newScene.m_id = 0; // TODO - id is always zero, bug in subtitles.cpp? newScene.m_sorting_group = m_new_scene_group; m_subtitle_db.m_banks.at(m_current_language)->add_scene(newScene); m_subtitle_db.m_subtitle_groups->add_scene(newScene.m_sorting_group, newScene.m_name); if (m_add_new_scene_as_current) { auto& scenes = m_subtitle_db.m_banks.at(m_current_language)->m_scenes; - auto& scene_info = scenes[m_new_scene_name]; + auto& scene_info = scenes.at(m_new_scene_name); m_current_scene = &scene_info; } m_new_scene_name = ""; @@ -225,7 +225,7 @@ void SubtitleEditor::draw_window() { if (!is_scene_in_current_lang(m_new_scene_name) && !m_new_scene_name.empty() && !m_new_scene_group.empty()) { if (ImGui::Button("Add Scene")) { - GameSubtitleSceneInfo newScene; + GameSubtitleSceneInfo newScene(SubtitleSceneKind::Hint); newScene.m_name = m_new_scene_name; if (m_new_scene_id == "0") { newScene.m_kind = SubtitleSceneKind::Hint; @@ -235,7 +235,7 @@ void SubtitleEditor::draw_window() { newScene.m_id = strtoul(m_new_scene_id.c_str(), nullptr, 16); } // currently hints have no way in the editor to add a line, so give us one for free - newScene.add_line(0, "", "", "", "", false); + newScene.add_line(0, "", "", false); newScene.m_sorting_group = m_new_scene_group; m_subtitle_db.m_banks.at(m_current_language)->add_scene(newScene); m_subtitle_db.m_subtitle_groups->add_scene(newScene.m_sorting_group, newScene.m_name); @@ -378,7 +378,7 @@ bool SubtitleEditor::any_cutscenes_in_group(const std::string& group_name) { auto& scenes = m_subtitle_db.m_banks.at(m_current_language)->m_scenes; auto scenes_in_group = m_subtitle_db.m_subtitle_groups->m_groups[group_name]; for (auto& scene_name : scenes_in_group) { - auto& scene_info = scenes[scene_name]; + auto& scene_info = scenes.at(scene_name); if (scene_info.m_kind == SubtitleSceneKind::Movie) { return true; } @@ -407,7 +407,7 @@ bool SubtitleEditor::any_hints_in_group(const std::string& group_name) { auto& scenes = m_subtitle_db.m_banks.at(m_current_language)->m_scenes; auto scenes_in_group = m_subtitle_db.m_subtitle_groups->m_groups[group_name]; for (auto& scene_name : scenes_in_group) { - auto& scene_info = scenes[scene_name]; + auto& scene_info = scenes.at(scene_name); if (scene_info.m_kind != SubtitleSceneKind::Movie) { return true; } @@ -440,7 +440,7 @@ void SubtitleEditor::draw_all_scenes(std::string group_name, bool base_cutscenes if (scenes.count(scene_name) == 0) { continue; } - auto& scene_info = scenes[scene_name]; + auto& scene_info = scenes.at(scene_name); // Don't duplicate entries if (base_cutscenes && is_scene_in_current_lang(scene_name)) { continue; @@ -497,7 +497,7 @@ void SubtitleEditor::draw_all_hints(std::string group_name, bool base_cutscenes) if (scenes.count(scene_name) == 0) { continue; } - auto& scene_info = scenes[scene_name]; + auto& scene_info = scenes.at(scene_name); // Don't duplicate entries if (base_cutscenes && is_scene_in_current_lang(scene_name)) { continue; @@ -587,31 +587,34 @@ void SubtitleEditor::draw_subtitle_options(GameSubtitleSceneInfo& scene, bool cu if (current_scene) { draw_new_cutscene_line_form(); } + auto font = + get_font_bank(parse_text_only_version(m_subtitle_db.m_banks[m_current_language]->file_path)); for (size_t i = 0; i < scene.m_lines.size(); i++) { auto& subtitleLine = scene.m_lines.at(i); + auto linetext = font->convert_game_to_utf8(subtitleLine.line.c_str()); std::string summary; - if (subtitleLine.line_utf8.empty()) { + if (linetext.empty()) { summary = fmt::format("[{}] Clear Screen", subtitleLine.frame); - } else if (subtitleLine.line_utf8.length() >= 30) { - summary = fmt::format("[{}] {} - '{}...'", subtitleLine.frame, subtitleLine.speaker_utf8, - subtitleLine.line_utf8.substr(0, 30)); + } else if (linetext.length() >= 30) { + summary = fmt::format("[{}] {} - '{}...'", subtitleLine.frame, subtitleLine.speaker, + linetext.substr(0, 30)); } else { - summary = fmt::format("[{}] {} - '{}'", subtitleLine.frame, subtitleLine.speaker_utf8, - subtitleLine.line_utf8.substr(0, 30)); + summary = fmt::format("[{}] {} - '{}'", subtitleLine.frame, subtitleLine.speaker, + linetext.substr(0, 30)); } - if (subtitleLine.line_utf8.empty()) { + if (linetext.empty()) { ImGui::PushStyleColor(ImGuiCol_Text, m_disabled_text_color); } else if (subtitleLine.offscreen) { ImGui::PushStyleColor(ImGuiCol_Text, m_offscreen_text_color); } if (ImGui::TreeNode(fmt::format("{}", i).c_str(), "%s", summary.c_str())) { - if (subtitleLine.line_utf8.empty() || subtitleLine.offscreen) { + if (linetext.empty() || subtitleLine.offscreen) { ImGui::PopStyleColor(); } ImGui::InputInt("Starting Frame", &subtitleLine.frame, ImGuiInputTextFlags_::ImGuiInputTextFlags_CharsDecimal); - ImGui::InputText("Speaker", &subtitleLine.speaker_utf8); - ImGui::InputText("Text", &subtitleLine.line_utf8); + ImGui::InputText("Speaker", &subtitleLine.speaker); + ImGui::InputText("Text", &linetext); ImGui::Checkbox("Offscreen?", &subtitleLine.offscreen); ImGui::PushStyleColor(ImGuiCol_Button, m_warning_color); if (scene.m_lines.size() > 1) { // prevent creating an empty scene @@ -621,9 +624,11 @@ void SubtitleEditor::draw_subtitle_options(GameSubtitleSceneInfo& scene, bool cu } ImGui::PopStyleColor(); ImGui::TreePop(); - } else if (subtitleLine.line_utf8.empty() || subtitleLine.offscreen) { + } else if (linetext.empty() || subtitleLine.offscreen) { ImGui::PopStyleColor(); } + auto newtext = font->convert_utf8_to_game_with_escape(linetext); + subtitleLine.line = newtext; } } @@ -642,7 +647,7 @@ void SubtitleEditor::draw_new_cutscene_line_form() { } else { rendered_text_entry_btn = true; if (ImGui::Button("Add Text Entry")) { - m_current_scene->add_line(m_current_scene_frame, "", m_current_scene_text, "", + m_current_scene->add_line(m_current_scene_frame, m_current_scene_text, m_current_scene_speaker, m_current_scene_offscreen); } } @@ -655,7 +660,7 @@ void SubtitleEditor::draw_new_cutscene_line_form() { ImGui::SameLine(); } if (ImGui::Button("Add Clear Screen Entry")) { - m_current_scene->add_line(m_current_scene_frame, "", "", "", "", false); + m_current_scene->add_line(m_current_scene_frame, "", "", false); } } ImGui::NewLine(); diff --git a/goal_src/engine/ambient/mood.gc b/goal_src/engine/ambient/mood.gc index b0ac11e4dc..57a983f5de 100644 --- a/goal_src/engine/ambient/mood.gc +++ b/goal_src/engine/ambient/mood.gc @@ -1538,9 +1538,9 @@ (let ((s3-2 (new 'stack-no-clear 'vector)) (s4-3 (new 'stack-no-clear 'vector)) (f26-3 (+ 0.75 - (* 0.0625 (cos (the float (* 4000 (-> *display* time-adjust-ratio) (-> *display* integral-frame-counter))))) ;; changed for high fps - (* 0.0625 (cos (the float (shl (the int (* (-> *display* time-adjust-ratio) (-> *display* integral-frame-counter))) 11)))) ;; changed for high fps - (* 0.125 (cos (the float (* 1500 (-> *display* time-adjust-ratio) (-> *display* integral-frame-counter))))) ;; changed for high fps + (* 0.0625 (cos (the float (* 4000 (/ (-> *display* time-factor) 5) (-> *display* integral-frame-counter))))) ;; changed for high fps + (* 0.0625 (cos (the float (shl (the int (* (/ (-> *display* time-factor) 5) (-> *display* integral-frame-counter))) 11)))) ;; changed for high fps + (* 0.125 (cos (the float (* 1500 (/ (-> *display* time-factor) 5) (-> *display* integral-frame-counter))))) ;; changed for high fps ) ) ) @@ -2696,9 +2696,9 @@ (let* ((f2-5 (* 0.000012207031 (+ -409600.0 f0-41))) (f30-2 (- 1.0 (fmax 0.0 (fmin 1.0 f2-5)))) (f28-4 (+ 0.5 - (* 0.125 (cos (the float (* 4000 (-> *display* time-adjust-ratio) (-> *display* integral-frame-counter))))) ;; changed for high fps - (* 0.125 (cos (the float (shl (the int (* (-> *display* time-adjust-ratio) (-> *display* integral-frame-counter))) 11)))) ;; changed for high fps - (* 0.25 (cos (the float (* 1500 (-> *display* time-adjust-ratio) (-> *display* integral-frame-counter))))) ;; changed for high fps + (* 0.125 (cos (the float (* 4000 (/ (-> *display* time-factor) 5) (-> *display* integral-frame-counter))))) ;; changed for high fps + (* 0.125 (cos (the float (shl (the int (* (/ (-> *display* time-factor) 5) (-> *display* integral-frame-counter))) 11)))) ;; changed for high fps + (* 0.25 (cos (the float (* 1500 (/ (-> *display* time-factor) 5) (-> *display* integral-frame-counter))))) ;; changed for high fps ) ) (s3-1 (new 'stack-no-clear 'vector)) diff --git a/goal_src/engine/anim/bones.gc b/goal_src/engine/anim/bones.gc index 9d2b766580..b6e06beddc 100644 --- a/goal_src/engine/anim/bones.gc +++ b/goal_src/engine/anim/bones.gc @@ -115,7 +115,7 @@ (a1-1 (the-as mei-texture-scroll (+ (the-as uint a1-0) (* (-> a1-0 texture-scroll-offset) 16)))) ) (when (< v1-1 32) - (let* ((a3-1 (the int (* (-> *display* time-adjust-ratio) (-> *display* integral-frame-counter)))) ;; changed for high fps + (let* ((a3-1 (the int (* (/ (-> *display* time-factor) 5) (-> *display* integral-frame-counter)))) ;; changed for high fps (a2-3 (-> a1-1 time-factor)) (t0-2 (+ (ash 1 a2-3) -1)) ) diff --git a/goal_src/pc/pckernel.gc b/goal_src/pc/pckernel.gc index 3d498325ed..ac6e00b56d 100644 --- a/goal_src/pc/pckernel.gc +++ b/goal_src/pc/pckernel.gc @@ -66,8 +66,8 @@ (set! (-> obj win-height) height) ) (else - (format 0 "Nope! No changing fullscreen resolution for now!") - (format #t "Nope! No changing fullscreen resolution for now!") + (format 0 "Nope! No changing fullscreen resolution for now!~%") + (format #t "Nope! No changing fullscreen resolution for now!~%") ) ) (none)) diff --git a/goal_src/pc/subtitle.gc b/goal_src/pc/subtitle.gc index 665a13630c..f2465780c4 100644 --- a/goal_src/pc/subtitle.gc +++ b/goal_src/pc/subtitle.gc @@ -280,7 +280,8 @@ "rebuild and reload subtitles." `(begin (asm-text-file subtitle :files ("game/assets/game_subtitle.gp")) - (+! (-> *subtitle-text* lang) (the-as pc-subtitle-lang 1)))) + (if *subtitle-text* + (+! (-> *subtitle-text* lang) (the-as pc-subtitle-lang 1))))) @@ -597,6 +598,7 @@ ;; get a subtitle info that matches our current status (let ((keyframe (the subtitle-keyframe #f))) + (when *subtitle-text* (case (-> self cur-channel) (((pc-subtitle-channel movie)) ;; cutscenes. get our cutscene. @@ -637,7 +639,7 @@ ) ) ) - ) + )) ;; save whatever subtitle we got. (set! (-> self want-subtitle) keyframe)) diff --git a/goalc/compiler/compilation/CompilerControl.cpp b/goalc/compiler/compilation/CompilerControl.cpp index 2b7bf77244..566d454c22 100644 --- a/goalc/compiler/compilation/CompilerControl.cpp +++ b/goalc/compiler/compilation/CompilerControl.cpp @@ -85,7 +85,7 @@ Val* Compiler::compile_asm_text_file(const goos::Object& form, const goos::Objec va_check(form, args, {goos::ObjectType::SYMBOL}, {{"files", {true, goos::ObjectType::PAIR}}}); // list of files per text version. - std::unordered_map> inputs; + std::vector inputs; // what kind of text file? const auto kind = symbol_string(args.unnamed.at(0)); @@ -101,15 +101,11 @@ Val* Compiler::compile_asm_text_file(const goos::Object& form, const goos::Objec // compile files. if (kind == "subtitle") { - for (auto& [ver, in] : inputs) { - GameSubtitleDB db; - compile_game_subtitle(in, ver, db); - } + GameSubtitleDB db; + compile_game_subtitle(inputs, db); } else if (kind == "text") { - for (auto& [ver, in] : inputs) { - GameTextDB db; - compile_game_text(in, ver, db); - } + GameTextDB db; + compile_game_text(inputs, db); } else { throw_compiler_error(form, "The option {} was not recognized for asm-text-file.", kind); } diff --git a/goalc/data_compiler/game_text_common.cpp b/goalc/data_compiler/game_text_common.cpp index 7ae7b15540..8e6bbb55c2 100644 --- a/goalc/data_compiler/game_text_common.cpp +++ b/goalc/data_compiler/game_text_common.cpp @@ -148,26 +148,22 @@ void compile_subtitle(GameSubtitleDB& db) { /*! * Read a game text description file and generate GOAL objects. */ -void compile_game_text(const std::vector& filenames, - GameTextVersion text_ver, - GameTextDB& db) { +void compile_game_text(const std::vector& filenames, GameTextDB& db) { goos::Reader reader; for (auto& filename : filenames) { fmt::print("[Build Game Text] {}\n", filename.c_str()); auto code = reader.read_from_file({filename}); - parse_text(code, text_ver, db); + parse_text(code, db); } compile_text(db); } -void compile_game_subtitle(const std::vector& filenames, - GameTextVersion text_ver, - GameSubtitleDB& db) { +void compile_game_subtitle(const std::vector& filenames, GameSubtitleDB& db) { goos::Reader reader; for (auto& filename : filenames) { fmt::print("[Build Game Subtitle] {}\n", filename.c_str()); auto code = reader.read_from_file({filename}); - parse_subtitle(code, text_ver, db, filename); + parse_subtitle(code, db, filename); } compile_subtitle(db); } diff --git a/goalc/data_compiler/game_text_common.h b/goalc/data_compiler/game_text_common.h index 19200dd25b..5e7f355112 100644 --- a/goalc/data_compiler/game_text_common.h +++ b/goalc/data_compiler/game_text_common.h @@ -9,13 +9,5 @@ #include "common/util/Assert.h" #include "common/util/FontUtils.h" -void compile_game_text(const std::vector& filenames, - GameTextVersion text_ver, - GameTextDB& db); -void compile_game_subtitle(const std::vector& filenames, - GameTextVersion text_ver, - GameSubtitleDB& db); - -void open_text_project(const std::string& kind, - const std::string& filename, - std::unordered_map>& inputs); +void compile_game_text(const std::vector& filenames, GameTextDB& db); +void compile_game_subtitle(const std::vector& filenames, GameSubtitleDB& db); diff --git a/goalc/make/Tools.cpp b/goalc/make/Tools.cpp index 75c1d302e3..99a3e8e5ec 100644 --- a/goalc/make/Tools.cpp +++ b/goalc/make/Tools.cpp @@ -136,23 +136,15 @@ bool TextTool::needs_run(const ToolInput& task) { } std::vector deps; - std::unordered_map> inputs; - open_text_project("text", task.input.at(0), inputs); - for (auto& [ver, files] : inputs) { - for (auto& in : files) { - deps.push_back(in); - } - } + open_text_project("text", task.input.at(0), deps); return Tool::needs_run({task.input, deps, task.output, task.arg}); } bool TextTool::run(const ToolInput& task) { GameTextDB db; - std::unordered_map> inputs; + std::vector inputs; open_text_project("text", task.input.at(0), inputs); - for (auto& [ver, in] : inputs) { - compile_game_text(in, ver, db); - } + compile_game_text(inputs, db); return true; } @@ -170,13 +162,7 @@ bool SubtitleTool::needs_run(const ToolInput& task) { } std::vector deps; - std::unordered_map> inputs; - open_text_project("subtitle", task.input.at(0), inputs); - for (auto& [ver, files] : inputs) { - for (auto& in : files) { - deps.push_back(in); - } - } + open_text_project("subtitle", task.input.at(0), deps); return Tool::needs_run({task.input, deps, task.output, task.arg}); } @@ -184,11 +170,9 @@ bool SubtitleTool::run(const ToolInput& task) { GameSubtitleDB db; db.m_subtitle_groups = std::make_unique(); db.m_subtitle_groups->hydrate_from_asset_file(); - std::unordered_map> inputs; + std::vector inputs; open_text_project("subtitle", task.input.at(0), inputs); - for (auto& [ver, in] : inputs) { - compile_game_subtitle(in, ver, db); - } + compile_game_subtitle(inputs, db); return true; } diff --git a/test/test_data/test_game_text.gp b/test/test_data/test_game_text.gp index b3bef689af..e8f3662477 100644 --- a/test/test_data/test_game_text.gp +++ b/test/test_data/test_game_text.gp @@ -1,2 +1,2 @@ (text - (jak1-v1 "test/test_data/test_game_text.txt")) \ No newline at end of file + (file "test/test_data/test_game_text.txt")) \ No newline at end of file diff --git a/test/test_data/test_game_text.txt b/test/test_data/test_game_text.txt index 6d8e660ad4..ab72cf0fb2 100644 --- a/test/test_data/test_game_text.txt +++ b/test/test_data/test_game_text.txt @@ -1,5 +1,6 @@ (group-name "test") (language-id 0 1 2) +(text-version jak1-v2) (#x123 "language 0" "language 1"