[decompiler] jak 2 cutscene file support (#2390)

fixes #2332 

also fills out the japanese character set
This commit is contained in:
ManDude 2023-03-22 21:31:13 +00:00 committed by GitHub
parent b18198e655
commit 217a979048
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1285 additions and 64 deletions

View File

@ -66,6 +66,16 @@ std::optional<int> get_power_of_two(T in) {
bool integer_fits(s64 in, int size, bool is_signed);
u32 float_as_u32(float x);
template <typename T>
T align64(T in) {
return (in + 63) & (~T(63));
}
template <typename T>
T align32(T in) {
return (in + 31) & (~T(31));
}
template <typename T>
T align16(T in) {
return (in + 15) & (~T(15));
@ -82,8 +92,8 @@ T align4(T in) {
}
template <typename T>
T align64(T in) {
return (in + 63) & (~T(63));
T align2(T in) {
return (in + 1) & (~T(1));
}
inline u32 count_leading_zeros_u32(u32 in) {

View File

@ -1017,8 +1017,8 @@ static std::vector<ReplaceInfo> s_replace_info_jak2 = {
"<FLAG_UK>"},
{"~Y~1L<SYM19>~Z~39L<SYM36>~<SYM26>~-1H~Y~1L<SYM19>~Z~39L<SYM39>~Z~-11H~7L<SYM41>~Z~-11H~3L<"
"SYM43>~Z~+26H",
"<FLAG_JAPAN>"},
{"~Y~1L<SYM19>~<SYM26>~-1H~Y~1L<SYM19>~Z~-11H~3L<SYM27>~Z~+26H", "<FLAG_SOUTH_KOREA>"},
"<FLAG_KOREA>"},
{"~Y~1L<SYM19>~<SYM26>~-1H~Y~1L<SYM19>~Z~-11H~3L<SYM27>~Z~+26H", "<FLAG_JAPAN>"},
// weird stuff
// - descenders
@ -1028,7 +1028,7 @@ static std::vector<ReplaceInfo> s_replace_info_jak2 = {
{"~+7Vq~-7V", "q"},
{"~+1Vj~-1V", "j"},
{"\\\\\\\\", "\\n"},
{"\\\\\\\\", "\\n"}, // wtf happened here!?
// - symbols and ligatures
{"~-4H~-3V\\c19~+3V~-4H",
@ -1051,8 +1051,8 @@ static std::vector<EncodeInfo> s_encode_info_jak2 = {
{"º", {0x16}}, // numero/overring
{"¡", {0x17}}, // inverted exclamation mark
{"¿", {0x18}}, // inverted question mark
{"Ç", {0x1d}}, // c-cedilla
{"ç", {0x1d}}, // c-cedilla
{"Ç", {0x1e}}, // c-cedilla
{"ß", {0x1f}}, // eszett
{"œ", {0x5e}}, // ligature o+e
@ -1104,11 +1104,373 @@ static std::vector<EncodeInfo> s_encode_info_jak2 = {
{"<SYM4>", {0xb1}},
{"<SYM14>", {0xb3}},
{"<SYM15>", {0xb2}},
// {"入", {1, 0x00}},
// {"年", {1, 0x01}},
// punctuation
{"", {1, 0x10}},
{"", {1, 0x11}},
{"", {1, 0x12}},
{"", {1, 0x13}},
{"", {1, 0x14}},
{"", {1, 0x15}},
// hiragana
{"", {1, 0x16}}, // -a
{"", {1, 0x17}}, // a
{"", {1, 0x18}}, // -i
{"", {1, 0x19}}, // i
{"", {1, 0x1a}}, // -u
{"", {1, 0x1b}}, // u
{"", {1, 0x1c}}, // -e
{"", {1, 0x1d}}, // e
{"", {1, 0x1e}}, // -o
{"", {1, 0x1f}}, // o
{"", {1, 0x20}}, // ka
{"", {1, 0x21}}, // ki
{"", {1, 0x22}}, // ku
{"", {1, 0x23}}, // ke
{"", {1, 0x24}}, // ko
{"", {1, 0x25}}, // sa
{"", {1, 0x26}}, // shi
{"", {1, 0x27}}, // su
{"", {1, 0x28}}, // se
{"", {1, 0x29}}, // so
{"", {1, 0x2a}}, // ta
{"", {1, 0x2b}}, // chi
{"", {1, 0x2c}}, // sokuon
{"", {1, 0x2d}}, // tsu
{"", {1, 0x2e}}, // te
{"", {1, 0x2f}}, // to
{"", {1, 0x30}}, // na
{"", {1, 0x31}}, // ni
{"", {1, 0x32}}, // nu
{"", {1, 0x33}}, // ne
{"", {1, 0x34}}, // no
{"", {1, 0x35}}, // ha
{"", {1, 0x36}}, // hi
{"", {1, 0x37}}, // fu
{"", {1, 0x38}}, // he
{"", {1, 0x39}}, // ho
{"", {1, 0x3a}}, // ma
{"", {1, 0x3b}}, // mi
{"", {1, 0x3c}}, // mu
{"", {1, 0x3d}}, // me
{"", {1, 0x3e}}, // mo
{"", {1, 0x3f}}, // youon ya
{"", {1, 0x40}}, // ya
{"", {1, 0x41}}, // youon yu
{"", {1, 0x42}}, // yu
{"", {1, 0x43}}, // youon yo
{"", {1, 0x44}}, // yo
{"", {1, 0x45}}, // ra
{"", {1, 0x46}}, // ri
{"", {1, 0x47}}, // ru
{"", {1, 0x48}}, // re
{"", {1, 0x49}}, // ro
{"", {1, 0x4a}}, // -wa
{"", {1, 0x4b}}, // wa
{"", {1, 0x4c}}, // wo
{"", {1, 0x4d}}, // -n
// katakana
{"", {1, 0x4e}}, // -a
{"", {1, 0x4f}}, // a
{"", {1, 0x50}}, // -i
{"", {1, 0x51}}, // i
{"", {1, 0x52}}, // -u
{"", {1, 0x53}}, // u
{"", {1, 0x54}}, // -e
{"", {1, 0x55}}, // e
{"", {1, 0x56}}, // -o
{"", {1, 0x57}}, // o
{"", {1, 0x58}}, // ka
{"", {1, 0x59}}, // ki
{"", {1, 0x5a}}, // ku
{"", {1, 0x5b}}, // ke
{"", {1, 0x5c}}, // ko
{"", {1, 0x5d}}, // sa
{"", {1, 0x5e}}, // shi
{"", {1, 0x5f}}, // su
{"", {1, 0x60}}, // se
{"", {1, 0x61}}, // so
{"", {1, 0x62}}, // ta
{"", {1, 0x63}}, // chi
{"", {1, 0x64}}, // sokuon
{"", {1, 0x65}}, // tsu
{"", {1, 0x66}}, // te
{"", {1, 0x67}}, // to
{"", {1, 0x68}}, // na
{"", {1, 0x69}}, // ni
{"", {1, 0x6a}}, // nu
{"", {1, 0x6b}}, // ne
{"", {1, 0x6c}}, // no
{"", {1, 0x6d}}, // ha
{"", {1, 0x6e}}, // hi
{"", {1, 0x6f}}, // fu
{"", {1, 0x70}}, // he
{"", {1, 0x71}}, // ho
{"", {1, 0x72}}, // ma
{"", {1, 0x73}}, // mi
{"", {1, 0x74}}, // mu
{"", {1, 0x75}}, // me
{"", {1, 0x76}}, // mo
{"", {1, 0x77}}, // youon ya
{"", {1, 0x78}}, // ya
{"", {1, 0x79}}, // youon yu
{"", {1, 0x7a}}, // yu
{"", {1, 0x7b}}, // youon yo
{"", {1, 0x7c}}, // yo
{"", {1, 0x7d}}, // ra
{"", {1, 0x7e}}, // ri
{"", {1, 0x7f}}, // ru
{"", {1, 0x80}}, // re
{"", {1, 0x81}}, // ro
{"", {1, 0x82}}, // -wa
{"", {1, 0x83}}, // wa
{"", {1, 0x84}}, // wo
{"", {1, 0x85}}, // -n
{"", {1, 0x8c}},
{"", {1, 0x8d}},
{"", {1, 0x8e}},
{"", {1, 0x8f}},
{"", {1, 0x90}},
{"", {1, 0x91}},
{"", {1, 0x92}},
{"", {1, 0x93}},
{"", {1, 0x94}},
{"", {1, 0x95}},
{"", {1, 0x96}},
{"", {1, 0x97}},
{"", {1, 0x98}},
{"", {1, 0x99}},
{"", {1, 0x9a}},
{"", {1, 0x9b}},
{"", {1, 0x9c}},
{"", {1, 0x9d}},
{"", {1, 0x9e}},
{"", {1, 0x9f}},
{"", {1, 0xa0}},
{"", {1, 0xa1}},
{"", {1, 0xa2}},
{"", {1, 0xa3}},
{"", {1, 0xa4}},
{"", {1, 0xa5}},
{"", {1, 0xa6}},
{"", {1, 0xa7}},
{"", {1, 0xa8}},
{"", {1, 0xa9}},
{"", {1, 0xaa}},
{"", {1, 0xab}},
{"", {1, 0xac}},
{"", {1, 0xad}},
{"", {1, 0xae}},
{"", {1, 0xaf}},
{"", {1, 0xb0}},
{"", {1, 0xb1}},
{"", {1, 0xb2}},
{"", {1, 0xb3}},
{"", {1, 0xb4}},
{"", {1, 0xb5}},
{"", {1, 0xb6}},
{"", {1, 0xb7}},
{"", {1, 0xb8}},
{"", {1, 0xb9}},
{"", {1, 0xba}},
{"", {1, 0xbb}},
{"", {1, 0xbc}},
{"", {1, 0xbd}},
{"", {1, 0xbe}},
{"", {1, 0xbf}},
{"使", {1, 0xc0}},
{"", {1, 0xc1}},
{"", {1, 0xc2}},
{"", {1, 0xc3}},
{"", {1, 0xc4}},
{"", {1, 0xc5}},
{"", {1, 0xc6}},
{"", {1, 0xc7}},
{"", {1, 0xc8}},
{"", {1, 0xc9}},
{"", {1, 0xca}},
{"", {1, 0xcb}},
{"", {1, 0xcc}},
{"", {1, 0xcd}},
{"", {1, 0xce}},
{"", {1, 0xcf}},
{"", {1, 0xd0}},
{"", {1, 0xd1}},
{"", {1, 0xd2}},
{"", {1, 0xd3}},
{"", {1, 0xd4}},
{"", {1, 0xd5}},
{"", {1, 0xd6}},
{"", {1, 0xd7}},
{"", {1, 0xd8}},
{"", {1, 0xd9}},
{"", {1, 0xda}},
{"", {1, 0xdb}},
{"", {1, 0xdc}},
{"", {1, 0xdd}},
{"", {1, 0xde}},
{"", {1, 0xdf}},
{"", {1, 0xe0}},
{"", {1, 0xe1}},
{"", {1, 0xe2}},
{"", {1, 0xe3}},
{"", {1, 0xe4}},
{"", {1, 0xe5}},
{"", {1, 0xe6}},
{"", {1, 0xe7}},
{"", {1, 0xe8}},
{"", {1, 0xe9}},
{"", {1, 0xea}},
{"", {1, 0xeb}},
{"", {1, 0xec}},
{"", {1, 0xed}},
{"", {1, 0xee}},
{"", {1, 0xef}},
{"", {1, 0xf0}},
{"", {1, 0xf1}},
{"", {1, 0xf2}},
{"", {1, 0xf3}},
{"", {1, 0xf4}},
{"", {1, 0xf5}},
{"", {1, 0xf6}},
{"", {1, 0xf7}},
{"", {1, 0xf8}},
{"", {1, 0xf9}},
{"", {1, 0xfa}},
{"", {1, 0xfb}},
{"", {1, 0xfc}},
{"", {1, 0xfd}},
{"", {1, 0xfe}},
{"", {1, 0xff}},
{"", {2, 0x10}},
{"", {2, 0x11}},
{"", {2, 0x12}},
{"", {2, 0x13}},
{"", {2, 0x14}},
{"", {2, 0x15}},
{"", {2, 0x16}},
{"", {2, 0x17}},
{"", {2, 0x18}},
{"", {2, 0x19}},
{"", {2, 0x1a}},
{"", {2, 0x1b}},
{"", {2, 0x1c}},
{"", {2, 0x1d}},
{"", {2, 0x1e}},
{"", {2, 0x1f}},
{"", {2, 0x20}},
{"", {2, 0x21}},
{"", {2, 0x22}},
{"", {2, 0x23}},
{"", {2, 0x24}},
{"", {2, 0x25}},
{"", {2, 0x26}},
{"", {2, 0x27}},
{"", {2, 0x28}},
{"", {2, 0x29}},
{"", {2, 0x2a}},
{"", {2, 0x2b}},
{"", {2, 0x2c}},
{"", {2, 0x2d}},
{"", {2, 0x2e}},
{"", {2, 0x2f}},
{"", {2, 0x30}},
{"", {2, 0x31}},
{"", {2, 0x32}},
{"", {2, 0x33}},
// {"成", {2, 0x34}},
{"", {2, 0x35}},
{"", {2, 0x36}},
{"", {2, 0x37}},
{"", {2, 0x38}},
{"", {2, 0x39}},
{"", {2, 0x3a}},
{"", {2, 0x3b}},
{"", {2, 0x3c}},
{"", {2, 0x3d}},
{"", {2, 0x3e}},
{"", {2, 0x3f}},
{"", {2, 0x40}},
{"", {2, 0x41}},
// {"乗", {2, 0x42}},
{"", {2, 0x43}},
{"", {2, 0x44}},
{"", {2, 0x45}},
{"", {2, 0x46}},
{"", {2, 0x47}},
{"", {2, 0x48}},
{"", {2, 0x49}},
{"", {2, 0x4a}},
{"", {2, 0x4b}},
// {"対", {2, 0x4c}},
{"", {2, 0x4d}},
{"", {2, 0x4e}},
{"", {2, 0x4f}},
{"", {2, 0x50}},
{"", {2, 0x56}},
{"", {2, 0x57}},
{"", {2, 0x58}},
{"", {2, 0x59}},
{"", {2, 0x5a}},
{"", {2, 0x5b}},
{"", {2, 0x5c}},
{"", {2, 0x5d}},
{"", {2, 0x5e}},
{"", {2, 0x5f}},
{"", {2, 0x60}},
{"", {2, 0x61}},
{"", {2, 0x62}},
{"", {2, 0x63}},
{"", {2, 0x64}},
// {"高", {2, 0x65}},
{"", {2, 0x66}},
{"", {2, 0x67}},
{"", {2, 0x68}},
{"", {2, 0x69}},
{"", {2, 0x6a}},
{"", {2, 0x6b}},
{"", {2, 0x6c}},
{"", {2, 0x6d}},
{"", {2, 0x6e}},
{"", {2, 0x6f}},
{"", {2, 0x70}},
{"", {2, 0x71}},
{"", {2, 0x72}},
{"", {2, 0x73}},
{"", {2, 0x74}},
{"", {2, 0x75}},
{"退", {2, 0x76}},
{"", {2, 0x77}},
{"", {2, 0x78}},
{"", {2, 0x79}},
{"", {2, 0x7a}},
{"", {2, 0x7b}},
{"", {2, 0x7c}},
{"", {2, 0x7d}},
{"", {2, 0x7e}},
{"", {2, 0x7f}},
{"", {2, 0x80}},
{"", {2, 0x81}},
{"", {2, 0x82}},
{"", {2, 0x83}},
{"", {2, 0x84}},
{"", {2, 0x85}},
// {"録", {2, 0x86}},
{"", {2, 0x88}},
{"", {2, 0x89}},
{"", {2, 0x8a}},
{"", {2, 0x8b}},
{"", {2, 0x8c}},
{"", {2, 0x8d}},
};
GameTextFontBank g_font_bank_jak2(GameTextVersion::JAK2,
&s_encode_info_jak2,
//&s_replace_info_null,
&s_replace_info_jak2,
&s_passthrus_jak2);

View File

@ -276,6 +276,10 @@ std::string LinkedObjectFile::print_words() {
auto& word = words_by_seg[seg][i];
append_word_to_string(result, word);
if (word.kind() == LinkedWord::TYPE_PTR && word.symbol_name() == "string") {
result += "; " + get_goal_string(seg, i) + "\n";
}
}
}

View File

@ -29,6 +29,7 @@
#include "decompiler/data/StrFileReader.h"
#include "decompiler/data/dir_tpages.h"
#include "decompiler/data/game_count.h"
#include "decompiler/data/game_subs.h"
#include "decompiler/data/game_text.h"
#include "decompiler/data/tpage.h"
@ -204,9 +205,10 @@ ObjectFileDB::ObjectFileDB(const std::vector<fs::path>& _dgos,
add_obj_from_dgo(name, name, data.data(), data.size(), "NO-XGO", config);
}
if (config.read_spools) {
lg::info("-Loading {} streaming object files...", str_files.size());
for (auto& obj : str_files) {
StrFileReader reader(obj);
StrFileReader reader(obj, version());
// name from the file name
std::string base_name = obj_filename_to_name(obj.string());
// name from inside the file (this does a lot of sanity checking)
@ -215,7 +217,8 @@ ObjectFileDB::ObjectFileDB(const std::vector<fs::path>& _dgos,
// append the chunk ID to the full name
std::string name = obj_name + fmt::format("+{}", i);
auto& data = reader.get_chunk(i);
add_obj_from_dgo(name, name, data.data(), data.size(), "NO-XGO", config);
add_obj_from_dgo(name, name, data.data(), data.size(), "ALLSPOOL", config, obj_name);
}
}
}
@ -369,7 +372,8 @@ void ObjectFileDB::add_obj_from_dgo(const std::string& obj_name,
const uint8_t* obj_data,
uint32_t obj_size,
const std::string& dgo_name,
const Config& config) {
const Config& config,
const std::string& cut_name) {
if (config.banned_objects.find(obj_name) != config.banned_objects.end()) {
return;
}
@ -415,6 +419,7 @@ void ObjectFileDB::add_obj_from_dgo(const std::string& obj_name,
// if this is the first time we've seen this object file name, add it in the order.
obj_file_order.push_back(obj_name);
}
data.base_name_from_chunk = cut_name;
data.record.version = obj_files_by_name[obj_name].size();
data.name_in_dgo = name_in_dgo;
data.obj_version = version;
@ -489,9 +494,10 @@ std::string ObjectFileDB::generate_obj_listing(const std::unordered_set<std::str
for (auto& obj_file : obj_file_order) {
for (auto& x : obj_files_by_name.at(obj_file)) {
std::string dgos = "[";
for (auto& y : x.dgo_names) {
ASSERT(y.length() >= 5);
std::string new_str = y == "NO-XGO" ? y : y.substr(0, y.length() - 4);
for (auto& name : x.dgo_names) {
ASSERT(name.length() >= 5);
std::string new_str =
(name == "NO-XGO" || name == "ALLSPOOL") ? name : name.substr(0, name.length() - 4);
dgos += "\"" + new_str + "\", ";
}
dgos.pop_back();
@ -725,6 +731,58 @@ std::string ObjectFileDB::process_tpages(TextureDB& tex_db, const fs::path& outp
return result;
}
std::string ObjectFileDB::process_all_spool_subtitles(const Config& cfg,
const fs::path& image_out) {
try {
lg::info("- Finding spool subtitles...");
Timer timer;
int obj_count = 0;
int string_count = 0;
int image_count = 0;
int subs_count = 0;
std::unordered_map<std::string, std::vector<SpoolSubtitleRange>> all_subs;
for_each_obj_in_dgo("ALLSPOOL", [&](ObjectFileData& data) {
int this_string_count = 0;
int this_image_count = 0;
obj_count++;
auto this_spool_subs = process_spool_subtitles(data, cfg.text_version);
if (!this_spool_subs.empty()) {
for (auto& s : this_spool_subs) {
subs_count++;
for (int i = 0; i < 8; ++i) {
if (s.message[i].kind == SpoolSubtitleMessage::Kind::IMAGE) {
this_image_count++;
} else if (s.message[i].kind == SpoolSubtitleMessage::Kind::STRING) {
this_string_count++;
}
}
}
auto& spool_subs = all_subs[data.base_name_from_chunk];
for (auto& x : this_spool_subs) {
bool skip = false;
for (auto& other : spool_subs) {
skip |= other == x;
}
if (!skip) {
all_subs[data.base_name_from_chunk].push_back(x);
image_count += this_image_count;
string_count += this_string_count;
}
}
}
});
lg::info("Processed {} subtitles in {} spool objects ({} strings, {} images) in {:.2f} ms",
subs_count, obj_count, string_count, image_count, timer.getMs());
return write_spool_subtitles(cfg.text_version, image_out, all_subs);
} catch (std::runtime_error& e) {
lg::warn("Error when extracting spool subtitles: {}", e.what());
return {};
}
}
std::string ObjectFileDB::process_game_text_files(const Config& cfg) {
try {
lg::info("- Finding game text...");

View File

@ -47,6 +47,7 @@ struct ObjectFileData {
std::string name_in_dgo;
std::string name_from_map;
std::string to_unique_name() const;
std::string base_name_from_chunk;
uint32_t reference_count = 0; // number of times its used.
std::string full_output;
@ -246,6 +247,7 @@ class ObjectFileDB {
std::string process_tpages(TextureDB& tex_db, const fs::path& output_path);
std::string process_game_count_file();
std::string process_game_text_files(const Config& cfg);
std::string process_all_spool_subtitles(const Config& cfg, const fs::path& image_out);
const ObjectFileData& lookup_record(const ObjectFileRecord& rec) const;
DecompilerTypeSystem dts;
@ -255,7 +257,6 @@ class ObjectFileDB {
const Config& config,
TypeSpec* result);
public:
void load_map_file(const std::string& map_data);
void get_objs_from_dgo(const fs::path& filename, const Config& config);
void add_obj_from_dgo(const std::string& obj_name,
@ -263,7 +264,8 @@ class ObjectFileDB {
const uint8_t* obj_data,
uint32_t obj_size,
const std::string& dgo_name,
const Config& config);
const Config& config,
const std::string& cut_name = "");
/*!
* Apply f to all ObjectFileData's. Does it in the right order.
@ -273,7 +275,20 @@ class ObjectFileDB {
ASSERT(obj_files_by_name.size() == obj_file_order.size());
for (const auto& name : obj_file_order) {
for (auto& obj : obj_files_by_name.at(name)) {
// lg::info("{}...", name);
f(obj);
}
}
}
/*!
* Apply f to all ObjectFileData's in a specific DGO. Does it in the right order.
*/
template <typename Func>
void for_each_obj_in_dgo(const std::string& dgo_name, Func f) {
ASSERT(obj_files_by_name.size() == obj_file_order.size());
const auto& dgo_objs = obj_files_by_dgo.at(dgo_name);
for (const auto& rec : dgo_objs) {
for (auto& obj : obj_files_by_name.at(rec.name)) {
f(obj);
}
}

View File

@ -62,6 +62,12 @@ Config make_config_via_json(nlohmann::json& json) {
config.process_game_text = json.at("process_game_text").get<bool>();
config.process_game_count = json.at("process_game_count").get<bool>();
config.process_art_groups = json.at("process_art_groups").get<bool>();
if (json.contains("process_subtitle_text")) {
config.process_subtitle_text = json.at("process_subtitle_text").get<bool>();
}
if (json.contains("process_subtitle_images")) {
config.process_subtitle_images = json.at("process_subtitle_images").get<bool>();
}
config.dump_art_group_info = json.at("dump_art_group_info").get<bool>();
config.hexdump_code = json.at("hexdump_code").get<bool>();
config.hexdump_data = json.at("hexdump_data").get<bool>();
@ -73,6 +79,9 @@ Config make_config_via_json(nlohmann::json& json) {
config.rip_levels = json.at("rip_levels").get<bool>();
config.extract_collision = json.at("extract_collision").get<bool>();
config.generate_all_types = json.at("generate_all_types").get<bool>();
if (json.contains("read_spools")) {
config.read_spools = json.at("read_spools").get<bool>();
}
if (json.contains("old_all_types_file")) {
config.old_all_types_file = json.at("old_all_types_file").get<std::string>();
}
@ -304,9 +313,11 @@ Config read_config_file(const fs::path& path_to_config_file,
}
// Then, update any config overrides
if (override_json != "{}" && !override_json.empty()) {
lg::info("Config Overide: '{}'", override_json);
auto cfg_override = parse_commented_json(override_json, "");
json.update(cfg_override);
}
// debugging, dump the JSON config to a file
// fs::path debug_path = path_to_config_file.parent_path() / "config-debug.jsonc";

View File

@ -112,10 +112,13 @@ struct Config {
bool process_game_text = false;
bool process_game_count = false;
bool process_art_groups = false;
bool process_subtitle_text = false;
bool process_subtitle_images = false;
bool dump_art_group_info = false;
bool rip_levels = false;
bool extract_collision = false;
bool find_functions = false;
bool read_spools = false;
bool write_hex_near_instructions = false;
bool hexdump_code = false;

View File

@ -3094,7 +3094,7 @@
(tex-all-map 319) ;; tex
(progress 320) ;; hud | progress
(screen-filter 321) ;; hud letterbox, no zbuf
(bucket-322 322) ;; hud
(subtitle 322) ;; hud
(bucket-323 323) ;; hud
(debug2 324) ;; debug
(debug-no-zbuf2 325) ;; debug

View File

@ -43,6 +43,13 @@
// write out a json file containing the art info mapping, run this with all objects allowed
"dump_art_group_info": false,
// set to false to skip adding .STR files to the decompiler database
"read_spools": false,
// write out spool subtitle text, implies read_spools
"process_subtitle_text": true,
// write out spool subtitle images, implies read_spools
"process_subtitle_images": true,
///////////////////////////
// WEIRD OPTIONS
///////////////////////////

View File

@ -164,7 +164,247 @@
],
// some objects are part of STR files (streaming data).
"str_file_names": [],
"str_file_names": [
"STR/AT1INT.STR",
"STR/AT1RES.STR",
"STR/AT2INTRO.STR",
"STR/AT3INTRO.STR",
"STR/ATSA.STR",
"STR/ATSARA.STR",
"STR/ATSARB.STR",
"STR/ATSB.STR",
"STR/ATSC.STR",
"STR/ATSD.STR",
"STR/ATSE.STR",
"STR/ATSINTRO.STR",
"STR/ATSTANK.STR",
"STR/BACONSIT.STR",
"STR/BASQUID.STR",
"STR/BAWIDOW.STR",
"STR/CAATIN.STR",
"STR/CAATOUT.STR",
"STR/CAIIINTR.STR",
"STR/CAIIRES.STR",
"STR/CAKBFINT.STR",
"STR/CAKBFRES.STR",
"STR/CASEXPLO.STR",
"STR/CIADOFF.STR",
"STR/CIATICAS.STR",
"STR/CIATINES.STR",
"STR/CIATOUT.STR",
"STR/CIC1RIA.STR",
"STR/CIC1RIB.STR",
"STR/CIC1RRES.STR",
"STR/CIC2RINT.STR",
"STR/CIC2RRES.STR",
"STR/CIC3RINT.STR",
"STR/CIC3RRES.STR",
"STR/CIDGVINT.STR",
"STR/CIDSINTR.STR",
"STR/CIDSRES.STR",
"STR/CIECINTR.STR",
"STR/CIECRES.STR",
"STR/CIEKINTR.STR",
"STR/CIGDGUN.STR",
"STR/CIGHOVER.STR",
"STR/CIGYGUN.STR",
"STR/CIHKINTR.STR",
"STR/CIHKRESO.STR",
"STR/CIIDINTR.STR",
"STR/CIIHCINT.STR",
"STR/CIIHCRES.STR",
"STR/CIITINTR.STR",
"STR/CIITRES.STR",
"STR/CIKCINTR.STR",
"STR/CIKCRES.STR",
"STR/CIKDINTR.STR",
"STR/CIMBINTR.STR",
"STR/CIMBRES.STR",
"STR/CIOINTRO.STR",
"STR/CIOL0.STR",
"STR/CIOL1.STR",
"STR/CIOL2.STR",
"STR/CIOL3.STR",
"STR/CIPHOVER.STR",
"STR/CIPOGINT.STR",
"STR/CIPOGRES.STR",
"STR/CIPSINTR.STR",
"STR/CISBBINT.STR",
"STR/CISLINTR.STR",
"STR/CISOPINT.STR",
"STR/CISUINTR.STR",
"STR/CIWAMINT.STR",
"STR/CIWAMRES.STR",
"STR/COFBRES.STR",
"STR/CRINTRO.STR",
"STR/CRVICTOR.STR",
"STR/DAMOLE.STR",
"STR/DEDINTRO.STR",
// "STR/DESCREEN.STR",
"STR/DIDEXPLO.STR",
"STR/DIFTINTR.STR",
"STR/DIFTRES.STR",
"STR/DIKDSINT.STR",
"STR/DRBSBREA.STR",
"STR/DRCBREAK.STR",
"STR/DRDCTINT.STR",
"STR/DRDSINTR.STR",
"STR/DRKMHINT.STR",
"STR/DRTEXPLO.STR",
"STR/DRW1.STR",
"STR/DRW2.STR",
"STR/ECINTRO.STR",
"STR/ECVICTOR.STR",
"STR/FO2INTRO.STR",
"STR/FOBUARA.STR",
"STR/FOBUARB.STR",
"STR/FOCMHINT.STR",
"STR/FOFA.STR",
"STR/FOFB.STR",
"STR/FOHCMHIN.STR",
"STR/FOPSIA.STR",
"STR/FOPSIB.STR",
"STR/FOPSRES.STR",
"STR/FOSFIA.STR",
"STR/FOSFRES.STR",
"STR/GRMANIMS.STR",
"STR/INCSQUAR.STR",
"STR/INPRISON.STR",
"STR/INSHUT.STR",
"STR/INVORTEX.STR",
"STR/JAA1.STR",
"STR/JAA2.STR",
"STR/JAA3.STR",
"STR/JAA4.STR",
"STR/JAA5.STR",
"STR/JAA6.STR",
"STR/JAA7.STR",
"STR/JABOARD.STR",
"STR/JACARRY.STR",
"STR/JAD1.STR",
"STR/JAD2.STR",
"STR/JAD3.STR",
"STR/JAD4.STR",
"STR/JAD5.STR",
"STR/JADARK.STR",
"STR/JADON.STR",
"STR/JADUMMY.STR",
"STR/JAFLUT.STR",
"STR/JAGUN.STR",
"STR/JAICE.STR",
"STR/JAINDAX.STR",
"STR/JAMECH.STR",
"STR/JAPEGASU.STR",
"STR/JAPIDAX.STR",
"STR/JAPILOT.STR",
"STR/JAPOLE.STR",
"STR/JARACER.STR",
"STR/JASWIM.STR",
"STR/JATUBE.STR",
"STR/JATURRET.STR",
"STR/KEANIM.STR",
"STR/KEGARAGE.STR",
"STR/KILTRNKR.STR",
"STR/KILYSKDC.STR",
"STR/KINESTB.STR",
"STR/KITOMBD.STR",
"STR/KRDRES.STR",
"STR/MOFINTRO.STR",
"STR/MOGRES.STR",
"STR/MOLRES.STR",
"STR/MOSRES.STR",
"STR/MTAR1.STR",
"STR/MTPBRA.STR",
"STR/MTSPRA.STR",
"STR/MTSPRB.STR",
"STR/MTSPRC.STR",
"STR/NEATIN.STR",
"STR/NEATOUT.STR",
"STR/NEBBRES.STR",
"STR/NEKBFIB.STR",
"STR/NEKBFMID.STR",
"STR/ONGAME.STR",
"STR/OUHIPHOG.STR",
"STR/OUNEST.STR",
"STR/OUPALACE.STR",
"STR/OUPORT.STR",
"STR/PABRES.STR",
"STR/PAOWRB.STR",
"STR/PAOWRES.STR",
"STR/PASIRES.STR",
// "STR/PRMINIMA.STR",
"STR/RHW1.STR",
"STR/RHW2.STR",
"STR/RUB1.STR",
"STR/RUBW1.STR",
"STR/RUBW2.STR",
"STR/RUBW3.STR",
"STR/RUBW4.STR",
"STR/RUBW5.STR",
"STR/RUBW6.STR",
"STR/RUDPA1.STR",
"STR/RUDPB1.STR",
"STR/RUDPC1.STR",
"STR/RUGTHRES.STR",
"STR/RUPC1.STR",
"STR/RUPC2.STR",
"STR/RUPC3.STR",
"STR/RUSINTRO.STR",
"STR/RUSVICTO.STR",
"STR/RUTINTRO.STR",
"STR/RUTVICTO.STR",
"STR/SALSAMER.STR",
// "STR/SCBOOK.STR",
"STR/SE1INTRO.STR",
"STR/SE1RES.STR",
"STR/SE2INTRO.STR",
"STR/SEBUSINT.STR",
"STR/SEBUSRES.STR",
"STR/SEC1.STR",
"STR/SEDRES.STR",
"STR/SEHOSEHE.STR",
"STR/SESGRUNT.STR",
"STR/SEW1.STR",
"STR/SEW2.STR",
"STR/TELHIPHO.STR",
"STR/TELTRNTE.STR",
"STR/TELWHACK.STR",
"STR/TESCENE.STR",
"STR/TIDINTRO.STR",
"STR/TOBBA.STR",
"STR/TOBBB.STR",
"STR/TOBINTRO.STR",
"STR/TOBOPEN.STR",
"STR/TOBRES.STR",
"STR/TOBSTART.STR",
"STR/TOFTINTR.STR",
"STR/TOSC0.STR",
"STR/TOSC1.STR",
"STR/TOSC2.STR",
"STR/TOSSCARE.STR",
"STR/TOTURRET.STR",
"STR/TOUPOLES.STR",
"STR/TOUSTART.STR",
"STR/TOUWATER.STR",
"STR/UNBD1.STR",
"STR/UNBD2.STR",
"STR/UNBD3.STR",
"STR/UNBD4.STR",
"STR/UNCONE.STR",
"STR/UNCTHREE.STR",
"STR/UNCTWO.STR",
"STR/UNFSRES.STR",
"STR/UNGSORES.STR",
"STR/VIRESCUE.STR",
"STR/VIRINTRO.STR",
// "STR/WOMAP.STR",
"STR/YOFOREST.STR",
"STR/YOLTRNYS.STR",
"STR/YOLYSAMS.STR",
"STR/YOLYSKDC.STR",
"STR/YOONINTE.STR",
"STR/YOTOMBD.STR"
],
// some objects are directly stored as files on the DVD. This is just text files.
"object_file_names": [

View File

@ -13,8 +13,23 @@
#include "game/common/overlord_common.h"
#include "game/common/str_rpc_types.h"
#include "third-party/fmt/format.h"
namespace decompiler {
StrFileReader::StrFileReader(const fs::path& file_path) {
StrFileReader::StrFileReader(const fs::path& file_path, GameVersion version) : m_version(version) {
switch (version) {
case GameVersion::Jak1:
init_jak1(file_path);
break;
case GameVersion::Jak2:
init_jak2(file_path);
break;
default:
throw std::runtime_error("[StrFileReader] NYI game version");
}
}
void StrFileReader::init_jak1(const fs::path& file_path) {
auto data = file_util::read_binary_file(file_path);
ASSERT(data.size() >= SECTOR_SIZE); // must have at least the header sector
ASSERT(data.size() % SECTOR_SIZE == 0); // should be multiple of the sector size.
@ -62,6 +77,49 @@ StrFileReader::StrFileReader(const fs::path& file_path) {
}
}
void StrFileReader::init_jak2(const fs::path& file_path) {
auto data = file_util::read_binary_file(file_path);
ASSERT(data.size() >= SECTOR_SIZE); // must have at least the header sector
ASSERT(data.size() % SECTOR_SIZE == 0); // should be multiple of the sector size.
int end_sector = int(data.size()) / SECTOR_SIZE;
auto* header = (StrFileHeaderJ2*)data.data();
bool got_zero = false;
for (int i = 0; i < SECTOR_TABLE_SIZE_J2; i++) {
// the chunk is from sector to next_sector
int sector = header->sectors[i];
// assume this chunk continues to the end...
int next_sector = end_sector;
// unless there's another chunk.
if (i + 1 < SECTOR_TABLE_SIZE_J2 && header->sectors[i + 1]) {
next_sector = header->sectors[i + 1];
}
if (sector) {
ASSERT(!got_zero); // shouldn't have a non-zero after a zero!
ASSERT(next_sector > sector); // should have a positive size.
ASSERT(next_sector * SECTOR_SIZE <= int(data.size())); // check for overflowing the file
// get chunk data.
std::vector<u8> chunk;
chunk.insert(chunk.end(), data.begin() + sector * SECTOR_SIZE,
data.begin() + next_sector * SECTOR_SIZE);
m_chunks.emplace_back(std::move(chunk));
} else {
got_zero = true;
}
}
// check our sizes are accurate. Will make sure that we include all data, as our m_chunks
// are sized assuming they are packed in order and dense (sectors);
for (int i = 0; i < SECTOR_TABLE_SIZE_J2; i++) {
if (header->sectors[i]) {
ASSERT(header->sizes[i] == m_chunks.at(i).size());
} else {
ASSERT(header->sizes[i] == 0);
}
}
}
int StrFileReader::chunk_count() const {
return m_chunks.size();
}
@ -138,7 +196,7 @@ std::string StrFileReader::get_full_name(const std::string& short_name) const {
bool done_first = false;
// this string is part of the file info struct and the stuff after it is the file name.
const std::string file_info_string = "/src/next/data/art-group6/";
const auto& file_info_string = get_file_info_string();
// it should occur in each chunk.
int chunk_id = 0;
@ -150,7 +208,7 @@ std::string StrFileReader::get_full_name(const std::string& short_name) const {
if (find_string_in_data(chunk.data(), int(chunk.size()), file_info_string, &offset)) {
offset += file_info_string.length();
} else {
ASSERT(false);
ASSERT_MSG(false, fmt::format("did not find string '{}'", file_info_string));
}
// extract the name info as a "name" + "chunk id" + "-ag.go" format.

View File

@ -9,17 +9,37 @@
#include <vector>
#include "common/common_types.h"
#include "common/util/Assert.h"
#include "common/util/FileUtil.h"
#include "common/versions.h"
namespace decompiler {
class StrFileReader {
public:
explicit StrFileReader(const fs::path& file_path);
explicit StrFileReader(const fs::path& file_path, GameVersion version);
int chunk_count() const;
const std::vector<u8>& get_chunk(int idx) const;
std::string get_full_name(const std::string& short_name) const;
private:
void init_jak1(const fs::path& file_path);
void init_jak2(const fs::path& file_path);
GameVersion m_version;
const std::string get_file_info_string() const {
switch (m_version) {
case GameVersion::Jak1:
return "/src/next/data/art-group6/";
break;
case GameVersion::Jak2:
return "/src/jak2/final/art-group7/";
break;
default:
ASSERT_MSG(false, "NYI get_file_info_string version");
break;
}
}
std::vector<std::vector<u8>> m_chunks;
};
} // namespace decompiler

View File

@ -0,0 +1,55 @@
#pragma once
#include <array>
#include <string>
#include <unordered_map>
#include <vector>
#include "common/util/FileUtil.h"
#include "common/util/FontUtils.h"
namespace decompiler {
struct ObjectFileData;
struct SpoolSubtitleMessage {
enum class Kind { NIL = 0, STRING, IMAGE } kind;
std::string text;
s16 w, h;
std::array<u32, 16> palette;
std::vector<u8> data;
};
struct SpoolSubtitleRange {
float start_frame;
float end_frame;
SpoolSubtitleMessage message[8];
bool operator==(const SpoolSubtitleRange& other) const {
if (start_frame != other.start_frame || end_frame != other.end_frame)
return false;
for (int i = 0; i < 8; ++i) {
if (message[i].kind != other.message[i].kind)
return false;
if (message[i].kind == SpoolSubtitleMessage::Kind::IMAGE) {
if (message[i].data != other.message[i].data ||
message[i].palette != other.message[i].palette || message[i].w != other.message[i].w ||
message[i].h != other.message[i].h)
return false;
} else {
if (message[i].text != other.message[i].text)
return false;
}
}
return true;
}
bool operator!=(const SpoolSubtitleRange& other) const { return !(*this == other); }
};
std::vector<SpoolSubtitleRange> process_spool_subtitles(ObjectFileData& data,
GameTextVersion version);
std::string write_spool_subtitles(
GameTextVersion version,
const fs::path& image_out,
const std::unordered_map<std::string, std::vector<SpoolSubtitleRange>>& data);
} // namespace decompiler

View File

@ -5,9 +5,12 @@
#include <map>
#include <vector>
#include "game_subs.h"
#include "common/goos/Reader.h"
#include "common/util/BitUtils.h"
#include "common/util/FontUtils.h"
#include "common/util/print_float.h"
#include "decompiler/ObjectFile/ObjectFileDB.h"
@ -33,6 +36,9 @@ static const std::unordered_map<GameTextVersion, std::pair<int, int>> sTextCredi
{GameTextVersion::JAK1_V1, {0xb00, 0xf00}},
{GameTextVersion::JAK1_V2, {0xb00, 0xf00}}};
bool word_is_type(const LinkedWord& word, const std::string& type_name) {
return word.kind() == LinkedWord::TYPE_PTR && word.symbol_name() == type_name;
}
} // namespace
/*
@ -241,4 +247,245 @@ std::string write_game_text(
return result;
}
/*
(defun unpack-comp-rle ((arg0 (pointer int8)) (arg1 (pointer int8)))
(local-vars (v1-2 int) (v1-3 uint))
(nop!)
(loop
(loop
(set! v1-2 (-> arg1 0))
(set! arg1 (&-> arg1 1))
(b! (<= v1-2 0) cfg-5 :delay (nop!))
(let ((a2-0 (-> arg1 0)))
(set! arg1 (&-> arg1 1))
(label cfg-3)
(nop!)
(nop!)
(nop!)
(nop!)
(set! (-> arg0 0) a2-0)
)
(set! arg0 (&-> arg0 1))
(b! (> v1-2 0) cfg-3 :delay (set! v1-2 (+ v1-2 -1)))
)
(label cfg-5)
(b! (zero? v1-2) cfg-8 :delay (set! v1-3 (the-as uint (- v1-2))))
(label cfg-6)
(let ((a2-1 (-> arg1 0)))
(set! arg1 (&-> arg1 1))
(nop!)
(nop!)
(set! (-> arg0 0) a2-1)
)
(+! v1-3 -1)
(b! (> (the-as int v1-3) 0) cfg-6 :delay (set! arg0 (&-> arg0 1)))
)
(label cfg-8)
(none)
)
*/
void unpack_comp_rle(s8* dst, s8* src) {
while (true) {
int amt;
while (true) {
// how many bytes to duplicate?
amt = *(src++);
// no more data or copy from src
if (amt <= 0)
break;
// the byte to duplicate
int dup = *(src++);
do {
*(dst++) = dup;
} while (amt-- > 0);
}
// no more data
if (amt == 0)
return;
// how many bytes to simply copy?
int copy = -amt;
do {
*(dst++) = *(src++);
} while (--copy > 0);
}
}
/*
(deftype subtitle-range (basic)
((start-frame float :offset-assert 4)
(end-frame float :offset-assert 8)
(message basic 8 :offset-assert 12)
)
:method-count-assert 9
:size-assert #x2c
:flag-assert #x90000002c
;; Failed to read fields.
)
*/
std::vector<SpoolSubtitleRange> process_spool_subtitles(ObjectFileData& data,
GameTextVersion version) {
std::vector<SpoolSubtitleRange> result;
auto& words = data.linked_data.words_by_seg.at(0);
// type tag for art-group and get art count
ASSERT(word_is_type(words.at(0), "art-group"));
auto elt_count = get_word<s32>(words.at(3));
for (int i = 0; i < elt_count; ++i) {
auto art_label = get_label(data, words.at(8 + i));
auto art_word_ofs = art_label.offset / 4;
if (word_is_type(words.at(art_word_ofs - 1), "art-joint-anim")) {
auto lump_for_art_word = words.at(art_word_ofs + 3);
if (lump_for_art_word.kind() == LinkedWord::PTR) {
// got lump
auto lump_word_ofs = get_label(data, lump_for_art_word).offset / 4;
ASSERT(word_is_type(words.at(lump_word_ofs - 1), "res-lump"));
auto tag_amt = get_word<s32>(words.at(lump_word_ofs));
auto data_base = get_label(data, words.at(lump_word_ofs + 2)).offset / 4;
auto tag_ofs = get_label(data, words.at(lump_word_ofs + 6)).offset / 4;
for (int t = 0; t < tag_amt; ++t) {
// check for a specific kind of res-tag (name, frame, type and length)
if (words.at(tag_ofs).symbol_name() == "subtitle-range" &&
get_word<float>(words.at(tag_ofs + 1)) == -1000000000.0f &&
word_is_type(words.at(tag_ofs + 2), "array") &&
(get_word<u32>(words.at(tag_ofs + 3)) >> 16) == 0x1) {
auto data_ofs = get_word<u32>(words.at(tag_ofs + 3)) & 0xffff;
data_ofs = data_base + align4(data_ofs) / 4;
// the res will be a (array subtitle-range)
auto subtitle_array_ofs = get_label(data, words.at(data_ofs)).offset / 4;
ASSERT(word_is_type(words.at(subtitle_array_ofs + 2), "subtitle-range"));
auto subtitle_amount = get_word<s32>(words.at(subtitle_array_ofs));
for (int s = 0; s < subtitle_amount; ++s) {
auto subtitle_lbl = get_label(data, words.at(subtitle_array_ofs + 3 + s));
auto subtitle_ofs = subtitle_lbl.offset / 4;
// add our new subtitle range
auto& subtitles = result.emplace_back();
subtitles.start_frame = get_word<float>(words.at(subtitle_ofs));
subtitles.end_frame = get_word<float>(words.at(subtitle_ofs + 1));
for (int m = 0; m < 8; ++m) {
// process the message for each language
auto& msg = subtitles.message[m];
const auto& msg_ptr = words.at(subtitle_ofs + 2 + m);
if (msg_ptr.kind() == LinkedWord::Kind::SYM_PTR && msg_ptr.symbol_name() == "#f") {
msg.kind = SpoolSubtitleMessage::Kind::NIL;
} else {
auto m_lbl = get_label(data, msg_ptr);
auto m_ofs = m_lbl.offset / 4;
auto m_type_w = words.at(m_ofs - 1);
ASSERT(m_type_w.kind() == LinkedWord::TYPE_PTR);
if (m_type_w.symbol_name() == "string") {
msg.kind = SpoolSubtitleMessage::Kind::STRING;
auto text = data.linked_data.get_goal_string_by_label(m_lbl);
// escape characters
if (font_bank_exists(version)) {
msg.text = get_font_bank(version)->convert_game_to_utf8(text.c_str());
} else {
msg.text = goos::get_readable_string(text.c_str()); // HACK!
}
} else if (m_type_w.symbol_name() == "subtitle-image") {
msg.kind = SpoolSubtitleMessage::Kind::IMAGE;
auto wh = get_word<u32>(words.at(m_ofs));
msg.w = wh;
msg.h = (wh >> 16) + 1;
msg.h--; // correct height
for (int p = 0; p < 16; ++p) {
msg.palette[p] = get_word<u32>(words.at(m_ofs + 3 + p));
}
// unpack the image data. we have no idea what the input size is,
// so we just copy all of the plain data after this.
auto img_data_ofs = m_ofs + 3 + 16;
int img_data_top = words.size();
for (int check = img_data_ofs; check < img_data_top; ++check) {
if (words.at(check).kind() != LinkedWord::Kind::PLAIN_DATA) {
img_data_top = check;
break;
}
}
// it's a 4-bit image, meaning 2 pixels per byte, so we round up.
msg.data.resize(align2(msg.w * msg.h / 2));
std::vector<u8> input_data;
input_data.resize((img_data_top - img_data_ofs) * 4);
for (int copy = 0; copy < (img_data_top - img_data_ofs); ++copy) {
*((u32*)(input_data.data() + copy * 4)) =
get_word<u32>(words.at(img_data_ofs + copy));
}
// unpack now! hopefully there was enough input data...
unpack_comp_rle((s8*)msg.data.data(), (s8*)input_data.data());
} else {
ASSERT_MSG(false, fmt::format("unknown subtitle message type {}",
m_type_w.symbol_name()));
}
}
}
}
}
tag_ofs += 4;
}
}
}
}
return result;
}
std::string write_spool_subtitles(
GameTextVersion version,
const fs::path& image_out,
const std::unordered_map<std::string, std::vector<SpoolSubtitleRange>>& data) {
// write!
std::string result; // = "\xEF\xBB\xBF"; // UTF-8 encode (don't need this anymore)
bool dump_images = !image_out.empty();
if (dump_images) {
file_util::create_dir_if_needed(image_out);
}
for (auto& [spool_name, subs] : data) {
int image_count = 0;
result += "(\"" + spool_name + "\"\n";
for (auto& sub : subs) {
std::string temp_for_indent = fmt::format(" (({} {}) (", float_to_string(sub.start_frame),
float_to_string(sub.end_frame));
auto indent = temp_for_indent.length();
result += temp_for_indent;
for (int i = 0; i < 8; ++i) {
const auto& msg = sub.message[i];
if (i > 0) {
result += "\n" + std::string(indent, ' ');
}
if (msg.kind == SpoolSubtitleMessage::Kind::NIL) {
result += "#f";
} else {
result += "(";
if (msg.kind == SpoolSubtitleMessage::Kind::IMAGE) {
auto img_name = fmt::format("{}-{}-{}.png", spool_name, i, image_count++);
result += "image " + img_name;
if (dump_images) {
std::vector<u32> rgba_out;
rgba_out.resize(msg.w * msg.h);
for (int px = 0; px < rgba_out.size(); ++px) {
int idx = px & 1 ? msg.data[px / 2] >> 4 : msg.data[px / 2] & 0xf;
rgba_out.at(px) = msg.palette[idx];
}
file_util::write_rgba_png(image_out / img_name, rgba_out.data(), msg.w, msg.h);
}
} else if (msg.kind == SpoolSubtitleMessage::Kind::STRING) {
result += "\"" + msg.text + "\"";
}
result += ")";
}
}
result += ")\n )\n";
}
result += " )\n\n";
}
return result;
}
} // namespace decompiler

View File

@ -21,6 +21,13 @@
#include "third-party/CLI11.hpp"
#include "third-party/json.hpp"
template <typename... Args>
static void mem_log(const std::string& format, Args&&... args) {
#ifndef _WIN32
lg::info("[Mem] " + format, std::forward<Args>(args)...);
#endif
}
int main(int argc, char** argv) {
ArgumentGuard u8_guard(argc, argv);
@ -80,6 +87,9 @@ int main(int argc, char** argv) {
return 1;
}
// these options imply read_spools
config.read_spools |= config.process_subtitle_text || config.process_subtitle_images;
// Check if any banned objects are also in the allowed objects list
// if so, throw an error as this can be a confusing situation
auto intersection = set_util::intersection(config.allowed_objects, config.banned_objects);
@ -112,11 +122,11 @@ int main(int argc, char** argv) {
Timer decomp_timer;
lg::info("[Mem] Top of main: {} MB\n", get_peak_rss() / (1024 * 1024));
mem_log("Top of main: {} MB\n", get_peak_rss() / (1024 * 1024));
init_opcode_info();
lg::info("[Mem] After init: {} MB\n", get_peak_rss() / (1024 * 1024));
mem_log("After init: {} MB\n", get_peak_rss() / (1024 * 1024));
std::vector<fs::path> dgos, objs, strs;
for (const auto& dgo_name : config.dgo_names) {
@ -131,7 +141,7 @@ int main(int argc, char** argv) {
strs.push_back(in_folder / str_name);
}
lg::info("[Mem] After config read: {} MB", get_peak_rss() / (1024 * 1024));
mem_log("After config read: {} MB", get_peak_rss() / (1024 * 1024));
// build file database
lg::info("Setting up object file DB...");
@ -152,7 +162,7 @@ int main(int argc, char** argv) {
}
}
lg::info("[Mem] After DB setup: {} MB", get_peak_rss() / (1024 * 1024));
mem_log("After DB setup: {} MB", get_peak_rss() / (1024 * 1024));
// write out DGO file info
file_util::write_text_file(out_folder / "dgo.txt", db.generate_dgo_listing());
@ -169,10 +179,10 @@ int main(int argc, char** argv) {
// process files (required for all analysis)
db.process_link_data(config);
lg::info("[Mem] After link data: {} MB", get_peak_rss() / (1024 * 1024));
mem_log("After link data: {} MB", get_peak_rss() / (1024 * 1024));
db.find_code(config);
db.process_labels();
lg::info("[Mem] After code: {} MB", get_peak_rss() / (1024 * 1024));
mem_log("After code: {} MB", get_peak_rss() / (1024 * 1024));
// top level decompile (do this before printing asm so we get function names)
if (config.find_functions) {
@ -216,7 +226,7 @@ int main(int argc, char** argv) {
config.hacks.types_with_bad_inspect_methods);
}
lg::info("[Mem] After decomp: {} MB", get_peak_rss() / (1024 * 1024));
mem_log("After decomp: {} MB", get_peak_rss() / (1024 * 1024));
// write out all symbols
file_util::write_text_file(out_folder / "all-syms.gc", db.dts.dump_symbol_types());
@ -242,7 +252,17 @@ int main(int argc, char** argv) {
}
}
lg::info("[Mem] After text: {} MB", get_peak_rss() / (1024 * 1024));
mem_log("After text: {} MB", get_peak_rss() / (1024 * 1024));
if (config.process_subtitle_text || config.process_subtitle_images) {
auto result = db.process_all_spool_subtitles(
config, config.process_subtitle_images ? out_folder / "assets" / "subtitle-images" : "");
if (!result.empty()) {
file_util::write_text_file(out_folder / "assets" / "game_subs.txt", result);
}
}
mem_log("After spool handling: {} MB", get_peak_rss() / (1024 * 1024));
decompiler::TextureDB tex_db;
if (config.process_tpages || config.levels_extract) {
@ -256,7 +276,8 @@ int main(int argc, char** argv) {
}
}
lg::info("[Mem] After textures: {} MB", get_peak_rss() / (1024 * 1024));
mem_log("After textures: {} MB", get_peak_rss() / (1024 * 1024));
auto replacements_path = file_util::get_jak_project_dir() / "texture_replacements";
if (fs::exists(replacements_path)) {
tex_db.replace_textures(replacements_path);
@ -277,7 +298,7 @@ int main(int argc, char** argv) {
config.rip_levels, config.extract_collision, level_out_path);
}
lg::info("[Mem] After extraction: {} MB", get_peak_rss() / (1024 * 1024));
mem_log("After extraction: {} MB", get_peak_rss() / (1024 * 1024));
if (!config.audio_dir_file_name.empty()) {
auto streaming_audio_in = in_folder / "VAG";

View File

@ -205,7 +205,7 @@ void OpenGLRenderer::init_bucket_renderers_jak2() {
init_bucket_renderer<TextureUploadHandler>("tex-all-sprite", BucketCategory::TEX,
BucketId::TEX_ALL_SPRITE);
init_bucket_renderer<Sprite3>("particles", BucketCategory::SPRITE, BucketId::PARTICLES);
init_bucket_renderer<LightningRenderer>("lightning", BucketCategory::OTHER, BucketId::EFFECTS);
init_bucket_renderer<LightningRenderer>("effects", BucketCategory::OTHER, BucketId::EFFECTS);
init_bucket_renderer<TextureUploadHandler>("tex-all-warp", BucketCategory::TEX,
BucketId::TEX_ALL_WARP);
init_bucket_renderer<DirectRenderer>("debug-no-zbuf1", BucketCategory::OTHER,
@ -217,7 +217,7 @@ void OpenGLRenderer::init_bucket_renderers_jak2() {
0x8000);
init_bucket_renderer<DirectRenderer>("screen-filter", BucketCategory::OTHER,
BucketId::SCREEN_FILTER, 256);
init_bucket_renderer<DirectRenderer>("bucket-322", BucketCategory::OTHER, BucketId::BUCKET_322,
init_bucket_renderer<DirectRenderer>("subtitle", BucketCategory::OTHER, BucketId::SUBTITLE,
0x8000);
init_bucket_renderer<DirectRenderer>("debug2", BucketCategory::OTHER, BucketId::DEBUG2, 0x8000);
init_bucket_renderer<DirectRenderer>("debug-no-zbuf2", BucketCategory::OTHER,

View File

@ -403,7 +403,7 @@ enum class BucketId {
TEX_ALL_MAP,
PROGRESS,
SCREEN_FILTER,
BUCKET_322,
SUBTITLE,
BUCKET_323,
DEBUG2,
DEBUG_NO_ZBUF2,

View File

@ -330,7 +330,7 @@
a2-3
a3-2
(if (or (= v1-31 (gui-channel notice)) (= v1-31 (gui-channel notice-low)) (= v1-31 (gui-channel subtitle)))
(bucket-id bucket-322)
(bucket-id subtitle)
(bucket-id progress)
)
)

View File

@ -150,4 +150,4 @@ At any point in time, there are 3 frames in progress:
`(the int (* ,frac 512)))
(defmacro get-screen-y (frac)
`(the int (* ,frac 224)))
`(the int (* ,frac 416)))

View File

@ -405,7 +405,7 @@ for example: tie-s-l1-tfrag
(tex-all-map) ;; tex
(progress) ;; hud | progress
(screen-filter) ;; hud letterbox, no zbuf
(bucket-322) ;; hud
(subtitle) ;; hud
(bucket-323) ;; hud
(debug2) ;; debug
(debug-no-zbuf2) ;; debug

View File

@ -763,7 +763,7 @@
)
(dma-bucket-insert-tag
(-> *display* frames (-> *display* on-screen) bucket-group)
(bucket-id bucket-322)
(bucket-id subtitle)
s4-1
(the-as (pointer dma-tag) a3-15)
)
@ -822,7 +822,7 @@
(set! (-> s2-0 origin y) (+ 1.0 (-> s2-0 origin y)))
(set! (-> s2-0 color) (font-color default))
(set! (-> s2-0 flags) (font-flags shadow kerning middle left large))
(print-game-text (the-as string s3-0) s2-0 #f 44 (bucket-id bucket-322))
(print-game-text (the-as string s3-0) s2-0 #f 44 (bucket-id subtitle))
(gui-control-method-12
*gui-control*
self
@ -1463,7 +1463,7 @@
gp-6
#f
44
(bucket-id bucket-322)
(bucket-id subtitle)
)
)
)

View File

@ -2329,7 +2329,7 @@
)
(dma-bucket-insert-tag
(-> *display* frames (-> *display* on-screen) bucket-group)
(bucket-id bucket-322)
(bucket-id subtitle)
s3-0
(the-as (pointer dma-tag) a3-0)
)
@ -2456,7 +2456,7 @@
)
(dma-bucket-insert-tag
(-> *display* frames (-> *display* on-screen) bucket-group)
(bucket-id bucket-322)
(bucket-id subtitle)
s2-0
(the-as (pointer dma-tag) a3-0)
)

View File

@ -0,0 +1,110 @@
;;-*-Lisp-*-
(in-package goal)
;; This file is used for debugging and testing the large font encoding.
;; This file should *not* be included as part of any packages, it should be manually loaded by the user.
;; To run this:
#|
(mng) ;; build the engine
(lt) ;; connect to the runtime
(ml "goal_src/jak2/pc/util/font-encode-test.gc") ;; build and load this file.
|#
(declare-file (debug))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; constants
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defconstant FONT_ENCODE_TEXT_LEFT (get-screen-x 0.35))
(defconstant FONT_ENCODE_TEXT_Y (get-screen-y 0.4))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define *font-string* (new 'global 'string 64 (the string #f)))
(define *font-string-val* #x96)
(define *text-id-val* #x100)
(define *music-to-be-less-annoying* 'finboss1)
(defun font-encode-test-stop ()
"stop the encode test proc"
(kill-by-name "font-encode" *active-pool*)
)
(defun font-encode-test-start ()
"start the encode test proc"
(font-encode-test-stop)
(process-spawn-function process :name "font-encode"
(lambda :behavior process ()
(let ((fnt (new 'stack 'font-context *font-default-matrix* FONT_ENCODE_TEXT_LEFT FONT_ENCODE_TEXT_Y 0.0
(font-color red) (font-flags shadow kerning large middle)))
)
(set-width! fnt (get-screen-x 0.8))
(set-height! fnt (get-screen-y 0.1))
(loop
(suspend)
(set-setting! 'music *music-to-be-less-annoying* 0 0)
(cond
((not (cpad-hold? 0 r3))
(if (or (cpad-pressed? 0 left) (cpad-hold? 0 l1))
(-! *font-string-val* 1))
(if (or (cpad-pressed? 0 right) (cpad-hold? 0 r1))
(+! *font-string-val* 1))
)
(else
(if (or (cpad-pressed? 0 left) (cpad-hold? 0 l1))
(-! *text-id-val* 1))
(if (or (cpad-pressed? 0 right) (cpad-hold? 0 r1))
(+! *text-id-val* 1))
)
)
(minmax! *font-string-val* 1 #xfff)
(let ((chars (1+ (/ (log2 *font-string-val*) 8))))
(dotimes (i chars)
(set! (-> *font-string* data (- chars i 1)) (ash *font-string-val* (* i -8))))
(set! (-> *font-string* data (1+ (/ (log2 *font-string-val*) 8))) 0)
)
(with-dma-buffer-add-bucket ((buf (-> (current-frame) debug-buf))
(bucket-id debug-no-zbuf2))
(set-scale! fnt 1.0)
(set-origin! fnt (get-screen-x 0.5) (- FONT_ENCODE_TEXT_Y 25))
(set-flags! fnt (font-flags shadow kerning middle))
(draw-string (string-format "#x~x (~x ~x ~x ~x)" *font-string-val*
(-> *font-string* data 0)
(-> *font-string* data 1)
(-> *font-string* data 2)
(-> *font-string* data 3))
buf fnt)
(set-origin! fnt (get-screen-x 0.5) (+ FONT_ENCODE_TEXT_Y 50))
(draw-string (string-format "#x~x" *text-id-val*) buf fnt)
(set-flags! fnt (font-flags shadow kerning large middle))
(set-origin! fnt (get-screen-x 0.5) FONT_ENCODE_TEXT_Y)
(draw-string *font-string* buf fnt)
(set-origin! fnt (get-screen-x 0.5) (+ FONT_ENCODE_TEXT_Y 75))
(set-scale! fnt 0.75)
(draw-string (lookup-text! *common-text* (the game-text-id *text-id-val*) #f) buf fnt)
)
)
)
)
)
)
(font-encode-test-start)

View File

@ -345,7 +345,7 @@
a2-3
a3-2
(if (or (= v1-31 (gui-channel notice)) (= v1-31 (gui-channel notice-low)) (= v1-31 (gui-channel subtitle)))
(bucket-id bucket-322)
(bucket-id subtitle)
(bucket-id progress)
)
)

View File

@ -925,7 +925,7 @@
)
(dma-bucket-insert-tag
(-> *display* frames (-> *display* on-screen) bucket-group)
(bucket-id bucket-322)
(bucket-id subtitle)
s4-1
(the-as (pointer dma-tag) a3-15)
)
@ -986,7 +986,7 @@
(set! (-> s2-0 origin y) (+ 1.0 (-> s2-0 origin y)))
(set! (-> s2-0 color) (font-color default))
(set! (-> s2-0 flags) (font-flags shadow kerning middle left large))
(print-game-text (the-as string s3-0) s2-0 #f 44 (bucket-id bucket-322))
(print-game-text (the-as string s3-0) s2-0 #f 44 (bucket-id subtitle))
(gui-control-method-12
*gui-control*
self
@ -1629,7 +1629,7 @@
gp-6
#f
44
(bucket-id bucket-322)
(bucket-id subtitle)
)
)
)

View File

@ -2470,7 +2470,7 @@
)
(dma-bucket-insert-tag
(-> *display* frames (-> *display* on-screen) bucket-group)
(bucket-id bucket-322)
(bucket-id subtitle)
s3-0
(the-as (pointer dma-tag) a3-0)
)
@ -2597,7 +2597,7 @@
)
(dma-bucket-insert-tag
(-> *display* frames (-> *display* on-screen) bucket-group)
(bucket-id bucket-322)
(bucket-id subtitle)
s2-0
(the-as (pointer dma-tag) a3-0)
)