mirror of
https://github.com/open-goal/jak-project
synced 2026-07-07 06:05:15 -04:00
remove old ir1 code (#1287)
This commit is contained in:
@@ -37,10 +37,6 @@ add_library(
|
||||
Function/BasicBlocks.cpp
|
||||
Function/CfgVtx.cpp
|
||||
Function/Function.cpp
|
||||
Function/TypeInspector.cpp
|
||||
|
||||
IR/BasicOpBuilder.cpp
|
||||
IR/IR.cpp
|
||||
|
||||
IR2/AtomicOp.cpp
|
||||
IR2/AtomicOpForm.cpp
|
||||
|
||||
@@ -14,11 +14,6 @@ struct BasicBlock {
|
||||
int start_word;
|
||||
int end_word;
|
||||
|
||||
// [start, end)
|
||||
int start_basic_op = -1;
|
||||
int end_basic_op = -1;
|
||||
int basic_op_size() const { return end_basic_op - start_basic_op; }
|
||||
|
||||
std::string label_name;
|
||||
|
||||
std::vector<int> pred;
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#include "decompiler/Disasm/InstructionMatching.h"
|
||||
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
||||
#include "decompiler/util/DecompilerTypeSystem.h"
|
||||
#include "TypeInspector.h"
|
||||
#include "decompiler/IR/IR.h"
|
||||
#include "decompiler/IR2/Form.h"
|
||||
#include "common/util/BitUtils.h"
|
||||
#include "common/util/Assert.h"
|
||||
@@ -673,17 +671,6 @@ void Function::find_type_defs(LinkedObjectFile& file, DecompilerTypeSystem& dts)
|
||||
}
|
||||
}
|
||||
|
||||
void Function::add_basic_op(std::shared_ptr<IR_Atomic> op, int start_instr, int end_instr) {
|
||||
op->is_basic_op = true;
|
||||
ASSERT(end_instr > start_instr);
|
||||
|
||||
for (int i = start_instr; i < end_instr; i++) {
|
||||
instruction_to_basic_op[i] = basic_ops.size();
|
||||
}
|
||||
basic_op_to_instruction[basic_ops.size()] = start_instr;
|
||||
basic_ops.push_back(op);
|
||||
}
|
||||
|
||||
bool Function::instr_starts_basic_op(int idx) {
|
||||
auto op = instruction_to_basic_op.find(idx);
|
||||
if (op != instruction_to_basic_op.end()) {
|
||||
@@ -693,10 +680,6 @@ bool Function::instr_starts_basic_op(int idx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<IR_Atomic> Function::get_basic_op_at_instr(int idx) {
|
||||
return basic_ops.at(instruction_to_basic_op.at(idx));
|
||||
}
|
||||
|
||||
bool Function::instr_starts_atomic_op(int idx) {
|
||||
auto op = ir2.atomic_ops->instruction_to_atomic_op.find(idx);
|
||||
if (op != ir2.atomic_ops->instruction_to_atomic_op.end()) {
|
||||
@@ -710,20 +693,6 @@ const AtomicOp& Function::get_atomic_op_at_instr(int idx) {
|
||||
return *ir2.atomic_ops->ops.at(ir2.atomic_ops->instruction_to_atomic_op.at(idx));
|
||||
}
|
||||
|
||||
int Function::get_basic_op_count() {
|
||||
return basic_ops.size();
|
||||
}
|
||||
|
||||
int Function::get_failed_basic_op_count() {
|
||||
int count = 0;
|
||||
for (auto& x : basic_ops) {
|
||||
if (dynamic_cast<IR_Failed*>(x.get())) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Topological sort of basic blocks.
|
||||
* Returns a valid ordering + a list of blocks that you can't reach and therefore
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
|
||||
namespace decompiler {
|
||||
class DecompilerTypeSystem;
|
||||
class IR_Atomic;
|
||||
class IR;
|
||||
|
||||
struct FunctionName {
|
||||
enum class FunctionKind {
|
||||
@@ -104,21 +102,14 @@ class Function {
|
||||
void find_global_function_defs(LinkedObjectFile& file, DecompilerTypeSystem& dts);
|
||||
void find_method_defs(LinkedObjectFile& file, DecompilerTypeSystem& dts);
|
||||
void find_type_defs(LinkedObjectFile& file, DecompilerTypeSystem& dts);
|
||||
void add_basic_op(std::shared_ptr<IR_Atomic> op, int start_instr, int end_instr);
|
||||
bool has_basic_ops() { return !basic_ops.empty(); }
|
||||
bool instr_starts_basic_op(int idx);
|
||||
std::shared_ptr<IR_Atomic> get_basic_op_at_instr(int idx);
|
||||
bool instr_starts_atomic_op(int idx);
|
||||
const AtomicOp& get_atomic_op_at_instr(int idx);
|
||||
int get_basic_op_count();
|
||||
int get_failed_basic_op_count();
|
||||
BlockTopologicalSort bb_topo_sort();
|
||||
std::string name() const;
|
||||
|
||||
TypeSpec type;
|
||||
|
||||
std::shared_ptr<IR> ir = nullptr;
|
||||
|
||||
int segment = -1;
|
||||
int start_word = -1;
|
||||
int end_word = -1; // not inclusive, but does include padding.
|
||||
@@ -175,7 +166,6 @@ class Function {
|
||||
} prologue;
|
||||
|
||||
bool uses_fp_register = false;
|
||||
std::vector<std::shared_ptr<IR_Atomic>> basic_ops;
|
||||
|
||||
struct {
|
||||
bool atomic_ops_attempted = false;
|
||||
|
||||
@@ -1,848 +0,0 @@
|
||||
#include "decompiler/config.h"
|
||||
#include "decompiler/Disasm/InstructionMatching.h"
|
||||
#include "TypeInspector.h"
|
||||
#include "Function.h"
|
||||
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "decompiler/util/DecompilerTypeSystem.h"
|
||||
#include "common/type_system/deftype.h"
|
||||
#include "decompiler/IR/IR.h"
|
||||
|
||||
namespace decompiler {
|
||||
namespace {
|
||||
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();
|
||||
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;
|
||||
}
|
||||
|
||||
bool is_int(IR* ir, s64 value) {
|
||||
auto as_int = dynamic_cast<IR_IntegerConstant*>(ir);
|
||||
return as_int && as_int->value == value;
|
||||
}
|
||||
|
||||
bool is_reg(IR* ir, Register reg) {
|
||||
auto as_reg = dynamic_cast<IR_Register*>(ir);
|
||||
return as_reg && as_reg->reg == reg;
|
||||
}
|
||||
|
||||
bool is_math_reg_constant(IR* ir, IR_IntMath2::Kind kind, Register src0, s64 src1) {
|
||||
auto as_math = dynamic_cast<IR_IntMath2*>(ir);
|
||||
return as_math && as_math->kind == kind && is_reg(as_math->arg0.get(), src0) &&
|
||||
is_int(as_math->arg1.get(), src1);
|
||||
}
|
||||
|
||||
bool is_load_with_offset(IR* ir, IR_Load::Kind kind, int load_size, Register base, s64 offset) {
|
||||
auto as_load = dynamic_cast<IR_Load*>(ir);
|
||||
return as_load && as_load->kind == kind && as_load->size == load_size &&
|
||||
is_math_reg_constant(as_load->location.get(), IR_IntMath2::ADD, base, offset);
|
||||
}
|
||||
|
||||
bool is_get_load_with_offset(IR* ir,
|
||||
Register dst,
|
||||
IR_Load::Kind kind,
|
||||
int load_size,
|
||||
Register base,
|
||||
s64 offset) {
|
||||
auto as_set = dynamic_cast<IR_Set*>(ir);
|
||||
return as_set && is_reg(as_set->dst.get(), dst) &&
|
||||
is_load_with_offset(as_set->src.get(), kind, load_size, base, offset);
|
||||
}
|
||||
|
||||
struct LoadInfo {
|
||||
int offset = 0;
|
||||
int size = 0;
|
||||
IR_Load::Kind kind;
|
||||
};
|
||||
|
||||
LoadInfo get_load_info_from_set(IR* load) {
|
||||
auto as_set = dynamic_cast<IR_Set*>(load);
|
||||
ASSERT(as_set);
|
||||
auto as_load = dynamic_cast<IR_Load*>(as_set->src.get());
|
||||
ASSERT(as_load);
|
||||
LoadInfo info;
|
||||
info.kind = as_load->kind;
|
||||
info.size = as_load->size;
|
||||
if (dynamic_cast<IR_Register*>(as_load->location.get())) {
|
||||
info.offset = 0;
|
||||
return info;
|
||||
}
|
||||
|
||||
auto as_math = dynamic_cast<IR_IntMath2*>(as_load->location.get());
|
||||
ASSERT(as_math);
|
||||
ASSERT(as_math->kind == IR_IntMath2::ADD);
|
||||
auto as_int = dynamic_cast<IR_IntegerConstant*>(as_math->arg1.get());
|
||||
ASSERT(as_int);
|
||||
info.offset = as_int->value;
|
||||
return info;
|
||||
}
|
||||
|
||||
Register get_base_of_load(IR_Load* load) {
|
||||
auto as_reg = dynamic_cast<IR_Register*>(load->location.get());
|
||||
if (as_reg) {
|
||||
return as_reg->reg;
|
||||
}
|
||||
|
||||
auto as_math = dynamic_cast<IR_IntMath2*>(load->location.get());
|
||||
ASSERT(as_math->kind == IR_IntMath2::ADD);
|
||||
ASSERT(dynamic_cast<IR_IntegerConstant*>(as_math->arg1.get()));
|
||||
auto math_reg = dynamic_cast<IR_Register*>(as_math->arg0.get());
|
||||
if (math_reg) {
|
||||
return math_reg->reg;
|
||||
} else {
|
||||
ASSERT(false);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool is_load_with_base(IR* ir, Register base) {
|
||||
auto as_load = dynamic_cast<IR_Load*>(ir);
|
||||
return as_load && base == get_base_of_load(as_load);
|
||||
}
|
||||
|
||||
bool is_get_load(IR* ir, Register dst, Register base) {
|
||||
auto as_set = dynamic_cast<IR_Set*>(ir);
|
||||
return as_set && is_reg(as_set->dst.get(), dst) && is_load_with_base(as_set->src.get(), base);
|
||||
}
|
||||
|
||||
bool is_reg_reg_move(IR* ir, Register dst, Register src) {
|
||||
auto as_set = dynamic_cast<IR_Set*>(ir);
|
||||
return as_set && is_reg(as_set->dst.get(), dst) && is_reg(as_set->src.get(), src);
|
||||
}
|
||||
|
||||
bool is_sym_value(IR* ir, const std::string& sym_name) {
|
||||
auto as_sym_value = dynamic_cast<IR_SymbolValue*>(ir);
|
||||
return as_sym_value && as_sym_value->name == sym_name;
|
||||
}
|
||||
|
||||
bool is_sym(IR* ir, const std::string& sym_name) {
|
||||
auto as_sym = dynamic_cast<IR_Symbol*>(ir);
|
||||
return as_sym && as_sym->name == sym_name;
|
||||
}
|
||||
|
||||
bool is_get_sym_value(IR* ir, Register dst, const std::string& sym_name) {
|
||||
auto as_set = dynamic_cast<IR_Set*>(ir);
|
||||
return as_set && is_reg(as_set->dst.get(), dst) && is_sym_value(as_set->src.get(), sym_name);
|
||||
}
|
||||
|
||||
bool is_get_sym(IR* ir, Register dst, const std::string& sym_name) {
|
||||
auto as_set = dynamic_cast<IR_Set*>(ir);
|
||||
return as_set && is_reg(as_set->dst.get(), dst) && is_sym(as_set->src.get(), sym_name);
|
||||
}
|
||||
|
||||
bool is_label(IR* ir) {
|
||||
return dynamic_cast<IR_StaticAddress*>(ir);
|
||||
}
|
||||
|
||||
bool is_get_label(IR* ir, Register dst) {
|
||||
auto as_set = dynamic_cast<IR_Set*>(ir);
|
||||
return as_set && is_reg(as_set->dst.get(), dst) && is_label(as_set->src.get());
|
||||
}
|
||||
|
||||
int get_label_id_of_set(IR* ir) {
|
||||
return dynamic_cast<IR_StaticAddress*>(dynamic_cast<IR_Set*>(ir)->src.get())->label_id;
|
||||
}
|
||||
|
||||
bool is_set_shift(IR* ir) {
|
||||
auto as_set = dynamic_cast<IR_Set*>(ir);
|
||||
if (as_set) {
|
||||
auto as_math = dynamic_cast<IR_IntMath2*>(as_set->src.get());
|
||||
if (as_math && (as_math->kind == IR_IntMath2::LEFT_SHIFT ||
|
||||
as_math->kind == IR_IntMath2::RIGHT_SHIFT_LOGIC ||
|
||||
as_math->kind == IR_IntMath2::RIGHT_SHIFT_ARITH)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
auto as_asm = dynamic_cast<IR_AsmOp*>(ir);
|
||||
return as_asm && as_asm->name == "sllv";
|
||||
}
|
||||
|
||||
bool get_ptr_offset_constant_nonzero(IR_IntMath2* math, Register base, int* result) {
|
||||
if (!is_reg(math->arg0.get(), base)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto as_int = dynamic_cast<IR_IntegerConstant*>(math->arg1.get());
|
||||
if (!as_int) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = as_int->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get_ptr_offset_zero(IR_IntMath2* math, Register base, int* result) {
|
||||
if (!is_reg(math->arg0.get(), make_gpr(Reg::R0)) || !is_reg(math->arg1.get(), base)) {
|
||||
return false;
|
||||
}
|
||||
*result = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get_ptr_offset(IR* ir, Register dst, Register base, int* result) {
|
||||
auto as_set = dynamic_cast<IR_Set*>(ir);
|
||||
if (!as_set) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_reg(as_set->dst.get(), dst)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto as_math = dynamic_cast<IR_IntMath2*>(as_set->src.get());
|
||||
if (!as_math) {
|
||||
return false;
|
||||
}
|
||||
return get_ptr_offset_constant_nonzero(as_math, base, result) ||
|
||||
get_ptr_offset_zero(as_math, base, result);
|
||||
}
|
||||
|
||||
int get_start_idx(Function& function,
|
||||
LinkedObjectFile& file,
|
||||
TypeInspectorResult* result,
|
||||
const std::string& parent_type) {
|
||||
if (function.basic_blocks.size() > 1) {
|
||||
result->warnings += " too many basic blocks";
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
;; for a basic
|
||||
or gp, a0, r0 ;; (set! gp a0)
|
||||
lw t9, format(s7) ;; (set! t9 format)
|
||||
daddiu a0, s7, #t ;; (set! a0 '#t)
|
||||
daddiu a1, fp, L362 ;; (set! a1 L362) "[~8x] ~A~%"
|
||||
or a2, gp, r0 ;; (set! a2 gp)
|
||||
lwu a3, -4(gp) ;; (set! a3 (l.wu (+.i gp -4)))
|
||||
jalr ra, t9 ;; (call!)
|
||||
sll v0, ra, 0
|
||||
;; for a struct
|
||||
or gp, a0, r0 ;; (set! gp a0)
|
||||
lw t9, format(s7) ;; (set! t9 format)
|
||||
daddiu a0, s7, #t ;; (set! a0 '#t)
|
||||
daddiu a1, fp, L79 ;; (set! a1 L79) "[~8x] ~A~%"
|
||||
or a2, gp, r0 ;; (set! a2 gp)
|
||||
daddiu a3, s7, dead-pool-heap-rec;; (set! a3 'dead-pool-heap-rec)
|
||||
jalr ra, t9 ;; (call!)
|
||||
*/
|
||||
|
||||
// check size
|
||||
if (function.basic_ops.size() < 7) {
|
||||
result->warnings += " not enough basic ops";
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& move_op = function.basic_ops.at(0);
|
||||
if (!is_reg_reg_move(move_op.get(), make_gpr(Reg::GP), make_gpr(Reg::A0))) {
|
||||
result->warnings += "bad first move";
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& get_format_op = function.basic_ops.at(1);
|
||||
|
||||
if (is_get_sym_value(get_format_op.get(), make_gpr(Reg::T9), "format")) {
|
||||
auto& get_true = function.basic_ops.at(2);
|
||||
if (!is_get_sym(get_true.get(), make_gpr(Reg::A0), "#t")) {
|
||||
result->warnings += "bad get true";
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& get_str = function.basic_ops.at(3);
|
||||
if (!is_get_label(get_str.get(), make_gpr(Reg::A1))) {
|
||||
result->warnings += "bad get label";
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto str = file.get_goal_string_by_label(file.labels.at(get_label_id_of_set(get_str.get())));
|
||||
if (str != "[~8x] ~A~%") {
|
||||
result->warnings += "bad type dec string: " + str;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& move2_op = function.basic_ops.at(4);
|
||||
if (!is_reg_reg_move(move2_op.get(), make_gpr(Reg::A2), make_gpr(Reg::GP))) {
|
||||
result->warnings += "bad second move";
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& load_op = function.basic_ops.at(5);
|
||||
bool is_basic_load = is_get_load_with_offset(load_op.get(), make_gpr(Reg::A3),
|
||||
IR_Load::UNSIGNED, 4, make_gpr(Reg::GP), -4);
|
||||
result->is_basic = is_basic_load;
|
||||
|
||||
bool is_struct_load = is_get_sym(load_op.get(), make_gpr(Reg::A3), function.method_of_type);
|
||||
|
||||
if (!is_basic_load && !is_struct_load) {
|
||||
result->warnings += "bad load";
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& call = function.basic_ops.at(6);
|
||||
if (!dynamic_cast<IR_Call*>(call.get())) {
|
||||
result->warnings += "bad call";
|
||||
return 0;
|
||||
}
|
||||
|
||||
// okay!
|
||||
return 7;
|
||||
} else {
|
||||
if (is_get_sym_value(get_format_op.get(), make_gpr(Reg::V1), parent_type)) {
|
||||
// now get the inspect method.
|
||||
auto& get_method_op = function.basic_ops.at(2);
|
||||
if (!is_get_load_with_offset(get_method_op.get(), make_gpr(Reg::T9), IR_Load::UNSIGNED, 4,
|
||||
make_gpr(Reg::V1), 28)) {
|
||||
result->warnings += "bad get method op " + get_method_op->print(file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& move2_op = function.basic_ops.at(3);
|
||||
if (!is_reg_reg_move(move2_op.get(), make_gpr(Reg::A0), make_gpr(Reg::GP))) {
|
||||
result->warnings += "bad move2 op " + move2_op->print(file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& call_op = function.basic_ops.at(4);
|
||||
if (!dynamic_cast<IR_Call*>(call_op.get())) {
|
||||
result->warnings += "bad call op " + call_op->print(file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
result->warnings += "inherited inpspect of " + parent_type;
|
||||
result->is_basic = true;
|
||||
return 5;
|
||||
|
||||
} else {
|
||||
result->warnings +=
|
||||
"unrecognized get op: " + get_format_op->print(file) + " parent was " + parent_type;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.basic_ops.at(idx++).get());
|
||||
ASSERT(load_info.size == 4);
|
||||
ASSERT(load_info.kind == IR_Load::UNSIGNED || load_info.kind == IR_Load::SIGNED);
|
||||
|
||||
if (load_info.kind == IR_Load::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 identify_pointer_field(int idx,
|
||||
Function& function,
|
||||
LinkedObjectFile& file,
|
||||
TypeInspectorResult* result,
|
||||
FieldPrint& print_info) {
|
||||
(void)file;
|
||||
auto load_info = get_load_info_from_set(function.basic_ops.at(idx++).get());
|
||||
ASSERT(load_info.size == 4);
|
||||
ASSERT(load_info.kind == IR_Load::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;
|
||||
}
|
||||
|
||||
int identify_array_field(int idx,
|
||||
Function& function,
|
||||
LinkedObjectFile& file,
|
||||
TypeInspectorResult* result,
|
||||
FieldPrint& print_info) {
|
||||
auto& get_op = function.basic_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->print(file).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_float_field(int idx,
|
||||
Function& function,
|
||||
LinkedObjectFile& file,
|
||||
TypeInspectorResult* result,
|
||||
FieldPrint& print_info) {
|
||||
auto load_info = get_load_info_from_set(function.basic_ops.at(idx++).get());
|
||||
ASSERT(load_info.size == 4);
|
||||
ASSERT(load_info.kind == IR_Load::FLOAT);
|
||||
|
||||
auto& float_move = function.basic_ops.at(idx++);
|
||||
if (!is_reg_reg_move(float_move.get(), make_gpr(Reg::A2), make_fpr(0))) {
|
||||
printf("bad float move: %s\n", float_move->print(file).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_struct_not_inline_field(int idx,
|
||||
Function& function,
|
||||
LinkedObjectFile& file,
|
||||
TypeInspectorResult* result,
|
||||
FieldPrint& print_info) {
|
||||
(void)file;
|
||||
auto load_info = get_load_info_from_set(function.basic_ops.at(idx++).get());
|
||||
|
||||
if (!(load_info.size == 4 && load_info.kind == IR_Load::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,
|
||||
LinkedObjectFile& file,
|
||||
TypeInspectorResult* result,
|
||||
FieldPrint& print_info) {
|
||||
auto& get_op = function.basic_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->print(file).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_int_field(int idx,
|
||||
Function& function,
|
||||
LinkedObjectFile& file,
|
||||
TypeInspectorResult* result,
|
||||
FieldPrint& print_info) {
|
||||
(void)file;
|
||||
auto load_info = get_load_info_from_set(function.basic_ops.at(idx++).get());
|
||||
|
||||
std::string field_type_name;
|
||||
if (load_info.kind == IR_Load::UNSIGNED) {
|
||||
field_type_name += "u";
|
||||
} else if (load_info.kind == IR_Load::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') {
|
||||
switch (load_info.kind) {
|
||||
case IR_Load::SIGNED:
|
||||
field_type_name = "sseconds";
|
||||
break;
|
||||
case IR_Load::UNSIGNED:
|
||||
field_type_name = "useconds";
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
}
|
||||
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 detect(int idx, Function& function, LinkedObjectFile& file, TypeInspectorResult* result) {
|
||||
auto& get_format_op = function.basic_ops.at(idx++);
|
||||
if (!is_get_sym_value(get_format_op.get(), make_gpr(Reg::T9), "format")) {
|
||||
printf("bad get format");
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
auto& get_true = function.basic_ops.at(idx++);
|
||||
if (!is_get_sym(get_true.get(), make_gpr(Reg::A0), "#t")) {
|
||||
printf("bad get true");
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
auto& get_str = function.basic_ops.at(idx++);
|
||||
if (!is_get_label(get_str.get(), make_gpr(Reg::A1))) {
|
||||
result->warnings += "bad get label";
|
||||
return true;
|
||||
}
|
||||
|
||||
auto str = file.get_goal_string_by_label(file.labels.at(get_label_id_of_set(get_str.get())));
|
||||
auto info = get_field_print(str);
|
||||
|
||||
auto& first_get_op = function.basic_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, file, 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, file, 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, file, result, info);
|
||||
} else if (info.has_array && (info.format == 'X' || info.format == 'P') &&
|
||||
info.field_type_name.empty()) {
|
||||
idx = identify_array_field(idx, function, file, 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, file, result, info);
|
||||
} else {
|
||||
idx = identify_struct_inline_field(idx, function, file, result, info);
|
||||
}
|
||||
}
|
||||
|
||||
else if (is_set_shift(first_get_op.get())) {
|
||||
result->warnings += "likely a bitfield type";
|
||||
return -1;
|
||||
} else {
|
||||
printf("couldn't do %s, %s\n", str.c_str(), first_get_op->print(file).c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto& call_op = function.basic_ops.at(idx++);
|
||||
if (!dynamic_cast<IR_Call*>(call_op.get())) {
|
||||
printf("bad call\n");
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TypeInspectorResult inspect_inspect_method(Function& inspect,
|
||||
const std::string& type_name,
|
||||
DecompilerTypeSystem& dts,
|
||||
LinkedObjectFile& file,
|
||||
bool skip_fields) {
|
||||
TypeInspectorResult result;
|
||||
TypeFlags flags;
|
||||
flags.flag = 0;
|
||||
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;
|
||||
ASSERT(flags.pad == 0);
|
||||
|
||||
int idx = get_start_idx(inspect, file, &result, result.parent_type_name);
|
||||
if (idx == 0 || skip_fields) {
|
||||
// printf("was weird: %s\n", result.warnings.c_str());
|
||||
return result;
|
||||
}
|
||||
while (idx < int(inspect.basic_ops.size()) - 1 && idx != -1) {
|
||||
idx = detect(idx, inspect, file, &result);
|
||||
}
|
||||
|
||||
// todo, continue to identify fields, then identify the return.
|
||||
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string TypeInspectorResult::print_as_deftype() {
|
||||
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(")\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 ");
|
||||
for (int i = 9; i < type_method_count; i++) {
|
||||
result.append(fmt::format("(dummy-{} () none {})\n ", i, i));
|
||||
}
|
||||
result.append(")\n ");
|
||||
}
|
||||
result.append(")\n");
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace decompiler
|
||||
@@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
/*!
|
||||
* @file TypeInspector.h
|
||||
* Analyze an auto-generated GOAL inspect method to determine the layout of a type in memory.
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
class Field;
|
||||
|
||||
namespace decompiler {
|
||||
class Function;
|
||||
class DecompilerTypeSystem;
|
||||
class LinkedObjectFile;
|
||||
|
||||
struct TypeInspectorResult {
|
||||
bool success = false;
|
||||
int type_size = -1;
|
||||
int type_method_count = -1;
|
||||
int type_heap_base = -1;
|
||||
|
||||
std::string warnings;
|
||||
std::vector<Field> fields_of_type;
|
||||
bool is_basic = false;
|
||||
|
||||
std::string type_name;
|
||||
std::string parent_type_name;
|
||||
u64 flags = 0;
|
||||
|
||||
std::string print_as_deftype();
|
||||
};
|
||||
|
||||
TypeInspectorResult inspect_inspect_method(Function& inspect,
|
||||
const std::string& type_name,
|
||||
DecompilerTypeSystem& dts,
|
||||
LinkedObjectFile& file,
|
||||
bool skip_fields);
|
||||
} // namespace decompiler
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +0,0 @@
|
||||
/*!
|
||||
* @file BasicOpBuilder.h
|
||||
* Analyzes a basic block and converts instructions to BasicOps.
|
||||
* These will be used later to convert the Cfg into the nested IR format.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace decompiler {
|
||||
class Function;
|
||||
struct BasicBlock;
|
||||
class LinkedObjectFile;
|
||||
|
||||
void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjectFile* file);
|
||||
} // namespace decompiler
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,454 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef JAK_IR_H
|
||||
#define JAK_IR_H
|
||||
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include "decompiler/Disasm/Register.h"
|
||||
#include "common/type_system/TypeSpec.h"
|
||||
#include "decompiler/util/DecompilerTypeSystem.h"
|
||||
#include "decompiler/util/TP_Type.h"
|
||||
#include "common/util/Assert.h"
|
||||
|
||||
namespace goos {
|
||||
class Object;
|
||||
}
|
||||
|
||||
namespace decompiler {
|
||||
class LinkedObjectFile;
|
||||
class DecompilerTypeSystem;
|
||||
class ExpressionStack;
|
||||
|
||||
class IR {
|
||||
public:
|
||||
virtual goos::Object to_form(const LinkedObjectFile& file) const = 0;
|
||||
std::vector<std::shared_ptr<IR>> get_all_ir(LinkedObjectFile& file) const;
|
||||
std::string print(const LinkedObjectFile& file) const;
|
||||
virtual void get_children(std::vector<std::shared_ptr<IR>>* output) const = 0;
|
||||
bool is_basic_op = false;
|
||||
virtual ~IR() = default;
|
||||
};
|
||||
|
||||
class IR_Atomic : public virtual IR {
|
||||
public:
|
||||
std::vector<Register> read_regs, write_regs, clobber_regs;
|
||||
std::unordered_set<Register, Register::hash> consumed, written_and_unused;
|
||||
bool reg_info_set = false;
|
||||
|
||||
TypeState end_types; // types at the end of this instruction
|
||||
std::vector<std::string> warnings;
|
||||
void warn(const std::string& str) { warnings.emplace_back(str); }
|
||||
};
|
||||
|
||||
class IR_Failed : public virtual IR {
|
||||
public:
|
||||
IR_Failed() = default;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_Failed_Atomic : public IR_Failed, public IR_Atomic {
|
||||
public:
|
||||
IR_Failed_Atomic() = default;
|
||||
};
|
||||
|
||||
class IR_Register : public virtual IR {
|
||||
public:
|
||||
IR_Register(Register _reg, int _instr_idx) : reg(_reg), instr_idx(_instr_idx) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
Register reg;
|
||||
int instr_idx = -1;
|
||||
};
|
||||
|
||||
class IR_Set : public virtual IR {
|
||||
public:
|
||||
enum Kind {
|
||||
REG_64,
|
||||
LOAD,
|
||||
STORE,
|
||||
SYM_LOAD,
|
||||
SYM_STORE,
|
||||
FPR_TO_GPR64,
|
||||
GPR_TO_FPR,
|
||||
REG_FLT,
|
||||
REG_I128,
|
||||
EXPR
|
||||
} kind;
|
||||
IR_Set(Kind _kind, std::shared_ptr<IR> _dst, std::shared_ptr<IR> _src)
|
||||
: kind(_kind), dst(std::move(_dst)), src(std::move(_src)) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
|
||||
std::shared_ptr<IR> dst, src;
|
||||
std::shared_ptr<IR> clobber = nullptr;
|
||||
};
|
||||
|
||||
// todo
|
||||
class IR_Set_Atomic : public IR_Set, public IR_Atomic {
|
||||
public:
|
||||
IR_Set_Atomic(IR_Set::Kind _kind, std::shared_ptr<IR> _dst, std::shared_ptr<IR> _src)
|
||||
: IR_Set(_kind, std::move(_dst), std::move(_src)) {}
|
||||
|
||||
template <typename T>
|
||||
void update_reginfo_self(int n_dest, int n_src, int n_clobber);
|
||||
void update_reginfo_regreg();
|
||||
};
|
||||
|
||||
class IR_IntMath2;
|
||||
template <>
|
||||
void IR_Set_Atomic::update_reginfo_self<IR_IntMath2>(int n_dest, int n_src, int n_clobber);
|
||||
|
||||
class IR_Store : public virtual IR_Set {
|
||||
public:
|
||||
enum class Kind { INTEGER, FLOAT } kind;
|
||||
IR_Store(Kind _kind, std::shared_ptr<IR> _dst, std::shared_ptr<IR> _src, int _size)
|
||||
: IR_Set(IR_Set::STORE, std::move(_dst), std::move(_src)), kind(_kind), size(_size) {}
|
||||
int size;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Note, IR_Store_Atomic does not appear as a IR_Set_Atomic.
|
||||
* This is to avoid the "diamond problem".
|
||||
*/
|
||||
class IR_Store_Atomic : public IR_Set_Atomic {
|
||||
public:
|
||||
enum class Kind { INTEGER, FLOAT } kind;
|
||||
IR_Store_Atomic(Kind _kind, std::shared_ptr<IR> _dst, std::shared_ptr<IR> _src, int _size)
|
||||
: IR_Set_Atomic(IR_Set::STORE, std::move(_dst), std::move(_src)), kind(_kind), size(_size) {}
|
||||
int size;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void update_reginfo_self(int n_dest, int n_src, int n_clobber);
|
||||
};
|
||||
|
||||
class IR_Symbol : public virtual IR {
|
||||
public:
|
||||
explicit IR_Symbol(std::string _name) : name(std::move(_name)) {}
|
||||
std::string name;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_SymbolValue : public virtual IR {
|
||||
public:
|
||||
explicit IR_SymbolValue(std::string _name) : name(std::move(_name)) {}
|
||||
std::string name;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_EmptyPair : public virtual IR {
|
||||
public:
|
||||
explicit IR_EmptyPair() = default;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_StaticAddress : public virtual IR {
|
||||
public:
|
||||
explicit IR_StaticAddress(int _label_id) : label_id(_label_id) {}
|
||||
int label_id = -1;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_Load : public virtual IR {
|
||||
public:
|
||||
enum Kind { UNSIGNED, SIGNED, FLOAT } kind;
|
||||
|
||||
IR_Load(Kind _kind, int _size, std::shared_ptr<IR> _location)
|
||||
: kind(_kind), size(_size), location(std::move(_location)) {}
|
||||
int size;
|
||||
std::shared_ptr<IR> location;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
|
||||
// this load_path stuff is just for debugging and shouldn't be used as part of the real
|
||||
// decompilation.
|
||||
void clear_load_path() {
|
||||
load_path_set = false;
|
||||
load_path_addr_of = false;
|
||||
load_path.clear();
|
||||
load_path_base = nullptr;
|
||||
}
|
||||
std::shared_ptr<IR> load_path_base = nullptr;
|
||||
bool load_path_set = false;
|
||||
bool load_path_addr_of = false;
|
||||
std::vector<std::string> load_path;
|
||||
};
|
||||
|
||||
class IR_FloatMath2 : public virtual IR {
|
||||
public:
|
||||
enum Kind { DIV, MUL, ADD, SUB, MIN, MAX } kind;
|
||||
IR_FloatMath2(Kind _kind, std::shared_ptr<IR> _arg0, std::shared_ptr<IR> _arg1)
|
||||
: kind(_kind), arg0(std::move(_arg0)), arg1(std::move(_arg1)) {}
|
||||
std::shared_ptr<IR> arg0, arg1;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_FloatMath1 : public virtual IR {
|
||||
public:
|
||||
enum Kind { FLOAT_TO_INT, INT_TO_FLOAT, ABS, NEG, SQRT } kind;
|
||||
IR_FloatMath1(Kind _kind, std::shared_ptr<IR> _arg) : kind(_kind), arg(std::move(_arg)) {}
|
||||
std::shared_ptr<IR> arg;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_IntMath2 : public virtual IR {
|
||||
public:
|
||||
enum Kind {
|
||||
ADD,
|
||||
SUB,
|
||||
MUL_SIGNED,
|
||||
DIV_SIGNED,
|
||||
MOD_SIGNED,
|
||||
DIV_UNSIGNED,
|
||||
MOD_UNSIGNED,
|
||||
OR,
|
||||
AND,
|
||||
NOR,
|
||||
XOR,
|
||||
LEFT_SHIFT,
|
||||
RIGHT_SHIFT_ARITH,
|
||||
RIGHT_SHIFT_LOGIC,
|
||||
MUL_UNSIGNED,
|
||||
MIN_SIGNED,
|
||||
MAX_SIGNED
|
||||
} kind;
|
||||
IR_IntMath2(Kind _kind, std::shared_ptr<IR> _arg0, std::shared_ptr<IR> _arg1)
|
||||
: kind(_kind), arg0(std::move(_arg0)), arg1(std::move(_arg1)) {}
|
||||
std::shared_ptr<IR> arg0, arg1;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_IntMath1 : public virtual IR {
|
||||
public:
|
||||
enum Kind { NOT, ABS, NEG } kind;
|
||||
IR_IntMath1(Kind _kind, std::shared_ptr<IR> _arg) : kind(_kind), arg(std::move(_arg)) {}
|
||||
IR_IntMath1(Kind _kind, std::shared_ptr<IR> _arg, std::shared_ptr<IR_Atomic> _abs_op)
|
||||
: kind(_kind), arg(std::move(_arg)), abs_op(std::move(_abs_op)) {
|
||||
ASSERT(abs_op);
|
||||
}
|
||||
std::shared_ptr<IR> arg;
|
||||
std::shared_ptr<IR_Atomic> abs_op = nullptr;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_Call : public virtual IR {
|
||||
public:
|
||||
IR_Call() = default;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
std::vector<std::shared_ptr<IR>> args;
|
||||
TypeSpec call_type;
|
||||
bool call_type_set = false;
|
||||
};
|
||||
|
||||
// todo
|
||||
class IR_Call_Atomic : public virtual IR_Call, public IR_Atomic {
|
||||
public:
|
||||
IR_Call_Atomic() = default;
|
||||
};
|
||||
|
||||
class IR_IntegerConstant : public virtual IR {
|
||||
public:
|
||||
int64_t value;
|
||||
explicit IR_IntegerConstant(int64_t _value) : value(_value) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
struct BranchDelay {
|
||||
enum Kind {
|
||||
NOP,
|
||||
SET_REG_FALSE,
|
||||
SET_REG_TRUE,
|
||||
SET_REG_REG,
|
||||
SET_BINTEGER,
|
||||
SET_PAIR,
|
||||
DSLLV,
|
||||
NEGATE,
|
||||
UNKNOWN
|
||||
} kind;
|
||||
std::shared_ptr<IR> destination = nullptr, source = nullptr, source2 = nullptr;
|
||||
explicit BranchDelay(Kind _kind) : kind(_kind) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const;
|
||||
|
||||
std::vector<Register> read_regs;
|
||||
std::vector<Register> write_regs;
|
||||
std::vector<Register> clobber_regs;
|
||||
|
||||
void type_prop(TypeState& output, const LinkedObjectFile& file, DecompilerTypeSystem& dts);
|
||||
};
|
||||
|
||||
struct Condition {
|
||||
enum Kind {
|
||||
NOT_EQUAL,
|
||||
EQUAL,
|
||||
LESS_THAN_SIGNED,
|
||||
GREATER_THAN_SIGNED,
|
||||
LEQ_SIGNED,
|
||||
GEQ_SIGNED,
|
||||
GREATER_THAN_ZERO_SIGNED,
|
||||
LEQ_ZERO_SIGNED,
|
||||
LESS_THAN_ZERO,
|
||||
GEQ_ZERO_SIGNED,
|
||||
LESS_THAN_UNSIGNED,
|
||||
GREATER_THAN_UNSIGNED,
|
||||
LEQ_UNSIGNED,
|
||||
GEQ_UNSIGNED,
|
||||
ZERO,
|
||||
NONZERO,
|
||||
FALSE,
|
||||
TRUTHY,
|
||||
ALWAYS,
|
||||
NEVER,
|
||||
FLOAT_EQUAL,
|
||||
FLOAT_NOT_EQUAL,
|
||||
FLOAT_LESS_THAN,
|
||||
FLOAT_GEQ,
|
||||
FLOAT_LEQ,
|
||||
FLOAT_GREATER_THAN,
|
||||
} kind;
|
||||
|
||||
Condition(Kind _kind,
|
||||
std::shared_ptr<IR> _src0,
|
||||
std::shared_ptr<IR> _src1,
|
||||
std::shared_ptr<IR> _clobber)
|
||||
: kind(_kind), src0(std::move(_src0)), src1(std::move(_src1)), clobber(std::move(_clobber)) {
|
||||
int nargs = num_args();
|
||||
if (nargs == 2) {
|
||||
ASSERT(src0 && src1);
|
||||
} else if (nargs == 1) {
|
||||
ASSERT(src0 && !src1);
|
||||
} else if (nargs == 0) {
|
||||
ASSERT(!src0 && !src1);
|
||||
}
|
||||
}
|
||||
|
||||
int num_args() const;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const;
|
||||
std::shared_ptr<IR> src0, src1, clobber;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const;
|
||||
void invert();
|
||||
};
|
||||
|
||||
class IR_Branch : public virtual IR {
|
||||
public:
|
||||
IR_Branch(Condition _condition, int _dest_label_idx, BranchDelay _branch_delay, bool _likely)
|
||||
: condition(std::move(_condition)),
|
||||
dest_label_idx(_dest_label_idx),
|
||||
branch_delay(std::move(_branch_delay)),
|
||||
likely(_likely) {}
|
||||
|
||||
Condition condition;
|
||||
int dest_label_idx;
|
||||
BranchDelay branch_delay;
|
||||
bool likely;
|
||||
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
// todo
|
||||
class IR_Branch_Atomic : public virtual IR_Branch, public IR_Atomic {
|
||||
public:
|
||||
IR_Branch_Atomic(Condition _condition,
|
||||
int _dest_label_idx,
|
||||
BranchDelay _branch_delay,
|
||||
bool _likely)
|
||||
: IR_Branch(std::move(_condition), _dest_label_idx, std::move(_branch_delay), _likely) {}
|
||||
// note - counts only for the condition.
|
||||
void update_reginfo_self(int n_dst, int n_src, int n_clobber);
|
||||
};
|
||||
|
||||
class IR_Compare : public virtual IR {
|
||||
public:
|
||||
explicit IR_Compare(Condition _condition, IR_Atomic* _root_op)
|
||||
: condition(std::move(_condition)), root_op(_root_op) {}
|
||||
|
||||
Condition condition;
|
||||
|
||||
// the basic op that the comparison comes from. If the condition is "ALWAYS", this may be null.
|
||||
// if this is the source of an IR_Set_Atomic, this may also be null. This should only be used
|
||||
// from IR_Compare's expression_stack, when the IR_Compare is being used as a branch condition,
|
||||
// and not as a literal #f/#t that's being assigned.
|
||||
IR_Atomic* root_op = nullptr;
|
||||
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_Nop : public virtual IR {
|
||||
public:
|
||||
IR_Nop() = default;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_Nop_Atomic : public IR_Nop, public IR_Atomic {
|
||||
public:
|
||||
IR_Nop_Atomic() = default;
|
||||
};
|
||||
|
||||
class IR_Suspend_Atomic : public virtual IR, public IR_Atomic {
|
||||
public:
|
||||
IR_Suspend_Atomic() = default;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_Breakpoint_Atomic : public virtual IR_Atomic {
|
||||
public:
|
||||
IR_Breakpoint_Atomic() = default;
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_AsmOp : public virtual IR {
|
||||
public:
|
||||
std::shared_ptr<IR> dst = nullptr;
|
||||
std::shared_ptr<IR> src0 = nullptr;
|
||||
std::shared_ptr<IR> src1 = nullptr;
|
||||
std::shared_ptr<IR> src2 = nullptr;
|
||||
std::string name;
|
||||
IR_AsmOp(std::string _name) : name(std::move(_name)) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_AsmOp_Atomic : public virtual IR_AsmOp, public IR_Atomic {
|
||||
public:
|
||||
IR_AsmOp_Atomic(std::string _name) : IR_AsmOp(std::move(_name)) {}
|
||||
void set_reg_info();
|
||||
};
|
||||
|
||||
class IR_CMoveF : public virtual IR {
|
||||
public:
|
||||
std::shared_ptr<IR> src = nullptr;
|
||||
bool on_zero = false;
|
||||
explicit IR_CMoveF(std::shared_ptr<IR> _src, bool _on_zero)
|
||||
: src(std::move(_src)), on_zero(_on_zero) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
class IR_AsmReg : public virtual IR {
|
||||
public:
|
||||
enum Kind { VU_Q, VU_ACC } kind;
|
||||
explicit IR_AsmReg(Kind _kind) : kind(_kind) {}
|
||||
goos::Object to_form(const LinkedObjectFile& file) const override;
|
||||
void get_children(std::vector<std::shared_ptr<IR>>* output) const override;
|
||||
};
|
||||
|
||||
} // namespace decompiler
|
||||
#endif // JAK_IR_H
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <numeric>
|
||||
#include "decompiler/IR/IR.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "LinkedObjectFile.h"
|
||||
#include "decompiler/Disasm/InstructionDecode.h"
|
||||
@@ -583,23 +582,6 @@ std::string LinkedObjectFile::print_function_disassembly(Function& func,
|
||||
result += " ;;";
|
||||
auto& word = words_by_seg[seg].at(func.start_word + i);
|
||||
append_word_to_string(result, word);
|
||||
} else {
|
||||
// print basic op stuff
|
||||
if (func.has_basic_ops() && func.instr_starts_basic_op(i)) {
|
||||
if (line.length() < 30) {
|
||||
line.append(30 - line.length(), ' ');
|
||||
}
|
||||
line += ";; " + func.get_basic_op_at_instr(i)->print(*this);
|
||||
for (int iidx = 0; iidx < instr.n_src; iidx++) {
|
||||
if (instr.get_src(iidx).is_label()) {
|
||||
auto lab = labels.at(instr.get_src(iidx).get_label());
|
||||
if (is_string(lab.target_segment, lab.offset)) {
|
||||
line += " " + get_goal_string(lab.target_segment, lab.offset / 4 - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result += line + "\n";
|
||||
}
|
||||
|
||||
if (in_delay_slot) {
|
||||
@@ -659,11 +641,6 @@ std::string LinkedObjectFile::print_function_disassembly(Function& func,
|
||||
*/
|
||||
}
|
||||
|
||||
if (func.ir) {
|
||||
result += ";; ir\n";
|
||||
result += func.ir->print(*this);
|
||||
}
|
||||
|
||||
result += "\n\n\n";
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
#include "common/util/Timer.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "decompiler/Function/BasicBlocks.h"
|
||||
#include "decompiler/IR/BasicOpBuilder.h"
|
||||
#include "decompiler/Function/TypeInspector.h"
|
||||
#include "common/log/log.h"
|
||||
#include "common/util/json_util.h"
|
||||
|
||||
@@ -134,7 +132,7 @@ ObjectFileDB::ObjectFileDB(const std::vector<std::string>& _dgos,
|
||||
try {
|
||||
get_objs_from_dgo(dgo, config);
|
||||
} catch (std::runtime_error& e) {
|
||||
lg::warn("Error when reading DGOs: {}", e.what());
|
||||
lg::warn("Error when reading DGOs: {} on {}", e.what(), dgo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -735,8 +733,6 @@ void ObjectFileDB::analyze_functions_ir1(const Config& config) {
|
||||
|
||||
int total_trivial_cfg_functions = 0;
|
||||
int total_named_functions = 0;
|
||||
int total_basic_ops = 0;
|
||||
int total_failed_basic_ops = 0;
|
||||
|
||||
int asm_funcs = 0;
|
||||
|
||||
@@ -774,24 +770,8 @@ void ObjectFileDB::analyze_functions_ir1(const Config& config) {
|
||||
if (label_id != -1) {
|
||||
block.label_name = data.linked_data.get_label_name(label_id);
|
||||
}
|
||||
|
||||
block.start_basic_op = func.basic_ops.size();
|
||||
add_basic_ops_to_block(&func, block, &data.linked_data);
|
||||
block.end_basic_op = func.basic_ops.size();
|
||||
}
|
||||
}
|
||||
total_basic_ops += func.get_basic_op_count();
|
||||
total_failed_basic_ops += func.get_failed_basic_op_count();
|
||||
|
||||
// if we got an inspect method, inspect it.
|
||||
if (func.is_inspect_method) {
|
||||
auto result = inspect_inspect_method(
|
||||
func, func.method_of_type, dts, data.linked_data,
|
||||
config.hacks.types_with_bad_inspect_methods.find(func.method_of_type) !=
|
||||
config.hacks.types_with_bad_inspect_methods.end());
|
||||
all_type_defs += ";; " + data.to_unique_name() + "\n";
|
||||
all_type_defs += result.print_as_deftype() + "\n";
|
||||
}
|
||||
} else {
|
||||
asm_funcs++;
|
||||
}
|
||||
@@ -802,10 +782,6 @@ void ObjectFileDB::analyze_functions_ir1(const Config& config) {
|
||||
lg::info("Named {}/{} functions ({:.3f}%)", total_named_functions, total_functions,
|
||||
100.f * float(total_named_functions) / float(total_functions));
|
||||
lg::info("Excluding {} asm functions", asm_funcs);
|
||||
lg::info("Found {} basic blocks in {:.3f} ms", total_basic_blocks, timer.getMs());
|
||||
int successful_basic_ops = total_basic_ops - total_failed_basic_ops;
|
||||
lg::info(" {}/{} basic ops converted successfully ({:.3f}%)", successful_basic_ops,
|
||||
total_basic_ops, 100.f * float(successful_basic_ops) / float(total_basic_ops));
|
||||
}
|
||||
|
||||
void ObjectFileDB::dump_raw_objects(const std::string& output_dir) {
|
||||
|
||||
@@ -103,7 +103,6 @@ class ObjectFileDB {
|
||||
|
||||
ObjectFileData& lookup_record(const ObjectFileRecord& rec);
|
||||
DecompilerTypeSystem dts;
|
||||
std::string all_type_defs;
|
||||
|
||||
bool lookup_function_type(const FunctionName& name,
|
||||
const std::string& obj_name,
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "common/log/log.h"
|
||||
#include "common/util/Timer.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "decompiler/Function/TypeInspector.h"
|
||||
#include "decompiler/analysis/type_analysis.h"
|
||||
#include "decompiler/analysis/reg_usage.h"
|
||||
#include "decompiler/analysis/insert_lets.h"
|
||||
|
||||
@@ -54,7 +54,6 @@ Config read_config_file(const std::string& path_to_config_file,
|
||||
}
|
||||
config.disassemble_code = cfg.at("disassemble_code").get<bool>();
|
||||
config.decompile_code = cfg.at("decompile_code").get<bool>();
|
||||
config.regenerate_all_types = cfg.at("regenerate_all_types").get<bool>();
|
||||
config.write_hex_near_instructions = cfg.at("write_hex_near_instructions").get<bool>();
|
||||
config.write_scripts = cfg.at("write_scripts").get<bool>();
|
||||
config.disassemble_data = cfg.at("disassemble_data").get<bool>();
|
||||
|
||||
@@ -101,7 +101,6 @@ struct Config {
|
||||
bool process_game_count = false;
|
||||
bool rip_levels = false;
|
||||
|
||||
bool regenerate_all_types = false;
|
||||
bool write_hex_near_instructions = false;
|
||||
bool hexdump_code = false;
|
||||
bool hexdump_data = false;
|
||||
|
||||
@@ -42,9 +42,6 @@
|
||||
|
||||
// these options are used rarely and should usually be left at false
|
||||
|
||||
// output a file type_defs.gc which is used for the types part of all-types.gc
|
||||
"regenerate_all_types": false,
|
||||
|
||||
// generate the symbol_map.json file.
|
||||
// this is a guess at where each symbol is first defined/used.
|
||||
"generate_symbol_definition_map": false,
|
||||
|
||||
@@ -155,13 +155,6 @@ int main(int argc, char** argv) {
|
||||
config.write_hex_near_instructions);
|
||||
}
|
||||
|
||||
// regenerate all-types if needed
|
||||
if (config.regenerate_all_types) {
|
||||
db.analyze_functions_ir1(config);
|
||||
file_util::write_text_file(file_util::combine_path(out_folder, "type_defs.gc"),
|
||||
db.all_type_defs);
|
||||
}
|
||||
|
||||
// main decompile.
|
||||
if (config.decompile_code) {
|
||||
db.analyze_functions_ir2(out_folder, config, {});
|
||||
|
||||
@@ -199,7 +199,7 @@ Decompiler setup_decompiler(const std::vector<DecompilerFile>& files,
|
||||
std::vector<std::string> dgo_paths;
|
||||
if (args.iso_data_path.empty()) {
|
||||
for (auto& x : offline_config.dgos) {
|
||||
dgo_paths.push_back((file_util::get_jak_project_dir() / "iso_data" / "jak1").string());
|
||||
dgo_paths.push_back((file_util::get_jak_project_dir() / "iso_data" / "jak1" / x).string());
|
||||
}
|
||||
} else {
|
||||
for (auto& x : offline_config.dgos) {
|
||||
|
||||
Reference in New Issue
Block a user