mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 00:57:44 -04:00
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
This commit is contained in:
parent
870be7151a
commit
2649fd17ec
|
@ -3,6 +3,7 @@
|
|||
#include <algorithm>
|
||||
#include <regex>
|
||||
|
||||
#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";
|
||||
|
|
|
@ -7,37 +7,6 @@
|
|||
|
||||
#include "third-party/fmt/core.h"
|
||||
|
||||
static const std::unordered_map<std::string, GameTextVersion> 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<GameTextVersion, std::vector<std::string>>& 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<std::shared_ptr<GameTextBank>> 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 <entry 1> <entry 2> ... )
|
||||
* 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<int, std::shared_ptr<GameSubtitleBank>> 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<std::string>& 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<GameSubtitleGroups>();
|
||||
db.m_subtitle_groups->hydrate_from_asset_file();
|
||||
goos::Reader reader;
|
||||
std::unordered_map<GameTextVersion, std::vector<std::string>> 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<std::string> 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;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#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<std::string>& inputs);
|
||||
GameSubtitleDB load_subtitle_project();
|
||||
|
|
|
@ -10,9 +10,36 @@
|
|||
#include "FontUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
|
||||
#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<std::string, GameTextVersion> 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<EncodeInfo>* encode_info,
|
||||
std::vector<ReplaceInfo>* 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.
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
|
@ -26,6 +27,10 @@ enum class GameTextVersion {
|
|||
JAKX = 40 // jak x
|
||||
};
|
||||
|
||||
extern const std::unordered_map<std::string, GameTextVersion> 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<ReplaceInfo>* replace_info() const { return m_replace_info; }
|
||||
const std::unordered_set<char>* 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;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -105,6 +105,119 @@ std::pair<std::optional<std::string>, std::optional<xxh::hash64_t>> findElfFile(
|
|||
return {serial, elf_hash};
|
||||
}
|
||||
|
||||
std::pair<ExtractorErrorCode, std::optional<ISOMetadata>> 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<ExtractorErrorCode> error_code;
|
||||
std::optional<std::string> serial = std::nullopt;
|
||||
std::optional<xxh::hash64_t> 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<ISOMetadata> 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<ISOMetadata>(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<ExtractorErrorCode, std::optional<ISOMetadata>> 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<int>(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<int>(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
|
||||
|
|
|
@ -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")
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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")
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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<til>")
|
||||
(0 "WILLARD" "EH HEH... BIRDIE IS MY BEST FRIEND<TIL>")
|
||||
)
|
||||
|
||||
("MTA-AM09" :hint #x0
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
(language-id 3)
|
||||
(text-version jak1-v2)
|
||||
|
||||
;; -----------------
|
||||
;; intro
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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プレイヤー")
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
(group-name "common")
|
||||
(language-id 5)
|
||||
(text-version jak1-v2)
|
||||
|
||||
;; -----------------
|
||||
;; fixes
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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<GameTextVersion, std::vector<std::string>> inputs;
|
||||
std::vector<std::string> 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);
|
||||
}
|
||||
|
|
|
@ -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<std::string>& filenames,
|
||||
GameTextVersion text_ver,
|
||||
GameTextDB& db) {
|
||||
void compile_game_text(const std::vector<std::string>& 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<std::string>& filenames,
|
||||
GameTextVersion text_ver,
|
||||
GameSubtitleDB& db) {
|
||||
void compile_game_subtitle(const std::vector<std::string>& 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);
|
||||
}
|
||||
|
|
|
@ -9,13 +9,5 @@
|
|||
#include "common/util/Assert.h"
|
||||
#include "common/util/FontUtils.h"
|
||||
|
||||
void compile_game_text(const std::vector<std::string>& filenames,
|
||||
GameTextVersion text_ver,
|
||||
GameTextDB& db);
|
||||
void compile_game_subtitle(const std::vector<std::string>& filenames,
|
||||
GameTextVersion text_ver,
|
||||
GameSubtitleDB& db);
|
||||
|
||||
void open_text_project(const std::string& kind,
|
||||
const std::string& filename,
|
||||
std::unordered_map<GameTextVersion, std::vector<std::string>>& inputs);
|
||||
void compile_game_text(const std::vector<std::string>& filenames, GameTextDB& db);
|
||||
void compile_game_subtitle(const std::vector<std::string>& filenames, GameSubtitleDB& db);
|
||||
|
|
|
@ -136,23 +136,15 @@ bool TextTool::needs_run(const ToolInput& task) {
|
|||
}
|
||||
|
||||
std::vector<std::string> deps;
|
||||
std::unordered_map<GameTextVersion, std::vector<std::string>> 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<GameTextVersion, std::vector<std::string>> inputs;
|
||||
std::vector<std::string> 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<std::string> deps;
|
||||
std::unordered_map<GameTextVersion, std::vector<std::string>> 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<GameSubtitleGroups>();
|
||||
db.m_subtitle_groups->hydrate_from_asset_file();
|
||||
std::unordered_map<GameTextVersion, std::vector<std::string>> inputs;
|
||||
std::vector<std::string> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
(text
|
||||
(jak1-v1 "test/test_data/test_game_text.txt"))
|
||||
(file "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"
|
||||
|
|
Loading…
Reference in a new issue