[decomp] better handling of animation code and art files (#1352)

* update refs

* [decompiler] read and process art groups

* finish decompiler art group selection & detect in `ja-group?`

* make art stuff work on offline tests!

* [decompiler] detect `ja-group!` (primitive)

* corrections.

* more

* use new feature on skel groups!

* find `loop!` as well

* fully fledged `ja` macro & decomp + `loop` detect

* fancy fixed point printing!

* update source

* `:num! max` (i knew i should've done this)

* Update jak1_ntsc_black_label.jsonc

* hi imports

* make compiling the game work

* fix `defskelgroup`

* clang

* update refs

* fix chan

* fix seek and finalboss

* fix tests

* delete unused function

* track let rewrite stats

* reorder `rewrite_let`

* Update .gitattributes

* fix bug with `:num! max`

* Update robotboss-part.gc

* Update goal-lib.gc

* document `ja`

* get rid of pc fixes thing

* use std::abs
This commit is contained in:
ManDude
2022-05-20 02:30:14 +01:00
committed by GitHub
parent 971a69e15c
commit 0212aa10c9
786 changed files with 21658 additions and 49501 deletions
+83 -107
View File
@@ -667,124 +667,94 @@ std::string ObjectFileDB::process_game_count_file() {
return result;
}
namespace {
void get_art_info(ObjectFileDB& db, ObjectFileData& obj) {
if (obj.obj_version == 4) {
const auto& words = obj.linked_data.words_by_seg.at(MAIN_SEGMENT);
if (words.at(0).kind() == LinkedWord::Kind::TYPE_PTR &&
words.at(0).symbol_name() == "art-group") {
// fmt::print("art-group {}:\n", obj.to_unique_name());
auto name = obj.linked_data.get_goal_string_by_label(words.at(2).label_id());
int length = words.at(3).data;
// fmt::print(" length: {}\n", length);
std::unordered_map<int, std::string> art_group_elts;
for (int i = 0; i < length; ++i) {
const auto& word = words.at(8 + i);
if (word.kind() == LinkedWord::Kind::SYM_PTR && word.symbol_name() == "#f") {
continue;
}
const auto& label = obj.linked_data.labels.at(word.label_id());
auto elt_name =
obj.linked_data.get_goal_string_by_label(words.at(label.offset / 4 + 1).label_id());
std::string elt_type = words.at(label.offset / 4 - 1).symbol_name();
std::string unique_name = elt_name;
if (elt_type == "art-joint-geo") {
// the skeleton!
unique_name += "-jg";
} else if (elt_type == "merc-ctrl" || elt_type == "shadow-geo") {
// (maybe mesh-geo as well but that doesnt exist)
// the skin!
unique_name += "-mg";
} else if (elt_type == "art-joint-anim") {
// the animations!
unique_name += "-ja";
} else {
// the something idk!
throw std::runtime_error(
fmt::format("unknown art elt type {} in {}", elt_type, obj.to_unique_name()));
}
art_group_elts[i] = unique_name;
// fmt::print(" {}: {} ({}) -> {}\n", i, elt_name, elt_type, unique_name);
}
db.dts.art_group_info[obj.to_unique_name()] = art_group_elts;
}
}
}
} // namespace
/*!
* This is the main decompiler routine which runs after we've identified functions.
* Get information from art groups.
*/
void ObjectFileDB::analyze_functions_ir1(const Config& config) {
lg::info("- Analyzing Functions...");
void ObjectFileDB::extract_art_info() {
lg::info("Processing art groups...");
Timer timer;
int total_functions = 0;
// Step 1 - analyze the "top level" or "login" code for each object file.
// this will give us type definitions, method definitions, and function definitions...
lg::info(" - Processing top levels...");
timer.start();
for_each_obj([&](ObjectFileData& data) {
if (data.linked_data.segments == 3) {
// the top level segment should have a single function
ASSERT(data.linked_data.functions_by_seg.at(2).size() == 1);
auto& func = data.linked_data.functions_by_seg.at(2).front();
ASSERT(func.guessed_name.empty());
func.guessed_name.set_as_top_level(data.to_unique_name());
func.find_global_function_defs(data.linked_data, dts);
func.find_type_defs(data.linked_data, dts);
func.find_method_defs(data.linked_data, dts);
for_each_obj([&](ObjectFileData& obj) {
try {
get_art_info(*this, obj);
} catch (std::runtime_error& e) {
lg::warn("Error when extracting art group info: {}", e.what());
}
});
// check for function uniqueness.
std::unordered_set<std::string> unique_names;
std::unordered_map<std::string, std::unordered_set<std::string>> duplicated_functions;
lg::info("Processed art groups: in {:.2f} ms\n", timer.getMs());
}
int uid = 1;
for_each_obj([&](ObjectFileData& data) {
int func_in_obj = 0;
for (int segment_id = 0; segment_id < int(data.linked_data.segments); segment_id++) {
for (auto& func : data.linked_data.functions_by_seg.at(segment_id)) {
func.guessed_name.unique_id = uid++;
func.guessed_name.id_in_object = func_in_obj++;
func.guessed_name.object_name = data.to_unique_name();
auto name = func.name();
/*!
* Write out the art group information.
*/
void ObjectFileDB::dump_art_info(const std::string& output_dir) {
lg::info("Writing art group info...");
Timer timer;
if (unique_names.find(name) != unique_names.end()) {
duplicated_functions[name].insert(data.to_unique_name());
}
unique_names.insert(name);
if (config.hacks.asm_functions_by_name.find(name) !=
config.hacks.asm_functions_by_name.end()) {
func.warnings.info("Flagged as asm by config");
func.suspected_asm = true;
}
}
if (!dts.art_group_info.empty()) {
file_util::create_dir_if_needed(file_util::combine_path(output_dir, "import"));
}
for (const auto& [ag_name, info] : dts.art_group_info) {
auto ag_fname = ag_name + ".gc";
auto filename = file_util::get_file_path({output_dir, "import", ag_fname});
std::string result = ";;-*-Lisp-*-\n";
result += "(in-package goal)\n\n";
result += fmt::format(";; {} - art group OpenGOAL import file\n", ag_fname);
result += ";; THIS FILE IS AUTOMATICALLY GENERATED!\n\n";
for (const auto& [idx, elt_name] : info) {
result += print_art_elt_for_dump(ag_name, elt_name, idx);
}
});
result += "\n";
file_util::write_text_file(filename, result);
}
for_each_function([&](Function& func, int segment_id, ObjectFileData& data) {
(void)segment_id;
auto name = func.name();
if (duplicated_functions.find(name) != duplicated_functions.end()) {
duplicated_functions[name].insert(data.to_unique_name());
func.warnings.info("Exists in multiple non-identical object files");
}
});
int total_trivial_cfg_functions = 0;
int total_named_functions = 0;
int asm_funcs = 0;
std::map<int, std::vector<std::string>> unresolved_by_length;
timer.start();
int total_basic_blocks = 0;
// Main Pass over each function...
for_each_function_def_order([&](Function& func, int segment_id, ObjectFileData& data) {
total_functions++;
// first, find basic blocks.
auto blocks = find_blocks_in_function(data.linked_data, segment_id, func);
total_basic_blocks += blocks.size();
func.basic_blocks = blocks;
// analyze the proluge
if (!func.suspected_asm) {
// first, find the prologue/epilogue
func.analyze_prologue(data.linked_data);
}
if (!func.suspected_asm) {
// run analysis
// build a control flow graph, just looking at branch instructions.
func.cfg = build_cfg(data.linked_data, segment_id, func, {}, {});
// convert individual basic blocks to sequences of IR Basic Ops
for (auto& block : func.basic_blocks) {
if (block.end_word > block.start_word) {
auto label_id =
data.linked_data.get_label_at(segment_id, (func.start_word + block.start_word) * 4);
if (label_id != -1) {
block.label_name = data.linked_data.get_label_name(label_id);
}
}
}
} else {
asm_funcs++;
}
});
lg::info("Found {} functions ({} with no control flow)", total_functions,
total_trivial_cfg_functions);
lg::info("Named {}/{} functions ({:.3f}%)", total_named_functions, total_functions,
100.f * float(total_named_functions) / float(total_functions));
lg::info("Excluding {} asm functions", asm_funcs);
lg::info("Written art group info: in {:.2f} ms\n", timer.getMs());
}
void ObjectFileDB::dump_raw_objects(const std::string& output_dir) {
@@ -796,4 +766,10 @@ void ObjectFileDB::dump_raw_objects(const std::string& output_dir) {
file_util::write_binary_file(dest, data.data.data(), data.data.size());
});
}
std::string print_art_elt_for_dump(const std::string& group_name,
const std::string& name,
int idx) {
return fmt::format("(def-art-elt {} {} {})\n", group_name, name, idx);
}
} // namespace decompiler