From d15b5a4a21890cc755b14fb72d86cd000a6a48b5 Mon Sep 17 00:00:00 2001 From: ManDude <7569514+ManDude@users.noreply.github.com> Date: Mon, 20 Jun 2022 00:15:06 +0100 Subject: [PATCH] patch more text lines, special handling for credits --- common/serialization/subtitles/subtitles.cpp | 54 ++++++++++++++- decompiler/ObjectFile/ObjectFileDB.cpp | 2 +- decompiler/config/jak1_pal.jsonc | 2 +- decompiler/data/game_text.cpp | 73 +++++++++++++++++++- decompiler/data/game_text.h | 2 +- game/assets/game_text.gp | 4 ++ game/assets/jak1/text/game_text_ja.gs | 7 -- game/assets/jak1/text/text_patch_de.gs | 9 +++ game/assets/jak1/text/text_patch_fr.gs | 16 +++++ game/assets/jak1/text/text_patch_ja.gs | 26 +++++++ 10 files changed, 180 insertions(+), 15 deletions(-) create mode 100644 game/assets/jak1/text/text_patch_de.gs create mode 100644 game/assets/jak1/text/text_patch_fr.gs create mode 100644 game/assets/jak1/text/text_patch_ja.gs diff --git a/common/serialization/subtitles/subtitles.cpp b/common/serialization/subtitles/subtitles.cpp index 59c000732c..26a9699a01 100644 --- a/common/serialization/subtitles/subtitles.cpp +++ b/common/serialization/subtitles/subtitles.cpp @@ -82,7 +82,7 @@ void parse_text(const goos::Object& data, GameTextVersion text_ver, GameTextDB& for_each_in_list(data.as_pair()->cdr, [&](const goos::Object& obj) { if (obj.is_pair()) { auto& head = car(obj); - if (head.is_symbol() && head.as_symbol()->name == "language-id") { + if (head.is_symbol("language-id")) { if (banks.size() != 0) { throw std::runtime_error("Languages have been set multiple times."); } @@ -104,7 +104,7 @@ void parse_text(const goos::Object& data, GameTextVersion text_ver, GameTextDB& banks.push_back(db.bank_by_id(possible_group_name, lang)); } }); - } else if (head.is_symbol() && head.as_symbol()->name == "group-name") { + } else if (head.is_symbol("group-name")) { if (!possible_group_name.empty()) { throw std::runtime_error("group-name has been set multiple times."); } @@ -118,6 +118,56 @@ void parse_text(const goos::Object& data, GameTextVersion text_ver, GameTextDB& if (!cdr(cdr(obj)).is_empty_list()) { throw std::runtime_error("group-name has too many arguments"); } + } else if (head.is_symbol("credits")) { + // parse a "credits" object. it's a list of lines where the ID automatically increments, and + // empty lines are skipped + if (banks.size() == 0) { + throw std::runtime_error("At least one language must be set before defining entries."); + } + + if (cdr(obj).is_empty_list() || cdr(cdr(obj)).is_empty_list() || + !car(cdr(obj)).is_symbol(":begin") || !car(cdr(cdr(obj))).is_int()) { + throw std::runtime_error("Invalid credits begin param"); + } + + const auto& it = cdr(cdr(obj)); + int begin_id = car(it).as_int(); + int id = begin_id - 1; + for_each_in_list(cdr(it), [&](const goos::Object& entry) { + ++id; + if (entry.is_string()) { + if (entry.as_string()->data.empty()) { + // empty string! just advance + return; + } + + auto line = font->convert_utf8_to_game(entry.as_string()->data); + // add to all langs + for (auto& bank : banks) { + bank->set_line(id, line); + } + } else if (entry.is_pair()) { + int b_i = 0; + for_each_in_list(entry, [&](const goos::Object& entry) { + if (entry.is_string()) { + if (b_i >= int(banks.size())) { + throw std::runtime_error(fmt::format("Too many strings in text id #x{:x}", id)); + } + + auto line = font->convert_utf8_to_game(entry.as_string()->data); + banks[b_i++]->set_line(id, line); + } else { + throw std::runtime_error(fmt::format("Non-string value in text id #x{:x}", id)); + } + }); + if (b_i != int(banks.size())) { + throw std::runtime_error( + fmt::format("Not enough strings specified in text id #x{:x}", id)); + } + } else { + throw std::runtime_error(fmt::format("Non-string value in text id #x{:x}", id)); + } + }); } else if (head.is_int()) { diff --git a/decompiler/ObjectFile/ObjectFileDB.cpp b/decompiler/ObjectFile/ObjectFileDB.cpp index ab27f3b06a..f5d7815787 100644 --- a/decompiler/ObjectFile/ObjectFileDB.cpp +++ b/decompiler/ObjectFile/ObjectFileDB.cpp @@ -647,7 +647,7 @@ std::string ObjectFileDB::process_game_text_files(const Config& cfg) { if (text_by_language_by_id.empty()) { return {}; } - return write_game_text(cfg, text_by_language_by_id); + return write_game_text(cfg.text_version, text_by_language_by_id); } std::string ObjectFileDB::process_game_count_file() { diff --git a/decompiler/config/jak1_pal.jsonc b/decompiler/config/jak1_pal.jsonc index 413d9dc953..9118f7c101 100644 --- a/decompiler/config/jak1_pal.jsonc +++ b/decompiler/config/jak1_pal.jsonc @@ -18,7 +18,7 @@ "disassemble_code": false, // Run the decompiler - "decompile_code": true, + "decompile_code": false, // run the first pass of the decompiler "find_functions": true, diff --git a/decompiler/data/game_text.cpp b/decompiler/data/game_text.cpp index 6f94a8fb5b..74f5c6dfb2 100644 --- a/decompiler/data/game_text.cpp +++ b/decompiler/data/game_text.cpp @@ -24,6 +24,10 @@ DecompilerLabel get_label(ObjectFileData& data, const LinkedWord& word) { return data.linked_data.labels.at(word.label_id()); } +static const std::unordered_map> sTextCreditsIDs = { + {GameTextVersion::JAK1_V1, {0xb00, 0xf00}}, + {GameTextVersion::JAK1_V2, {0xb00, 0xf00}}}; + } // namespace /* @@ -133,7 +137,7 @@ GameTextResult process_game_text(ObjectFileData& data, GameTextVersion version) } std::string write_game_text( - const Config& /*cfg*/, + GameTextVersion version, const std::unordered_map>& data) { // first sort languages: std::vector languages; @@ -144,9 +148,30 @@ std::string write_game_text( // build map std::map> text_by_id; + std::map> text_by_id_credits; + std::map> text_by_id_post_credits; + int last_credits_id = 0; + int credits_begin = 0; + int credits_end = 0; + if (auto it = sTextCreditsIDs.find(version); it != sTextCreditsIDs.end()) { + credits_begin = it->second.first; + credits_end = it->second.second; + } for (auto lang : languages) { - for (auto& text : data.at(lang)) { - text_by_id[text.first].push_back(text.second); + for (auto& [id, text] : data.at(lang)) { + if (id < credits_begin) { + // comes before credits + text_by_id[id].push_back(text); + } else if (id < credits_end) { + // comes before credits + text_by_id_credits[id].push_back(text); + if (id > last_credits_id) { + last_credits_id = id; + } + } else { + // comes after credits + text_by_id_post_credits[id].push_back(text); + } } } @@ -165,6 +190,48 @@ std::string write_game_text( } result += ")\n\n"; } + if (text_by_id_credits.size() > 0) { + result += fmt::format("(credits :begin #x{:04x}\n ", sTextCreditsIDs.at(version).first); + + for (int id = sTextCreditsIDs.at(version).first; id <= last_credits_id; ++id) { + // check if the line exists first + if (text_by_id_credits.count(id) == 0) { + result += fmt::format("\"\"\n "); + continue; + } + // check if all lines are identical first + bool diff_langs = false; + bool is_first = true; + std::string last_lang; + for (auto& y : text_by_id_credits.at(id)) { + if (is_first) { + is_first = false; + } else if (last_lang != y) { + diff_langs = true; + break; + } + last_lang = y; + } + // now write them + if (!diff_langs) { + result += fmt::format("\"{}\"\n ", last_lang); + } else { + result += fmt::format("("); + for (auto& y : text_by_id_credits.at(id)) { + result += fmt::format("\"{}\"\n ", y); + } + result += ")\n "; + } + } + result += ")\n\n"; + } + for (auto& x : text_by_id_post_credits) { + result += fmt::format("(#x{:04x}\n ", x.first); + for (auto& y : x.second) { + result += fmt::format("\"{}\"\n ", y); + } + result += ")\n\n"; + } return result; } diff --git a/decompiler/data/game_text.h b/decompiler/data/game_text.h index 01fc17c234..b62dd1d978 100644 --- a/decompiler/data/game_text.h +++ b/decompiler/data/game_text.h @@ -16,6 +16,6 @@ struct GameTextResult { GameTextResult process_game_text(ObjectFileData& data, GameTextVersion version); std::string write_game_text( - const Config& cfg, + GameTextVersion version, const std::unordered_map>& data); } // namespace decompiler diff --git a/game/assets/game_text.gp b/game/assets/game_text.gp index 8b8c237908..cedd1532e0 100644 --- a/game/assets/game_text.gp +++ b/game/assets/game_text.gp @@ -6,6 +6,10 @@ (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! + ;; "patch" files so we can fix some errors and maintain consistency + (jak1-v2 "game/assets/jak1/text/text_patch_fr.gs") + (jak1-v2 "game/assets/jak1/text/text_patch_de.gs") + (jak1-v2 "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") diff --git a/game/assets/jak1/text/game_text_ja.gs b/game/assets/jak1/text/game_text_ja.gs index 25ae462180..5c8d0456b6 100644 --- a/game/assets/jak1/text/game_text_ja.gs +++ b/game/assets/jak1/text/game_text_ja.gs @@ -1,13 +1,6 @@ (group-name "common") (language-id 5) -;; ----------------- -;; fixes - - ; 闇の眷者 ゴルとマイアの城 -(#x122 "闇の賢者 ゴルとマイアの城") -(#x801 "闇の賢者 ゴルとマイアの城") - ;; ----------------- ;; progress menu (insanity) diff --git a/game/assets/jak1/text/text_patch_de.gs b/game/assets/jak1/text/text_patch_de.gs new file mode 100644 index 0000000000..b03e30da3f --- /dev/null +++ b/game/assets/jak1/text/text_patch_de.gs @@ -0,0 +1,9 @@ +(group-name "common") +(language-id 2) + +;; ----------------- +;; official fixes + + ; SUCHE DIE VERSTECKTE ENERGIEZELLE! +(#x012f "FINDE DIE VERSTECKTE ENERGIEZELLE!") + diff --git a/game/assets/jak1/text/text_patch_fr.gs b/game/assets/jak1/text/text_patch_fr.gs new file mode 100644 index 0000000000..083226725b --- /dev/null +++ b/game/assets/jak1/text/text_patch_fr.gs @@ -0,0 +1,16 @@ +(group-name "common") +(language-id 1) + +;; ----------------- +;; official fixes + + ; ATTRAPÉ(S): +(#x02b4 "ATTRAPÉ(S)_:") + ; RATÉ(S): +(#x02b5 "RATÉ(S)_:") + ; PERDU! +(#x02b6 "PERDU_!") + ; RAMÈNE 90 ORBES À LA GÉOLOGISTE +(#x0301 "RAMÈNE 90 ORBES À LA GÉOLOGUE") + ; RETOURNE VOIR LA GÉOLOGISTE +(#x0311 "RETOURNE VOIR LA GÉOLOGUE") diff --git a/game/assets/jak1/text/text_patch_ja.gs b/game/assets/jak1/text/text_patch_ja.gs new file mode 100644 index 0000000000..a7906b318b --- /dev/null +++ b/game/assets/jak1/text/text_patch_ja.gs @@ -0,0 +1,26 @@ +(group-name "common") +(language-id 5) + +;; ----------------- +;; official fixes + + ; ボタンで やめる +(#x011d "で やめる") + ; ジャックXダクスターの ゲームデータを セーブするには ~DKBの 空きようりょうが ひつようです +(#x0133 "ジャックXダクスターの ゲームデータを セーブするには ~DKBいじょうの 空きようりょうが ひつようです") + ; メモリーカードスロット~Dに ジャックXダクスターの データのはいった メモリーカードを さしてください +(#x0143 "メモリーカードスロット~Dに ジャックXダクスターの データのはいった メモリーカード(PS2)を さしてください") + +(#x0400 "90コのオーブを 宝石掘りにわたせ") +(#x0402 "ダークエコ発掘3部隊を すべてたおせ") +(#x0403 "ダークエコ発掘2部隊を すべてたおせ") +(#x0404 "ダークエコ発掘隊の さいごの1隊をたおせ") +(#x0409 "宝石掘りたちの おたからをまもれ") +(#x041f "ダークエコ発掘隊を たおせ") + +;; ----------------- +;; fixes + + ; 闇の眷者 ゴルとマイアの城 +(#x122 "闇の賢者 ゴルとマイアの城") +(#x801 "闇の賢者 ゴルとマイアの城")