2022-06-22 23:37:46 -04:00
|
|
|
#include <memory>
|
2021-10-12 20:33:26 -04:00
|
|
|
#include <string>
|
|
|
|
#include <unordered_set>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include "common/common_types.h"
|
2022-06-22 23:37:46 -04:00
|
|
|
#include "common/log/log.h"
|
2021-10-12 20:33:26 -04:00
|
|
|
#include "common/util/FileUtil.h"
|
2022-06-22 23:37:46 -04:00
|
|
|
#include "common/util/Timer.h"
|
|
|
|
#include "common/util/diff.h"
|
2021-10-12 20:33:26 -04:00
|
|
|
#include "common/util/json_util.h"
|
2022-07-05 20:38:13 -04:00
|
|
|
#include <common/util/unicode_util.h>
|
2022-06-22 23:37:46 -04:00
|
|
|
|
2021-03-03 15:42:55 -05:00
|
|
|
#include "decompiler/ObjectFile/ObjectFileDB.h"
|
|
|
|
#include "goalc/compiler/Compiler.h"
|
2022-06-22 23:37:46 -04:00
|
|
|
|
2022-07-06 18:10:38 -04:00
|
|
|
#include "third-party/CLI11.hpp"
|
2022-06-22 23:37:46 -04:00
|
|
|
#include "third-party/fmt/format.h"
|
2021-05-09 17:03:58 -04:00
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
// json config file data (previously was in source of offline_test_main.cpp)
|
|
|
|
struct OfflineTestConfig {
|
|
|
|
std::vector<std::string> dgos;
|
|
|
|
std::unordered_set<std::string> skip_compile_files;
|
|
|
|
std::unordered_set<std::string> skip_compile_functions;
|
|
|
|
std::unordered_map<std::string, std::unordered_set<std::string>> skip_compile_states;
|
2021-10-10 19:53:19 -04:00
|
|
|
};
|
2021-03-03 20:52:25 -05:00
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
struct DecompilerFile {
|
2022-07-05 20:38:13 -04:00
|
|
|
fs::path path;
|
2021-10-12 20:33:26 -04:00
|
|
|
std::string name_in_dgo;
|
|
|
|
std::string unique_name;
|
|
|
|
std::string reference;
|
2021-09-06 21:10:19 -04:00
|
|
|
};
|
|
|
|
|
2022-05-19 21:30:14 -04:00
|
|
|
struct DecompilerArtFile {
|
|
|
|
std::string name_in_dgo;
|
|
|
|
std::string unique_name;
|
|
|
|
};
|
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
struct Decompiler {
|
|
|
|
std::unique_ptr<decompiler::ObjectFileDB> db;
|
|
|
|
std::unique_ptr<decompiler::Config> config;
|
|
|
|
};
|
2021-07-11 16:35:25 -04:00
|
|
|
|
2022-07-06 18:10:38 -04:00
|
|
|
// TODO - this should probably go somewhere common when it's needed eventually
|
|
|
|
std::unordered_map<std::string, std::string> game_name_to_config = {
|
|
|
|
{"jak1", "jak1_ntsc_black_label.jsonc"},
|
|
|
|
{"jak2", "jak2_ntsc_v1.jsonc"}};
|
|
|
|
|
|
|
|
// TODO - i think these should be partitioned by game name instead of it being in the filename
|
|
|
|
// (and the names not being consistent)
|
2022-07-06 21:18:08 -04:00
|
|
|
std::unordered_map<std::string, std::string> game_name_to_all_types = {
|
|
|
|
{"jak1", "all-types.gc"},
|
|
|
|
{"jak2", "jak2/all-types.gc"}};
|
2022-07-06 18:10:38 -04:00
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
Decompiler setup_decompiler(const std::vector<DecompilerFile>& files,
|
2022-05-19 21:30:14 -04:00
|
|
|
const std::vector<DecompilerArtFile>& art_files,
|
2022-07-06 18:10:38 -04:00
|
|
|
const fs::path& iso_data_path,
|
|
|
|
const OfflineTestConfig& offline_config,
|
|
|
|
const std::string& game_name) {
|
|
|
|
// TODO - pull out extractor logic to determine release into common and use here
|
2021-10-12 20:33:26 -04:00
|
|
|
Decompiler dc;
|
|
|
|
decompiler::init_opcode_info();
|
|
|
|
dc.config = std::make_unique<decompiler::Config>(decompiler::read_config_file(
|
2022-07-06 18:10:38 -04:00
|
|
|
(file_util::get_jak_project_dir() / "decompiler" / "config" / game_name_to_config[game_name])
|
2022-04-03 19:17:03 -04:00
|
|
|
.string(),
|
|
|
|
{}));
|
2021-10-12 20:33:26 -04:00
|
|
|
|
|
|
|
// modify the config
|
|
|
|
std::unordered_set<std::string> object_files;
|
|
|
|
for (auto& file : files) {
|
|
|
|
object_files.insert(file.name_in_dgo); // todo, make this work with unique_name
|
|
|
|
}
|
2022-05-19 21:30:14 -04:00
|
|
|
for (auto& file : art_files) {
|
|
|
|
object_files.insert(file.unique_name);
|
|
|
|
}
|
2021-07-11 16:35:25 -04:00
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
dc.config->allowed_objects = object_files;
|
|
|
|
// don't try to do this because we can't write the file
|
|
|
|
dc.config->generate_symbol_definition_map = false;
|
2021-03-03 15:42:55 -05:00
|
|
|
|
2022-07-05 20:38:13 -04:00
|
|
|
std::vector<fs::path> dgo_paths;
|
2022-07-06 18:10:38 -04:00
|
|
|
for (auto& x : offline_config.dgos) {
|
|
|
|
dgo_paths.push_back(iso_data_path / x);
|
2021-03-03 15:42:55 -05:00
|
|
|
}
|
|
|
|
|
2022-07-05 20:38:13 -04:00
|
|
|
dc.db = std::make_unique<decompiler::ObjectFileDB>(dgo_paths, dc.config->obj_file_name_map_file,
|
|
|
|
std::vector<fs::path>{},
|
|
|
|
std::vector<fs::path>{}, *dc.config);
|
2021-03-03 15:42:55 -05:00
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
std::unordered_set<std::string> db_files;
|
|
|
|
for (auto& files_by_name : dc.db->obj_files_by_name) {
|
|
|
|
for (auto& f : files_by_name.second) {
|
|
|
|
db_files.insert(f.to_unique_name());
|
2021-03-03 15:42:55 -05:00
|
|
|
}
|
2021-10-12 20:33:26 -04:00
|
|
|
}
|
2021-03-03 15:42:55 -05:00
|
|
|
|
2022-05-19 21:30:14 -04:00
|
|
|
if (db_files.size() != files.size() + art_files.size()) {
|
2022-07-06 18:10:38 -04:00
|
|
|
lg::error("DB file error.");
|
2021-10-12 20:33:26 -04:00
|
|
|
for (auto& f : files) {
|
|
|
|
if (!db_files.count(f.unique_name)) {
|
2022-07-06 18:10:38 -04:00
|
|
|
lg::error("didn't find {}\n", f.unique_name);
|
2021-03-03 15:42:55 -05:00
|
|
|
}
|
|
|
|
}
|
2022-05-19 21:30:14 -04:00
|
|
|
for (auto& f : art_files) {
|
|
|
|
if (!db_files.count(f.unique_name)) {
|
2022-07-06 18:10:38 -04:00
|
|
|
lg::error("didn't find {}\n", f.unique_name);
|
2022-05-19 21:30:14 -04:00
|
|
|
}
|
|
|
|
}
|
2021-10-12 20:33:26 -04:00
|
|
|
exit(1);
|
|
|
|
}
|
2021-03-03 15:42:55 -05:00
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
return dc;
|
2021-03-03 15:42:55 -05:00
|
|
|
}
|
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
void disassemble(Decompiler& dc) {
|
|
|
|
dc.db->process_link_data(*dc.config);
|
|
|
|
dc.db->find_code(*dc.config);
|
|
|
|
dc.db->process_labels();
|
2021-03-03 15:42:55 -05:00
|
|
|
}
|
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
void decompile(Decompiler& dc, const OfflineTestConfig& config) {
|
2022-05-19 21:30:14 -04:00
|
|
|
dc.db->extract_art_info();
|
2022-06-08 18:34:52 -04:00
|
|
|
dc.db->ir2_top_level_pass(*dc.config);
|
2021-10-16 21:01:23 -04:00
|
|
|
dc.db->analyze_functions_ir2({}, *dc.config, config.skip_compile_functions,
|
|
|
|
config.skip_compile_states);
|
2021-03-03 15:42:55 -05:00
|
|
|
}
|
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
std::string strip_trailing_newlines(const std::string& in) {
|
|
|
|
std::string out = in;
|
|
|
|
while (!out.empty() && out.back() == '\n') {
|
|
|
|
out.pop_back();
|
|
|
|
}
|
|
|
|
return out;
|
2021-03-03 15:42:55 -05:00
|
|
|
}
|
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
decompiler::ObjectFileData& get_data(Decompiler& dc,
|
|
|
|
const std::string& unique_name,
|
|
|
|
const std::string& name_in_dgo) {
|
|
|
|
auto& files = dc.db->obj_files_by_name.at(name_in_dgo);
|
|
|
|
auto it = std::find_if(files.begin(), files.end(), [&](const decompiler::ObjectFileData& data) {
|
|
|
|
return data.to_unique_name() == unique_name;
|
2021-03-03 15:42:55 -05:00
|
|
|
});
|
2022-02-08 19:02:47 -05:00
|
|
|
ASSERT(it != files.end());
|
2021-10-12 20:33:26 -04:00
|
|
|
return *it;
|
2021-03-03 15:42:55 -05:00
|
|
|
}
|
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
int line_count(const std::string& str) {
|
|
|
|
int result = 0;
|
|
|
|
for (auto& c : str) {
|
|
|
|
if (c == '\n') {
|
|
|
|
result++;
|
2021-03-03 15:42:55 -05:00
|
|
|
}
|
|
|
|
}
|
2021-10-12 20:33:26 -04:00
|
|
|
return result;
|
2021-03-03 15:42:55 -05:00
|
|
|
}
|
2021-07-11 16:35:25 -04:00
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
struct CompareResult {
|
|
|
|
std::vector<std::string> failing_files;
|
|
|
|
int total_files = 0;
|
|
|
|
int ok_files = 0;
|
|
|
|
int total_lines = 0;
|
2021-07-11 16:35:25 -04:00
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
bool total_pass = true;
|
|
|
|
};
|
2021-03-03 15:42:55 -05:00
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
CompareResult compare(Decompiler& dc, const std::vector<DecompilerFile>& refs, bool dump_mode) {
|
|
|
|
CompareResult compare_result;
|
|
|
|
|
|
|
|
for (const auto& file : refs) {
|
|
|
|
auto& data = get_data(dc, file.unique_name, file.name_in_dgo);
|
|
|
|
std::string result = strip_trailing_newlines(data.full_output);
|
|
|
|
std::string ref = strip_trailing_newlines(file_util::read_text_file(file.path.string()));
|
|
|
|
compare_result.total_files++;
|
|
|
|
compare_result.total_lines += line_count(result);
|
|
|
|
if (result != ref) {
|
|
|
|
compare_result.failing_files.push_back(file.unique_name);
|
|
|
|
compare_result.total_pass = false;
|
|
|
|
fmt::print("Reference test failure on {}:\n", file.unique_name);
|
2021-10-15 21:31:22 -04:00
|
|
|
fmt::print("{}\n", diff_strings(ref, result));
|
2021-10-12 20:33:26 -04:00
|
|
|
|
|
|
|
if (dump_mode) {
|
2022-07-06 18:10:38 -04:00
|
|
|
auto failure_dir = file_util::get_jak_project_dir() / "failures";
|
|
|
|
file_util::create_dir_if_needed(failure_dir);
|
|
|
|
file_util::write_text_file(failure_dir / fmt::format("{}_REF.gc", file.unique_name),
|
|
|
|
result);
|
2021-05-06 00:42:49 -04:00
|
|
|
}
|
|
|
|
} else {
|
2021-10-12 20:33:26 -04:00
|
|
|
compare_result.ok_files++;
|
2021-05-06 00:42:49 -04:00
|
|
|
}
|
2021-03-03 15:42:55 -05:00
|
|
|
}
|
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
return compare_result;
|
2021-03-24 19:16:31 -04:00
|
|
|
}
|
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
bool compile(Decompiler& dc,
|
|
|
|
const std::vector<DecompilerFile>& refs,
|
2022-07-06 18:10:38 -04:00
|
|
|
const OfflineTestConfig& config,
|
|
|
|
const std::string& game_name) {
|
2021-10-12 20:33:26 -04:00
|
|
|
fmt::print("Setting up compiler...\n");
|
2022-07-06 21:18:08 -04:00
|
|
|
Compiler compiler(game_name_to_version(game_name));
|
2021-03-03 15:42:55 -05:00
|
|
|
|
2022-07-06 18:10:38 -04:00
|
|
|
compiler.run_front_end_on_file({"decompiler", "config", game_name_to_all_types[game_name]});
|
|
|
|
compiler.run_front_end_on_file(
|
|
|
|
{"test", "decompiler", "reference", game_name, "decompiler-macros.gc"});
|
2021-03-05 18:48:01 -05:00
|
|
|
|
2021-03-24 19:16:31 -04:00
|
|
|
Timer timer;
|
|
|
|
int total_lines = 0;
|
2021-10-12 20:33:26 -04:00
|
|
|
for (const auto& file : refs) {
|
|
|
|
if (config.skip_compile_files.count(file.name_in_dgo)) {
|
|
|
|
fmt::print("Skipping {}\n", file.name_in_dgo);
|
2021-05-01 15:51:53 -04:00
|
|
|
continue;
|
|
|
|
}
|
2021-05-03 08:54:49 -04:00
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
fmt::print("Compiling {}...\n", file.unique_name);
|
2021-05-03 08:54:49 -04:00
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
auto& data = get_data(dc, file.unique_name, file.name_in_dgo);
|
2021-03-03 15:42:55 -05:00
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
try {
|
|
|
|
const auto& src = data.output_with_skips;
|
2021-07-11 16:35:25 -04:00
|
|
|
total_lines += line_count(src);
|
2022-01-15 23:31:07 -05:00
|
|
|
compiler.run_full_compiler_on_string_no_save(src, file.name_in_dgo);
|
2021-10-12 20:33:26 -04:00
|
|
|
} catch (const std::exception& e) {
|
|
|
|
fmt::print("Compiler exception: {}\n", e.what());
|
|
|
|
return false;
|
2021-07-11 16:35:25 -04:00
|
|
|
}
|
2021-03-03 15:42:55 -05:00
|
|
|
}
|
2021-03-24 19:16:31 -04:00
|
|
|
auto time = timer.getSeconds();
|
2021-10-12 20:33:26 -04:00
|
|
|
fmt::print("Total Lines Compiled: {}. Lines/second: {:.1f}\n", total_lines,
|
|
|
|
(float)total_lines / time);
|
|
|
|
|
|
|
|
return true;
|
2021-04-10 21:17:36 -04:00
|
|
|
}
|
2021-10-12 20:33:26 -04:00
|
|
|
|
2022-07-06 18:10:38 -04:00
|
|
|
std::vector<DecompilerArtFile> find_art_files(const std::string& game_name,
|
|
|
|
const std::vector<std::string>& dgos) {
|
|
|
|
std::vector<DecompilerArtFile> result;
|
|
|
|
|
|
|
|
// use the all_objs.json file to place them in the correct build order
|
|
|
|
auto obj_json = parse_commented_json(
|
|
|
|
file_util::read_text_file(
|
|
|
|
(file_util::get_jak_project_dir() / "goal_src" / game_name / "build" / "all_objs.json")
|
|
|
|
.string()),
|
|
|
|
"all_objs.json");
|
|
|
|
|
|
|
|
for (const auto& x : obj_json) {
|
|
|
|
auto unique_name = x[0].get<std::string>();
|
|
|
|
auto version = x[2].get<int>();
|
|
|
|
|
|
|
|
std::vector<std::string> dgoList = x[3].get<std::vector<std::string>>();
|
|
|
|
if (version == 4) {
|
|
|
|
bool skip_this = false;
|
|
|
|
|
|
|
|
// Check to see if we've included atleast one of the DGO/CGOs in our hardcoded list
|
|
|
|
// If not BLOW UP
|
|
|
|
bool dgoValidated = false;
|
|
|
|
for (int i = 0; i < (int)dgoList.size(); i++) {
|
|
|
|
std::string& dgo = dgoList.at(i);
|
|
|
|
if (dgo == "NO-XGO") {
|
|
|
|
skip_this = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// can either be in the DGO or CGO folder, and can either end with .CGO or .DGO
|
|
|
|
// TODO - Jak 2 Folder structure will be different!
|
|
|
|
if (std::find(dgos.begin(), dgos.end(), fmt::format("DGO/{}.DGO", dgo)) != dgos.end() ||
|
|
|
|
std::find(dgos.begin(), dgos.end(), fmt::format("DGO/{}.CGO", dgo)) != dgos.end() ||
|
|
|
|
std::find(dgos.begin(), dgos.end(), fmt::format("CGO/{}.DGO", dgo)) != dgos.end() ||
|
|
|
|
std::find(dgos.begin(), dgos.end(), fmt::format("CGO/{}.CGO", dgo)) != dgos.end()) {
|
|
|
|
dgoValidated = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (skip_this) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!dgoValidated) {
|
|
|
|
lg::error(
|
|
|
|
"File [{}] is in the following DGOs [{}], and not one of these is in our list! Add "
|
|
|
|
"it!",
|
|
|
|
unique_name, fmt::join(dgoList, ", "));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
DecompilerArtFile file;
|
|
|
|
file.unique_name = unique_name;
|
|
|
|
file.name_in_dgo = x[1];
|
|
|
|
result.push_back(file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<DecompilerFile> find_files(const std::string& game_name,
|
|
|
|
const std::vector<std::string>& dgos) {
|
|
|
|
std::vector<DecompilerFile> result;
|
|
|
|
|
|
|
|
auto base_dir =
|
|
|
|
file_util::get_jak_project_dir() / "test" / "decompiler" / "reference" / game_name;
|
|
|
|
auto ref_file_paths = file_util::find_files_recursively(base_dir, std::regex(".*_REF\\..*"));
|
|
|
|
std::unordered_map<std::string, fs::path> ref_file_names = {};
|
|
|
|
for (const auto& path : ref_file_paths) {
|
|
|
|
auto ref_name = path.filename().replace_extension().string();
|
|
|
|
ref_name.erase(ref_name.begin() + ref_name.find("_REF"), ref_name.end());
|
|
|
|
ref_file_names[ref_name] = path;
|
|
|
|
}
|
|
|
|
|
|
|
|
lg::info("Found {} reference files", ref_file_paths.size());
|
|
|
|
|
|
|
|
// use the all_objs.json file to place them in the correct build order
|
|
|
|
auto obj_json = parse_commented_json(
|
|
|
|
file_util::read_text_file(
|
|
|
|
(file_util::get_jak_project_dir() / "goal_src" / game_name / "build" / "all_objs.json")
|
|
|
|
.string()),
|
|
|
|
"all_objs.json");
|
|
|
|
|
|
|
|
std::unordered_set<std::string> matched_files;
|
|
|
|
for (auto& x : obj_json) {
|
|
|
|
auto unique_name = x[0].get<std::string>();
|
|
|
|
|
|
|
|
std::vector<std::string> dgoList = x[3].get<std::vector<std::string>>();
|
|
|
|
auto it = ref_file_names.find(unique_name);
|
|
|
|
if (it != ref_file_names.end()) {
|
|
|
|
// Check to see if we've included atleast one of the DGO/CGOs in our hardcoded list
|
|
|
|
// If not BLOW UP
|
|
|
|
bool dgoValidated = false;
|
|
|
|
for (int i = 0; i < (int)dgoList.size(); i++) {
|
|
|
|
std::string& dgo = dgoList.at(i);
|
|
|
|
// can either be in the DGO or CGO folder, and can either end with .CGO or .DGO
|
|
|
|
// TODO - Jak 2 Folder structure will be different!
|
|
|
|
if (std::find(dgos.begin(), dgos.end(), fmt::format("DGO/{}.DGO", dgo)) != dgos.end() ||
|
|
|
|
std::find(dgos.begin(), dgos.end(), fmt::format("DGO/{}.CGO", dgo)) != dgos.end() ||
|
|
|
|
std::find(dgos.begin(), dgos.end(), fmt::format("CGO/{}.DGO", dgo)) != dgos.end() ||
|
|
|
|
std::find(dgos.begin(), dgos.end(), fmt::format("CGO/{}.CGO", dgo)) != dgos.end()) {
|
|
|
|
dgoValidated = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!dgoValidated) {
|
|
|
|
lg::error(
|
|
|
|
"File [{}] is in the following DGOs [{}], and not one of these is in our list! Add "
|
|
|
|
"it!",
|
|
|
|
unique_name, fmt::join(dgoList, ", "));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
DecompilerFile file;
|
|
|
|
file.path = it->second;
|
|
|
|
file.unique_name = it->first;
|
|
|
|
file.name_in_dgo = x[1];
|
|
|
|
result.push_back(file);
|
|
|
|
matched_files.insert(unique_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (matched_files.size() != ref_file_names.size()) {
|
|
|
|
lg::error("Some REF files were not matched to files in all_objs.json:");
|
|
|
|
for (const auto& [path, flag] : ref_file_names) {
|
|
|
|
if (matched_files.count(path) == 0) {
|
|
|
|
lg::error("- '{}'", path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Read and parse the json config file, config.json, located in test/offline
|
|
|
|
*/
|
|
|
|
std::optional<OfflineTestConfig> parse_config(const std::string_view& game_name) {
|
|
|
|
lg::info("Reading Configuration...");
|
|
|
|
auto json_file_path =
|
|
|
|
file_util::get_jak_project_dir() / "test" / "offline" / "config" / game_name / "config.jsonc";
|
|
|
|
if (!fs::exists(json_file_path)) {
|
|
|
|
lg::error("Couldn't load configuration, '{}' doesn't exist", json_file_path.string());
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
auto json = parse_commented_json(file_util::read_text_file(json_file_path.string()),
|
|
|
|
json_file_path.string());
|
|
|
|
OfflineTestConfig result;
|
|
|
|
result.dgos = json["dgos"].get<std::vector<std::string>>();
|
|
|
|
result.skip_compile_files = json["skip_compile_files"].get<std::unordered_set<std::string>>();
|
|
|
|
result.skip_compile_functions =
|
|
|
|
json["skip_compile_functions"].get<std::unordered_set<std::string>>();
|
|
|
|
result.skip_compile_states =
|
|
|
|
json["skip_compile_states"]
|
|
|
|
.get<std::unordered_map<std::string, std::unordered_set<std::string>>>();
|
|
|
|
return std::make_optional(result);
|
|
|
|
}
|
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
int main(int argc, char* argv[]) {
|
2022-07-05 20:38:13 -04:00
|
|
|
#ifdef _WIN32
|
|
|
|
auto utf8_args = get_widechar_cli_args();
|
|
|
|
std::vector<char*> string_ptrs;
|
|
|
|
for (auto& str : utf8_args) {
|
|
|
|
string_ptrs.push_back(str.data());
|
|
|
|
}
|
|
|
|
argv = string_ptrs.data();
|
|
|
|
#endif
|
|
|
|
|
2021-10-12 20:33:26 -04:00
|
|
|
lg::initialize();
|
2022-07-06 18:10:38 -04:00
|
|
|
|
|
|
|
bool dump_current_output = false;
|
|
|
|
std::string iso_data_path;
|
|
|
|
std::string game_name;
|
|
|
|
// Useful for testing in debug mode (dont have to wait for everything to finish)
|
|
|
|
int max_files = -1;
|
|
|
|
|
|
|
|
CLI::App app{"OpenGOAL - Offline Reference Test Runner"};
|
|
|
|
app.add_option("--iso_data_path", iso_data_path, "The path to the folder with the ISO data files")
|
|
|
|
->check(CLI::ExistingPath)
|
|
|
|
->required();
|
|
|
|
app.add_option("--game", game_name, "The game name, for example 'jak1'")->required();
|
|
|
|
app.add_flag("-d,--dump_current_output", dump_current_output,
|
|
|
|
"Output the current output to a folder, use in conjunction with the reference test "
|
|
|
|
"files update script");
|
|
|
|
app.add_flag("-m,--max_files", max_files,
|
|
|
|
"Limit the amount of files ran in a single test, picks the first N");
|
|
|
|
app.validate_positionals();
|
|
|
|
CLI11_PARSE(app, argc, argv);
|
|
|
|
|
2022-04-15 18:01:47 -04:00
|
|
|
if (!file_util::setup_project_path(std::nullopt)) {
|
2022-07-06 18:10:38 -04:00
|
|
|
lg::error("Couldn't setup project path, tool is supposed to be ran in the jak-project repo!");
|
2022-04-03 19:17:03 -04:00
|
|
|
return 1;
|
|
|
|
}
|
2021-10-12 20:33:26 -04:00
|
|
|
|
2022-07-06 18:10:38 -04:00
|
|
|
auto config = parse_config(game_name);
|
|
|
|
if (!config.has_value()) {
|
|
|
|
return 1;
|
|
|
|
}
|
2021-10-12 20:33:26 -04:00
|
|
|
|
2022-07-06 18:10:38 -04:00
|
|
|
lg::info("Finding files...");
|
|
|
|
auto files = find_files(game_name, config->dgos);
|
|
|
|
if (max_files > 0 && max_files < files.size()) {
|
|
|
|
files.erase(files.begin() + max_files, files.end());
|
2021-10-12 20:33:26 -04:00
|
|
|
}
|
2022-07-06 18:10:38 -04:00
|
|
|
auto art_files = find_art_files(game_name, config->dgos);
|
2021-10-12 20:33:26 -04:00
|
|
|
|
2022-07-06 18:10:38 -04:00
|
|
|
lg::info("Setting up decompiler and loading files...");
|
|
|
|
auto decompiler =
|
|
|
|
setup_decompiler(files, art_files, fs::path(iso_data_path), config.value(), game_name);
|
2021-10-12 20:33:26 -04:00
|
|
|
|
2022-07-06 18:10:38 -04:00
|
|
|
lg::info("Disassembling files...");
|
2021-10-12 20:33:26 -04:00
|
|
|
disassemble(decompiler);
|
|
|
|
|
2022-07-06 18:10:38 -04:00
|
|
|
lg::info("Decompiling...");
|
|
|
|
decompile(decompiler, config.value());
|
2021-10-12 20:33:26 -04:00
|
|
|
|
2022-07-06 18:10:38 -04:00
|
|
|
lg::info("Comparing...");
|
|
|
|
auto compare_result = compare(decompiler, files, dump_current_output);
|
|
|
|
lg::info("Compared {} lines. {}/{} files passed.", compare_result.total_lines,
|
|
|
|
compare_result.ok_files, compare_result.total_files);
|
2021-10-12 20:33:26 -04:00
|
|
|
|
|
|
|
if (!compare_result.failing_files.empty()) {
|
2022-07-06 18:10:38 -04:00
|
|
|
lg::error("Failing files:");
|
2021-10-12 20:33:26 -04:00
|
|
|
for (auto& f : compare_result.failing_files) {
|
2022-07-06 18:10:38 -04:00
|
|
|
lg::error("- {}", f);
|
2021-10-12 20:33:26 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-06 18:10:38 -04:00
|
|
|
bool compile_result = compile(decompiler, files, config.value(), game_name);
|
2021-10-12 20:33:26 -04:00
|
|
|
|
|
|
|
if (compare_result.total_pass && compile_result) {
|
2022-07-06 18:10:38 -04:00
|
|
|
lg::info("Pass!");
|
2021-10-12 20:33:26 -04:00
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
if (!compile_result) {
|
2022-07-06 18:10:38 -04:00
|
|
|
lg::error("Compilation failed.");
|
2021-10-12 20:33:26 -04:00
|
|
|
}
|
|
|
|
if (!compare_result.total_pass) {
|
2022-07-06 18:10:38 -04:00
|
|
|
lg::error("Comparison failed.");
|
2021-10-12 20:33:26 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 1;
|
2021-10-16 21:01:23 -04:00
|
|
|
}
|