mirror of
https://github.com/open-goal/jak-project
synced 2026-07-12 07:38:53 -04:00
[decompiler] Jak 2 modifications, new all-types code (#1553)
* temp * look at old game types * clean up
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
add_library(
|
||||
decomp
|
||||
|
||||
analysis/analyze_inspect_method.cpp
|
||||
analysis/atomic_op_builder.cpp
|
||||
analysis/cfg_builder.cpp
|
||||
analysis/expression_build.cpp
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "Function.h"
|
||||
|
||||
#include "common/goos/PrettyPrinter.h"
|
||||
#include "common/symbols.h"
|
||||
#include "common/util/Assert.h"
|
||||
|
||||
#include "decompiler/Disasm/InstructionMatching.h"
|
||||
@@ -2559,13 +2560,14 @@ void ControlFlowGraph::flag_early_exit(const std::vector<BasicBlock>& blocks) {
|
||||
}
|
||||
}
|
||||
|
||||
CfgVtx::DelaySlotKind get_delay_slot(const Instruction& i) {
|
||||
CfgVtx::DelaySlotKind get_delay_slot(const Instruction& i, GameVersion version) {
|
||||
if (is_nop(i)) {
|
||||
return CfgVtx::DelaySlotKind::NOP;
|
||||
} else if (is_gpr_3(i, InstructionKind::OR, {}, Register(Reg::GPR, Reg::S7),
|
||||
Register(Reg::GPR, Reg::R0))) {
|
||||
return CfgVtx::DelaySlotKind::SET_REG_FALSE;
|
||||
} else if (is_gpr_2_imm_int(i, InstructionKind::DADDIU, {}, Register(Reg::GPR, Reg::S7), 8)) {
|
||||
} else if (is_gpr_2_imm_int(i, InstructionKind::DADDIU, {}, Register(Reg::GPR, Reg::S7),
|
||||
true_symbol_offset(version))) {
|
||||
return CfgVtx::DelaySlotKind::SET_REG_TRUE;
|
||||
} else {
|
||||
return CfgVtx::DelaySlotKind::OTHER;
|
||||
@@ -2576,7 +2578,7 @@ namespace {
|
||||
/*!
|
||||
* Is this instruction possible in the delay slot, without using inline assembly?
|
||||
*/
|
||||
bool branch_delay_asm(const Instruction& i) {
|
||||
bool branch_delay_asm(const Instruction& i, GameVersion version) {
|
||||
if (is_nop(i)) {
|
||||
// nop can be used as a delay
|
||||
return false;
|
||||
@@ -2584,7 +2586,8 @@ bool branch_delay_asm(const Instruction& i) {
|
||||
Register(Reg::GPR, Reg::R0))) {
|
||||
// set false is used in ifs, etc
|
||||
return false;
|
||||
} else if (is_gpr_2_imm_int(i, InstructionKind::DADDIU, {}, Register(Reg::GPR, Reg::S7), 8)) {
|
||||
} else if (is_gpr_2_imm_int(i, InstructionKind::DADDIU, {}, Register(Reg::GPR, Reg::S7),
|
||||
true_symbol_offset(version))) {
|
||||
// set true is used in sc
|
||||
return false;
|
||||
} else if (is_gpr_3(i, InstructionKind::OR, {}, {}, Register(Reg::GPR, Reg::R0))) {
|
||||
@@ -2609,12 +2612,12 @@ bool branch_delay_asm(const Instruction& i) {
|
||||
/*!
|
||||
* Build and resolve a Control Flow Graph as much as possible.
|
||||
*/
|
||||
std::shared_ptr<ControlFlowGraph> build_cfg(
|
||||
const LinkedObjectFile& file,
|
||||
int seg,
|
||||
Function& func,
|
||||
const CondWithElseLengthHack& cond_with_else_hack,
|
||||
const std::unordered_set<int>& blocks_ending_in_asm_br) {
|
||||
std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file,
|
||||
int seg,
|
||||
Function& func,
|
||||
const CondWithElseLengthHack& cond_with_else_hack,
|
||||
const std::unordered_set<int>& blocks_ending_in_asm_br,
|
||||
GameVersion version) {
|
||||
// fmt::print("START {}\n", func.guessed_name.to_string());
|
||||
auto cfg = std::make_shared<ControlFlowGraph>();
|
||||
|
||||
@@ -2708,7 +2711,7 @@ std::shared_ptr<ControlFlowGraph> build_cfg(
|
||||
if (is_branch(branch_candidate, false)) {
|
||||
blocks.at(i)->end_branch.has_branch = true;
|
||||
blocks.at(i)->end_branch.branch_likely = false;
|
||||
blocks.at(i)->end_branch.kind = get_delay_slot(delay_slot_candidate);
|
||||
blocks.at(i)->end_branch.kind = get_delay_slot(delay_slot_candidate, version);
|
||||
bool branch_always = is_always_branch(branch_candidate);
|
||||
|
||||
// need to find block target
|
||||
@@ -2779,7 +2782,7 @@ std::shared_ptr<ControlFlowGraph> build_cfg(
|
||||
if (is_branch(likely_branch_candidate, true)) {
|
||||
// likely branch!
|
||||
auto following = func.instructions.at(likely_branch_idx + 1);
|
||||
if (branch_delay_asm(following)) {
|
||||
if (branch_delay_asm(following, version)) {
|
||||
b->end_branch.asm_branch = true;
|
||||
if (debug_asm_branch) {
|
||||
fmt::print("LIKELY ASM BRANCH: {} and {}\n",
|
||||
@@ -2795,7 +2798,7 @@ std::shared_ptr<ControlFlowGraph> build_cfg(
|
||||
auto& branch_candidate = func.instructions.at(idx);
|
||||
auto& delay_slot_candidate = func.instructions.at(idx + 1);
|
||||
if (is_branch(branch_candidate, false)) {
|
||||
if (branch_delay_asm(delay_slot_candidate)) {
|
||||
if (branch_delay_asm(delay_slot_candidate, version)) {
|
||||
b->end_branch.asm_branch = true;
|
||||
if (debug_asm_branch) {
|
||||
fmt::print("NORMAL ASM BRANCH: {} and {}\n", branch_candidate.to_string(file.labels),
|
||||
|
||||
@@ -393,5 +393,6 @@ std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file,
|
||||
int seg,
|
||||
Function& func,
|
||||
const CondWithElseLengthHack& cond_with_else_hack,
|
||||
const std::unordered_set<int>& blocks_ending_in_asm_br);
|
||||
const std::unordered_set<int>& blocks_ending_in_asm_br,
|
||||
GameVersion version);
|
||||
} // namespace decompiler
|
||||
|
||||
@@ -34,8 +34,10 @@ Register get_expected_fpr_backup(int n, int total) {
|
||||
|
||||
} // namespace
|
||||
|
||||
Function::Function(int _start_word, int _end_word) : start_word(_start_word), end_word(_end_word) {
|
||||
Function::Function(int _start_word, int _end_word, GameVersion version)
|
||||
: start_word(_start_word), end_word(_end_word) {
|
||||
ir2.form_pool.reset(new FormPool());
|
||||
ir2.env.version = version;
|
||||
}
|
||||
|
||||
Function::~Function() {}
|
||||
|
||||
@@ -99,7 +99,7 @@ struct FunctionName {
|
||||
|
||||
class Function {
|
||||
public:
|
||||
Function(int _start_word, int _end_word);
|
||||
Function(int _start_word, int _end_word, GameVersion version);
|
||||
~Function();
|
||||
void analyze_prologue(const LinkedObjectFile& file);
|
||||
void find_global_function_defs(LinkedObjectFile& file, DecompilerTypeSystem& dts);
|
||||
|
||||
@@ -1842,4 +1842,44 @@ RegisterAccess StackSpillLoadOp::get_set_destination() const {
|
||||
throw std::runtime_error("StackSpillLoadOp cannot be treated as a set! operation");
|
||||
}
|
||||
|
||||
bool is_op_2(AtomicOp* op,
|
||||
MatchParam<SimpleExpression::Kind> kind,
|
||||
MatchParam<Register> dst,
|
||||
MatchParam<Register> src0,
|
||||
Register* dst_out,
|
||||
Register* src0_out) {
|
||||
// should be a set reg to int math 2 ir
|
||||
auto set = dynamic_cast<SetVarOp*>(op);
|
||||
if (!set) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// destination should be a register
|
||||
auto dest = set->dst();
|
||||
if (dst != dest.reg()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto math = set->src();
|
||||
if (kind != math.kind()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto arg = math.get_arg(0);
|
||||
|
||||
if (!arg.is_var() || src0 != arg.var().reg()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// it's a match!
|
||||
if (dst_out) {
|
||||
*dst_out = dest.reg();
|
||||
}
|
||||
|
||||
if (src0_out) {
|
||||
*src0_out = arg.var().reg();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace decompiler
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "decompiler/Disasm/Instruction.h"
|
||||
#include "decompiler/Disasm/Register.h"
|
||||
#include "decompiler/IR2/IR2_common.h"
|
||||
#include "decompiler/util/MatchParam.h"
|
||||
|
||||
namespace decompiler {
|
||||
class FormElement;
|
||||
@@ -807,4 +808,11 @@ class StackSpillLoadOp : public AtomicOp {
|
||||
};
|
||||
|
||||
bool get_as_reg_offset(const SimpleExpression& expr, IR2_RegOffset* out);
|
||||
|
||||
bool is_op_2(AtomicOp* op,
|
||||
MatchParam<SimpleExpression::Kind> kind,
|
||||
MatchParam<Register> dst,
|
||||
MatchParam<Register> src0,
|
||||
Register* dst_out = nullptr,
|
||||
Register* src0_out = nullptr);
|
||||
} // namespace decompiler
|
||||
|
||||
@@ -188,6 +188,11 @@ TP_Type SimpleExpression::get_type(const TypeState& input,
|
||||
// GOAL is smart enough to use binary 0b0 as floating point 0.
|
||||
return TP_Type::make_from_ts("float");
|
||||
}
|
||||
// new for jak 2:
|
||||
if (env.version == GameVersion::Jak2 && in_type.is_integer_constant() &&
|
||||
in_type.get_integer_constant() <= UINT32_MAX) {
|
||||
return TP_Type::make_from_ts("float");
|
||||
}
|
||||
return in_type;
|
||||
}
|
||||
case Kind::FPR_TO_GPR:
|
||||
@@ -398,6 +403,9 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input,
|
||||
}
|
||||
|
||||
if (m_kind == Kind::RIGHT_SHIFT_ARITH) {
|
||||
if (env.version == GameVersion::Jak2 && arg0_type.typespec().base_type() == "float") {
|
||||
return TP_Type::make_from_ts(TypeSpec("float"));
|
||||
}
|
||||
return TP_Type::make_from_ts(TypeSpec("int"));
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -58,6 +58,7 @@ struct FunctionVariableDefinitions {
|
||||
*/
|
||||
class Env {
|
||||
public:
|
||||
GameVersion version = GameVersion::Jak1;
|
||||
bool types_succeeded = false;
|
||||
bool has_local_vars() const { return m_has_local_vars; }
|
||||
bool has_type_analysis() const { return m_has_types; }
|
||||
|
||||
@@ -801,6 +801,7 @@ class UntilElement : public FormElement {
|
||||
bool allow_in_if() const override { return false; }
|
||||
Form* condition = nullptr;
|
||||
Form* body = nullptr;
|
||||
std::optional<RegisterAccess> false_destination; // used in jak 2, sometimes.
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -708,10 +708,28 @@ void SimpleExpressionElement::update_from_stack_gpr_to_fpr(const Env& env,
|
||||
result->push_back(x);
|
||||
}
|
||||
} else {
|
||||
// converting something else to an FPR, put an expression around it.
|
||||
result->push_back(pool.alloc_element<GenericElement>(
|
||||
GenericOperator::make_fixed(FixedOperatorKind::GPR_TO_FPR),
|
||||
pool.alloc_sequence_form(nullptr, src_fes)));
|
||||
if (env.version != GameVersion::Jak1) {
|
||||
auto frm = pool.alloc_sequence_form(nullptr, src_fes);
|
||||
if (src_fes.size() == 1) {
|
||||
auto int_constant = get_goal_integer_constant(frm, env);
|
||||
|
||||
if (int_constant && (*int_constant <= UINT32_MAX)) {
|
||||
float flt;
|
||||
|
||||
memcpy(&flt, &int_constant.value(), sizeof(float));
|
||||
|
||||
result->push_back(pool.alloc_element<ConstantFloatElement>(flt));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// converting something else to an FPR, put an expression around it.
|
||||
result->push_back(pool.alloc_element<GenericElement>(
|
||||
GenericOperator::make_fixed(FixedOperatorKind::GPR_TO_FPR), frm));
|
||||
} else {
|
||||
result->push_back(pool.alloc_element<GenericElement>(
|
||||
GenericOperator::make_fixed(FixedOperatorKind::GPR_TO_FPR),
|
||||
pool.alloc_sequence_form(nullptr, src_fes)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3538,6 +3556,22 @@ void UntilElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stac
|
||||
}
|
||||
|
||||
stack.push_form_element(this, true);
|
||||
if (false_destination) {
|
||||
env.func->warnings.general_warning("new jak 2 until loop case, check carefully");
|
||||
stack.push_value_to_reg(*false_destination,
|
||||
pool.form<SimpleAtomElement>(SimpleAtom::make_sym_val("#f")), true,
|
||||
TypeSpec("symbol"));
|
||||
RegAccessSet accessed_regs;
|
||||
body->collect_vars(accessed_regs, true);
|
||||
condition->collect_vars(accessed_regs, true);
|
||||
auto check_name = env.get_variable_name(*false_destination);
|
||||
for (auto& reg : accessed_regs) {
|
||||
if (env.get_variable_name(reg) == check_name) {
|
||||
ASSERT_MSG(false, fmt::format("Jak 2 loop uses delay slot variable improperly: {} {}\n",
|
||||
env.func->name(), check_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WhileElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) {
|
||||
|
||||
@@ -400,7 +400,7 @@ void LinkedObjectFile::find_code() {
|
||||
/*!
|
||||
* Find all the functions in each segment.
|
||||
*/
|
||||
void LinkedObjectFile::find_functions() {
|
||||
void LinkedObjectFile::find_functions(GameVersion version) {
|
||||
if (segments == 1) {
|
||||
// it's a v2 file, shouldn't have any functions
|
||||
ASSERT(offset_of_data_zone_by_seg.at(0) == 0);
|
||||
@@ -427,7 +427,7 @@ void LinkedObjectFile::find_functions() {
|
||||
// mark this as a function, and try again from the current function start
|
||||
ASSERT(found_function_tag_loc);
|
||||
stats.function_count++;
|
||||
functions_by_seg.at(seg).emplace_back(function_tag_loc, function_end);
|
||||
functions_by_seg.at(seg).emplace_back(function_tag_loc, function_end, version);
|
||||
function_end = function_tag_loc;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class LinkedObjectFile {
|
||||
uint32_t set_ordered_label_names();
|
||||
void find_code();
|
||||
std::string print_words();
|
||||
void find_functions();
|
||||
void find_functions(GameVersion version);
|
||||
void disassemble_functions();
|
||||
void process_fp_relative_links();
|
||||
std::string print_scripts();
|
||||
|
||||
@@ -589,7 +589,7 @@ void ObjectFileDB::find_code(const Config& config) {
|
||||
for_each_obj([&](ObjectFileData& obj) {
|
||||
// printf("fc %s\n", obj.record.to_unique_name().c_str());
|
||||
obj.linked_data.find_code();
|
||||
obj.linked_data.find_functions();
|
||||
obj.linked_data.find_functions(config.game_version);
|
||||
obj.linked_data.disassemble_functions();
|
||||
|
||||
if (config.game_version == GameVersion::Jak1 || obj.to_unique_name() != "effect-control-v0") {
|
||||
|
||||
@@ -158,7 +158,6 @@ class ObjectFileDB {
|
||||
void extract_art_info();
|
||||
void dump_art_info(const std::string& output_dir);
|
||||
void dump_raw_objects(const std::string& output_dir);
|
||||
|
||||
void write_object_file_words(const std::string& output_dir, bool dump_data, bool dump_code);
|
||||
void write_disassembly(const std::string& output_dir,
|
||||
bool disassemble_data,
|
||||
@@ -192,6 +191,9 @@ class ObjectFileDB {
|
||||
void ir2_do_segment_analysis_phase2(int seg, const Config& config, ObjectFileData& data);
|
||||
void ir2_setup_labels(const Config& config, ObjectFileData& data);
|
||||
void ir2_run_mips2c(const Config& config, ObjectFileData& data);
|
||||
void ir2_analyze_all_types(const std::string& output_file,
|
||||
const std::optional<std::string>& previous_game_types,
|
||||
const std::unordered_set<std::string>& bad_types);
|
||||
std::string ir2_to_file(ObjectFileData& data, const Config& config);
|
||||
std::string ir2_function_to_string(ObjectFileData& data, Function& function, int seg);
|
||||
std::string ir2_final_out(ObjectFileData& data,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "common/util/Timer.h"
|
||||
|
||||
#include "decompiler/IR2/Form.h"
|
||||
#include "decompiler/analysis/analyze_inspect_method.h"
|
||||
#include "decompiler/analysis/cfg_builder.h"
|
||||
#include "decompiler/analysis/expression_build.h"
|
||||
#include "decompiler/analysis/final_output.h"
|
||||
@@ -103,7 +104,22 @@ void ObjectFileDB::analyze_functions_ir2(
|
||||
data.full_output = ir2_final_out(data, imports, {});
|
||||
}
|
||||
|
||||
for_each_function_def_order_in_obj(data, [&](Function& f, int) { f.ir2 = {}; });
|
||||
if (!config.generate_all_types) {
|
||||
// this frees ir2 memory, but means future passes can't look back on this function.
|
||||
for_each_function_def_order_in_obj(data, [&](Function& f, int) { f.ir2 = {}; });
|
||||
} else {
|
||||
for_each_function_def_order_in_obj(data, [&](Function& f, int seg) {
|
||||
if (seg == 0) {
|
||||
return; // keep top-levels
|
||||
}
|
||||
if (f.guessed_name.kind == FunctionName::FunctionKind::METHOD &&
|
||||
f.guessed_name.method_id == GOAL_INSPECT_METHOD) {
|
||||
return; // keep inspects
|
||||
}
|
||||
// otherwise free memory
|
||||
f.ir2 = {};
|
||||
});
|
||||
}
|
||||
|
||||
fmt::print("Done in {:.2f}ms\n", file_timer.getMs());
|
||||
});
|
||||
@@ -280,6 +296,61 @@ void ObjectFileDB::ir2_top_level_pass(const Config& config) {
|
||||
lg::info("{:4d} logins {:.2f}%\n", total_top_levels, 100.f * total_top_levels / total_functions);
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_analyze_all_types(const std::string& output_file,
|
||||
const std::optional<std::string>& previous_game_types,
|
||||
const std::unordered_set<std::string>& bad_types) {
|
||||
struct PerObject {
|
||||
std::string object_name;
|
||||
std::vector<std::string> type_defs;
|
||||
std::string symbol_defs;
|
||||
};
|
||||
|
||||
std::vector<PerObject> per_object;
|
||||
|
||||
DecompilerTypeSystem previous_game_ts;
|
||||
if (previous_game_types) {
|
||||
previous_game_ts.parse_type_defs({*previous_game_types});
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> already_seen;
|
||||
for_each_obj([&](ObjectFileData& data) {
|
||||
if (data.obj_version != 3) {
|
||||
return;
|
||||
}
|
||||
auto& object_result = per_object.emplace_back();
|
||||
object_result.object_name = data.to_unique_name();
|
||||
|
||||
for_each_function_def_order_in_obj(data, [&](Function& f, int seg) {
|
||||
if (seg == TOP_LEVEL_SEGMENT) {
|
||||
object_result.symbol_defs += inspect_top_level_symbol_defines(
|
||||
already_seen, f, data.linked_data, dts, previous_game_ts);
|
||||
} else {
|
||||
if (f.is_inspect_method && bad_types.find(f.guessed_name.type_name) == bad_types.end()) {
|
||||
object_result.type_defs.push_back(inspect_inspect_method(
|
||||
f, f.guessed_name.type_name, dts, data.linked_data, previous_game_ts.ts));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
std::string result;
|
||||
result += ";; All Types\n\n";
|
||||
|
||||
for (auto& obj : per_object) {
|
||||
result += fmt::format(";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n");
|
||||
result += fmt::format(";; {:30s} ;;\n", obj.object_name);
|
||||
result += fmt::format(";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n\n");
|
||||
for (auto& t : obj.type_defs) {
|
||||
result += t;
|
||||
result += "\n";
|
||||
}
|
||||
result += obj.symbol_defs;
|
||||
result += "\n";
|
||||
}
|
||||
|
||||
file_util::write_text_file(output_file, result);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Initial Function Analysis Pass to build the control flow graph.
|
||||
* - Find basic blocks
|
||||
@@ -322,7 +393,7 @@ void ObjectFileDB::ir2_basic_block_pass(int seg, const Config& config, ObjectFil
|
||||
asm_br_blocks = asm_lookup->second;
|
||||
}
|
||||
|
||||
func.cfg = build_cfg(data.linked_data, seg, func, hack, asm_br_blocks);
|
||||
func.cfg = build_cfg(data.linked_data, seg, func, hack, asm_br_blocks, config.game_version);
|
||||
if (!func.cfg->is_fully_resolved()) {
|
||||
lg::warn("Function {} from {} failed to build control flow graph!", func.name(),
|
||||
data.to_unique_name());
|
||||
@@ -548,23 +619,6 @@ void ObjectFileDB::ir2_cfg_build_pass(int seg, ObjectFileData& data) {
|
||||
});
|
||||
}
|
||||
|
||||
// void ObjectFileDB::ir2_store_current_forms(int seg) {
|
||||
// Timer timer;
|
||||
// int total = 0;
|
||||
//
|
||||
// for_each_function_in_seg(seg, [&](Function& func, ObjectFileData& data) {
|
||||
// (void)data;
|
||||
//
|
||||
// if (func.ir2.top_form) {
|
||||
// total++;
|
||||
// func.ir2.debug_form_string =
|
||||
// pretty_print::to_string(func.ir2.top_form->to_form(func.ir2.env));
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// lg::info("Stored debug forms for {} functions in {:.2f} ms\n", total, timer.getMs());
|
||||
//}
|
||||
//
|
||||
void ObjectFileDB::ir2_build_expressions(int seg, const Config& config, ObjectFileData& data) {
|
||||
for_each_function_in_seg_in_obj(seg, data, [&](Function& func) {
|
||||
(void)data;
|
||||
|
||||
@@ -0,0 +1,988 @@
|
||||
#include "analyze_inspect_method.h"
|
||||
#include "decompiler/Disasm/InstructionMatching.h"
|
||||
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
||||
|
||||
namespace decompiler {
|
||||
|
||||
struct TypeInspectorResult {
|
||||
bool success = false;
|
||||
int type_size = -1;
|
||||
int type_method_count = -1;
|
||||
int parent_method_count = 9;
|
||||
int type_heap_base = -1;
|
||||
|
||||
std::string warnings;
|
||||
std::vector<Field> fields_of_type;
|
||||
bool is_basic = false;
|
||||
bool found_flags = false;
|
||||
|
||||
std::string type_name;
|
||||
std::string parent_type_name;
|
||||
u64 flags = 0;
|
||||
|
||||
std::string print_as_deftype(StructureType* old_game_type);
|
||||
};
|
||||
|
||||
bool is_set_reg_to_int(AtomicOp* op, Register dst, s64 value) {
|
||||
// should be a set reg to int math 2 ir
|
||||
auto set = dynamic_cast<SetVarOp*>(op);
|
||||
if (!set) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// destination should be a register
|
||||
auto dest = set->dst();
|
||||
if (dst != dest.reg()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto math = set->src();
|
||||
if (SimpleExpression::Kind::IDENTITY != math.kind()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto arg = math.get_arg(0);
|
||||
|
||||
if (!arg.is_int() || value != arg.get_int()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_set_reg_to_symbol_value(AtomicOp* op, Register dst, const std::string& value) {
|
||||
// should be a set reg to int math 2 ir
|
||||
auto set = dynamic_cast<SetVarOp*>(op);
|
||||
if (!set) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// destination should be a register
|
||||
auto dest = set->dst();
|
||||
if (dst != dest.reg()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto math = set->src();
|
||||
if (SimpleExpression::Kind::IDENTITY != math.kind()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto arg = math.get_arg(0);
|
||||
|
||||
if (!arg.is_sym_val(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_set_reg_to_symbol_ptr(AtomicOp* op, Register dst, const std::string& value) {
|
||||
// should be a set reg to int math 2 ir
|
||||
auto set = dynamic_cast<SetVarOp*>(op);
|
||||
if (!set) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// destination should be a register
|
||||
auto dest = set->dst();
|
||||
if (dst != dest.reg()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto math = set->src();
|
||||
if (SimpleExpression::Kind::IDENTITY != math.kind()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto arg = math.get_arg(0);
|
||||
|
||||
if (!arg.is_sym_ptr(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::string> get_string_loaded_to_reg(AtomicOp* op,
|
||||
Register reg,
|
||||
LinkedObjectFile& file) {
|
||||
// should be a set reg to int math 2 ir
|
||||
auto set = dynamic_cast<SetVarOp*>(op);
|
||||
if (!set) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// destination should be a register
|
||||
auto dest = set->dst();
|
||||
if (reg != dest.reg()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto math = set->src();
|
||||
if (SimpleExpression::Kind::IDENTITY != math.kind()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto& src_atom = set->src().get_arg(0);
|
||||
if (!src_atom.is_label()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return file.get_goal_string_by_label(src_atom.label());
|
||||
}
|
||||
|
||||
struct FieldPrint {
|
||||
char format = '\0';
|
||||
std::string field_name;
|
||||
std::string field_type_name;
|
||||
bool has_array = false;
|
||||
int array_size = -1;
|
||||
};
|
||||
|
||||
FieldPrint get_field_print(const std::string& str) {
|
||||
int idx = 0;
|
||||
auto next = [&]() { return str.at(idx++); };
|
||||
|
||||
auto peek = [&](int off) { return str.at(idx + off); };
|
||||
|
||||
FieldPrint field_print;
|
||||
|
||||
// first is ~T
|
||||
char c0 = next();
|
||||
ASSERT(c0 == '~');
|
||||
char c1 = next();
|
||||
if (c1 == '1') {
|
||||
c1 = next();
|
||||
}
|
||||
ASSERT(c1 == 'T');
|
||||
|
||||
// next the name:
|
||||
char name_char = next();
|
||||
while (name_char != ':' && name_char != '[') {
|
||||
field_print.field_name.push_back(name_char);
|
||||
name_char = next();
|
||||
}
|
||||
|
||||
// possibly array thing
|
||||
if (name_char == '[') {
|
||||
int size = 0;
|
||||
char num_char = next();
|
||||
while (num_char >= '0' && num_char <= '9') {
|
||||
size = size * 10 + (num_char - '0');
|
||||
num_char = next();
|
||||
}
|
||||
field_print.has_array = true;
|
||||
field_print.array_size = size;
|
||||
|
||||
ASSERT(num_char == ']');
|
||||
char c = next();
|
||||
ASSERT(c == ' ');
|
||||
c = next();
|
||||
ASSERT(c == '@');
|
||||
c = next();
|
||||
ASSERT(c == ' ');
|
||||
c = next();
|
||||
ASSERT(c == '#');
|
||||
c = next();
|
||||
ASSERT(c == 'x');
|
||||
} else {
|
||||
// next a space
|
||||
char space_char = next();
|
||||
ASSERT(space_char == ' ');
|
||||
}
|
||||
|
||||
// next the format
|
||||
char fmt1 = next();
|
||||
if (fmt1 == '~' && peek(0) != '`') { // normal ~_~%
|
||||
char fmt_code = next();
|
||||
field_print.format = fmt_code;
|
||||
char end1 = next();
|
||||
ASSERT(end1 == '~');
|
||||
char end2 = next();
|
||||
ASSERT(end2 == '%');
|
||||
ASSERT(idx == (int)str.size());
|
||||
} else if (fmt1 == '#' && peek(0) == '<') { // struct #<my-struct @ #x~X>~%
|
||||
next();
|
||||
char type_name_c = next();
|
||||
while (type_name_c != ' ') {
|
||||
field_print.field_type_name += type_name_c;
|
||||
type_name_c = next();
|
||||
}
|
||||
|
||||
std::string expect_end = "@ #x~X>~%";
|
||||
for (char i : expect_end) {
|
||||
char c = next();
|
||||
ASSERT(i == c);
|
||||
}
|
||||
field_print.format = 'X';
|
||||
|
||||
ASSERT(idx == (int)str.size());
|
||||
} else if (fmt1 == '#' && peek(0) == 'x') { // #x~X~%
|
||||
next();
|
||||
std::string expect_end = "~X~%";
|
||||
for (char i : expect_end) {
|
||||
char c = next();
|
||||
ASSERT(i == c);
|
||||
}
|
||||
field_print.format = 'X';
|
||||
} else if (fmt1 == '~' && peek(0) == '`') { // ~`my-type-with-overriden-print`P~%
|
||||
next();
|
||||
char type_name_c = next();
|
||||
while (type_name_c != '`') {
|
||||
field_print.field_type_name += type_name_c;
|
||||
type_name_c = next();
|
||||
}
|
||||
|
||||
std::string expect_end = "P~%";
|
||||
for (char i : expect_end) {
|
||||
char c = next();
|
||||
ASSERT(i == c);
|
||||
}
|
||||
field_print.format = 'P';
|
||||
|
||||
ASSERT(idx == (int)str.size());
|
||||
} else if (str.substr(idx - 1) == "(meters ~m)~%") {
|
||||
field_print.format = 'm';
|
||||
} else if (str.substr(idx - 1) == "(deg ~r)~%") {
|
||||
field_print.format = 'r';
|
||||
} else if (str.substr(idx - 1) == "(seconds ~e)~%") {
|
||||
field_print.format = 'e';
|
||||
}
|
||||
|
||||
else {
|
||||
throw std::runtime_error("other format nyi in get_field_print " + str.substr(idx));
|
||||
}
|
||||
|
||||
return field_print;
|
||||
}
|
||||
|
||||
int get_start_idx(Function& function,
|
||||
LinkedObjectFile& file,
|
||||
TypeInspectorResult* result,
|
||||
const std::string& parent_type,
|
||||
const std::string& type_name,
|
||||
Env& env) {
|
||||
if (function.basic_blocks.size() != 5) {
|
||||
fmt::print("[iim] inspect {} had {} basic blocks, expected 5\n", function.name(),
|
||||
function.basic_blocks.size());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!function.ir2.atomic_ops) {
|
||||
fmt::print("[iim] no atomic ops in {}\n", function.name());
|
||||
return -1;
|
||||
}
|
||||
auto& aos = *function.ir2.atomic_ops;
|
||||
|
||||
int op_idx = 0;
|
||||
// block 0:
|
||||
/*
|
||||
* (set! gp a0)
|
||||
* (b! (truthy gp) L370 (set! v1 #f))
|
||||
*/
|
||||
|
||||
if (aos.block_id_to_end_atomic_op.at(0) != 2) {
|
||||
fmt::print("[iim] block 0 had the wrong number of ops: {} for {}\n",
|
||||
aos.block_id_to_end_atomic_op.at(0), function.name());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!is_op_2(aos.ops.at(op_idx).get(), SimpleExpression::Kind::IDENTITY,
|
||||
Register(Reg::GPR, Reg::GP), Register(Reg::GPR, Reg::A0))) {
|
||||
fmt::print("[iim] block 0 op 0 bad in {}: {}\n", aos.ops.at(op_idx)->to_string(env),
|
||||
function.name());
|
||||
return -1;
|
||||
}
|
||||
op_idx++;
|
||||
|
||||
auto br = dynamic_cast<BranchOp*>(aos.ops.at(op_idx).get());
|
||||
if (!br) {
|
||||
fmt::print("[iim] block 0 op 1 bad in {}: {} (not branch)\n", aos.ops.at(1)->to_string(env),
|
||||
function.name());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (br->likely() || br->condition().kind() != IR2_Condition::Kind::TRUTHY ||
|
||||
!br->condition().src(0).is_var() ||
|
||||
br->condition().src(0).var().reg() != Register(Reg::GPR, Reg::GP) ||
|
||||
br->branch_delay().kind() != IR2_BranchDelay::Kind::SET_REG_FALSE ||
|
||||
br->branch_delay().var(0).reg() != Register(Reg::GPR, Reg::V1)) {
|
||||
fmt::print("[iim] block 0 op 1 bad in {}: {} (bad branch)\n", aos.ops.at(1)->to_string(env),
|
||||
function.name());
|
||||
return -1;
|
||||
}
|
||||
op_idx++;
|
||||
|
||||
// block 1:
|
||||
/*
|
||||
* (set! gp gp)
|
||||
* (b! #t L371 (nop!))
|
||||
*/
|
||||
if (aos.block_id_to_end_atomic_op.at(1) != 4) {
|
||||
fmt::print("[iim] block 1 had the wrong number of ops: {} for {}\n",
|
||||
aos.block_id_to_end_atomic_op.at(1), function.name());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!is_op_2(aos.ops.at(op_idx).get(), SimpleExpression::Kind ::IDENTITY,
|
||||
Register(Reg::GPR, Reg::GP), Register(Reg::GPR, Reg::GP))) {
|
||||
fmt::print("[iim] op 2 bad in {}: {}\n", aos.ops.at(op_idx)->to_string(env), function.name());
|
||||
return -1;
|
||||
}
|
||||
op_idx++;
|
||||
|
||||
auto br2 = dynamic_cast<BranchOp*>(aos.ops.at(op_idx).get());
|
||||
if (!br2) {
|
||||
fmt::print("[iim] op 3 bad in {}: {} (not branch)\n", aos.ops.at(op_idx)->to_string(env),
|
||||
function.name());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (br2->likely() || br2->condition().kind() != IR2_Condition::Kind::ALWAYS ||
|
||||
br2->branch_delay().kind() != IR2_BranchDelay::Kind::NOP) {
|
||||
fmt::print("[iim] op3 bad in {}: {} (bad branch)\n", aos.ops.at(op_idx)->to_string(env),
|
||||
function.name());
|
||||
return -1;
|
||||
}
|
||||
op_idx++;
|
||||
|
||||
// setup
|
||||
/*
|
||||
(set! v1 0)
|
||||
(set! t9 format)
|
||||
(set! a0 #t)
|
||||
(set! a1 L386)
|
||||
(set! a2 gp)
|
||||
(set! a3 'vector) (also can be (set! a3 (l.wu (+ gp -4))))
|
||||
(call!)
|
||||
*/
|
||||
|
||||
if (!is_set_reg_to_int(aos.ops.at(op_idx).get(), Register(Reg::GPR, Reg::V1), 0)) {
|
||||
fmt::print("[iim] op4 bad in {}: {} (bad set 0)\n", aos.ops.at(op_idx)->to_string(env),
|
||||
function.name());
|
||||
}
|
||||
op_idx++;
|
||||
|
||||
if (!is_set_reg_to_symbol_value(aos.ops.at(op_idx).get(), Register(Reg::GPR, Reg::T9),
|
||||
"format")) {
|
||||
fmt::print("[iim] op5 bad in {}: {} (bad set format)\n", aos.ops.at(op_idx)->to_string(env),
|
||||
function.name());
|
||||
}
|
||||
op_idx++;
|
||||
|
||||
if (!is_set_reg_to_symbol_ptr(aos.ops.at(op_idx).get(), Register(Reg::GPR, Reg::A0), "#t")) {
|
||||
fmt::print("[iim] op6 bad in {}: {} (bad set #t)\n", aos.ops.at(op_idx)->to_string(env),
|
||||
function.name());
|
||||
}
|
||||
op_idx++;
|
||||
|
||||
auto type_name_str =
|
||||
get_string_loaded_to_reg(aos.ops.at(op_idx).get(), Register(Reg::GPR, Reg::A1), file);
|
||||
if (!type_name_str) {
|
||||
fmt::print("[iim] op7 bad in {}: {} (bad string)\n", aos.ops.at(op_idx)->to_string(env),
|
||||
function.name());
|
||||
}
|
||||
if (type_name_str != "[~8x] ~A~%") {
|
||||
fmt::print("[iim] op7 bad in {}: {} (bad string: {})\n", aos.ops.at(op_idx)->to_string(env),
|
||||
function.name(), *type_name_str);
|
||||
}
|
||||
op_idx++;
|
||||
|
||||
if (!is_op_2(aos.ops.at(op_idx).get(), SimpleExpression::Kind ::IDENTITY,
|
||||
Register(Reg::GPR, Reg::A2), Register(Reg::GPR, Reg::GP))) {
|
||||
fmt::print("[iim] op 8 bad in {}: {}\n", aos.ops.at(op_idx)->to_string(env), function.name());
|
||||
return -1;
|
||||
}
|
||||
op_idx++;
|
||||
|
||||
if (is_set_reg_to_symbol_ptr(aos.ops.at(op_idx).get(), Register(Reg::GPR, Reg::A3), type_name)) {
|
||||
result->is_basic = false;
|
||||
} else if (aos.ops.at(op_idx)->to_string(env) == "(set! a3 (l.wu (+ gp -4)))") {
|
||||
result->is_basic = true;
|
||||
} else {
|
||||
fmt::print("[iim] op 9 bad in {}: {}\n", aos.ops.at(op_idx)->to_string(env), function.name());
|
||||
return -1;
|
||||
}
|
||||
op_idx++;
|
||||
|
||||
if (!dynamic_cast<CallOp*>(aos.ops.at(op_idx).get())) {
|
||||
fmt::print("[iim] op 10 bad in {}: {}\n", aos.ops.at(op_idx)->to_string(env), function.name());
|
||||
return -1;
|
||||
}
|
||||
op_idx++;
|
||||
|
||||
return op_idx;
|
||||
}
|
||||
|
||||
std::pair<Register, int> get_base_of_load(const SimpleExpression& load_addr) {
|
||||
if (load_addr.kind() == SimpleExpression::Kind::IDENTITY) {
|
||||
const auto& src = load_addr.get_arg(0);
|
||||
if (src.is_var()) {
|
||||
return {src.var().reg(), 0};
|
||||
}
|
||||
}
|
||||
|
||||
if (load_addr.kind() == SimpleExpression::Kind::ADD) {
|
||||
const auto& src0 = load_addr.get_arg(0);
|
||||
const auto& src1 = load_addr.get_arg(1);
|
||||
if (src1.get_kind() == SimpleAtom::Kind::INTEGER_CONSTANT &&
|
||||
src0.get_kind() == SimpleAtom::Kind::VARIABLE) {
|
||||
return {src0.var().reg(), src1.get_int()};
|
||||
}
|
||||
}
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
bool is_load_with_base(const SimpleExpression& expr, Register base) {
|
||||
return get_base_of_load(expr).first == base;
|
||||
}
|
||||
|
||||
bool is_get_load(AtomicOp* ir, Register dst, Register base) {
|
||||
auto as_set = dynamic_cast<LoadVarOp*>(ir);
|
||||
return as_set && as_set->get_set_destination().reg() == dst &&
|
||||
is_load_with_base(as_set->src(), base);
|
||||
}
|
||||
|
||||
struct LoadInfo {
|
||||
int offset = 0;
|
||||
int size = 0;
|
||||
LoadVarOp::Kind kind;
|
||||
};
|
||||
|
||||
LoadInfo get_load_info_from_set(AtomicOp* load) {
|
||||
auto as_load = dynamic_cast<LoadVarOp*>(load);
|
||||
ASSERT(as_load);
|
||||
LoadInfo info;
|
||||
info.kind = as_load->kind();
|
||||
info.size = as_load->size();
|
||||
auto base = get_base_of_load(as_load->src());
|
||||
info.offset = base.second;
|
||||
return info;
|
||||
}
|
||||
|
||||
int identify_int_field(int idx,
|
||||
Function& function,
|
||||
TypeInspectorResult* result,
|
||||
FieldPrint& print_info) {
|
||||
auto load_info = get_load_info_from_set(function.ir2.atomic_ops->ops.at(idx++).get());
|
||||
|
||||
std::string field_type_name;
|
||||
if (load_info.kind == LoadVarOp::Kind::UNSIGNED) {
|
||||
field_type_name += "u";
|
||||
} else if (load_info.kind == LoadVarOp::Kind::FLOAT) {
|
||||
ASSERT(false); // ...
|
||||
}
|
||||
field_type_name += "int";
|
||||
|
||||
switch (load_info.size) {
|
||||
case 1:
|
||||
field_type_name += "8";
|
||||
break;
|
||||
case 2:
|
||||
field_type_name += "16";
|
||||
break;
|
||||
case 4:
|
||||
field_type_name += "32";
|
||||
break;
|
||||
case 8:
|
||||
field_type_name += "64";
|
||||
break;
|
||||
case 16:
|
||||
field_type_name += "128";
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("unknown load op size in identify int field " +
|
||||
std::to_string((int)load_info.size));
|
||||
}
|
||||
|
||||
if (print_info.format == 'e') {
|
||||
field_type_name = "seconds";
|
||||
ASSERT(load_info.size == 8);
|
||||
}
|
||||
|
||||
int offset = load_info.offset;
|
||||
if (result->is_basic) {
|
||||
offset += BASIC_OFFSET;
|
||||
}
|
||||
|
||||
Field field(print_info.field_name, TypeSpec(field_type_name), offset);
|
||||
result->fields_of_type.push_back(field);
|
||||
|
||||
return idx;
|
||||
}
|
||||
int identify_float_field(int idx,
|
||||
Function& function,
|
||||
TypeInspectorResult* result,
|
||||
FieldPrint& print_info) {
|
||||
auto load_info = get_load_info_from_set(function.ir2.atomic_ops->ops.at(idx++).get());
|
||||
ASSERT(load_info.size == 4);
|
||||
ASSERT(load_info.kind == LoadVarOp::Kind::FLOAT);
|
||||
|
||||
auto& float_move = function.ir2.atomic_ops->ops.at(idx++);
|
||||
if (!is_op_2(float_move.get(), SimpleExpression::Kind::FPR_TO_GPR, make_gpr(Reg::A2),
|
||||
make_fpr(0))) {
|
||||
printf("bad float move: %s\n", float_move->to_string(function.ir2.env).c_str());
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
std::string type;
|
||||
switch (print_info.format) {
|
||||
case 'f':
|
||||
type = "float";
|
||||
break;
|
||||
case 'm':
|
||||
type = "meters";
|
||||
break;
|
||||
case 'r':
|
||||
type = "deg";
|
||||
break;
|
||||
case 'X':
|
||||
type = "float";
|
||||
result->warnings += "field " + print_info.field_name + " is a float printed as hex? ";
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
}
|
||||
int offset = load_info.offset;
|
||||
if (result->is_basic) {
|
||||
offset += BASIC_OFFSET;
|
||||
}
|
||||
|
||||
Field field(print_info.field_name, TypeSpec(type), offset);
|
||||
result->fields_of_type.push_back(field);
|
||||
return idx;
|
||||
}
|
||||
|
||||
int identify_pointer_field(int idx,
|
||||
Function& function,
|
||||
TypeInspectorResult* result,
|
||||
FieldPrint& print_info) {
|
||||
auto load_info = get_load_info_from_set(function.ir2.atomic_ops->ops.at(idx++).get());
|
||||
ASSERT(load_info.size == 4);
|
||||
ASSERT(load_info.kind == LoadVarOp::Kind::UNSIGNED);
|
||||
|
||||
int offset = load_info.offset;
|
||||
if (result->is_basic) {
|
||||
offset += BASIC_OFFSET;
|
||||
}
|
||||
|
||||
Field field(print_info.field_name, TypeSpec("pointer"), offset);
|
||||
result->fields_of_type.push_back(field);
|
||||
return idx;
|
||||
}
|
||||
|
||||
bool get_ptr_offset_constant_nonzero(const SimpleExpression& math, Register base, int* result) {
|
||||
// if (!is_reg(math->arg0.get(), base)) {
|
||||
if (!math.get_arg(0).is_var() || math.get_arg(0).var().reg() != base) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!math.get_arg(1).is_int()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = math.get_arg(1).get_int();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get_ptr_offset(AtomicOp* ir, Register dst, Register base, int* result) {
|
||||
auto as_set = dynamic_cast<SetVarOp*>(ir);
|
||||
if (!as_set) {
|
||||
return false;
|
||||
}
|
||||
if (as_set->dst().reg() != dst) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return get_ptr_offset_constant_nonzero(as_set->src(), base, result);
|
||||
}
|
||||
|
||||
int identify_array_field(int idx,
|
||||
Function& function,
|
||||
TypeInspectorResult* result,
|
||||
FieldPrint& print_info) {
|
||||
auto& get_op = function.ir2.atomic_ops->ops.at(idx++);
|
||||
int offset = 0;
|
||||
if (!get_ptr_offset(get_op.get(), make_gpr(Reg::A2), make_gpr(Reg::GP), &offset)) {
|
||||
printf("bad get ptr offset %s\n", get_op->to_string(function.ir2.env).c_str());
|
||||
ASSERT(false);
|
||||
}
|
||||
if (result->is_basic) {
|
||||
offset += BASIC_OFFSET;
|
||||
}
|
||||
|
||||
Field field(print_info.field_name, TypeSpec("UNKNOWN"), offset);
|
||||
if (print_info.array_size) {
|
||||
field.set_array(print_info.array_size);
|
||||
} else {
|
||||
field.set_dynamic();
|
||||
}
|
||||
result->fields_of_type.push_back(field);
|
||||
return idx;
|
||||
}
|
||||
|
||||
int identify_struct_not_inline_field(int idx,
|
||||
Function& function,
|
||||
TypeInspectorResult* result,
|
||||
FieldPrint& print_info) {
|
||||
auto load_info = get_load_info_from_set(function.ir2.atomic_ops->ops.at(idx++).get());
|
||||
|
||||
if (!(load_info.size == 4 && load_info.kind == LoadVarOp::Kind::UNSIGNED)) {
|
||||
result->warnings += "field " + print_info.field_type_name + " is likely a value type";
|
||||
}
|
||||
int offset = load_info.offset;
|
||||
if (result->is_basic) {
|
||||
offset += BASIC_OFFSET;
|
||||
}
|
||||
|
||||
Field field(print_info.field_name, TypeSpec(print_info.field_type_name), offset);
|
||||
result->fields_of_type.push_back(field);
|
||||
return idx;
|
||||
}
|
||||
|
||||
int identify_struct_inline_field(int idx,
|
||||
Function& function,
|
||||
TypeInspectorResult* result,
|
||||
FieldPrint& print_info) {
|
||||
auto& get_op = function.ir2.atomic_ops->ops.at(idx++);
|
||||
int offset = 0;
|
||||
if (!get_ptr_offset(get_op.get(), make_gpr(Reg::A2), make_gpr(Reg::GP), &offset)) {
|
||||
printf("bad get ptr offset %s\n", get_op->to_string(function.ir2.env).c_str());
|
||||
ASSERT(false);
|
||||
}
|
||||
if (result->is_basic) {
|
||||
offset += BASIC_OFFSET;
|
||||
}
|
||||
|
||||
Field field(print_info.field_name, TypeSpec(print_info.field_type_name), offset);
|
||||
field.set_inline();
|
||||
result->fields_of_type.push_back(field);
|
||||
return idx;
|
||||
}
|
||||
|
||||
int identify_basic_field(int idx,
|
||||
Function& function,
|
||||
LinkedObjectFile& file,
|
||||
TypeInspectorResult* result,
|
||||
FieldPrint& print_info) {
|
||||
(void)file;
|
||||
auto load_info = get_load_info_from_set(function.ir2.atomic_ops->ops.at(idx++).get());
|
||||
ASSERT(load_info.size == 4);
|
||||
ASSERT(load_info.kind == LoadVarOp::Kind::UNSIGNED || load_info.kind == LoadVarOp::Kind::SIGNED);
|
||||
|
||||
if (load_info.kind == LoadVarOp::Kind::SIGNED) {
|
||||
result->warnings += "field " + print_info.field_name + " is a basic loaded with a signed load ";
|
||||
}
|
||||
|
||||
int offset = load_info.offset;
|
||||
if (result->is_basic) {
|
||||
offset += BASIC_OFFSET;
|
||||
}
|
||||
|
||||
Field field(print_info.field_name, TypeSpec("basic"), offset);
|
||||
result->fields_of_type.push_back(field);
|
||||
return idx;
|
||||
}
|
||||
|
||||
int detect(int idx, Function& function, LinkedObjectFile& file, TypeInspectorResult* result) {
|
||||
auto& get_format_op = function.ir2.atomic_ops->ops.at(idx++);
|
||||
if (!is_set_reg_to_symbol_value(get_format_op.get(), make_gpr(Reg::T9), "format")) {
|
||||
ASSERT_MSG(false,
|
||||
fmt::format("bad get format: {}\n", get_format_op->to_string(function.ir2.env)));
|
||||
}
|
||||
|
||||
auto& get_true = function.ir2.atomic_ops->ops.at(idx++);
|
||||
if (!is_set_reg_to_symbol_ptr(get_true.get(), make_gpr(Reg::A0), "#t")) {
|
||||
ASSERT_MSG(false, "bad get true");
|
||||
}
|
||||
|
||||
auto sstr = get_string_loaded_to_reg(function.ir2.atomic_ops->ops.at(idx++).get(),
|
||||
make_gpr(Reg::A1), file);
|
||||
if (!sstr) {
|
||||
ASSERT_MSG(false, "bad sstr");
|
||||
}
|
||||
|
||||
auto info = get_field_print(*sstr);
|
||||
|
||||
auto& first_get_op = function.ir2.atomic_ops->ops.at(idx);
|
||||
|
||||
if (is_get_load(first_get_op.get(), make_gpr(Reg::A2), make_gpr(Reg::GP)) &&
|
||||
(info.format == 'D' || info.format == 'X' || info.format == 'e') && !info.has_array &&
|
||||
info.field_type_name.empty()) {
|
||||
idx = identify_int_field(idx, function, result, info);
|
||||
// it's a load!
|
||||
} else if (is_get_load(first_get_op.get(), make_fpr(0), make_gpr(Reg::GP)) &&
|
||||
(info.format == 'f' || info.format == 'm' || info.format == 'r' ||
|
||||
info.format == 'X') &&
|
||||
!info.has_array && info.field_type_name.empty()) {
|
||||
idx = identify_float_field(idx, function, result, info);
|
||||
} else if (is_get_load(first_get_op.get(), make_gpr(Reg::A2), make_gpr(Reg::GP)) &&
|
||||
info.format == 'A' && !info.has_array && info.field_type_name.empty()) {
|
||||
idx = identify_basic_field(idx, function, file, result, info);
|
||||
} else if (is_get_load(first_get_op.get(), make_gpr(Reg::A2), make_gpr(Reg::GP)) &&
|
||||
info.format == 'X' && !info.has_array && info.field_type_name.empty()) {
|
||||
idx = identify_pointer_field(idx, function, result, info);
|
||||
} else if (info.has_array && (info.format == 'X' || info.format == 'P') &&
|
||||
info.field_type_name.empty()) {
|
||||
idx = identify_array_field(idx, function, result, info);
|
||||
} else if (!info.has_array && (info.format == 'X' || info.format == 'P') &&
|
||||
!info.field_type_name.empty()) {
|
||||
// structure.
|
||||
if (is_get_load(first_get_op.get(), make_gpr(Reg::A2), make_gpr(Reg::GP))) {
|
||||
// not inline
|
||||
idx = identify_struct_not_inline_field(idx, function, result, info);
|
||||
} else {
|
||||
idx = identify_struct_inline_field(idx, function, result, info);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
printf("couldn't do %s, %s\n", sstr->c_str(),
|
||||
first_get_op->to_string(function.ir2.env).c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!dynamic_cast<CallOp*>(function.ir2.atomic_ops->ops.at(idx++).get())) {
|
||||
printf("bad call\n");
|
||||
ASSERT(false);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
std::string inspect_inspect_method(Function& inspect_method,
|
||||
const std::string& type_name,
|
||||
DecompilerTypeSystem& dts,
|
||||
LinkedObjectFile& file,
|
||||
TypeSystem& previous_game_ts) {
|
||||
fmt::print(" iim: {}\n", inspect_method.name());
|
||||
TypeInspectorResult result;
|
||||
ASSERT(type_name == inspect_method.guessed_name.type_name);
|
||||
TypeFlags flags;
|
||||
flags.flag = 0;
|
||||
result.found_flags = dts.lookup_flags(type_name, &flags.flag);
|
||||
result.type_name = type_name;
|
||||
result.parent_type_name = dts.lookup_parent_from_inspects(type_name);
|
||||
result.flags = flags.flag;
|
||||
result.type_size = flags.size;
|
||||
result.type_method_count = flags.methods;
|
||||
result.type_heap_base = flags.heap_base;
|
||||
|
||||
{
|
||||
TypeFlags parent_flags;
|
||||
parent_flags.flag = 0;
|
||||
if (result.parent_type_name != "UNKNOWN" &&
|
||||
dts.lookup_flags(result.parent_type_name, &parent_flags.flag)) {
|
||||
result.parent_method_count = parent_flags.methods;
|
||||
}
|
||||
}
|
||||
if (!result.found_flags) {
|
||||
fmt::print("[iim] no flags found for {}, maybe defined in the kernel\n", type_name);
|
||||
}
|
||||
|
||||
result.parent_type_name = dts.lookup_parent_from_inspects(type_name);
|
||||
int idx = get_start_idx(inspect_method, file, &result, result.parent_type_name, type_name,
|
||||
inspect_method.ir2.env);
|
||||
StructureType* old_game_type = nullptr;
|
||||
if (previous_game_ts.fully_defined_type_exists(type_name)) {
|
||||
old_game_type = dynamic_cast<StructureType*>(previous_game_ts.lookup_type(type_name));
|
||||
}
|
||||
if (idx <= 0) {
|
||||
// can't get any field...
|
||||
result.warnings += "Failed to read fields. ";
|
||||
idx = -2;
|
||||
return result.print_as_deftype(old_game_type);
|
||||
}
|
||||
while (idx < int(inspect_method.ir2.atomic_ops->ops.size()) - 2 && idx != -1) {
|
||||
idx = detect(idx, inspect_method, file, &result);
|
||||
}
|
||||
|
||||
if (idx == -1) {
|
||||
result.warnings += "Failed to read some fields. ";
|
||||
}
|
||||
|
||||
return result.print_as_deftype(old_game_type);
|
||||
}
|
||||
|
||||
std::string old_method_string(const MethodInfo& info) {
|
||||
if (info.type.arg_count() > 0) {
|
||||
if (info.type.base_type() == "function" || info.type.base_type() == "state") {
|
||||
std::string result = fmt::format(" ;; ({} (", info.name);
|
||||
bool add = false;
|
||||
for (int i = 0; i < (int)info.type.arg_count() - 1; i++) {
|
||||
result += info.type.get_arg(i).print();
|
||||
result += ' ';
|
||||
add = true;
|
||||
}
|
||||
if (add) {
|
||||
result.pop_back();
|
||||
}
|
||||
result += ") ";
|
||||
result += info.type.get_arg(info.type.arg_count() - 1).print();
|
||||
if (info.type.base_type() == "state") {
|
||||
result += " :state";
|
||||
}
|
||||
result += fmt::format(" {})", info.id);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return fmt::format(" ;; ({} {}) weird method", info.name, info.type.print());
|
||||
}
|
||||
|
||||
/*
|
||||
* old_game_type may be null
|
||||
*/
|
||||
std::string TypeInspectorResult::print_as_deftype(StructureType* old_game_type) {
|
||||
std::string result;
|
||||
|
||||
result += fmt::format("(deftype {} ({})\n (", type_name, parent_type_name);
|
||||
|
||||
int longest_field_name = 0;
|
||||
int longest_type_name = 0;
|
||||
int longest_mods = 0;
|
||||
|
||||
std::string inline_string = ":inline";
|
||||
std::string dynamic_string = ":dynamic";
|
||||
|
||||
for (auto& field : fields_of_type) {
|
||||
longest_field_name = std::max(longest_field_name, int(field.name().size()));
|
||||
longest_type_name = std::max(longest_type_name, int(field.type().print().size()));
|
||||
|
||||
int mods = 0;
|
||||
// mods are array size, :inline, :dynamic
|
||||
if (field.is_array() && !field.is_dynamic()) {
|
||||
mods += std::to_string(field.array_size()).size();
|
||||
}
|
||||
|
||||
if (field.is_inline()) {
|
||||
if (mods) {
|
||||
mods++; // space
|
||||
}
|
||||
mods += inline_string.size();
|
||||
}
|
||||
|
||||
if (field.is_dynamic()) {
|
||||
if (mods) {
|
||||
mods++; // space
|
||||
}
|
||||
mods += dynamic_string.size();
|
||||
}
|
||||
longest_mods = std::max(longest_mods, mods);
|
||||
}
|
||||
|
||||
for (auto& field : fields_of_type) {
|
||||
result += "(";
|
||||
result += field.name();
|
||||
result.append(1 + (longest_field_name - int(field.name().size())), ' ');
|
||||
result += field.type().print();
|
||||
result.append(1 + (longest_type_name - int(field.type().print().size())), ' ');
|
||||
|
||||
std::string mods;
|
||||
if (field.is_array() && !field.is_dynamic()) {
|
||||
mods += std::to_string(field.array_size());
|
||||
mods += " ";
|
||||
}
|
||||
|
||||
if (field.is_inline()) {
|
||||
mods += inline_string;
|
||||
mods += " ";
|
||||
}
|
||||
|
||||
if (field.is_dynamic()) {
|
||||
mods += dynamic_string;
|
||||
mods += " ";
|
||||
}
|
||||
result.append(mods);
|
||||
result.append(longest_mods - int(mods.size() - 1), ' ');
|
||||
|
||||
result.append(":offset-assert ");
|
||||
result.append(std::to_string(field.offset()));
|
||||
result.append(")");
|
||||
if (old_game_type) {
|
||||
Field old_field;
|
||||
if (old_game_type->lookup_field(field.name(), &old_field)) {
|
||||
if (old_field.type() != field.type()) {
|
||||
result += fmt::format(" ;; {}", old_field.type().print());
|
||||
if (old_field.is_array() && !old_field.is_dynamic()) {
|
||||
result += fmt::format(" {}", old_field.array_size());
|
||||
}
|
||||
if (old_field.is_inline()) {
|
||||
result += " :inline";
|
||||
}
|
||||
if (old_field.is_dynamic()) {
|
||||
result += " :dynamic";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.append("\n ");
|
||||
}
|
||||
result.append(")\n");
|
||||
|
||||
result.append(fmt::format(" :method-count-assert {}\n", type_method_count));
|
||||
result.append(fmt::format(" :size-assert #x{:x}\n", type_size));
|
||||
result.append(fmt::format(" :flag-assert #x{:x}\n ", flags));
|
||||
if (!warnings.empty()) {
|
||||
result.append(";; ");
|
||||
result.append(warnings);
|
||||
result.append("\n ");
|
||||
}
|
||||
|
||||
if (type_method_count > 9) {
|
||||
result.append("(:methods\n ");
|
||||
MethodInfo old_new_method;
|
||||
if (old_game_type && old_game_type->get_my_new_method(&old_new_method)) {
|
||||
result.append(old_method_string(old_new_method));
|
||||
result.append("\n ");
|
||||
}
|
||||
for (int i = parent_method_count; i < type_method_count; i++) {
|
||||
result.append(fmt::format("(dummy-{} () none {})", i, i));
|
||||
if (old_game_type) {
|
||||
MethodInfo info;
|
||||
if (old_game_type->get_my_method(i, &info)) {
|
||||
result += old_method_string(info);
|
||||
}
|
||||
}
|
||||
result.append("\n ");
|
||||
}
|
||||
result.append(")\n ");
|
||||
}
|
||||
result.append(")\n");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string inspect_top_level_symbol_defines(std::unordered_set<std::string>& already_seen,
|
||||
Function& top_level,
|
||||
LinkedObjectFile& file,
|
||||
DecompilerTypeSystem& dts,
|
||||
DecompilerTypeSystem& previous_game_ts) {
|
||||
if (!top_level.ir2.atomic_ops) {
|
||||
return {};
|
||||
}
|
||||
std::string result;
|
||||
for (auto& aop : top_level.ir2.atomic_ops->ops) {
|
||||
auto* as_store = dynamic_cast<StoreOp*>(aop.get());
|
||||
if (as_store && as_store->addr().kind() == SimpleExpression::Kind::IDENTITY &&
|
||||
as_store->addr().get_arg(0).is_sym_val()) {
|
||||
auto& sym_name = as_store->addr().get_arg(0).get_str();
|
||||
if (already_seen.find(sym_name) == already_seen.end()) {
|
||||
already_seen.insert(sym_name);
|
||||
if (dts.ts.partially_defined_type_exists(sym_name)) {
|
||||
continue;
|
||||
}
|
||||
result += fmt::format(";; (define-extern {} object)", sym_name);
|
||||
auto it = previous_game_ts.symbol_types.find(sym_name);
|
||||
if (it != previous_game_ts.symbol_types.end()) {
|
||||
result += fmt::format(" ;; {}", it->second.print());
|
||||
}
|
||||
result += '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "decompiler/Function/Function.h"
|
||||
#include "decompiler/util/DecompilerTypeSystem.h"
|
||||
|
||||
namespace decompiler {
|
||||
|
||||
std::string inspect_inspect_method(Function& inspect_method,
|
||||
const std::string& type_name,
|
||||
DecompilerTypeSystem& dts,
|
||||
LinkedObjectFile& file,
|
||||
TypeSystem& previous_game_ts);
|
||||
|
||||
std::string inspect_top_level_symbol_defines(std::unordered_set<std::string>& already_seen,
|
||||
Function& top_level,
|
||||
LinkedObjectFile& file,
|
||||
DecompilerTypeSystem& dts,
|
||||
DecompilerTypeSystem& previous_game_ts);
|
||||
|
||||
} // namespace decompiler
|
||||
@@ -1746,11 +1746,23 @@ std::unique_ptr<AtomicOp> convert_5(const Instruction& i0,
|
||||
const Instruction& i2,
|
||||
const Instruction& i3,
|
||||
const Instruction& i4,
|
||||
int idx) {
|
||||
int idx,
|
||||
GameVersion version) {
|
||||
auto s6 = make_gpr(Reg::S6);
|
||||
|
||||
int process_offset = -1;
|
||||
switch (version) {
|
||||
case GameVersion::Jak1:
|
||||
process_offset = 44;
|
||||
break;
|
||||
case GameVersion::Jak2:
|
||||
process_offset = 48;
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
}
|
||||
if (i0.kind == InstructionKind::LWU && i0.get_dst(0).is_reg(s6) &&
|
||||
i0.get_src(0).get_imm() == 44 && i0.get_src(1).is_reg(s6) &&
|
||||
i0.get_src(0).get_imm() == process_offset && i0.get_src(1).is_reg(s6) &&
|
||||
i1.kind == InstructionKind::MTLO1 && i1.get_src(0).is_reg(s6) &&
|
||||
i2.kind == InstructionKind::LWU && i2.get_dst(0).is_reg(s6) &&
|
||||
i2.get_src(0).get_imm() == 12 && i2.get_src(1).is_reg(s6) &&
|
||||
@@ -2111,7 +2123,7 @@ int convert_block_to_atomic_ops(int begin_idx,
|
||||
|
||||
if (!converted && n_instr >= 5) {
|
||||
// try 5 instructions
|
||||
op = convert_5(instr[0], instr[1], instr[2], instr[3], instr[4], op_idx);
|
||||
op = convert_5(instr[0], instr[1], instr[2], instr[3], instr[4], op_idx, version);
|
||||
if (op) {
|
||||
converted = true;
|
||||
length = 5;
|
||||
|
||||
@@ -110,12 +110,12 @@ void clean_up_until_loop(FormPool& pool, UntilElement* ir, const Env& env) {
|
||||
ASSERT(condition_branch.first);
|
||||
if (condition_branch.first->op()->branch_delay().kind() != IR2_BranchDelay::Kind::NOP) {
|
||||
ASSERT_MSG(
|
||||
false,
|
||||
condition_branch.first->op()->branch_delay().kind() == IR2_BranchDelay::Kind::SET_REG_FALSE,
|
||||
fmt::format(
|
||||
"bad delay slot in until loop: {} in {}\n", env.func->name(),
|
||||
condition_branch.first->op()->branch_delay().to_form(env.file->labels, env).print()));
|
||||
ir->false_destination = condition_branch.first->op()->branch_delay().var(0);
|
||||
}
|
||||
ASSERT(condition_branch.first->op()->branch_delay().kind() == IR2_BranchDelay::Kind::NOP);
|
||||
auto replacement = condition_branch.first->op()->get_condition_as_form(pool, env);
|
||||
replacement->invert();
|
||||
*(condition_branch.second) = replacement;
|
||||
@@ -573,7 +573,7 @@ bool try_splitting_nested_sc(FormPool& pool, Function& func, ShortCircuitElement
|
||||
ASSERT(ir->entries.at(i).branch_delay.has_value());
|
||||
bool is_and = delay_slot_sets_false(branch.first, *ir->entries.at(i).branch_delay);
|
||||
bool is_or = delay_slot_sets_truthy(branch.first, *ir->entries.at(i).branch_delay);
|
||||
ASSERT(is_and != is_or);
|
||||
ASSERT_MSG(is_and != is_or, fmt::format("bad nested sc in {}", func.name()));
|
||||
|
||||
if (first_different == -1) {
|
||||
// haven't seen a change yet.
|
||||
@@ -925,47 +925,6 @@ bool is_op_3(AtomicOp* op,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_op_2(AtomicOp* op,
|
||||
MatchParam<SimpleExpression::Kind> kind,
|
||||
MatchParam<Register> dst,
|
||||
MatchParam<Register> src0,
|
||||
Register* dst_out = nullptr,
|
||||
Register* src0_out = nullptr) {
|
||||
// should be a set reg to int math 2 ir
|
||||
auto set = dynamic_cast<SetVarOp*>(op);
|
||||
if (!set) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// destination should be a register
|
||||
auto dest = set->dst();
|
||||
if (dst != dest.reg()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto math = set->src();
|
||||
if (kind != math.kind()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto arg = math.get_arg(0);
|
||||
|
||||
if (!arg.is_var() || src0 != arg.var().reg()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// it's a match!
|
||||
if (dst_out) {
|
||||
*dst_out = dest.reg();
|
||||
}
|
||||
|
||||
if (src0_out) {
|
||||
*src0_out = arg.var().reg();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_op_2(FormElement* ir,
|
||||
MatchParam<SimpleExpression::Kind> kind,
|
||||
MatchParam<Register> dst,
|
||||
|
||||
@@ -76,7 +76,10 @@ Config read_config_file(const std::string& path_to_config_file,
|
||||
config.is_pal = cfg.at("is_pal").get<bool>();
|
||||
config.rip_levels = cfg.at("levels_convert_to_obj").get<bool>();
|
||||
config.extract_collision = cfg.at("extract_collision").get<bool>();
|
||||
|
||||
config.generate_all_types = cfg.at("generate_all_types").get<bool>();
|
||||
if (cfg.contains("old_all_types_file")) {
|
||||
config.old_all_types_file = cfg.at("old_all_types_file").get<std::string>();
|
||||
}
|
||||
auto allowed = cfg.at("allowed_objects").get<std::vector<std::string>>();
|
||||
for (const auto& x : allowed) {
|
||||
config.allowed_objects.insert(x);
|
||||
|
||||
@@ -123,6 +123,9 @@ struct Config {
|
||||
|
||||
bool generate_symbol_definition_map = false;
|
||||
|
||||
bool generate_all_types = false;
|
||||
std::optional<std::string> old_all_types_file;
|
||||
|
||||
bool is_pal = false;
|
||||
|
||||
bool write_patches = false;
|
||||
|
||||
@@ -13,4 +13,7 @@
|
||||
(declare-type sparticle-launch-group basic)
|
||||
(declare-type lightning-spec basic)
|
||||
(declare-type sparticle-launcher basic)
|
||||
(declare-type state basic)
|
||||
(declare-type state basic)
|
||||
|
||||
;; debug
|
||||
(define-extern looping-code (function symbol))
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
////////////////////////////
|
||||
|
||||
"types_with_bad_inspect_methods": [
|
||||
|
||||
"game-task-event",
|
||||
"game-task-control",
|
||||
"predator-edge"
|
||||
],
|
||||
|
||||
"no_type_analysis_functions_by_name": [],
|
||||
@@ -55,7 +57,8 @@
|
||||
"(anon-function 2 scene)", "progress-trans", "(method 10 bigmap)", "(method 9 editable-region)", "(method 57 enemy)",
|
||||
"(anon-function 10 meet-brutter)", "(method 154 vehicle-racer)", "(method 188 predator)", "(anon-function 13 sig0-course)",
|
||||
"(method 228 hal-sewer)", "(method 154 vehicle-city-racer)", "(method 53 squid)", "(anon-function 11 fort-floor-spike)",
|
||||
"(method 29 gun-dummy)", "vehicle-explode-post", "(method 158 vehicle-guard)", "(method 207 metalhead-predator)"
|
||||
"(method 29 gun-dummy)", "vehicle-explode-post", "(method 158 vehicle-guard)", "(method 207 metalhead-predator)",
|
||||
"(anon-function 4 gun-states)", "(anon-function 28 grenadier)", "(anon-function 24 grenadier)"
|
||||
],
|
||||
|
||||
// these functions use pairs and the decompiler
|
||||
|
||||
+153
-153
@@ -8,159 +8,159 @@
|
||||
// the DGOs will be processed in this order. Usually it's best to have KERNEL, ENGINE, then the levels when
|
||||
// you want to run on the entire game.
|
||||
"dgo_names": [
|
||||
"./CGO/ART.CGO",
|
||||
"./CGO/KERNEL.CGO",
|
||||
"./CGO/ENGINE.CGO",
|
||||
"./CGO/GAME.CGO",
|
||||
"./CGO/COMMON.CGO",
|
||||
"./DGO/LWIDEB.DGO",
|
||||
"./DGO/LMEETBRT.DGO",
|
||||
"./DGO/CTA.DGO",
|
||||
"./DGO/PALOUT.DGO",
|
||||
"./DGO/STD.DGO",
|
||||
"./DGO/FOR.DGO",
|
||||
"./DGO/CASEXT.DGO",
|
||||
"./DGO/HIDEOUT.DGO",
|
||||
"./DGO/LWIDESTA.DGO",
|
||||
"./DGO/LRACELIT.DGO",
|
||||
"./DGO/CTB.DGO",
|
||||
"./DGO/KIOSK.DGO",
|
||||
"./DGO/DG1.DGO",
|
||||
"./DGO/FEB.DGO",
|
||||
"./DGO/DMI.DGO",
|
||||
"./DGO/ORACLE.DGO",
|
||||
"./DGO/LERLTESS.DGO",
|
||||
"./DGO/DRI.DGO",
|
||||
"./DGO/LBRNERMK.DGO",
|
||||
"./DGO/LRACECF.DGO",
|
||||
"./DGO/CTC.DGO",
|
||||
"./DGO/LTHRNOUT.DGO",
|
||||
"./DGO/FRA.DGO",
|
||||
"./DGO/LGARCSTA.DGO",
|
||||
"./DGO/MTN.DGO",
|
||||
"./DGO/INTROCST.DGO",
|
||||
"./DGO/DRB.DGO",
|
||||
"./DGO/ATE.DGO",
|
||||
"./DGO/LERROL.DGO",
|
||||
"./DGO/LTRNYSAM.DGO",
|
||||
"./DGO/LOUTCSTB.DGO",
|
||||
"./DGO/LASHTHRN.DGO",
|
||||
"./DGO/TOC.DGO",
|
||||
"./DGO/CFB.DGO",
|
||||
"./DGO/CAB.DGO",
|
||||
"./DGO/STC.DGO",
|
||||
"./DGO/STR.DGO",
|
||||
"./DGO/ATO.DGO",
|
||||
"./DGO/SEB.DGO",
|
||||
"./DGO/LPRSNCST.DGO",
|
||||
"./DGO/SWB.DGO",
|
||||
"./DGO/LPOWER.DGO",
|
||||
"./DGO/FOB.DGO",
|
||||
"./DGO/CIB.DGO",
|
||||
"./DGO/LSHUTTLE.DGO",
|
||||
"./DGO/LJAKDAX.DGO",
|
||||
"./DGO/FORDUMPC.DGO",
|
||||
"./DGO/LTRNTESS.DGO",
|
||||
"./DGO/TBO.DGO",
|
||||
"./DGO/THR.DGO",
|
||||
"./DGO/PRI.DGO",
|
||||
"./DGO/LKIDDOGE.DGO",
|
||||
"./DGO/NESTT.DGO",
|
||||
"./DGO/LWIDEC.DGO",
|
||||
"./DGO/SAG.DGO",
|
||||
"./DGO/NEB.DGO",
|
||||
"./DGO/COB.DGO",
|
||||
"./DGO/LBOMBBOT.DGO",
|
||||
"./DGO/DEMO.DGO",
|
||||
"./DGO/LRACEDF.DGO",
|
||||
"./DGO/LERLCHAL.DGO",
|
||||
"./DGO/LHIPOUT.DGO",
|
||||
"./DGO/OUTROCST.DGO",
|
||||
"./DGO/NES.DGO",
|
||||
"./DGO/PAR.DGO",
|
||||
"./DGO/LERBRNGD.DGO",
|
||||
"./DGO/MTX.DGO",
|
||||
"./DGO/FDA.DGO",
|
||||
"./DGO/LKEIRIFT.DGO",
|
||||
"./DGO/LWHACK.DGO",
|
||||
"./DGO/LJKDXASH.DGO",
|
||||
"./DGO/CAS.DGO",
|
||||
"./DGO/COA.DGO",
|
||||
"./DGO/LTESS.DGO",
|
||||
"./DGO/CFA.DGO",
|
||||
"./DGO/TOMBEXT.DGO",
|
||||
"./DGO/LCGUARD.DGO",
|
||||
"./DGO/TOE.DGO",
|
||||
"./DGO/PALBOSS.DGO",
|
||||
"./DGO/FRB.DGO",
|
||||
"./DGO/PAE.DGO",
|
||||
"./DGO/TITLE.DGO",
|
||||
"./DGO/FORDUMPD.DGO",
|
||||
"./DGO/D3A.DGO",
|
||||
"./DGO/DRILLMTN.DGO",
|
||||
"./DGO/PAC.DGO",
|
||||
"./DGO/LTENTOB.DGO",
|
||||
"./DGO/LRACEBF.DGO",
|
||||
"./DGO/LPROTECT.DGO",
|
||||
"./DGO/FEA.DGO",
|
||||
"./DGO/ONINTENT.DGO",
|
||||
"./DGO/STA.DGO",
|
||||
"./DGO/CGC.DGO",
|
||||
"./DGO/CMA.DGO",
|
||||
"./DGO/FDB.DGO",
|
||||
"./DGO/SKA.DGO",
|
||||
"./DGO/LTRNKRKD.DGO",
|
||||
"./DGO/CIA.DGO",
|
||||
"./DGO/TOB.DGO",
|
||||
"./DGO/LRACEDB.DGO",
|
||||
"./DGO/LDJAKBRN.DGO",
|
||||
"./DGO/TOA.DGO",
|
||||
"./DGO/STADBLMP.DGO",
|
||||
"./DGO/UND.DGO",
|
||||
"./DGO/LYSKDCD.DGO",
|
||||
"./DGO/HALFPIPE.DGO",
|
||||
"./DGO/LSAMERGD.DGO",
|
||||
"./DGO/PAS.DGO",
|
||||
"./DGO/LBBUSH.DGO",
|
||||
"./DGO/LPACKAGE.DGO",
|
||||
"./DGO/LINTCSTB.DGO",
|
||||
"./DGO/LPORTRUN.DGO",
|
||||
"./DGO/LASHGRD.DGO",
|
||||
"./DGO/CGB.DGO",
|
||||
"./DGO/D3B.DGO",
|
||||
"./DGO/STB.DGO",
|
||||
"./DGO/GARAGE.DGO",
|
||||
"./DGO/PORTWALL.DGO",
|
||||
"./DGO/LHELLDOG.DGO",
|
||||
"./DGO/SWE.DGO",
|
||||
"./DGO/LRACECB.DGO",
|
||||
"./DGO/GGA.DGO",
|
||||
"./DGO/TOD.DGO",
|
||||
"./DGO/MCN.DGO",
|
||||
"./DGO/SEW.DGO",
|
||||
"./DGO/VIN.DGO",
|
||||
"./DGO/CGA.DGO",
|
||||
"./DGO/CMB.DGO",
|
||||
"./DGO/LGUARD.DGO",
|
||||
"./DGO/CPA.DGO",
|
||||
"./DGO/LCITYLOW.DGO",
|
||||
"./DGO/LTENTOUT.DGO",
|
||||
"./DGO/UNB.DGO",
|
||||
"./DGO/CPO.DGO",
|
||||
"./DGO/CAP.DGO",
|
||||
"./DGO/CWI.DGO",
|
||||
"./DGO/CTYKORA.DGO",
|
||||
"./DGO/RUI.DGO",
|
||||
"./DGO/LSACK.DGO",
|
||||
"./DGO/CTYASHA.DGO",
|
||||
"./DGO/LPRTRACE.DGO",
|
||||
"./DGO/LWIDEA.DGO",
|
||||
"./DGO/HIPHOG.DGO",
|
||||
"./DGO/LSMYSBRT.DGO",
|
||||
"./DGO/LRACEBB.DGO",
|
||||
"./DGO/CASCITY.DGO",
|
||||
"./DGO/LYSAMSAM.DGO",
|
||||
"./DGO/VI1.DGO"
|
||||
"CGO/ART.CGO",
|
||||
"CGO/KERNEL.CGO",
|
||||
"CGO/ENGINE.CGO",
|
||||
"CGO/GAME.CGO",
|
||||
"CGO/COMMON.CGO",
|
||||
"DGO/LWIDEB.DGO",
|
||||
"DGO/LMEETBRT.DGO",
|
||||
"DGO/CTA.DGO",
|
||||
"DGO/PALOUT.DGO",
|
||||
"DGO/STD.DGO",
|
||||
"DGO/FOR.DGO",
|
||||
"DGO/CASEXT.DGO",
|
||||
"DGO/HIDEOUT.DGO",
|
||||
"DGO/LWIDESTA.DGO",
|
||||
"DGO/LRACELIT.DGO",
|
||||
"DGO/CTB.DGO",
|
||||
"DGO/KIOSK.DGO",
|
||||
"DGO/DG1.DGO",
|
||||
"DGO/FEB.DGO",
|
||||
"DGO/DMI.DGO",
|
||||
"DGO/ORACLE.DGO",
|
||||
"DGO/LERLTESS.DGO",
|
||||
"DGO/DRI.DGO",
|
||||
"DGO/LBRNERMK.DGO",
|
||||
"DGO/LRACECF.DGO",
|
||||
"DGO/CTC.DGO",
|
||||
"DGO/LTHRNOUT.DGO",
|
||||
"DGO/FRA.DGO",
|
||||
"DGO/LGARCSTA.DGO",
|
||||
"DGO/MTN.DGO",
|
||||
"DGO/INTROCST.DGO",
|
||||
"DGO/DRB.DGO",
|
||||
"DGO/ATE.DGO",
|
||||
"DGO/LERROL.DGO",
|
||||
"DGO/LTRNYSAM.DGO",
|
||||
"DGO/LOUTCSTB.DGO",
|
||||
"DGO/LASHTHRN.DGO",
|
||||
"DGO/TOC.DGO",
|
||||
"DGO/CFB.DGO",
|
||||
"DGO/CAB.DGO",
|
||||
"DGO/STC.DGO",
|
||||
"DGO/STR.DGO",
|
||||
"DGO/ATO.DGO",
|
||||
"DGO/SEB.DGO",
|
||||
"DGO/LPRSNCST.DGO",
|
||||
"DGO/SWB.DGO",
|
||||
"DGO/LPOWER.DGO",
|
||||
"DGO/FOB.DGO",
|
||||
"DGO/CIB.DGO",
|
||||
"DGO/LSHUTTLE.DGO",
|
||||
"DGO/LJAKDAX.DGO",
|
||||
"DGO/FORDUMPC.DGO",
|
||||
"DGO/LTRNTESS.DGO",
|
||||
"DGO/TBO.DGO",
|
||||
"DGO/THR.DGO",
|
||||
"DGO/PRI.DGO",
|
||||
"DGO/LKIDDOGE.DGO",
|
||||
"DGO/NESTT.DGO",
|
||||
"DGO/LWIDEC.DGO",
|
||||
"DGO/SAG.DGO",
|
||||
"DGO/NEB.DGO",
|
||||
"DGO/COB.DGO",
|
||||
"DGO/LBOMBBOT.DGO",
|
||||
"DGO/DEMO.DGO",
|
||||
"DGO/LRACEDF.DGO",
|
||||
"DGO/LERLCHAL.DGO",
|
||||
"DGO/LHIPOUT.DGO",
|
||||
"DGO/OUTROCST.DGO",
|
||||
"DGO/NES.DGO",
|
||||
"DGO/PAR.DGO",
|
||||
"DGO/LERBRNGD.DGO",
|
||||
"DGO/MTX.DGO",
|
||||
"DGO/FDA.DGO",
|
||||
"DGO/LKEIRIFT.DGO",
|
||||
"DGO/LWHACK.DGO",
|
||||
"DGO/LJKDXASH.DGO",
|
||||
"DGO/CAS.DGO",
|
||||
"DGO/COA.DGO",
|
||||
"DGO/LTESS.DGO",
|
||||
"DGO/CFA.DGO",
|
||||
"DGO/TOMBEXT.DGO",
|
||||
"DGO/LCGUARD.DGO",
|
||||
"DGO/TOE.DGO",
|
||||
"DGO/PALBOSS.DGO",
|
||||
"DGO/FRB.DGO",
|
||||
"DGO/PAE.DGO",
|
||||
"DGO/TITLE.DGO",
|
||||
"DGO/FORDUMPD.DGO",
|
||||
"DGO/D3A.DGO",
|
||||
"DGO/DRILLMTN.DGO",
|
||||
"DGO/PAC.DGO",
|
||||
"DGO/LTENTOB.DGO",
|
||||
"DGO/LRACEBF.DGO",
|
||||
"DGO/LPROTECT.DGO",
|
||||
"DGO/FEA.DGO",
|
||||
"DGO/ONINTENT.DGO",
|
||||
"DGO/STA.DGO",
|
||||
"DGO/CGC.DGO",
|
||||
"DGO/CMA.DGO",
|
||||
"DGO/FDB.DGO",
|
||||
"DGO/SKA.DGO",
|
||||
"DGO/LTRNKRKD.DGO",
|
||||
"DGO/CIA.DGO",
|
||||
"DGO/TOB.DGO",
|
||||
"DGO/LRACEDB.DGO",
|
||||
"DGO/LDJAKBRN.DGO",
|
||||
"DGO/TOA.DGO",
|
||||
"DGO/STADBLMP.DGO",
|
||||
"DGO/UND.DGO",
|
||||
"DGO/LYSKDCD.DGO",
|
||||
"DGO/HALFPIPE.DGO",
|
||||
"DGO/LSAMERGD.DGO",
|
||||
"DGO/PAS.DGO",
|
||||
"DGO/LBBUSH.DGO",
|
||||
"DGO/LPACKAGE.DGO",
|
||||
"DGO/LINTCSTB.DGO",
|
||||
"DGO/LPORTRUN.DGO",
|
||||
"DGO/LASHGRD.DGO",
|
||||
"DGO/CGB.DGO",
|
||||
"DGO/D3B.DGO",
|
||||
"DGO/STB.DGO",
|
||||
"DGO/GARAGE.DGO",
|
||||
"DGO/PORTWALL.DGO",
|
||||
"DGO/LHELLDOG.DGO",
|
||||
"DGO/SWE.DGO",
|
||||
"DGO/LRACECB.DGO",
|
||||
"DGO/GGA.DGO",
|
||||
"DGO/TOD.DGO",
|
||||
"DGO/MCN.DGO",
|
||||
"DGO/SEW.DGO",
|
||||
"DGO/VIN.DGO",
|
||||
"DGO/CGA.DGO",
|
||||
"DGO/CMB.DGO",
|
||||
"DGO/LGUARD.DGO",
|
||||
"DGO/CPA.DGO",
|
||||
"DGO/LCITYLOW.DGO",
|
||||
"DGO/LTENTOUT.DGO",
|
||||
"DGO/UNB.DGO",
|
||||
"DGO/CPO.DGO",
|
||||
"DGO/CAP.DGO",
|
||||
"DGO/CWI.DGO",
|
||||
"DGO/CTYKORA.DGO",
|
||||
"DGO/RUI.DGO",
|
||||
"DGO/LSACK.DGO",
|
||||
"DGO/CTYASHA.DGO",
|
||||
"DGO/LPRTRACE.DGO",
|
||||
"DGO/LWIDEA.DGO",
|
||||
"DGO/HIPHOG.DGO",
|
||||
"DGO/LSMYSBRT.DGO",
|
||||
"DGO/LRACEBB.DGO",
|
||||
"DGO/CASCITY.DGO",
|
||||
"DGO/LYSAMSAM.DGO",
|
||||
"DGO/VI1.DGO"
|
||||
],
|
||||
|
||||
// some objects are part of STR files (streaming data).
|
||||
|
||||
@@ -68,6 +68,13 @@
|
||||
// set to true for PAL versions. this will forcefully skip files that have some data missing at the end.
|
||||
"is_pal": false,
|
||||
|
||||
"generate_all_types": true,
|
||||
"write_patches": false,
|
||||
"apply_patches": false,
|
||||
"object_patches": [],
|
||||
// jak 1's all-types, used to generate "hints"
|
||||
"old_all_types_file": "decompiler/config/all-types.gc",
|
||||
|
||||
////////////////////////////
|
||||
// CONFIG FILES
|
||||
////////////////////////////
|
||||
|
||||
@@ -174,6 +174,13 @@ int main(int argc, char** argv) {
|
||||
db.analyze_functions_ir2(out_folder, config, {});
|
||||
}
|
||||
|
||||
if (config.generate_all_types) {
|
||||
ASSERT_MSG(config.decompile_code, "Must decompile code to generate all-types");
|
||||
db.ir2_analyze_all_types(file_util::combine_path(out_folder, "new-all-types.gc"),
|
||||
config.old_all_types_file,
|
||||
config.hacks.types_with_bad_inspect_methods);
|
||||
}
|
||||
|
||||
fmt::print("[Mem] After decomp: {} MB\n", get_peak_rss() / (1024 * 1024));
|
||||
|
||||
// write out all symbols
|
||||
|
||||
@@ -403,8 +403,8 @@ int DecompilerTypeSystem::get_format_arg_count(const std::string& str) const {
|
||||
}
|
||||
|
||||
static const std::vector<char> single_char_ignore_list = {'%', 'T'};
|
||||
static const std::vector<std::string> multi_char_ignore_list = {"0L", "1L", "3L", "1K",
|
||||
"2j", "0k", "30L"};
|
||||
static const std::vector<std::string> multi_char_ignore_list = {"0L", "1L", "3L", "1K",
|
||||
"2j", "0k", "30L", "1T"};
|
||||
|
||||
int arg_count = 0;
|
||||
for (size_t i = 0; i < str.length(); i++) {
|
||||
|
||||
Reference in New Issue
Block a user