[decompiler] Jak 2 modifications, new all-types code (#1553)

* temp

* look at old game types

* clean up
This commit is contained in:
water111
2022-06-25 21:26:15 -04:00
committed by GitHub
parent c9de15ba64
commit 91fa0122d8
40 changed files with 1762 additions and 586 deletions
+1
View File
@@ -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
+16 -13
View File
@@ -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),
+2 -1
View File
@@ -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
+3 -1
View File
@@ -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() {}
+1 -1
View File
@@ -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);
+40
View File
@@ -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
+8
View File
@@ -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
+8
View File
@@ -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;
+1
View File
@@ -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; }
+1
View File
@@ -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.
};
/*!
+38 -4
View File
@@ -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) {
+2 -2
View File
@@ -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;
}
+1 -1
View File
@@ -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();
+1 -1
View File
@@ -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") {
+3 -1
View File
@@ -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,
+73 -19
View File
@@ -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
+15 -3
View File
@@ -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;
+3 -44
View File
@@ -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,
+4 -1
View File
@@ -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);
+3
View File
@@ -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;
+4 -1
View File
@@ -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,
+5 -2
View File
@@ -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
View File
@@ -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).
+7
View File
@@ -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
////////////////////////////
+7
View File
@@ -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
+2 -2
View File
@@ -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++) {