[decompiler] support for jak 2 (#1781)

* [decompiler] suppport jak 2

* cleanpu

* remove brief from gtest options

* fix test
This commit is contained in:
water111
2022-08-22 18:53:51 -04:00
committed by GitHub
parent 5af36ac610
commit 06ef52cd25
51 changed files with 9934 additions and 4555 deletions
+13 -14
View File
@@ -482,20 +482,6 @@ FieldReverseLookupOutput TypeSystem::reverse_field_lookup(
// just use the multi-lookup set to 1 and grab the first result.
auto multi_result = reverse_field_multi_lookup(input, 100);
for (auto& result : multi_result.results) {
// compute the score.
result.total_score = 0;
for (auto& tok : result.tokens) {
result.total_score += tok.score();
}
}
// use stable sort to make sure we break ties by being first in the order.
std::stable_sort(multi_result.results.begin(), multi_result.results.end(),
[](const FieldReverseLookupOutput& a, const FieldReverseLookupOutput& b) {
return a.total_score > b.total_score;
});
/*
if (multi_result.results.size() > 1) {
fmt::print("Multiple:\n");
@@ -532,6 +518,19 @@ FieldReverseMultiLookupOutput TypeSystem::reverse_field_multi_lookup(
try_reverse_lookup(input, *this, nullptr, &result, max_count);
if (!result.results.empty()) {
result.success = true;
for (auto& r : result.results) {
// compute the score.
r.total_score = 0;
for (auto& tok : r.tokens) {
r.total_score += tok.score();
}
}
// use stable sort to make sure we break ties by being first in the order.
std::stable_sort(result.results.begin(), result.results.end(),
[](const FieldReverseLookupOutput& a, const FieldReverseLookupOutput& b) {
return a.total_score > b.total_score;
});
}
return result;
}
+3
View File
@@ -69,6 +69,9 @@ add_library(
ObjectFile/ObjectFileDB.cpp
ObjectFile/ObjectFileDB_IR2.cpp
types2/ForwardProp.cpp
types2/types2.cpp
util/config_parsers.cpp
util/data_decompile.cpp
util/DataParser.cpp
-1
View File
@@ -104,6 +104,5 @@ class DecompWarnings {
}
std::vector<Warning> m_warnings;
bool m_used_lq_sq = false;
};
} // namespace decompiler
+47 -4
View File
@@ -142,16 +142,55 @@ SimpleAtom SimpleAtom::make_static_address(int static_label_id) {
return result;
}
/*!
* Mark this atom as a float. It will be printed as a float.
* This can only be applied to an "integer" atom.
* This should be used carefully, as this doesn't handle casts/types - it just changes the
* representation, which will do the wrong thing unless the type system is aware of this
* too.
*/
void SimpleAtom::mark_as_float() {
ASSERT(is_int());
m_display_int_as_float = true;
}
bool SimpleAtom::is_integer_promoted_to_float() const {
return m_kind == Kind::INTEGER_CONSTANT && m_display_int_as_float;
}
float SimpleAtom::get_integer_promoted_to_float() const {
ASSERT(is_integer_promoted_to_float());
s32 as_s32 = get_int();
ASSERT(get_int() == (s64)as_s32);
float result;
memcpy(&result, &as_s32, 4);
return result;
}
goos::Object SimpleAtom::to_form(const std::vector<DecompilerLabel>& labels, const Env& env) const {
switch (m_kind) {
case Kind::VARIABLE:
return m_variable.to_form(env);
case Kind::INTEGER_CONSTANT: {
if (std::abs(m_int) > INT32_MAX) {
u64 v = m_int;
return pretty_print::to_symbol(fmt::format("#x{:x}", v));
if (m_display_int_as_float) {
float f;
s32 as_s32 = m_int;
ASSERT(((s64)as_s32) == m_int); // float should always be a sign extended 32-bit value.
memcpy(&f, &as_s32, 4);
if (f == f) {
return goos::Object::make_float(f);
} else {
// nan or weird
ASSERT(false); // let's abort on this for now, can remove if it actually comes up.
return pretty_print::to_symbol(fmt::format("(the-as float #x{:x})", m_int));
}
} else {
return goos::Object::make_integer(m_int);
if (std::abs(m_int) > INT32_MAX) {
u64 v = m_int;
return pretty_print::to_symbol(fmt::format("#x{:x}", v));
} else {
return goos::Object::make_integer(m_int);
}
}
}
@@ -311,6 +350,8 @@ std::string get_simple_expression_op_name(SimpleExpression::Kind kind) {
return "vec3dot";
case SimpleExpression::Kind::VECTOR_4_DOT:
return "vec4dot";
case SimpleExpression::Kind::VECTOR_LENGTH:
return "veclength";
case SimpleExpression::Kind::SET_ON_LESS_THAN:
case SimpleExpression::Kind::SET_ON_LESS_THAN_IMM:
return "set-on-less-than";
@@ -378,6 +419,8 @@ int get_simple_expression_arg_count(SimpleExpression::Kind kind) {
case SimpleExpression::Kind::SET_ON_LESS_THAN:
case SimpleExpression::Kind::SET_ON_LESS_THAN_IMM:
return 2;
case SimpleExpression::Kind::VECTOR_LENGTH:
return 1;
default:
ASSERT(false);
return -1;
+88 -1
View File
@@ -19,6 +19,11 @@ class FormElement;
class ConditionElement;
class FormPool;
class DecompilerTypeSystem;
namespace types2 {
struct Instruction;
struct TypeState;
struct TypePropExtras;
} // namespace types2
/*!
* An atomic operation represents a single operation from the point of view of the IR2 system.
@@ -78,6 +83,12 @@ class AtomicOp {
TypeState propagate_types(const TypeState& input, const Env& env, DecompilerTypeSystem& dts);
virtual void propagate_types2(types2::Instruction& instr,
const Env& env,
types2::TypeState& input_types,
DecompilerTypeSystem& dts,
types2::TypePropExtras& extras) = 0;
int op_id() const { return m_my_idx; }
const std::vector<Register>& read_regs() const { return m_read_regs; }
const std::vector<Register>& write_regs() const { return m_write_regs; }
@@ -176,12 +187,16 @@ class SimpleAtom {
ASSERT(is_sym_ptr() || is_sym_val());
return m_string;
}
void mark_as_float();
bool is_integer_promoted_to_float() const;
float get_integer_promoted_to_float() const;
private:
Kind m_kind = Kind::INVALID;
std::string m_string; // for symbol ptr and symbol val
s64 m_int = -1; // for integer constant and static address label id
RegisterAccess m_variable;
bool m_display_int_as_float = false;
};
/*!
@@ -240,6 +255,7 @@ class SimpleExpression {
SUBU_L32_S7, // use SUBU X, src0, s7 to check if lower 32-bits are s7.
VECTOR_3_DOT,
VECTOR_4_DOT,
VECTOR_LENGTH, // jak 2 only.
SET_ON_LESS_THAN,
SET_ON_LESS_THAN_IMM
};
@@ -250,6 +266,11 @@ class SimpleExpression {
ASSERT(idx < args());
return m_args[idx];
}
SimpleAtom& get_arg(int idx) {
ASSERT(idx < args());
return m_args[idx];
}
Kind kind() const { return m_kind; }
SimpleExpression() = default;
SimpleExpression(Kind kind, const SimpleAtom& arg0);
@@ -304,6 +325,11 @@ class SetVarOp : public AtomicOp {
TypeState propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem& dts) override;
void propagate_types2(types2::Instruction& instr,
const Env& env,
types2::TypeState& input_types,
DecompilerTypeSystem& dts,
types2::TypePropExtras& extras) override;
void collect_vars(RegAccessSet& vars) const override;
const RegisterAccess& dst() const { return m_dst; }
const SimpleExpression& src() const { return m_src; }
@@ -334,6 +360,11 @@ class AsmOp : public AtomicOp {
TypeState propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem& dts) override;
void propagate_types2(types2::Instruction& instr,
const Env& env,
types2::TypeState& input_types,
DecompilerTypeSystem& dts,
types2::TypePropExtras& extras) override;
void collect_vars(RegAccessSet& vars) const override;
const Instruction& instruction() const { return m_instr; }
const std::optional<RegisterAccess> dst() const { return m_dst; }
@@ -437,6 +468,11 @@ class SetVarConditionOp : public AtomicOp {
TypeState propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem& dts) override;
void propagate_types2(types2::Instruction& instr,
const Env& env,
types2::TypeState& input_types,
DecompilerTypeSystem& dts,
types2::TypePropExtras& extras) override;
void collect_vars(RegAccessSet& vars) const override;
private:
@@ -463,6 +499,11 @@ class StoreOp : public AtomicOp {
TypeState propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem& dts) override;
void propagate_types2(types2::Instruction& instr,
const Env& env,
types2::TypeState& input_types,
DecompilerTypeSystem& dts,
types2::TypePropExtras& extras) override;
void collect_vars(RegAccessSet& vars) const override;
const SimpleExpression& addr() const { return m_addr; }
const SimpleAtom& value() const { return m_value; }
@@ -492,9 +533,13 @@ class LoadVarOp : public AtomicOp {
TypeState propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem& dts) override;
void propagate_types2(types2::Instruction& instr,
const Env& env,
types2::TypeState& input_types,
DecompilerTypeSystem& dts,
types2::TypePropExtras& extras) override;
TP_Type get_src_type(const TypeState& input, const Env& env, DecompilerTypeSystem& dts) const;
void collect_vars(RegAccessSet& vars) const override;
const SimpleExpression& src() const { return m_src; }
Kind kind() const { return m_kind; }
int size() const { return m_size; }
@@ -580,6 +625,11 @@ class BranchOp : public AtomicOp {
TypeState propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem& dts) override;
void propagate_types2(types2::Instruction& instr,
const Env& env,
types2::TypeState& input_types,
DecompilerTypeSystem& dts,
types2::TypePropExtras& extras) override;
void collect_vars(RegAccessSet& vars) const override;
const IR2_BranchDelay& branch_delay() const { return m_branch_delay; }
const IR2_Condition& condition() const { return m_condition; }
@@ -616,6 +666,11 @@ class AsmBranchOp : public AtomicOp {
TypeState propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem& dts) override;
void propagate_types2(types2::Instruction& instr,
const Env& env,
types2::TypeState& input_types,
DecompilerTypeSystem& dts,
types2::TypePropExtras& extras) override;
void collect_vars(RegAccessSet& vars) const override;
bool is_likely() const { return m_likely; }
const IR2_Condition& condition() const { return m_condition; }
@@ -653,6 +708,11 @@ class SpecialOp : public AtomicOp {
TypeState propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem& dts) override;
void propagate_types2(types2::Instruction& instr,
const Env& env,
types2::TypeState& input_types,
DecompilerTypeSystem& dts,
types2::TypePropExtras& extras) override;
void collect_vars(RegAccessSet& vars) const override;
Kind kind() const { return m_kind; }
@@ -676,6 +736,11 @@ class CallOp : public AtomicOp {
TypeState propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem& dts) override;
void propagate_types2(types2::Instruction& instr,
const Env& env,
types2::TypeState& input_types,
DecompilerTypeSystem& dts,
types2::TypePropExtras& extras) override;
void collect_vars(RegAccessSet& vars) const override;
const std::vector<RegisterAccess>& arg_vars() const { return m_arg_vars; }
RegisterAccess function_var() const { return m_function_var; }
@@ -717,6 +782,11 @@ class ConditionalMoveFalseOp : public AtomicOp {
TypeState propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem& dts) override;
void propagate_types2(types2::Instruction& instr,
const Env& env,
types2::TypeState& input_types,
DecompilerTypeSystem& dts,
types2::TypePropExtras& extras) override;
void collect_vars(RegAccessSet& vars) const override;
private:
@@ -748,6 +818,11 @@ class FunctionEndOp : public AtomicOp {
TypeState propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem& dts) override;
void propagate_types2(types2::Instruction& instr,
const Env& env,
types2::TypeState& input_types,
DecompilerTypeSystem& dts,
types2::TypePropExtras& extras) override;
void collect_vars(RegAccessSet& vars) const override;
void mark_function_as_no_return_value();
const RegisterAccess& return_var() const {
@@ -775,7 +850,13 @@ class StackSpillStoreOp : public AtomicOp {
TypeState propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem& dts) override;
void propagate_types2(types2::Instruction& instr,
const Env& env,
types2::TypeState& input_types,
DecompilerTypeSystem& dts,
types2::TypePropExtras& extras) override;
void collect_vars(RegAccessSet& vars) const override;
int offset() const { return m_offset; }
private:
SimpleAtom m_value;
@@ -798,7 +879,13 @@ class StackSpillLoadOp : public AtomicOp {
TypeState propagate_types_internal(const TypeState& input,
const Env& env,
DecompilerTypeSystem& dts) override;
void propagate_types2(types2::Instruction& instr,
const Env& env,
types2::TypeState& input_types,
DecompilerTypeSystem& dts,
types2::TypePropExtras& extras) override;
void collect_vars(RegAccessSet& vars) const override;
int offset() const { return m_offset; }
private:
RegisterAccess m_dst;
+1
View File
@@ -243,6 +243,7 @@ TP_Type SimpleExpression::get_type(const TypeState& input,
return TP_Type::make_from_ts("int");
case Kind::VECTOR_3_DOT:
case Kind::VECTOR_4_DOT:
case Kind::VECTOR_LENGTH:
return TP_Type::make_from_ts("float");
default:
throw std::runtime_error("Simple expression cannot get_type: " +
+64 -60
View File
@@ -507,69 +507,73 @@ void Env::disable_use(const RegisterAccess& access) {
*/
void Env::set_stack_structure_hints(const std::vector<StackStructureHint>& hints) {
for (auto& hint : hints) {
StackStructureEntry entry;
entry.hint = hint;
switch (hint.container_type) {
case StackStructureHint::ContainerType::NONE: {
// parse the type spec.
TypeSpec base_typespec = dts->parse_type_spec(hint.element_type);
auto type_info = dts->ts.lookup_type(base_typespec);
// just a plain object on the stack.
if (!type_info->is_reference()) {
throw std::runtime_error(
fmt::format("Stack variable type {} is not a reference and cannot be stored directly "
"on the stack. Use an array instead.",
base_typespec.print()));
}
entry.ref_type = base_typespec;
entry.size = type_info->get_size_in_memory();
// sanity check the alignment
if (align(entry.hint.stack_offset, type_info->get_in_memory_alignment()) !=
entry.hint.stack_offset) {
lg::error("Misaligned stack variable of type {} offset {} required align {}\n",
entry.ref_type.print(), entry.hint.stack_offset,
type_info->get_in_memory_alignment());
}
} break;
case StackStructureHint::ContainerType::INLINE_ARRAY: {
TypeSpec base_typespec = dts->parse_type_spec(hint.element_type);
auto type_info = dts->ts.lookup_type(base_typespec);
if (!type_info->is_reference()) {
throw std::runtime_error(
fmt::format("Stack inline-array element type {} is not a reference and cannot be "
"stored in an inline-array. Use an array instead.",
base_typespec.print()));
}
entry.ref_type = TypeSpec("inline-array", {TypeSpec(base_typespec)});
entry.size = 1; // we assume that there is no constant propagation into this array and
// make this only trigger in get_stack_type if we hit exactly.
// sanity check the alignment
if (align(entry.hint.stack_offset, type_info->get_in_memory_alignment()) !=
entry.hint.stack_offset) {
lg::error("Misaligned stack variable of type {} offset {} required align {}\n",
entry.ref_type.print(), entry.hint.stack_offset,
type_info->get_in_memory_alignment());
}
} break;
case StackStructureHint::ContainerType::ARRAY: {
TypeSpec base_typespec = dts->parse_type_spec(hint.element_type);
entry.ref_type = TypeSpec("pointer", {TypeSpec(base_typespec)});
entry.size = 1; // we assume that there is no constant propagation into this array and
// make this only trigger in get_stack_type if we hit exactly.
break;
}
default:
ASSERT(false);
}
m_stack_structures.push_back(entry);
add_stack_structure_hint(hint);
}
}
void Env::add_stack_structure_hint(const StackStructureHint& hint) {
StackStructureEntry entry;
entry.hint = hint;
switch (hint.container_type) {
case StackStructureHint::ContainerType::NONE: {
// parse the type spec.
TypeSpec base_typespec = dts->parse_type_spec(hint.element_type);
auto type_info = dts->ts.lookup_type(base_typespec);
// just a plain object on the stack.
if (!type_info->is_reference()) {
throw std::runtime_error(
fmt::format("Stack variable type {} is not a reference and cannot be stored directly "
"on the stack. Use an array instead.",
base_typespec.print()));
}
entry.ref_type = base_typespec;
entry.size = type_info->get_size_in_memory();
// sanity check the alignment
if (align(entry.hint.stack_offset, type_info->get_in_memory_alignment()) !=
entry.hint.stack_offset) {
lg::error("Misaligned stack variable of type {} offset {} required align {}\n",
entry.ref_type.print(), entry.hint.stack_offset,
type_info->get_in_memory_alignment());
}
} break;
case StackStructureHint::ContainerType::INLINE_ARRAY: {
TypeSpec base_typespec = dts->parse_type_spec(hint.element_type);
auto type_info = dts->ts.lookup_type(base_typespec);
if (!type_info->is_reference()) {
throw std::runtime_error(
fmt::format("Stack inline-array element type {} is not a reference and cannot be "
"stored in an inline-array. Use an array instead.",
base_typespec.print()));
}
entry.ref_type = TypeSpec("inline-array", {TypeSpec(base_typespec)});
entry.size = 1; // we assume that there is no constant propagation into this array and
// make this only trigger in get_stack_type if we hit exactly.
// sanity check the alignment
if (align(entry.hint.stack_offset, type_info->get_in_memory_alignment()) !=
entry.hint.stack_offset) {
lg::error("Misaligned stack variable of type {} offset {} required align {}\n",
entry.ref_type.print(), entry.hint.stack_offset,
type_info->get_in_memory_alignment());
}
} break;
case StackStructureHint::ContainerType::ARRAY: {
TypeSpec base_typespec = dts->parse_type_spec(hint.element_type);
entry.ref_type = TypeSpec("pointer", {TypeSpec(base_typespec)});
entry.size = 1; // we assume that there is no constant propagation into this array and
// make this only trigger in get_stack_type if we hit exactly.
break;
}
default:
ASSERT(false);
}
m_stack_structures.push_back(entry);
}
std::optional<std::string> Env::get_art_elt_name(int idx) const {
ASSERT(dts);
auto it = dts->art_group_info.find(art_group());
+1
View File
@@ -174,6 +174,7 @@ class Env {
}
void set_stack_structure_hints(const std::vector<StackStructureHint>& hints);
void add_stack_structure_hint(const StackStructureHint& hint);
const std::vector<StackStructureEntry>& stack_structure_hints() const {
return m_stack_structures;
}
+23 -8
View File
@@ -9,6 +9,20 @@
namespace decompiler {
// needed for jak 2.
std::optional<float> try_get_const_float(const Form* form) {
auto* as_cfe = form->try_as_element<ConstantFloatElement>();
if (as_cfe) {
return as_cfe->value();
}
auto atom = form_as_atom(form);
if (atom && atom->is_integer_promoted_to_float()) {
return atom->get_integer_promoted_to_float();
}
return {};
}
FormElement* handle_get_property_value_float(const std::vector<Form*>& forms,
FormPool& pool,
const Env& env) {
@@ -35,8 +49,8 @@ FormElement* handle_get_property_value_float(const std::vector<Form*>& forms,
}
// get the time. It must be DEFAULT_RES_TIME
auto lookup_time = forms.at(3)->try_as_element<ConstantFloatElement>();
if (!lookup_time || lookup_time->value() != DEFAULT_RES_TIME) {
auto lookup_time = try_get_const_float(forms.at(3));
if (!lookup_time || *lookup_time != DEFAULT_RES_TIME) {
fmt::print("fail: bad time {}\n", forms.at(3)->to_string(env));
return nullptr;
}
@@ -44,8 +58,8 @@ FormElement* handle_get_property_value_float(const std::vector<Form*>& forms,
// get the default value. It can be anything...
Form* default_value = forms.at(4);
// but let's see if it's 0, because that's the default in the macro
auto default_value_float = default_value->try_as_element<ConstantFloatElement>();
if (default_value_float && default_value_float->value() == 0) {
auto default_value_float = try_get_const_float(default_value);
if (default_value_float && *default_value_float == 0) {
default_value = nullptr;
}
@@ -111,8 +125,8 @@ FormElement* handle_get_property_data_or_structure(const std::vector<Form*>& for
// get the time. It can be anything, but there's a default.
auto time = forms.at(3);
auto lookup_time = time->try_as_element<ConstantFloatElement>();
if (lookup_time && lookup_time->value() == DEFAULT_RES_TIME) {
auto lookup_time = try_get_const_float(time);
if (lookup_time && *lookup_time == DEFAULT_RES_TIME) {
time = nullptr;
}
@@ -153,8 +167,9 @@ FormElement* handle_get_property_data(const std::vector<Form*>& forms,
FormElement* handle_get_property_struct(const std::vector<Form*>& forms,
FormPool& pool,
const Env& env) {
return handle_get_property_data_or_structure(forms, pool, env, ResLumpMacroElement::Kind::STRUCT,
"#f", TypeSpec("structure"));
return handle_get_property_data_or_structure(
forms, pool, env, ResLumpMacroElement::Kind::STRUCT,
env.version == GameVersion::Jak2 ? "(the-as structure #f)" : "#f", TypeSpec("structure"));
}
FormElement* handle_get_property_value(const std::vector<Form*>& forms,
+3
View File
@@ -1862,6 +1862,8 @@ std::string fixed_operator_to_string(FixedOperatorKind kind) {
return "cpad-pressed?";
case FixedOperatorKind::CPAD_HOLD_P:
return "cpad-hold?";
case FixedOperatorKind::VECTOR_LENGTH:
return "vector-length";
default:
ASSERT(false);
return "";
@@ -3193,6 +3195,7 @@ goos::Object DefpartElement::to_form_internal(const Env& env) const {
// sp-end
break;
}
ASSERT(env.version == GameVersion::Jak1); // need to update enums
item_forms.push_back(decompile_sparticle_field_init(e, env.dts->ts));
}
if (!item_forms.empty()) {
+6 -6
View File
@@ -228,12 +228,12 @@ class SimpleExpressionElement : public FormElement {
FormStack& stack,
std::vector<FormElement*>* result,
bool allow_side_effects);
void update_from_stack_vector_dot(FixedOperatorKind kind,
const Env& env,
FormPool& pool,
FormStack& stack,
std::vector<FormElement*>* result,
bool allow_side_effects);
void update_from_stack_vectors_in_common(FixedOperatorKind kind,
const Env& env,
FormPool& pool,
FormStack& stack,
std::vector<FormElement*>* result,
bool allow_side_effects);
const SimpleExpression& expr() const { return m_expr; }
+48 -22
View File
@@ -1504,24 +1504,28 @@ void SimpleExpressionElement::update_from_stack_vector_float_product(
result->push_back(new_form);
}
void SimpleExpressionElement::update_from_stack_vector_dot(FixedOperatorKind kind,
const Env& env,
FormPool& pool,
FormStack& stack,
std::vector<FormElement*>* result,
bool allow_side_effects) {
std::vector<Form*> popped_args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var()},
env, pool, stack, allow_side_effects);
void SimpleExpressionElement::update_from_stack_vectors_in_common(FixedOperatorKind kind,
const Env& env,
FormPool& pool,
FormStack& stack,
std::vector<FormElement*>* result,
bool allow_side_effects) {
std::vector<RegisterAccess> register_acccesses;
for (int arg_idx = 0; arg_idx < m_expr.args(); arg_idx++) {
register_acccesses.push_back(m_expr.get_arg(arg_idx).var());
}
std::vector<Form*> popped_args =
pop_to_forms(register_acccesses, env, pool, stack, allow_side_effects);
for (int i = 0; i < 2; i++) {
for (int i = 0; i < m_expr.args(); i++) {
auto arg_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(i).var().reg());
if (arg_type.typespec() != TypeSpec("vector")) {
popped_args.at(i) = cast_form(popped_args.at(i), TypeSpec("vector"), pool, env);
}
}
auto new_form = pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(kind), std::vector<Form*>{popped_args.at(0), popped_args.at(1)});
auto new_form =
pool.alloc_element<GenericElement>(GenericOperator::make_fixed(kind), popped_args);
result->push_back(new_form);
}
@@ -1606,7 +1610,14 @@ FormElement* SimpleExpressionElement::update_from_stack_logor_or_logand_helper(
FormStack& stack,
bool allow_side_effects) {
// grab the normal variable type
auto arg0_type = env.get_variable_type(m_expr.get_arg(0).var(), true);
TypeSpec arg0_type;
if (m_expr.get_arg(0).is_var()) {
arg0_type = env.get_variable_type(m_expr.get_arg(0).var(), true);
} else if (m_expr.get_arg(0).is_int(0)) {
arg0_type = TypeSpec("int"); // ??
} else {
ASSERT(false);
}
// and try to get it as a bitfield
auto type_info = env.dts->ts.lookup_type(arg0_type);
@@ -1614,7 +1625,7 @@ FormElement* SimpleExpressionElement::update_from_stack_logor_or_logand_helper(
bool had_pcpyud = false;
TypeSpec bitfield_type = arg0_type;
if (!bitfield_info) {
if (!bitfield_info && m_expr.get_arg(0).is_var()) {
// the above won't work if we're already done a pcpyud to grab the upper 64 bits.
// we need to grab the type in the register (a TP_type) and check
const auto& arg0_reg_type =
@@ -1672,15 +1683,25 @@ FormElement* SimpleExpressionElement::update_from_stack_logor_or_logand_helper(
} else {
// and, two forms
auto arg1_type = env.get_variable_type(m_expr.get_arg(1).var(), true);
auto arg0_i = is_int_type(env, m_my_idx, m_expr.get_arg(0).var());
auto arg0_u = is_uint_type(env, m_my_idx, m_expr.get_arg(0).var());
auto arg0_i =
m_expr.get_arg(0).is_var() ? is_int_type(env, m_my_idx, m_expr.get_arg(0).var()) : true;
auto arg0_u =
m_expr.get_arg(0).is_var() ? is_uint_type(env, m_my_idx, m_expr.get_arg(0).var()) : false;
auto arg1_i = is_int_type(env, m_my_idx, m_expr.get_arg(1).var());
auto arg1_u = is_uint_type(env, m_my_idx, m_expr.get_arg(1).var());
auto arg0_n = arg0_i || arg0_u;
auto arg1_n = arg1_i || arg1_u;
auto args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var()}, env, pool, stack,
allow_side_effects);
std::vector<Form*> args;
if (m_expr.get_arg(0).is_var()) {
args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var()}, env, pool, stack,
allow_side_effects);
} else {
args = pop_to_forms({m_expr.get_arg(1).var()}, env, pool, stack, allow_side_effects);
args.insert(args.begin(), pool.form<SimpleAtomElement>(
SimpleAtom::make_int_constant(m_expr.get_arg(0).get_int())));
}
if (bitfield_info) {
// either the immediate didn't fit in the 16-bit imm or it's with a variable
@@ -2318,12 +2339,16 @@ void SimpleExpressionElement::update_from_stack(const Env& env,
update_from_stack_subu_l32_s7(env, pool, stack, result, allow_side_effects);
break;
case SimpleExpression::Kind::VECTOR_3_DOT:
update_from_stack_vector_dot(FixedOperatorKind::VECTOR_3_DOT, env, pool, stack, result,
allow_side_effects);
update_from_stack_vectors_in_common(FixedOperatorKind::VECTOR_3_DOT, env, pool, stack, result,
allow_side_effects);
break;
case SimpleExpression::Kind::VECTOR_4_DOT:
update_from_stack_vector_dot(FixedOperatorKind::VECTOR_4_DOT, env, pool, stack, result,
allow_side_effects);
update_from_stack_vectors_in_common(FixedOperatorKind::VECTOR_4_DOT, env, pool, stack, result,
allow_side_effects);
break;
case SimpleExpression::Kind::VECTOR_LENGTH:
update_from_stack_vectors_in_common(FixedOperatorKind::VECTOR_LENGTH, env, pool, stack,
result, allow_side_effects);
break;
default:
throw std::runtime_error(
@@ -2701,7 +2726,8 @@ bool try_to_rewrite_matrix_inline_ctor(const Env& env, FormPool& pool, FormStack
} else {
matcher = Matcher::set(
Matcher::deref(Matcher::any_reg(0), false,
{DerefTokenMatcher::string("quad"), DerefTokenMatcher::integer(i)}),
{DerefTokenMatcher::string("vector"), DerefTokenMatcher::integer(i),
DerefTokenMatcher::string("quad")}),
Matcher::cast("uint128", Matcher::integer(0)));
}
+1
View File
@@ -169,6 +169,7 @@ enum class FixedOperatorKind {
PROCESS_TO_HANDLE,
PPOINTER_TO_PROCESS,
VECTOR_4_DOT,
VECTOR_LENGTH,
SEND_EVENT,
CPAD_PRESSED_P,
CPAD_HOLD_P,
+3 -1
View File
@@ -25,7 +25,7 @@ namespace decompiler {
*/
class LinkedObjectFile {
public:
LinkedObjectFile() = default;
LinkedObjectFile(GameVersion version) : version(version){};
void set_segment_count(int n_segs);
void push_back_word_to_segment(uint32_t word, int segment);
int get_label_id_for(int seg, int offset);
@@ -137,6 +137,8 @@ class LinkedObjectFile {
std::unique_ptr<LabelDB> label_db;
GameVersion version;
private:
goos::Object to_form_script(int seg, int word_idx, std::vector<bool>& seen);
goos::Object to_form_script_object(int seg, int byte_idx, std::vector<bool>& seen);
@@ -816,7 +816,7 @@ LinkedObjectFile to_linked_object_file(const std::vector<uint8_t>& data,
const std::string& name,
DecompilerTypeSystem& dts,
GameVersion game_version) {
LinkedObjectFile result;
LinkedObjectFile result(game_version);
const auto* header = (const LinkHeaderCommon*)&data.at(0);
// use appropriate linker
+1 -1
View File
@@ -359,7 +359,7 @@ void ObjectFileDB::add_obj_from_dgo(const std::string& obj_name,
}
// nope, have to add a new one.
ObjectFileData data;
ObjectFileData data(config.game_version);
data.data.resize(obj_size);
memcpy(data.data.data(), obj_data, obj_size);
data.record.hash = hash;
+1
View File
@@ -37,6 +37,7 @@ struct ObjectFileRecord {
* All of the data for a single object file
*/
struct ObjectFileData {
ObjectFileData(GameVersion version) : linked_data(version) {}
std::vector<uint8_t> data; // raw bytes
LinkedObjectFile linked_data; // data including linking annotations
ObjectFileRecord record; // name
+26 -4
View File
@@ -29,6 +29,7 @@
#include "decompiler/analysis/symbol_def_map.h"
#include "decompiler/analysis/type_analysis.h"
#include "decompiler/analysis/variable_naming.h"
#include "decompiler/types2/types2.h"
namespace decompiler {
@@ -538,14 +539,33 @@ void ObjectFileDB::ir2_type_analysis_pass(int seg, const Config& config, ObjectF
func.ir2.env.set_art_group(obj_name + "-ag");
}
if (run_type_analysis_ir2(ts, dts, func)) {
func.ir2.env.types_succeeded = true;
constexpr bool kForceNewTypes = false;
if (config.game_version == GameVersion::Jak2 || kForceNewTypes) {
// use new types for jak 2 always
types2::Input in;
types2::Output out;
in.func = &func;
in.function_type = ts;
in.dts = &dts;
try {
types2::run(out, in);
func.ir2.env.set_types(out.block_init_types, out.op_end_types, *func.ir2.atomic_ops,
ts);
} catch (const std::exception& e) {
func.warnings.warning("Type analysis failed: {}", e.what());
}
func.ir2.env.types_succeeded = out.succeeded;
} else {
func.warnings.error("Type Propagation failed: Type analysis failed");
// old type pass
if (run_type_analysis_ir2(ts, dts, func)) {
func.ir2.env.types_succeeded = true;
} else {
func.warnings.warning("Type analysis failed");
}
}
} else {
lg::warn("Function {} didn't know its type", func.name());
func.warnings.error("Function {} has unknown type", func.name());
func.warnings.warning("Function {} has unknown type", func.name());
}
}
});
@@ -872,9 +892,11 @@ std::string ObjectFileDB::ir2_function_to_string(ObjectFileData& data, Function&
result += ";; Warnings:\n" + func.warnings.get_warning_text(true) + "\n";
}
/*
if (func.ir2.env.has_local_vars()) {
result += func.ir2.env.print_local_var_types(func.ir2.top_form);
}
*/
bool print_atomics = func.ir2.atomic_ops_succeeded;
// print each instruction in the function.
@@ -1157,7 +1157,7 @@ bool allow_guess(const Field& field) {
std::string TypeInspectorResult::print_as_deftype(
StructureType* old_game_type,
std::unordered_map<std::string, TypeInspectorResult>& previous_results,
DecompilerTypeSystem& previous_game_ts,
DecompilerTypeSystem& /*previous_game_ts*/,
ObjectFileDB::PerObjectAllTypeInfo& object_file_meta) {
std::string result;
@@ -1408,8 +1408,8 @@ std::string get_label_type_name(LinkedObjectFile& file, std::string label_name)
void inspect_top_level_for_metadata(Function& top_level,
LinkedObjectFile& file,
DecompilerTypeSystem& dts,
DecompilerTypeSystem& previous_game_ts,
DecompilerTypeSystem& /*dts*/,
DecompilerTypeSystem& /*previous_game_ts*/,
ObjectFileDB::PerObjectAllTypeInfo& objectFile) {
// State as a method:
/*
@@ -1434,7 +1434,7 @@ void inspect_top_level_for_metadata(Function& top_level,
// Check for non-method states
std::string last_seen_label = "";
// TODO - safely increment op number
for (int i = 0; i < top_level.ir2.atomic_ops->ops.size(); i++) {
for (int i = 0; i < (int)top_level.ir2.atomic_ops->ops.size(); i++) {
const auto& aop = top_level.ir2.atomic_ops->ops.at(i);
const std::string as_str = aop.get()->to_string(top_level.ir2.env);
@@ -1488,7 +1488,6 @@ void inspect_top_level_for_metadata(Function& top_level,
// Check for types
// if there's no inspect method, we can use just use the call to the type's new method
// to find the type
const auto& env = top_level.ir2.env;
for (int i = 0; i < ((int)top_level.ir2.atomic_ops->ops.size()) - 5; i++) {
// lw v1, type(s7) ;; [ 20] (set! v1-10 type) [] -> [v1: <the etype type> ]
const auto& aop_0 = top_level.ir2.atomic_ops->ops.at(i);
+87 -1
View File
@@ -3,10 +3,10 @@
#include "atomic_op_builder.h"
#include "common/log/log.h"
#include "common/symbols.h"
#include "decompiler/Disasm/DecompilerLabel.h"
#include "decompiler/Disasm/InstructionMatching.h"
#include "decompiler/Function/Function.h"
#include "decompiler/Function/Warnings.h"
#include "decompiler/util/TP_Type.h"
namespace decompiler {
@@ -1944,6 +1944,87 @@ std::unique_ptr<AtomicOp> convert_vector3_dot(const Instruction* instrs, int idx
idx);
}
std::unique_ptr<AtomicOp> convert_vector_length(const Instruction* instrs, int idx) {
// 0: lqc2 vf1, 0(a1)
if (instrs[0].kind != InstructionKind::LQC2 || instrs[0].get_dst(0).get_reg() != make_vf(1) ||
!instrs[0].get_src(0).is_imm(0)) {
return nullptr;
}
Register vec_src = instrs[0].get_src(1).get_reg();
auto vf0 = make_vf(0);
auto vf1 = make_vf(1);
// 1: vmul.xyzw vf1, vf1, vf1
std::vector<DecompilerLabel> labels;
if (instrs[1].kind != InstructionKind::VMUL || instrs[1].get_dst(0).get_reg() != vf1 ||
instrs[1].get_src(0).get_reg() != vf1 || instrs[1].get_src(1).get_reg() != vf1 ||
instrs[1].cop2_dest != 0b1111) {
return nullptr;
}
// 2: vmulax.w acc, vf0, vf1
if (instrs[2].kind != InstructionKind::VMULA_BC || instrs[2].get_src(0).get_reg() != vf0 ||
instrs[2].get_src(1).get_reg() != vf1 || instrs[2].cop2_dest != 0b0001 ||
instrs[2].cop2_bc != 0) {
return nullptr;
}
// 3: vmadday.w acc, vf0, vf1
if (instrs[3].kind != InstructionKind::VMADDA_BC || instrs[3].get_src(0).get_reg() != vf0 ||
instrs[3].get_src(1).get_reg() != vf1 || instrs[3].cop2_dest != 0b0001 ||
instrs[3].cop2_bc != 1) {
return nullptr;
}
// 4: vmaddz.w vf1, vf0, vf1
if (instrs[4].kind != InstructionKind::VMADD_BC || instrs[4].get_dst(0).get_reg() != vf1 ||
instrs[4].get_src(0).get_reg() != vf0 || instrs[4].get_src(1).get_reg() != vf1 ||
instrs[4].cop2_dest != 0b0001 || instrs[4].cop2_bc != 2) {
return nullptr;
}
// 5: vsqrt Q, vf1.w
if (instrs[5].kind != InstructionKind::VSQRT || instrs[5].get_src(0).get_reg() != vf1) {
return nullptr;
}
// 6: vaddw.x vf1, vf0, vf0
if (instrs[6].kind != InstructionKind::VADD_BC || instrs[6].get_dst(0).get_reg() != vf1 ||
instrs[6].get_src(0).get_reg() != vf0 || instrs[6].get_src(1).get_reg() != vf0 ||
instrs[6].cop2_dest != 0b1000 || instrs[6].cop2_bc != 3) {
return nullptr;
}
// 7: vwaitq
if (instrs[7].kind != InstructionKind::VWAITQ) {
return nullptr;
}
// 8: vmulq.x vf1, vf1, Q
if (instrs[8].kind != InstructionKind::VMULQ || instrs[8].get_dst(0).get_reg() != vf1 ||
instrs[8].get_src(0).get_reg() != vf1 || instrs[8].cop2_dest != 0b1000) {
return nullptr;
}
// 9: vnop
if (instrs[9].kind != InstructionKind::VNOP) {
return nullptr;
}
// 10: vnop
if (instrs[10].kind != InstructionKind::VNOP) {
return nullptr;
}
// 11: qmfc2.i a1, vf1
if (instrs[11].kind != InstructionKind::QMFC2 || instrs[11].src->get_reg() != vf1) {
return nullptr;
}
auto dst = instrs[11].get_dst(0).get_reg();
return std::make_unique<SetVarOp>(
make_dst_var(dst, idx),
SimpleExpression(SimpleExpression::Kind::VECTOR_LENGTH, make_src_atom(vec_src, idx)), idx);
}
// 12 instructions
std::unique_ptr<AtomicOp> convert_vector4_dot(const Instruction* instrs, int idx) {
// lwc1 f0, 0(a0)
@@ -2061,6 +2142,11 @@ std::unique_ptr<AtomicOp> convert_12(const Instruction* instrs, int idx) {
if (as_vector4_dot) {
return as_vector4_dot;
}
auto as_vector_length = convert_vector_length(instrs, idx);
if (as_vector_length) {
return as_vector_length;
}
return nullptr;
}
+33 -6
View File
@@ -340,19 +340,46 @@ FormElement* rewrite_as_send_event(LetElement* in,
////////////////////////////////////////////////////////
// (set! (-> block-var from) <something>)
bool not_proc = false;
Matcher set_from_matcher = Matcher::set(
Matcher::deref(Matcher::reg(block_var_reg), false, {DerefTokenMatcher::string("from")}),
Matcher::any_reg(1));
Matcher set_from_matcher;
switch (env.version) {
case GameVersion::Jak1:
set_from_matcher = Matcher::set(
Matcher::deref(Matcher::reg(block_var_reg), false, {DerefTokenMatcher::string("from")}),
Matcher::any_reg(1));
break;
case GameVersion::Jak2:
// in jak 2, the event message block holds a ppointer instead.
set_from_matcher = Matcher::set(
Matcher::deref(Matcher::reg(block_var_reg), false, {DerefTokenMatcher::string("from")}),
Matcher::op_fixed(FixedOperatorKind::PROCESS_TO_PPOINTER, {Matcher::any_reg(1)}));
break;
default:
ASSERT(false);
}
auto from_mr = match(set_from_matcher, body->at(0));
if (!from_mr.matched) {
// initial matcher failed. try more advanced "from" matcher now.
Matcher set_from_form_matcher = Matcher::set(
Matcher::deref(Matcher::any_reg(0), false, {DerefTokenMatcher::string("from")}),
Matcher::any(1));
Matcher set_from_form_matcher;
switch (env.version) {
case GameVersion::Jak1:
set_from_form_matcher = Matcher::set(
Matcher::deref(Matcher::any_reg(0), false, {DerefTokenMatcher::string("from")}),
Matcher::any(1));
break;
case GameVersion::Jak2:
set_from_form_matcher = Matcher::set(
Matcher::deref(Matcher::any_reg(0), false, {DerefTokenMatcher::string("from")}),
Matcher::op_fixed(FixedOperatorKind::PROCESS_TO_PPOINTER, {Matcher::any(1)}));
break;
default:
ASSERT(false);
}
from_mr = match(set_from_form_matcher, body->at(0));
if (!from_mr.matched) {
return nullptr;
}
fmt::print("case 1: {}\n", from_mr.maps.forms.at(1)->to_string(env));
not_proc = true;
}
File diff suppressed because it is too large Load Diff
@@ -12,5 +12,35 @@
],
"main": [
[3, "(function none :behavior process)"]
],
"profile": [
[9, "(function profile-segment-array profile-collapse none)"]
],
"bigmap-h": [
[1, "(function external-art-buffer none)"],
[2, "(function external-art-buffer none)"]
],
"surface-h": [
[0, "(function none)"],
[1, "(function none)"]
],
"main": [
[10, "(function none)"],
[9, "(function none)"],
[8, "(function none)"],
[7, "(function time-frame none)"],
[3, "(function none)"]
],
"level-info": [
[0, "(function none)"],
[1, "(function none)"],
[2, "(function none)"]
],
"level": [
[5, "(function none)"],
[24, "(function level-group int symbol)"]
]
}
+30 -8
View File
@@ -16,13 +16,17 @@
// the second argument is the name of the first condition in the cond. Use print_cfg to find it out.
// The third argument is the number of cases. If you set it too small it may fail to build the CFG.
"cond_with_else_max_lengths": [
["(method 20 res-lump)", "b0", 2],
["(method 11 res-lump)", "b0", 1],
["(method 12 res-lump)", "b0", 1]
],
// if a cond with an else case is being used a value in a place where it looks wrong
// you can add the function name to this list and it will more aggressively reject this rewrite.
"aggressively_reject_cond_to_value_rewrite": [
"(method 10 res-lump)",
"(method 11 res-lump)",
"(method 12 res-lump)"
],
// this provides a hint to the decompiler that these functions will have a lot of inline assembly.
@@ -31,8 +35,8 @@
"asm_functions_by_name": [
// checking boxed type is different now - these make the cfg stuff sad
"name=", "(method 8 res-lump)", "joint-control-remap!", "(method 21 game-info)", "(method 12 level)",
"bg", "(anon-function 2 cam-combiner)", "cspace-inspect-tree", "command-get-process", "command-get-trans", "(method 10 script-context)",
"name=", "joint-control-remap!", "(method 21 game-info)",
"(anon-function 2 cam-combiner)", "cspace-inspect-tree", "command-get-process", "command-get-trans", "(method 10 script-context)",
"(method 11 script-context)", "(method 9 script-context)", "(anon-function 64 script)", "(anon-function 61 script)", "(anon-function 60 script)",
"(anon-function 54 script)", "(anon-function 52 script)", "(anon-function 49 script)", "(anon-function 33 script)", "debug-menu-func-decode",
"scene-player-init", "(method 77 spyder)", "(method 77 flamer)", "(method 77 grenadier)", "(method 224 bot)", "(method 77 rapid-gunner)",
@@ -47,7 +51,9 @@
// actual asm
"quad-copy!", "return-from-thread", "return-from-thread-dead", "reset-and-call", "(method 10 cpu-thread)",
"(method 11 cpu-thread)", "(method 0 catch-frame)", "throw-dispatch", "throw", "run-function-in-process",
"set-to-run-bootstrap", "return-from-exception", "exp",
"set-to-run-bootstrap", "return-from-exception", "exp", "(method 17 bounding-box)", "(method 9 bounding-box)",
"(method 9 matrix)", "quaternion->matrix-2", "sin-rad", "cos-rad", "atan-series-rad", "sign-float", "dma-count-until-done",
"(method 11 collide-mesh-cache)",
"symlink2", "blerc-a-fragment", "blerc-execute", "foreground-check-longest-edge-asm", "generic-light-proc",
"shadow-add-single-edges","shadow-add-facing-single-tris", "shadow-add-double-tris", "shadow-add-double-edges",
@@ -83,7 +89,11 @@
"delete-car!",
"insert-cons!",
"sort",
"unload-package"
"unload-package",
"display-loop-main",
"lookup-level-info",
"(method 24 level-group)",
"(method 19 level-group)"
],
// If format is used with the wrong number of arguments,
@@ -91,7 +101,12 @@
// that they used the correct number. This will override the decompiler's
// automatic detection.
"bad_format_strings": {
"~170h~5d~220h~5d~280h~5,,2f": 3,
"~338h~5d~388h~5d~448h~5,,2f": 3,
"~1k~%":0,
"~30Htf: ~8D~134Hpr: ~8D~252Hsh: ~8D~370Hhd: ~8D~%":4,
"~30Hal: ~8D~131Hwa: ~8D~252Hsp: ~8D~370Hwp: ~8D~%":4,
"~0Kload ~16S ~5S ~5DK ~5,,2fs ~5,,2fs~1K ~5,,0f k/s~%":6
},
"blocks_ending_in_asm_branch": {
@@ -103,7 +118,14 @@
"find-knot-span": [0, 1, 2, 3, 5, 6, 7, 8, 9],
"curve-evaluate!": [0, 2, 5, 6, 7, 8, 9]
"curve-evaluate!": [0, 2, 5, 6, 7, 8, 9],
"display-loop-main": [127, 130, 133, 136],
"real-main-draw-hook": [114, 115, 116, 118],
"(method 12 perf-stat)": [0],
"(method 11 perf-stat)": [0]
},
// Sometimes the game might use format strings that are fetched dynamically,
+40 -27
View File
@@ -8,33 +8,6 @@
["L102", "(pointer float)", 32]
],
// possible for auto-labeling
"vector-h": [
["L36", "vector"],
["L35", "vector"],
["L34", "vector"],
["L33", "vector"],
["L32", "vector"],
["L31", "vector"],
["L30", "vector"]
],
"matrix": [
["L65", "matrix"]
],
"quaternion-h": [
["L1", "quaternion"]
],
"quaternion": [
["L85", "vector"],
["L84", "vector"],
["L83", "vector"],
["L82", "vector"],
["L81", "vector"],
["L80", "vector"],
["L79", "vector"],
["L78", "vector"]
],
"trigonometry": [
["L93", "vector"],
["L92", "vector"],
@@ -63,5 +36,45 @@
"font-h": [
["L20", "matrix"],
["L19", "font-work"]
],
"capture": [
["L4", "gs-store-image-packet"]],
"task-control-h": [
["L877", "uint64", true],
["L878", "uint64", true],
["L879", "uint64", true],
["L880", "uint64", true],
["L881", "uint64", true],
["L882", "uint64", true],
["L883", "uint64", true]
],
"ocean-trans-tables": [
["L1", "(inline-array vector)", 4],
["L2", "(pointer float)", 160],
["L3", "(pointer float)", 100],
["L4", "(pointer float)", 72],
["L5", "(pointer float)", 72],
["L6", "(pointer float)", 72],
["L7", "(pointer float)", 72],
["L8", "(pointer float)", 44],
["L9", "(pointer float)", 44],
["L10", "(pointer float)", 44],
["L11", "(pointer float)", 44],
["L12", "(pointer float)", 40],
["L13", "(pointer float)", 40],
["L14", "(pointer float)", 40],
["L15", "(pointer float)", 40],
["L16", "(pointer float)", 28],
["L17", "(pointer float)", 28],
["L18", "(pointer float)", 28],
["L19", "(pointer float)", 28]
],
"ocean-frames": [["L1", "(pointer uint32)", 16384]],
"ambient-h": [["L1", "(inline-array talker-speech-class)", 188]],
"pat-h": [["L1", "(inline-array pat-mode-info)", 4]],
"joint-mod-h": [
["L43", "(inline-array vector)", 6]
]
}
+44 -115
View File
@@ -1,114 +1,6 @@
{
// possible for automatic detection:
"(method 23 trsqv)": [[16, "vector"]],
"(method 24 trsqv)": [[16, "vector"]],
"(method 18 bounding-box)": [[16, "vector"], [32, "vector"]],
"(method 12 bounding-box)": [[16, "liang-barsky-line-clip-params"]],
"matrixp*!": [[16, "matrix"]],
"vector3s-matrix*!": [[16, "vector"]],
"vector3s-rotate*!": [[16, "vector"]],
"matrix-rotate-zyx!": [
[16, "matrix"],
[80, "matrix"]
],
"matrix-rotate-xyz!": [
[16, "vector"],
[32, "vector"],
[80, "matrix"]
],
"matrix-rotate-zxy!": [
[16, "matrix"],
[80, "matrix"]
],
"matrix-rotate-yxz!": [
[16, "matrix"],
[80, "matrix"]
],
"matrix-rotate-yzx!": [
[16, "matrix"],
[80, "matrix"]
],
"matrix-rotate-yxy!": [
[16, "vector"],
[32, "vector"],
[48, "vector"]
],
"matrix-rotate-yx!": [[16, "matrix"]],
"transform-matrix-calc!": [
[16, "matrix"],
[80, "matrix"]
],
"transform-matrix-parent-calc!": [
[16, "matrix"],
[80, "matrix"]
],
"matrix->quat": [
[16, "matrix"]
],
"matrix<-quat": [
[16, "vector"],
[32, "matrix"]
],
"matrix->transformq": [
[16, "matrix"]
],
"matrix-rotate-xyz-2!": [
[16, "matrix"],
[80, "matrix"]
],
"matrix-with-scale->quaternion": [[16, "matrix"]],
"quaternion-exp!": [[16, "vector"]],
"quaternion-slerp!": [[16, "vector"]],
"quaternion-zxy!": [
[16, "vector"],
[32, "vector"],
[48, "vector"]
],
"vector-x-quaternion!": [[16, "matrix"]],
"vector-y-quaternion!": [[16, "matrix"]],
"vector-z-quaternion!": [[16, "matrix"]],
"quaternion-x-angle": [[16, "vector"]],
"quaternion-y-angle": [[16, "vector"]],
"quaternion-z-angle": [[16, "vector"]],
"quaternion-rotate-local-x!": [[16, "quaternion"]],
"quaternion-rotate-local-y!": [[16, "quaternion"]],
"quaternion-rotate-local-z!": [[16, "quaternion"]],
"quaternion-rotate-y!": [[16, "quaternion"]],
"quaternion-rotate-x!": [
[16, "quaternion"],
[32, "vector"]
],
"quaternion-rotate-z!": [
[16, "quaternion"],
[32, "vector"]
],
"quaternion-delta-y": [
[16, "vector"],
[32, "vector"]
],
"quaternion-rotate-y-to-vector!": [
[16, "quaternion"],
[32, "vector"],
[48, "quaternion"]
],
"quaternion-xz-angle": [
[16, "matrix"],
[80, "vector"]
],
"vector-rotate-x!": [
[16, "quaternion"],
[32, "matrix"]
],
"vector-rotate-y!": [
[16, "quaternion"],
[32, "matrix"]
],
"vector-rotate-z!": [
[16, "quaternion"],
[32, "matrix"]
],
"quaternion-axis-angle!": [
[16, "vector"]
],
@@ -118,16 +10,12 @@
"quaternion-look-at!": [
[16, "matrix"]
],
"quaternion-pseudo-seek": [
[16, "quaternion"],
[32, "quaternion"]
],
"quaternion-smooth-seek!": [
[16, ["inline-array", "quaternion", 2]]
],
// possible for automatic detection:
"eul->matrix": [[16, "vector"]],
"eul->quat": [[16, "matrix"]],
"quat->eul": [[16, "matrix"]],
"vector-sincos!": [[16, "vector"]],
"vector-reflect-flat-gravity!": [[16, "vector"], [32, "vector"]],
@@ -255,12 +143,53 @@
[32, "vector"],
[48, "vector"]
],
"init-for-transform": [
"init-for-transform": [
[16, "matrix"],
[80, "matrix"],
[144, "vector4s-3"],
[192, "vector"],
[208, "vector4s-3"]
],
"draw-sprite2d-xy": [
[16, "draw-context"]
],
"screen-gradient": [
[16, "draw-context"]
],
"play": [
[16, "event-message-block"],
[96, ["array", "symbol", 10]]
],
"store-image": [
[16, "file-stream"]
],
"joint-mod-blend-world-callback": [
[160, "vector"]
],
"joint-mod-rotate-local-callback": [
[16, "vector"]
],
"light-hash-get-bucket-index": [
[16, "vector4w"]
],
"(method 10 cam-vector-seeker)": [
[16, "vector"]
],
"(method 39 nav-mesh)" : [
[16, "vector"],
[32, "vector"]
],
"(method 41 nav-mesh)": [
[16, "vector"],
[32, "vector"]
],
"show-level": [
[16, ["array", "symbol", 10]]
],
"placeholder-do-not-add-below!": []
}
+358 -7
View File
@@ -1,4 +1,15 @@
{
// auto find-parent-method possible
"(method 3 entity-actor)" : [
[7, "t9", "(function entity entity)"]
],
"(method 3 entity)" : [
[7, "t9", "(function entity entity)"]
],
//
"(method 2 array)": [
[23, "gp", "(array int32)"],
[43, "gp", "(array uint32)"],
@@ -66,26 +77,30 @@
],
"send-event-function": [[[7, 15], "a0", "process"]],
// MATH
"logf": [
[12, "f0", "float"],
[12, "f1", "float"],
[19, "f0", "float"],
[19, "f1", "float"]
],
"log2f": [
[12, "f0", "float"],
[12, "f1", "float"],
[19, "f0", "float"],
[19, "f1", "float"]
],
"log2": [[3, "v1", "int"]],
"cube-root": [
[[0,33], "f0", "float"],
[[0,33], "f1", "float"],
[[0,33], "f2", "float"]
[17, "f0", "float"],
[17, "f1", "float"],
[18, "f0", "float"],
[18, "f1", "float"],
[[23,32], "f0", "float"]
],
// Quaternion
"quaternion-look-at!": [
[15, "v1", "vector"]
],
"vector-x-quaternion!": [[10, "v1", "(pointer uint128)"]],
@@ -177,5 +192,341 @@
[13, "s5", "(inline-array vector4)"],
[14, "s4", "(inline-array vector4)"],
[15, "gp", "(inline-array vector4)"]],
"vector-segment-distance-point!": [
[[21,30], "f1", "float"]
],
"(method 10 profile-array)": [
[[6, 10], "a0", "dma-packet"],
[[16, 19], "a0", "gs-gif-tag"],
[24, "a0", "(pointer gs-alpha)"],
[26, "a0", "(pointer gs-reg64)"],
[28, "a0", "(pointer gs-zbuf)"],
[30, "a0", "(pointer gs-reg64)"],
[32, "a0", "(pointer gs-test)"],
[34, "a0", "(pointer gs-reg64)"],
[35, "a0", "(pointer uint64)"],
[37, "a0", "(pointer gs-reg64)"],
[39, "a0", "(pointer gs-clamp)"],
[41, "a0", "(pointer gs-reg64)"],
[43, "a0", "(pointer gs-tex1)"],
[45, "a0", "(pointer gs-reg64)"],
[48, "a0", "(pointer gs-texa)"],
[50, "a0", "(pointer gs-reg64)"],
[52, "a0", "(pointer gs-texclut)"],
[54, "a0", "(pointer gs-reg64)"],
[56, "a0", "(pointer uint64)"],
[58, "a0", "(pointer gs-reg64)"],
[[69, 73], "a0", "(pointer uint128)"],
[[73, 82], "a1", "vector4w"],
[[82, 89], "a1", "vector4w"],
[[90, 96], "a0", "vector4w"],
[[113, 117], "a1", "(pointer uint128)"],
[[117, 126], "a2", "vector4w"],
[[126, 136], "a2", "vector4w"],
[[137, 149], "a1", "vector4w"],
[[187, 191], "t2", "(pointer int128)"],
[[191, 225], "t4", "vector4w"],
[[225, 231], "a2", "vector4w"],
[[231, 237], "a2", "vector4w"]
],
"draw-sprite2d-xy": [
[[35, 39], "t0", "dma-packet"],
[[45, 48], "t0", "gs-gif-tag"],
[53, "t0", "(pointer gs-prim)"],
[55, "t0", "(pointer gs-rgbaq)"],
[66, "t0", "(pointer gs-xyzf)"],
[87, "t0", "(pointer gs-xyzf)"],
[[96, 108], "v1", "(pointer uint64)"]
],
"draw-sprite2d-xy-absolute": [
[[6, 10], "t3", "dma-packet"],
[[16, 19], "t3", "gs-gif-tag"],
[24, "t3", "(pointer gs-prim)"],
[25, "t3", "(pointer gs-rgbaq)"],
[36, "t3", "(pointer gs-xyzf)"],
[49, "t3", "(pointer gs-xyzf)"],
[[62, 69], "v1", "(pointer uint64)"]
],
"draw-quad2d": [
[[18, 22], "t2", "dma-packet"],
[[28, 31], "t2", "gs-gif-tag"],
[36, "t2", "(pointer gs-prim)"],
[38, "t2", "(pointer gs-rgbaq)"],
[46, "t2", "(pointer gs-xyzf)"],
[48, "t2", "(pointer gs-rgbaq)"],
[61, "t2", "(pointer gs-xyzf)"],
[63, "t2", "(pointer gs-rgbaq)"],
[76, "t2", "(pointer gs-xyzf)"],
[78, "t2", "(pointer gs-rgbaq)"],
[96, "t2", "(pointer gs-xyzf)"],
[97, "t2", "(pointer uint64)"],
[[110, 117], "v1", "(pointer uint64)"]
],
"set-display-gs-state": [
[[3, 10], "t3", "dma-packet"],
[[13, 19], "t3", "gs-gif-tag"],
[30, "t3", "(pointer gs-scissor)"],
[32, "t3", "(pointer gs-reg64)"],
[33, "t3", "(pointer gs-xy-offset)"],
[35, "t3", "(pointer gs-reg64)"],
[46, "t3", "(pointer gs-frame)"],
[48, "t3", "(pointer gs-reg64)"],
[50, "t3", "(pointer gs-test)"],
[52, "t3", "(pointer gs-reg64)"],
[54, "t3", "(pointer gs-texa)"],
[56, "t3", "(pointer gs-reg64)"],
[58, "t3", "(pointer gs-zbuf)"],
[60, "t3", "(pointer gs-reg64)"],
[61, "t3", "(pointer uint64)"],
[63, "t3", "(pointer gs-reg64)"]
],
"set-display-gs-state-offset": [
[[3, 10], "t5", "dma-packet"],
[[13, 19], "t5", "gs-gif-tag"],
[30, "t5", "(pointer gs-scissor)"],
[32, "t5", "(pointer gs-reg64)"],
[40, "t5", "(pointer gs-xy-offset)"],
[42, "t5", "(pointer gs-reg64)"],
[53, "t5", "(pointer gs-frame)"],
[55, "t5", "(pointer gs-reg64)"],
[57, "t5", "(pointer gs-test)"],
[59, "t5", "(pointer gs-reg64)"],
[61, "t5", "(pointer gs-texa)"],
[63, "t5", "(pointer gs-reg64)"],
[65, "t5", "(pointer gs-zbuf)"],
[67, "t5", "(pointer gs-reg64)"],
[68, "t5", "(pointer uint64)"],
[70, "t5", "(pointer gs-reg64)"]
],
"reset-display-gs-state": [
[[3, 8], "a2", "dma-packet"],
[[14, 17], "a2", "gs-gif-tag"],
[22, "a1", "(pointer gs-scissor)"],
[24, "a1", "(pointer gs-reg64)"],
[26, "a1", "(pointer gs-xy-offset)"],
[28, "a1", "(pointer gs-reg64)"],
[30, "a1", "(pointer gs-frame)"],
[32, "a1", "(pointer gs-reg64)"],
[34, "a1", "(pointer gs-test)"],
[36, "a1", "(pointer gs-reg64)"],
[39, "a1", "(pointer gs-texa)"],
[41, "a1", "(pointer gs-reg64)"],
[43, "a1", "(pointer gs-zbuf)"],
[45, "a1", "(pointer gs-reg64)"],
[46, "a1", "(pointer uint64)"],
[48, "a1", "(pointer gs-reg64)"]
],
"(method 3 connection-pers)": [
[97, "f0", "float"]
],
"(method 9 connection)": [[8, "a0", "pointer"]],
"(method 10 connection)": [[8, "a0", "pointer"]],
"(method 11 connection)": [[5, "a1", "pointer"]],
"(method 0 engine)": [[44, "v1", "pointer"], [47, "v1", "pointer"], [53, "v1", "connectable"], [65, "v1", "connectable"]],
"(method 12 engine)": [
[[5, 18], "s4", "connection"],
[13, "t9", "(function object object object object object)"]
],
"(method 13 engine)": [
[[5, 28], "s4", "connection"],
[13, "t9", "(function object object object object object)"]
],
"(method 15 engine)": [[[0, 36], "v1", "connection"]],
"(method 19 engine)": [[8, "a0", "connection"]],
"(method 20 engine)": [[8, "a0", "connection"]],
"(method 21 engine)": [[8, "a0", "connection"]],
"(method 0 engine-pers)": [[32, "v1", "pointer"], [23, "v1", "pointer"], [26, "v1", "pointer"], [24, "v1", "(pointer pointer)"]],
"(method 3 connection-minimap)" : [[97, "f0", "float"]],
"dma-buffer-add-ref-texture": [
[[25, 29], "a3", "dma-packet"],
[[32, 44], "a3", "gs-gif-tag"],
[[47, 62], "a2", "dma-packet"]
],
"texture-page-default-allocate": [
[51, "a3", "texture-page"]
],
"texture-page-font-allocate": [
[33, "a3", "texture-page"]
],
"(method 24 texture-pool)": [
[67, "a1", "shader-ptr"],
[[70, 91], "a1", "adgif-shader"],
[92, "a1", "adgif-shader"]
],
"(method 3 generic-tie-interp-point)":[
[19, "gp", "(pointer uint128)"]
],
"(method 19 res-lump)": [
[46, "t2", "(pointer uint64)"],
[100, "t3", "(pointer uint64)"],
[184, "t5", "(pointer uint64)"],
[64, "t6", "(pointer uint64)"]
],
"(method 20 res-lump)": [[331, "a3", "(inline-array vector)"]],
"(method 16 res-lump)": [
[22, "t1", "(pointer uint64)"],
[29, "t2", "(pointer uint64)"]
],
"(method 15 res-lump)": [[132, "s5", "res-tag-pair"]],
"(method 17 res-lump)": [[22, "s4", "(pointer pointer)"]],
"(method 0 script-context)" : [[[8, 17], "v0", "script-context"]],
"joint-mod-wheel-callback": [[[2, 63], "s4", "joint-mod-wheel"]],
"joint-mod-set-local-callback": [[[0, 23], "v1", "joint-mod-set-local"]],
"joint-mod-add-local-callback": [[[2, 33], "s4", "joint-mod-add-local"]],
"joint-mod-set-world-callback": [[[0, 23], "v1", "joint-mod-set-world"]],
"joint-mod-blend-local-callback": [[[2, 63], "gp", "joint-mod-blend-local"]],
"joint-mod-spinner-callback": [[[2, 63], "gp", "joint-mod-spinner"]],
"joint-mod-blend-world-callback": [[[2, 148], "gp", "joint-mod-blend-world"]],
"joint-mod-rotate-local-callback": [[[2, 16], "v1", "joint-mod-rotate-local"]],
"(method 0 collide-shape-prim-sphere)": [[[3, 8], "v0", "collide-shape-prim-sphere"]],
"(method 0 collide-shape-prim-mesh)": [[[3, 11], "v0", "collide-shape-prim-mesh"]],
"(method 0 collide-shape-prim-group)": [[[3, 12], "v0", "collide-shape-prim-group"]],
"(method 0 collide-shape-moving)" : [[[2, 12], "v0", "collide-shape-moving"]],
"(method 11 touching-prims-entry-pool)": [
[[0, 8], "v1", "touching-prims-entry"],
[8, "v1", "pointer"],
[[9, 11], "v1", "touching-prims-entry"],
[[1, 20], "a1", "touching-prims-entry"]
],
"(method 0 touching-list)": [[[6, 9], "v0", "touching-list"]],
"display-loop-main": [[223, "t9", "(function none)"]],
"end-display": [[205, "f1", "float"], [205, "f0", "float"]],
"(method 18 res-lump)": [["_stack_", 16, "object"]],
"(method 21 res-lump)": [
["_stack_", 16, "res-tag"],
["_stack_", 32, "res-tag"]
],
"(method 8 res-lump)": [
[258, "s0", "array"],
// [[0, 100], "s0", "basic"],
// [[102, 120], "s0", "basic"],
// [[147, 150], "s0", "collide-mesh"],
[[157, 239], "s0", "(array object)"]
// [235, "s0", "basic"]
],
"(method 20 res-lump)": [
[341, "t0", "(pointer uint128)"]
],
"(method 0 fact-info-enemy)": [
[[0, 196], "gp", "fact-info-enemy"],
["_stack_", 16, "res-tag"],
["_stack_", 32, "res-tag"]
],
"(method 0 fact-info)": [
[87, "v1", "(pointer int32)"]
],
"(method 0 fact-info-crate)": [
[[0, 17], "gp", "fact-info-crate"]
],
"(method 0 fact-info-target)": [
[[0, 17], "gp", "fact-info-target"]
],
"joint-channel-float-delete!": [
[7, "a0", "pointer"],
[7, "a1", "pointer"]
],
"num-func-chan": [
[7, "v1", "joint-control-channel"]
],
"(method 20 process-focusable)": [
[15, "gp", "collide-shape-moving"],
[31, "gp", "collide-shape"]
],
"(method 10 focus)": [
[19, "v1", "collide-shape"]
],
"shrubbery-login-post-texture": [
[[13, 15], "a3", "qword"],
[16, "a3", "pointer"],
[24, "a3", "pointer"],
[[17, 23], "a3", "qword"],
[[13, 23], "a1", "qword"],
[14, "a2", "qword"],
[[27, 29], "a3", "qword"],
[[27, 29], "a1", "qword"],
[[35, 37], "a3", "qword"],
[[35, 37], "a2", "qword"]
],
"(top-level-login eye-h)": [
[[69, 77], "a1", "eye-control"]
],
"entity-actor-lookup": [
["_stack_", 16, "res-tag"]
],
"entity-actor-count": [
["_stack_", 16, "res-tag"]
],
"(method 0 path-control)": [["_stack_", 16, "res-tag"]],
"(method 9 actor-link-info)": [[[0, 36], "s3", "entity-actor"]],
"(method 41 nav-mesh)": [["_stack_", 56, "float"]],
"(method 39 nav-mesh)": [["_stack_", 56, "float"]],
"str-load": [[[18, 44], "s2", "load-chunk-msg"]],
"str-load-status": [
[[18, 22], "v1", "load-chunk-msg"],
[26, "v1", "load-chunk-msg"]
],
"str-play-async": [[[7, 36], "s4", "play-chunk-msg"]],
"str-play-stop": [[[7, 36], "s4", "play-chunk-msg"]],
"str-play-queue": [[[7, 98], "s4", "play-chunk-msg"]],
"str-ambient-play": [[[7, 20], "s5", "load-chunk-msg"]],
"str-ambient-stop": [[[7, 20], "s5", "load-chunk-msg"]],
"dgo-load-begin": [[[19, 41], "s2", "load-dgo-msg"]],
"dgo-load-get-next": [[[14, 31], "v1", "load-dgo-msg"]],
"dgo-load-continue": [[[5, 23], "gp", "load-dgo-msg"]],
"dgo-load-link": [[7, "s4", "uint"], [17, "s4", "uint"], [55, "s4", "uint"], [27, "s4", "uint"], [37, "s4", "uint"]],
"lookup-level-info": [[3, "a1", "symbol"], [[4, 24], "a1", "level-load-info"]],
"(method 30 level-group)": [[87, "v0", "level"]],
"(method 19 level-group)": [
[223, "s3", "continue-point"],
[[177, 209], "s1", "continue-point"],
[[182, 224], "s3", "continue-point"],
[434, "v1", "symbol"]],
"(method 18 level)": [
[[82, 89], "a1", "level"]
],
"(method 19 level)": [
[[45, 48], "a0", "texture-anim-array"]
],
"level-update-after-load": [
[[123, 152], "s0", "drawable-inline-array-tfrag"],
[[155, 158], "s0", "drawable-tree-instance-tie"],
[365, "a1", "(pointer int32)"],
[370, "a2", "(pointer int32)"]
],
"(method 25 level)": [
[97, "t9", "(function object none)"],
[169, "t9", "(function object symbol none)"]
],
"(method 9 level)": [
[54, "t9", "(function object none)"]
],
"placeholder-do-not-add-below": []
}
+44 -1
View File
@@ -1 +1,44 @@
{}
{
"(method 15 res-lump)": {
"vars": {
"s5-0": ["tag-pair", "res-tag-pair"],
"s2-0": "existing-tag",
"s3-0": "data-size",
"v1-25": "resource-mem"
}
},
"(method 0 lightning-control)": {
"vars": {
"gp-0": ["obj", "lightning-control"]
}
},
"(method 0 align-control)": {
"vars": {
"v0-0": ["obj", "align-control"]
}
},
"(method 16 nav-mesh)": {
"args": ["obj", "ray"],
"vars": {
"sv-16": "next-poly-idx",
"sv-24": "work",
"sv-28": "current-poly",
"sv-32": "current-poly-vtx-count",
"sv-36": "v0-table",
"sv-40": "v1-table",
"v1-9": "i",
"sv-52": "adj-vtx-0",
"sv-56": "adj-vtx-1",
"sv-44": "delta-x",
"sv-48": "delta-z",
"sv-60": "adj-edge-dz",
"sv-64": "adj-edge-minus-dx",
"f0-10": "heading-dot"
}
}
}
+1 -1
View File
@@ -7,7 +7,7 @@
// if you want to filter to only some object names.
// it will make the decompiler much faster.
"allowed_objects": [],
"allowed_objects": ["math"],
"banned_objects": ["effect-control"],
////////////////////////////
+2 -2
View File
@@ -716,7 +716,7 @@ void save_level_background_as_gltf(const tfrag3::Level& level, const fs::path& g
// a "scene" is a traditional scene graph, made up of Nodes.
// sadly, attempting to nest stuff makes the blender importer unhappy, so we just dump
// everything into the top level.
auto& scene = model.scenes.emplace_back();
model.scenes.emplace_back();
// hack, add a default material.
tinygltf::Material mat;
@@ -755,7 +755,7 @@ void save_level_foreground_as_gltf(const tfrag3::Level& level, const fs::path& g
// a "scene" is a traditional scene graph, made up of Nodes.
// sadly, attempting to nest stuff makes the blender importer unhappy, so we just dump
// everything into the top level.
auto& scene = model.scenes.emplace_back();
model.scenes.emplace_back();
// hack, add a default material.
tinygltf::Material mat;
File diff suppressed because it is too large Load Diff
+760
View File
@@ -0,0 +1,760 @@
#include "types2.h"
#include <set>
#include "decompiler/ObjectFile/LinkedObjectFile.h"
#include "decompiler/types2/types2.h"
#include "decompiler/util/goal_constants.h"
namespace decompiler::types2 {
/*!
* Construct a typestate from the types at the start of a block.
*/
TypeState make_typestate_from_block_types(BlockStartTypes& block_start_types) {
// create references to the gpr, fpr, and stack slot types of the start of the block.
TypeState result;
for (int i = 0; i < 32; i++) {
result.gpr_types[i] = &block_start_types.gpr_types[i];
result.fpr_types[i] = &block_start_types.fpr_types[i];
}
for (auto& s : block_start_types.stack_slot_types) {
result.stack_slot_types.push_back(&s);
}
result.next_state_type = &block_start_types.next_state_type;
return result;
}
/*!
* Find all of the used stack spill slots in a function.
* They are represented as byte offsets.
*/
std::set<int> find_stack_spill_slots(const Function& f) {
std::set<int> result;
for (auto& op : f.ir2.atomic_ops->ops) {
auto as_stack_spill_store = dynamic_cast<StackSpillStoreOp*>(op.get());
if (as_stack_spill_store) {
result.insert(as_stack_spill_store->offset());
}
auto as_stack_spill_load = dynamic_cast<StackSpillLoadOp*>(op.get());
if (as_stack_spill_load) {
result.insert(as_stack_spill_load->offset());
}
}
return result;
}
/*!
* Set up types for the entry of a function.
*/
void construct_function_entry_types(BlockStartTypes& result,
const TypeSpec& f_ts,
const std::set<int>& stack_slots) {
for (auto& x : result.gpr_types) {
x.type = TP_Type::make_uninitialized();
}
for (auto& x : result.fpr_types) {
x.type = TP_Type::make_uninitialized();
}
result.next_state_type.type = TP_Type::make_uninitialized();
for (auto x : stack_slots) {
auto slot = result.try_find_stack_spill_slot(x);
ASSERT(slot);
slot->slot = x;
slot->type.type = TP_Type::make_uninitialized();
}
int goal_args[] = {Reg::A0, Reg::A1, Reg::A2, Reg::A3, Reg::T0, Reg::T1, Reg::T2, Reg::T3};
ASSERT(f_ts.base_type() == "function");
ASSERT(f_ts.arg_count() >= 1);
ASSERT(f_ts.arg_count() <= 8 + 1); // 8 args + 1 return.
for (int i = 0; i < int(f_ts.arg_count()) - 1; i++) {
auto reg_id = goal_args[i];
const auto& reg_type = f_ts.get_arg(i);
result[Register(Reg::GPR, reg_id)].type = TP_Type::make_from_ts(reg_type);
}
result[Register(Reg::GPR, Reg::S6)].type =
TP_Type::make_from_ts(TypeSpec(f_ts.try_get_tag("behavior").value_or("process")));
// initialize stack slots as uninitialized (I think safe to skip)
}
/*!
* Set up function cache data structure.
*/
void build_function(FunctionCache& function_cache,
Function& func,
const std::set<int>& stack_slots) {
ASSERT(func.ir2.atomic_ops && func.ir2.atomic_ops_succeeded);
auto& aops = func.ir2.atomic_ops->ops;
// set up the instruction and block structures
function_cache.blocks.resize(func.basic_blocks.size());
function_cache.instructions.resize(aops.size());
for (size_t i = 0; i < aops.size(); i++) {
function_cache.instructions[i].aop_idx = i;
}
for (size_t block_idx = 0; block_idx < function_cache.blocks.size(); block_idx++) {
for (int instr_idx = func.ir2.atomic_ops->block_id_to_first_atomic_op.at(block_idx);
instr_idx < func.ir2.atomic_ops->block_id_to_end_atomic_op.at(block_idx); instr_idx++) {
function_cache.blocks[block_idx].instructions.push_back(
&function_cache.instructions.at(instr_idx));
}
}
// figure out the order we'll visit all blocks
// todo: do something with unreachables?
function_cache.block_visit_order = func.bb_topo_sort().vist_order;
// to save time, we store types at the entry of each block, then in the instructions inside
// each block, store types sparsely. This saves very slow copying around of types.
for (int block_idx : function_cache.block_visit_order) {
auto& block = function_cache.blocks.at(block_idx);
// add placeholders for all stack slots.
for (auto slot_addr : stack_slots) {
auto& new_slot = block.start_types.stack_slot_types.emplace_back();
new_slot.slot = slot_addr;
}
// this will contain pointers to the most recent types for each register.
// it gets initialized to the types on block entry (we store a copy of these)
TypeState state = make_typestate_from_block_types(block.start_types);
block.start_type_state = state; // stash this here, just makes it easier for later.
ASSERT(block.start_type_state.fpr_types[0]);
// loop through instructions, allocating new types for written registers.
for (auto instr : block.instructions) {
auto& aop = aops.at(instr->aop_idx);
// allocate types that we'll write/clobber
for (auto& reg : aop->write_regs()) {
RegType rt;
rt.reg = reg;
instr->written_reg_types.push_back(rt);
}
for (auto& reg : aop->clobber_regs()) {
RegType rt;
rt.reg = reg;
instr->written_reg_types.push_back(rt);
}
// now link the register types
for (auto& written_reg_type : instr->written_reg_types) {
auto reg_kind = written_reg_type.reg.get_kind();
// ignore weird registers
if (reg_kind == Reg::GPR || reg_kind == Reg::FPR) {
state[written_reg_type.reg] = &written_reg_type.type;
}
}
// do the same for stack spill types
auto as_stack_spill_store = dynamic_cast<StackSpillStoreOp*>(aop.get());
if (as_stack_spill_store) {
// make a written_stack_slot_type
StackSlotType ss;
ss.slot = as_stack_spill_store->offset();
instr->written_stack_slot_type = ss;
// and update state!
bool found = false;
for (auto& slot : state.stack_slot_types) {
if (slot->slot == ss.slot) {
ASSERT(!found);
slot = &instr->written_stack_slot_type.value();
found = true;
}
}
ASSERT(found);
}
// do the same for next state (maybe)
auto as_store_op = dynamic_cast<StoreOp*>(aop.get());
if (as_store_op) {
IR2_RegOffset ro;
// note that this isn't 100% sure to actually be a next state.
// the implementation of StoreOp will have to notice these false positives and copy
// the next state type (not a big deal).
if (get_as_reg_offset(as_store_op->addr(), &ro)) {
if (ro.reg == Register(Reg::GPR, Reg::S6) &&
ro.offset == OFFSET_OF_NEXT_STATE_STORE[func.ir2.env.version]) {
instr->written_next_state_type = types2::Type();
state.next_state_type = &instr->written_next_state_type.value();
}
}
}
// now store the state:
instr->types = state;
}
}
}
/*!
* Wrapper around a TypeState* that temporarily modifies types for a cast.
* When this is destroyed, the casts will be reverted.
*/
class TypeStateCasted {
public:
TypeStateCasted(TypeState* state) : m_state(state) {}
TypeStateCasted(TypeState* state, const Env& env, int aop_idx, DecompilerTypeSystem& dts)
: TypeStateCasted(state) {
const auto& reg_cast_it = env.casts().find(aop_idx);
if (reg_cast_it != env.casts().end()) {
// apply register casts!
for (auto& cast : reg_cast_it->second) {
push_reg_cast(cast.reg, dts.parse_type_spec(cast.type_name));
}
}
for (const auto& [offset, cast] : env.stack_casts()) {
push_stack_cast(offset, dts.parse_type_spec(cast.type_name), env.func);
}
}
TypeStateCasted(const TypeStateCasted&) = delete;
TypeStateCasted& operator=(const TypeStateCasted&) = delete;
void push_reg_cast(Register reg, const TypeSpec& type) {
auto& cast = m_restores.emplace_back();
cast.reg = reg;
cast.is_reg = true;
cast.previous = (*m_state)[reg]->type;
(*m_state)[reg]->type = TP_Type::make_from_ts(type);
}
void push_stack_cast(int slot, const TypeSpec& type, const Function* func) {
auto& cast = m_restores.emplace_back();
cast.stack_slot = slot;
cast.is_reg = false;
auto spill_slot = m_state->try_find_stack_spill_slot(slot);
ASSERT_MSG(spill_slot, fmt::format("Function {} has no stack slot at {}", func->name(), slot));
cast.previous = spill_slot->type;
spill_slot->type = TP_Type::make_from_ts(type);
}
~TypeStateCasted() {
for (auto it = m_restores.rbegin(); it != m_restores.rend(); it++) {
if (it->is_reg) {
(*m_state)[it->reg]->type = it->previous;
} else {
m_state->try_find_stack_spill_slot(it->stack_slot)->type = it->previous;
}
}
}
private:
struct Cast {
Register reg;
int stack_slot;
std::optional<TP_Type> previous;
bool is_reg;
};
std::vector<Cast> m_restores;
TypeState* m_state;
};
void backprop_from_preds(FunctionCache& cache,
int block_idx,
Function& func,
TypeState* block_end_typestate,
DecompilerTypeSystem& dts) {
auto& cblock = cache.blocks.at(block_idx);
auto& block = func.basic_blocks.at(block_idx);
// first, we'll see if we can get any information by looking at our successors.
// if not, we'll notify our successors to prepare information for us, and we'll get it next time.
// loop over registers (both gpr and fpr)
for (auto reg_type : {Reg::GPR, Reg::FPR}) {
for (int i = 0; i < 32; i++) {
auto reg = Register(reg_type, i);
// we're only interested in types with a "tag" that indicates they need more info.
if (block_end_typestate->operator[](reg)->tag.has_tag()) {
std::optional<TP_Type> resolve_type;
// loop over successors
for (auto& succ_idx : {block.succ_branch, block.succ_ft}) {
if (succ_idx >= 0) {
// the successor will use block entry tags to report back this info.
// we'll check for these, use them if they exist, and add them if they don't.
bool this_succ_has_tag = false;
auto& succ_cblock = cache.blocks.at(succ_idx);
// loop over all block entry tags - these are used within the block to backpropagate
// constraints to the start of the block.
for (auto& succ_tag : succ_cblock.block_entry_tags) {
// see if it matches...
if (succ_tag->is_reg && succ_tag->reg == reg) {
// remember the tag exists so we don't add another.
this_succ_has_tag = true;
// see if the tag has been resolved
if (succ_tag->selected_type) {
// it has, we can use this to update our guess of the type
if (resolve_type) {
// we already have 1 guess, lca of the guesses.
bool change;
resolve_type = dts.tp_lca(*resolve_type, *succ_tag->selected_type, &change);
} else {
// no existing guess, just use this one.
resolve_type = succ_tag->selected_type;
}
}
}
}
// if we didn't find a tag, we should add one
if (!this_succ_has_tag) {
// allocate it in the block
succ_cblock.block_entry_tags.push_back(std::make_unique<BlockEntryType>());
auto* tag = succ_cblock.block_entry_tags.back().get();
// set up for the register
tag->is_reg = true;
tag->reg = reg;
tag->type_to_clear = &cache.blocks.at(succ_idx).start_type_state[reg]->type;
// fmt::print("mark to clear {}\n", succ_idx);
// add the tag to the type.
auto& st = succ_cblock.start_types[reg];
ASSERT(!st.tag.has_tag());
st.tag.kind = Tag::BLOCK_ENTRY;
st.tag.block_entry = tag;
succ_cblock.needs_run = true;
}
}
}
// we got info from the successor, pass it back into this block
if (resolve_type) {
if (backprop_tagged_type(*resolve_type, *(*block_end_typestate)[reg], dts)) {
// if we've changed things, mark this block to be re-ran.
cblock.needs_run = true;
}
}
}
}
}
// if we updated somebody else's tags
bool tags_updated = false;
for (auto& my_tag : cblock.block_entry_tags) {
if (my_tag->updated) {
tags_updated = true;
my_tag->updated = false;
// fmt::print("clearing {}\n", block_idx);
cblock.needs_run = true; // maybe?
*my_tag->type_to_clear = {}; // meh..
}
}
if (tags_updated) {
for (auto& pred : block.pred) {
cache.blocks.at(pred).needs_run = true;
}
}
}
std::optional<TP_Type> tp_lca(const types2::Type& existing,
const types2::Type& add,
bool* changed,
DecompilerTypeSystem& dts) {
if (existing.type && add.type) {
return dts.tp_lca(*existing.type, *add.type, changed);
} else if (existing.type && !add.type) {
*changed = false;
return existing.type;
} else if (!existing.type && add.type) {
*changed = true;
return add.type;
} else {
// neither
*changed = false;
return {};
}
}
/*!
* Find the least common ancestor of an entire typestate.
*/
bool tp_lca(types2::TypeState* combined, const types2::TypeState& add, DecompilerTypeSystem& dts) {
bool result = false;
for (int i = 0; i < 32; i++) {
bool diff = false;
auto new_type = tp_lca(*combined->gpr_types[i], *add.gpr_types[i], &diff, dts);
if (diff) {
result = true;
combined->gpr_types[i]->type = new_type;
}
}
for (int i = 0; i < 32; i++) {
bool diff = false;
auto new_type = tp_lca(*combined->fpr_types[i], *add.fpr_types[i], &diff, dts);
if (diff) {
result = true;
combined->fpr_types[i]->type = new_type;
}
}
for (auto& x : add.stack_slot_types) {
bool diff = false;
auto comb = combined->try_find_stack_spill_slot(x->slot);
if (!comb) {
fmt::print("failed to find {}\n", x->slot);
for (auto& x : combined->stack_slot_types) {
fmt::print("x = {}\n", x->slot);
}
}
ASSERT(comb);
auto new_type = tp_lca(*comb, x->type, &diff, dts);
if (diff) {
result = true;
comb->type = new_type;
}
}
bool diff = false;
auto new_type = tp_lca(*combined->next_state_type, *add.next_state_type, &diff, dts);
if (diff) {
result = true;
combined->next_state_type->type = new_type;
}
return result;
}
/*!
* Propagate types from the beginning of this block.
*/
bool propagate_block(FunctionCache& cache,
int block_idx,
Function& func,
DecompilerTypeSystem& dts,
bool tag_lock) {
bool debug = false; // func.name() == "string->float";
auto& cblock = cache.blocks.at(block_idx);
auto& block = func.basic_blocks.at(block_idx);
// for now, assume we'll be done. something might change this later, we'll see
cblock.needs_run = false;
// propagate through instructions
TypeState* previous_typestate = &cblock.start_type_state;
for (auto instr : cblock.instructions) {
{
TypeStateCasted casted(previous_typestate, func.ir2.env, instr->aop_idx, *func.ir2.env.dts);
auto& aop = func.ir2.atomic_ops->ops.at(instr->aop_idx);
TypePropExtras extras;
extras.tags_locked = tag_lock;
// fmt::print("run: {}\n", aop->to_string(func.ir2.env));
try {
aop->propagate_types2(*instr, func.ir2.env, *previous_typestate, *func.ir2.env.dts, extras);
} catch (const std::exception& e) {
auto error = fmt::format("failed type prop at {}: {}", instr->aop_idx, e.what());
func.warnings.error(error);
lg::error("Function {} {}", func.name(), error);
return false;
}
if (extras.needs_rerun) {
cblock.needs_run = true;
}
// propagate forward
// TODO
// handle constraints
}
previous_typestate = &instr->types;
}
// now that we've reached the end, handle backprop across blocks
if (!tag_lock) {
backprop_from_preds(cache, block_idx, func, previous_typestate, *func.ir2.env.dts);
}
// deal with end crap
// lca
// set tags on succs/backprop from succs
for (auto succ_block_id : {block.succ_ft, block.succ_branch}) {
if (succ_block_id != -1) {
// set types to LCA (current, new)
if (tp_lca(&cache.blocks.at(succ_block_id).start_type_state, *previous_typestate, dts)) {
// if something changed, run again!
cache.blocks.at(succ_block_id).needs_run = true;
}
}
}
return true;
}
bool convert_to_old_format(TP_Type& out, const types2::Type* in, bool recovery_mode) {
if (!in->type) {
if (recovery_mode) {
out = TP_Type::make_uninitialized();
return true;
} else {
return false;
}
} else {
out = *in->type;
return true;
}
}
bool convert_to_old_format(::decompiler::TypeState& out,
const types2::TypeState& in,
std::string& error_string,
int my_idx,
const std::unordered_map<int, std::vector<RegisterTypeCast>>& casts,
const std::unordered_map<int, StackTypeCast>& stack_casts,
const DecompilerTypeSystem& dts,
bool recovery_mode) {
for (int i = 0; i < 32; i++) {
ASSERT(in.fpr_types[i]);
if (!convert_to_old_format(out.fpr_types[i], in.fpr_types[i], recovery_mode)) {
error_string += fmt::format("Failed to convert FPR: {} ", i);
return false;
}
if (!convert_to_old_format(out.gpr_types[i], in.gpr_types[i], recovery_mode)) {
error_string += fmt::format("Failed to convert GPR: {} ", Register(Reg::GPR, i).to_string());
return false;
}
}
if (!convert_to_old_format(out.next_state_type, in.next_state_type, recovery_mode)) {
error_string += "Failed to convert next state ";
return false;
}
const auto& reg_casts = casts.find(my_idx);
if (reg_casts != casts.end()) {
for (auto& cast : reg_casts->second) {
out.get(cast.reg) = TP_Type::make_from_ts(dts.parse_type_spec(cast.type_name));
}
}
for (auto& x : in.stack_slot_types) {
TP_Type temp;
if (!convert_to_old_format(temp, &x->type, recovery_mode)) {
error_string += fmt::format("Failed to convert stack slot: {} ", x->slot);
return false;
}
out.spill_slots[x->slot] = temp;
}
for (auto& [offset, type] : stack_casts) {
out.spill_slots[offset] = TP_Type::make_from_ts(dts.parse_type_spec(type.type_name));
}
return true;
}
bool convert_to_old_format(Output& out,
FunctionCache& in,
std::string& error_string,
const std::unordered_map<int, std::vector<RegisterTypeCast>>& casts,
const std::unordered_map<int, StackTypeCast>& stack_casts,
const DecompilerTypeSystem& dts,
bool recovery_mode) {
// for (auto& block : in.blocks) {
out.op_end_types.resize(in.instructions.size());
out.block_init_types.resize(in.blocks.size());
for (int block_idx : in.block_visit_order) {
auto& block = in.blocks[block_idx];
if (!convert_to_old_format(out.block_init_types.at(block_idx), block.start_type_state,
error_string, block.instructions.at(0)->aop_idx, casts, stack_casts,
dts, recovery_mode)) {
error_string += fmt::format(" at the start of block {}\n", block_idx);
return false;
}
for (auto& instr : block.instructions) {
if (!convert_to_old_format(out.op_end_types.at(instr->aop_idx), instr->types, error_string,
instr->aop_idx + 1, casts, stack_casts, dts, recovery_mode)) {
error_string += fmt::format(" at op {}\n", instr->aop_idx);
return false;
}
}
}
return true;
}
/*!
* Main Types2 Analysis pass.
*/
void run(Output& out, const Input& input) {
// First, construct our graph
FunctionCache function_cache;
auto stack_slots = find_stack_spill_slots(*input.func);
build_function(function_cache, *input.func, stack_slots);
// annoying hack
if (input.func->guessed_name.kind == FunctionName::FunctionKind::METHOD) {
input.dts->type_prop_settings.current_method_type = input.func->guessed_name.type_name;
}
if (input.function_type.last_arg() == TypeSpec("none")) {
auto as_end = dynamic_cast<FunctionEndOp*>(input.func->ir2.atomic_ops->ops.back().get());
ASSERT(as_end);
as_end->mark_function_as_no_return_value();
}
// mark the entry block
function_cache.blocks.at(0).needs_run = true;
construct_function_entry_types(function_cache.blocks.at(0).start_types, input.function_type,
stack_slots);
// Run propagation, until we get through an iteration with no changes
int blocks_run = 0;
int outer_iterations = 0;
bool needs_rerun = true;
bool hit_error = false;
while (needs_rerun) {
outer_iterations++;
needs_rerun = false;
for (auto block_idx : function_cache.block_visit_order) {
if (function_cache.blocks.at(block_idx).needs_run) {
blocks_run++;
needs_rerun = true;
if (!propagate_block(function_cache, block_idx, *input.func, *input.func->ir2.env.dts,
false)) {
hit_error = true;
goto end_type_pass;
}
}
}
auto& return_type = input.function_type.last_arg();
if (return_type != TypeSpec("none")) {
auto& last_instr = function_cache.instructions.back().types[Register(Reg::GPR, Reg::V0)];
if (last_instr->tag.has_tag()) {
if (!last_instr->type || !input.dts->ts.tc(return_type, last_instr->type->typespec())) {
if (backprop_tagged_type(TP_Type::make_from_ts(return_type), *last_instr, *input.dts)) {
needs_rerun = true;
}
}
}
}
}
for (auto block_idx : function_cache.block_visit_order) {
if (block_idx != 0) {
auto& cblock = function_cache.blocks.at(block_idx).start_types;
for (auto& x : cblock.gpr_types) {
x.type = TP_Type::make_uninitialized();
}
for (auto& x : cblock.fpr_types) {
x.type = TP_Type::make_uninitialized();
}
for (auto x : stack_slots) {
auto slot = cblock.try_find_stack_spill_slot(x);
ASSERT(slot);
slot->type.type = TP_Type::make_uninitialized();
}
cblock.next_state_type.type = TP_Type::make_uninitialized();
}
}
needs_rerun = true;
function_cache.blocks.at(0).needs_run = true;
while (needs_rerun) {
outer_iterations++;
needs_rerun = false;
for (auto block_idx : function_cache.block_visit_order) {
if (function_cache.blocks.at(block_idx).needs_run) {
blocks_run++;
needs_rerun = true;
if (!propagate_block(function_cache, block_idx, *input.func, *input.func->ir2.env.dts,
true)) {
hit_error = true;
goto end_type_pass;
}
}
}
}
end_type_pass:
std::string error;
if (!convert_to_old_format(out, function_cache, error, input.func->ir2.env.casts(),
input.func->ir2.env.stack_casts(), *input.dts, hit_error)) {
fmt::print("Failed convert_to_old_format: {}\n", error);
} else {
input.func->ir2.env.types_succeeded = true;
auto last_type = out.op_end_types.back().get(Register(Reg::GPR, Reg::V0)).typespec();
if (last_type != input.function_type.last_arg()) {
input.func->warnings.info("Return type mismatch {} vs {}.", last_type.print(),
input.function_type.last_arg().print());
}
}
// figure out the types of stack spill variables:
auto& env = input.func->ir2.env;
bool changed;
for (auto& type_info : out.op_end_types) {
for (auto& spill : type_info.spill_slots) {
auto& slot_info = env.stack_slot_entries[spill.first];
slot_info.tp_type =
input.dts->tp_lca(env.stack_slot_entries[spill.first].tp_type, spill.second, &changed);
slot_info.offset = spill.first;
}
}
for (auto& type_info : out.block_init_types) {
for (auto& spill : type_info.spill_slots) {
auto& slot_info = env.stack_slot_entries[spill.first];
slot_info.tp_type =
input.dts->tp_lca(env.stack_slot_entries[spill.first].tp_type, spill.second, &changed);
slot_info.offset = spill.first;
}
}
// convert to typespec
for (auto& info : env.stack_slot_entries) {
info.second.typespec = info.second.tp_type.typespec();
// debug
// fmt::print("STACK {} : {} ({})\n", info.first, info.second.typespec.print(),
// info.second.tp_type.print());
}
// notify the label db of guessed labels
for (auto& instr : function_cache.instructions) {
if (instr.unknown_label_tag) {
if (!instr.unknown_label_tag->selected_type) {
throw std::runtime_error(fmt::format("Failed to guess label use for {} in {}:{}",
instr.unknown_label_tag->label_name,
input.func->name(), instr.aop_idx));
}
auto& type = instr.unknown_label_tag->selected_type.value();
int idx = instr.unknown_label_tag->label_idx;
ASSERT(type.base_type() != "pointer"); // want to test this if we find example...
env.file->label_db->set_and_get_previous(idx, type, false, {});
}
if (instr.unknown_stack_structure_tag) {
if (!instr.unknown_stack_structure_tag->selected_type) {
throw std::runtime_error(fmt::format("Failed to guess stack use for {} in {}:{}",
instr.unknown_stack_structure_tag->stack_offset,
input.func->name(), instr.aop_idx));
}
auto& type = instr.unknown_stack_structure_tag->selected_type.value();
int offset = instr.unknown_stack_structure_tag->stack_offset;
ASSERT(type.base_type() != "pointer"); // want to test this if we find example...
StackStructureHint hint;
hint.stack_offset = offset;
hint.container_type = StackStructureHint::ContainerType::NONE;
hint.element_type = type.print();
env.add_stack_structure_hint(hint);
}
}
out.succeeded = !hit_error;
}
} // namespace decompiler::types2
+248
View File
@@ -0,0 +1,248 @@
#pragma once
#include <memory>
#include <optional>
#include <variant>
#include <vector>
#include "decompiler/Function/Function.h"
#include "decompiler/config.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "decompiler/util/TP_Type.h"
namespace decompiler::types2 {
// Backprop tag types:
// these classes are "tags" that can be added to types to give a path to propagate constraints
// backward. For example, if we encounter a function call, and we know the expected argument types,
// the type pass will use these tags to propagate information backward.
/*!
* Represents a case where there are multiple possible fields that could be accessed.
* For example, (&-> matrix vector 0), (&-> matrix data 0). Or any case with overlapping fields.
*/
struct AmbiguousFieldAccess {
struct Possibility {
TypeSpec type;
// TODO: probably stash more info here.
};
std::vector<Possibility> possibilities;
int selected_possibility = -1; // -1 if not selected.
};
/*!
* Tag to link an unknown type back to a label with unknown type.
*/
struct UnknownLabel {
int label_idx = -1;
std::string label_name; // just for debug prints
std::optional<TypeSpec> selected_type;
};
/*!
* Tag to link an unknown type back to a stack structure with unknown type.
*/
struct UnknownStackStructure {
int stack_offset = -1;
std::optional<TypeSpec> selected_type;
};
/*!
* Tag to link a type back to something outside the block.
*/
struct BlockEntryType {
Register reg;
bool is_reg = true;
int stack_slot = -1;
std::optional<TP_Type> selected_type;
bool updated = false;
std::optional<TP_Type>* type_to_clear = nullptr;
};
struct AmbiguousIntOrFloatConstant {
std::optional<bool> is_float;
};
/*!
* Union of all tag types.
*/
struct Tag {
bool has_tag() { return kind != NONE; }
enum Kind {
FIELD_ACCESS,
UNKNOWN_LABEL,
UNKNOWN_STACK_STRUCTURE,
BLOCK_ENTRY,
INT_OR_FLOAT,
NONE
} kind = NONE;
union {
AmbiguousFieldAccess* field_access;
BlockEntryType* block_entry;
UnknownLabel* unknown_label;
UnknownStackStructure* unknown_stack_structure;
AmbiguousIntOrFloatConstant* int_or_float;
};
};
/*!
* The basic "type" that we're trying to figure out for each register on each instruction.
*/
struct Type {
Tag tag; // may have type "none"
std::optional<TP_Type> type; // may be unknown
};
struct RegType {
Type type;
Register reg;
};
struct StackSlotType {
Type type;
int slot = -1;
};
struct TypeState {
Type* gpr_types[32];
Type* fpr_types[32];
Type* next_state_type = nullptr;
Type*& operator[](const Register& reg) {
switch (reg.get_kind()) {
case Reg::FPR:
return fpr_types[reg.get_fpr()];
case Reg::GPR:
return gpr_types[reg.get_gpr()];
default:
ASSERT(false);
}
}
const Type* operator[](const Register& reg) const {
switch (reg.get_kind()) {
case Reg::FPR:
return fpr_types[reg.get_fpr()];
case Reg::GPR:
return gpr_types[reg.get_gpr()];
default:
ASSERT(false);
}
}
Type* try_find_stack_spill_slot(int slot) {
for (auto ss : stack_slot_types) {
if (ss->slot == slot) {
return &ss->type;
}
}
return nullptr;
}
const Type* try_find_stack_spill_slot(int slot) const {
for (auto& ss : stack_slot_types) {
if (ss->slot == slot) {
return &ss->type;
}
}
return nullptr;
}
template <typename T>
void for_each_type(T&& f) {
for (auto gpr_type : gpr_types) {
f(*gpr_type);
}
for (auto fpr_type : fpr_types) {
f(*fpr_type);
}
for (auto spill : stack_slot_types) {
f(spill->type);
}
f(*next_state_type);
}
std::vector<StackSlotType*> stack_slot_types;
};
struct Instruction {
TypeState types;
size_t aop_idx = -1;
std::vector<RegType> written_reg_types;
std::optional<StackSlotType> written_stack_slot_type;
std::optional<Type> written_next_state_type;
std::unique_ptr<AmbiguousFieldAccess> field_access_tag;
std::unique_ptr<UnknownLabel> unknown_label_tag;
std::unique_ptr<UnknownStackStructure> unknown_stack_structure_tag;
std::unique_ptr<AmbiguousIntOrFloatConstant> int_or_float;
};
struct BlockStartTypes {
Type gpr_types[32];
Type fpr_types[32];
Type next_state_type;
std::vector<StackSlotType> stack_slot_types;
StackSlotType* try_find_stack_spill_slot(int slot) {
for (auto& ss : stack_slot_types) {
if (ss.slot == slot) {
return &ss;
}
}
return nullptr;
}
Type& operator[](const Register& reg) {
switch (reg.get_kind()) {
case Reg::FPR:
return fpr_types[reg.get_fpr()];
case Reg::GPR:
return gpr_types[reg.get_gpr()];
default:
ASSERT(false);
}
}
};
struct Block {
bool needs_run = false;
BlockStartTypes start_types;
TypeState start_type_state;
std::vector<Instruction*> instructions;
std::vector<std::shared_ptr<BlockEntryType>> block_entry_tags;
};
struct FunctionCache {
std::vector<Block> blocks;
std::vector<Instruction> instructions;
std::vector<RegType> reg_type_casts;
std::vector<StackSlotType> stack_slot_casts;
std::vector<int> block_visit_order;
};
struct Output {
std::vector<::decompiler::TypeState> block_init_types;
std::vector<::decompiler::TypeState> op_end_types;
std::vector<StackStructureHint> stack_structure_hints;
bool succeeded = false;
};
struct Input {
TypeSpec function_type;
DecompilerTypeSystem* dts;
Function* func;
};
struct TypePropExtras {
bool needs_rerun = false;
bool tags_locked = false;
};
void run(Output& out, const Input& input);
bool backprop_tagged_type(const TP_Type& expected_type,
types2::Type& actual_type,
const DecompilerTypeSystem& dts);
} // namespace decompiler::types2
+2 -2
View File
@@ -412,8 +412,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", "1T"};
static const std::vector<std::string> multi_char_ignore_list = {"0L", "1L", "3L", "1K", "2j",
"0k", "30L", "1T", "2T"};
int arg_count = 0;
for (size_t i = 0; i < str.length(); i++) {
+24 -3
View File
@@ -279,7 +279,9 @@ goos::Object decompile_value_array(const TypeSpec& elt_type,
for (int j = start; j < end; j++) {
auto& word = obj_words.at(j / 4);
if (word.kind() != LinkedWord::PLAIN_DATA) {
throw std::runtime_error("Got bad word in kind in array of values");
throw std::runtime_error(fmt::format(
"Got bad word in kind in array of values: expecting array of {}'s, got a {}\n",
elt_type.print(), (int)word.kind()));
}
elt_bytes.push_back(word.get_byte(j % 4));
}
@@ -592,6 +594,16 @@ goos::Object sp_launch_grp_launcher_decompile(const std::vector<LinkedWord>& wor
return decomp_ref_to_inline_array_guess_size(words, labels, my_seg, field_location, ts, all_words,
file, TypeSpec("sparticle-group-item"), 32);
}
goos::Object probe_dir_decompile(const std::vector<LinkedWord>& words,
const std::vector<DecompilerLabel>& labels,
int my_seg,
int field_location,
const TypeSystem& ts,
const std::vector<std::vector<LinkedWord>>& all_words,
const LinkedObjectFile* file) {
return decomp_ref_to_inline_array_guess_size(words, labels, my_seg, field_location, ts, all_words,
file, TypeSpec("vector"), 16);
}
goos::Object decompile_sound_spec(const TypeSpec& type,
const DecompilerLabel& label,
@@ -746,12 +758,14 @@ goos::Object decompile_structure(const TypeSpec& type,
// some structures we want to decompile to fancy macros instead of a raw static definiton
if (use_fancy_macros) {
if (type == TypeSpec("sp-field-init-spec")) {
ASSERT(file->version == GameVersion::Jak1); // need to update enums
return decompile_sparticle_field_init(type, label, labels, words, ts, file);
}
if (type == TypeSpec("sparticle-group-item")) {
ASSERT(file->version == GameVersion::Jak1); // need to update enums
return decompile_sparticle_group_item(type, label, labels, words, ts, file);
}
if (type == TypeSpec("sound-spec")) {
if (type == TypeSpec("sound-spec") && file->version != GameVersion::Jak2) {
return decompile_sound_spec(type, label, labels, words, ts, file);
}
}
@@ -769,7 +783,8 @@ goos::Object decompile_structure(const TypeSpec& type,
if (is_basic) {
const auto& word = words.at(label.target_segment).at((offset_location / 4));
if (word.kind() != LinkedWord::TYPE_PTR) {
throw std::runtime_error("Basic does not start with type pointer");
throw std::runtime_error(
fmt::format("Basic does not start with type pointer: {}", label.name));
}
if (word.symbol_name() != actual_type.base_type()) {
@@ -976,6 +991,10 @@ goos::Object decompile_structure(const TypeSpec& type,
field.name(), decomp_ref_to_integer_array_guess_size(
obj_words, labels, label.target_segment, field_start, ts, words,
file, TypeSpec("uint8"), 1));
} else if (field.name() == "probe-dirs" && type.print() == "lightning-probe-vars") {
field_defs_out.emplace_back(field.name(),
probe_dir_decompile(obj_words, labels, label.target_segment,
field_start, ts, words, file));
} else {
if (field.type().base_type() == "pointer") {
if (obj_words.at(field_start / 4).kind() != LinkedWord::SYM_PTR) {
@@ -1400,6 +1419,8 @@ goos::Object decompile_boxed_array(const DecompilerLabel& label,
(word.data & 0b111) == 0) {
s32 val = word.data;
result.push_back(pretty_print::to_symbol(fmt::format("(the binteger {})", val / 8)));
} else if (content_type == TypeSpec("type") && word.kind() == LinkedWord::TYPE_PTR) {
result.push_back(pretty_print::to_symbol(word.symbol_name()));
} else {
throw std::runtime_error(
fmt::format("Unknown content type in boxed array of references, word idx {}",
+3
View File
@@ -1,8 +1,11 @@
#pragma once
#include "common/common_types.h"
#include "common/versions.h"
// Separate constants for the decompiler, so we can make changes to the game without breaking
// decompilation.
constexpr s32 DECOMP_SYM_INFO_OFFSET = 8167 * 8 - 4;
constexpr PerGameVersion<int> OFFSET_OF_NEXT_STATE_STORE = {72, 64};
+1 -1
View File
@@ -103,7 +103,7 @@
(define-extern __mem-move (function pointer pointer uint none))
(define-extern file-stream-read (function file-stream pointer int int))
(define-extern file-stream-open (function file-stream basic symbol file-stream))
(define-extern file-stream-open (function file-stream string symbol file-stream))
(define-extern file-stream-length (function file-stream int))
+13 -49
View File
@@ -1,6 +1,7 @@
#include "collide_bvh.h"
#include <algorithm>
#include <map>
#include <unordered_set>
#include "common/log/log.h"
@@ -19,15 +20,6 @@ namespace collide {
namespace {
constexpr int MAX_UNIQUE_VERTS_IN_FRAG = 128;
struct BBox {
math::Vector3f mins, maxs;
std::string sz_to_string() const {
return fmt::format("{} {} ({})", (mins / 4096.f).to_string_aligned(),
(maxs / 4096.f).to_string_aligned(),
((maxs - mins) / 4096.f).to_string_aligned());
}
};
/*!
* The Collide node.
* If it's a leaf, it has faces
@@ -45,20 +37,6 @@ struct VectorHash {
}
};
/*!
* Recursively get a set of unique vertices.
*/
void collect_vertices(const CNode& node, std::unordered_set<math::Vector3f, VectorHash>& verts) {
for (auto& child : node.child_nodes) {
collect_vertices(child, verts);
}
for (auto& face : node.faces) {
verts.insert(face.v[0]);
verts.insert(face.v[1]);
verts.insert(face.v[2]);
}
}
/*!
* Recursively get a list of vertices.
*/
@@ -73,30 +51,6 @@ void collect_vertices(const CNode& node, std::vector<math::Vector3f>& verts) {
}
}
/*!
* Get the axis-aligned bounding box of these vertices
*/
BBox compute_my_bbox(const std::vector<math::Vector3f>& verts) {
ASSERT(!verts.empty());
BBox result;
result.mins = verts.front();
result.maxs = verts.front();
for (auto& v : verts) {
result.mins.min_in_place(v);
result.maxs.min_in_place(v);
}
return result;
}
/*!
* Get the axis-aligned bounding box of all vertices in this node and its children
*/
BBox compute_my_bbox(const CNode& node) {
std::vector<math::Vector3f> verts;
collect_vertices(node, verts);
return compute_my_bbox(verts);
}
/*!
* Find the vertex in verts that is most distant from pt.
*/
@@ -154,6 +108,7 @@ void split_along_dim(std::vector<CollideFace>& faces,
std::sort(faces.begin(), faces.end(), [=](const CollideFace& a, const CollideFace& b) {
return a.bsphere[dim] < b.bsphere[dim];
});
fmt::print("splitting with size: {}\n", faces.size());
size_t split_idx = faces.size() / 2;
out0->insert(out0->end(), faces.begin(), faces.begin() + split_idx);
out1->insert(out1->end(), faces.begin() + split_idx, faces.end());
@@ -165,7 +120,6 @@ void split_along_dim(std::vector<CollideFace>& faces,
void split_node_once(CNode& node, CNode* out0, CNode* out1) {
compute_my_bsphere_ritters(node);
CNode temps[6];
// split_along_dim(node.faces, pick_dim_for_split(node.faces), &out0->faces, &out1->faces);
split_along_dim(node.faces, 0, &temps[0].faces, &temps[1].faces);
split_along_dim(node.faces, 1, &temps[2].faces, &temps[3].faces);
split_along_dim(node.faces, 2, &temps[4].faces, &temps[5].faces);
@@ -211,7 +165,7 @@ bool needs_split(const CNode& node) {
}
}
return unique_verts.size() >= 128;
return unique_verts.size() >= MAX_UNIQUE_VERTS_IN_FRAG;
}
void split_recursive(CNode& to_split) {
@@ -342,7 +296,17 @@ CollideTree construct_collide_bvh(const std::vector<CollideFace>& tris) {
bvh_timer.start();
auto tree = build_collide_tree(root);
debug_stats(tree);
lg::info("Tree layout done in {:.2f} ms", bvh_timer.getMs());
std::map<int, int> size_histogram;
for (const auto& f : tree.frags.frags) {
size_histogram[f.faces.size()]++;
}
for (auto [size, count] : size_histogram) {
fmt::print(" [{:3d}] {:3d} ({})\n", size, count, size * count);
}
return tree;
}
+1 -1
View File
@@ -3,4 +3,4 @@
# Directory of this script
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
$DIR/build/goalc-test --gtest_brief=1 --gtest_color=yes "$@" --gtest_filter="-*MANUAL_TEST*"
$DIR/build/goalc-test --gtest_color=yes "$@" --gtest_filter="-*MANUAL_TEST*"
+1 -1
View File
@@ -31,7 +31,7 @@ class FormRegressionTest : public ::testing::TestWithParam<GameVersion> {
static void TearDownTestCase();
struct TestData {
explicit TestData(int instrs, GameVersion version) : func(0, instrs, version) {}
explicit TestData(int instrs, GameVersion version) : func(0, instrs, version), file(version) {}
decompiler::Function func;
decompiler::LinkedObjectFile file;
+1 -1
View File
@@ -541,7 +541,7 @@
;; definition for method 9 of type art-mesh-geo
;; ERROR: Type Propagation failed: Failed type prop at op 20 ((set! v1 (l.h (+ s4 6)))): Could not get type of load: (set! v1 (l.h (+ s4 6))).
;; ERROR: Type Propagation failed: Type analysis failed
;; WARN: Type analysis failed
(defmethod login art-mesh-geo ((a0-0 art-mesh-geo))
(local-vars
(v0-0 none)
+1 -1
View File
@@ -134,7 +134,7 @@
;; definition for function vis-cull
;; ERROR: Type Propagation failed: Failed type prop at op 3 ((set! v1 (l.b (+ v1 #x38b0)))): Could not get type of load: (set! v1 (l.b (+ v1 #x38b0))).
;; ERROR: Type Propagation failed: Type analysis failed
;; WARN: Type analysis failed
;; ERROR: Unsupported inline assembly instruction kind - [addiu a0, a0, 56]
(defun vis-cull ((a0-0 int))
(local-vars (v0-0 none) (v1-0 int) (v1-1 int) (v1-2 none) (v1-3 none) (a0-1 none) (a0-2 none) (a1-0 int))
+1 -1
View File
@@ -139,7 +139,7 @@
;; definition for function sky-draw
;; ERROR: Type Propagation failed: Failed type prop at op 12 ((set! a0 (+ a0 16))): Cannot get_type_int2: (+ a0 16), args float and <integer 16>
;; ERROR: Type Propagation failed: Type analysis failed
;; WARN: Type analysis failed
(defun sky-draw ((a0-0 sky-parms))
(local-vars
(v0-0 none)
+1 -21
View File
@@ -511,27 +511,7 @@
;; definition for function vector-length
(defun vector-length ((arg0 vector))
(local-vars (v0-0 float))
(rlet ((acc :class vf)
(Q :class vf)
(vf0 :class vf)
(vf1 :class vf)
)
(init-vf0-vector)
(.lvf vf1 (&-> arg0 quad))
(.mul.vf vf1 vf1 vf1)
(.mul.x.vf acc vf0 vf1 :mask #b1000)
(.add.mul.y.vf acc vf0 vf1 acc :mask #b1000)
(.add.mul.z.vf vf1 vf0 vf1 acc :mask #b1000)
(.sqrt.vf Q vf1 :ftf #b11)
(.add.w.vf vf1 vf0 vf0 :mask #b1)
(.wait.vf)
(.mul.vf vf1 vf1 Q :mask #b1)
(.nop.vf)
(.nop.vf)
(.mov v0-0 vf1)
v0-0
)
(vector-length arg0)
)
;; definition for function vector-length-squared
+2 -2
View File
@@ -112,7 +112,7 @@
;; definition for function glst-find-node-by-name
;; ERROR: Type Propagation failed: Failed type prop at op 7 ((set! a0 (l.wu (+ v1 8)))): Could not get type of load: (set! a0 (l.wu (+ v1 8))).
;; ERROR: Type Propagation failed: Type analysis failed
;; WARN: Type analysis failed
(defun glst-find-node-by-name ((a0-0 glst-list) (a1-0 string))
(local-vars
(v0-0 none)
@@ -173,7 +173,7 @@
;; definition for function glst-length-of-longest-name
;; ERROR: Type Propagation failed: Failed type prop at op 6 ((set! a0 (l.wu (+ v1 8)))): Could not get type of load: (set! a0 (l.wu (+ v1 8))).
;; ERROR: Type Propagation failed: Type analysis failed
;; WARN: Type analysis failed
(defun glst-length-of-longest-name ((a0-0 glst-list))
(local-vars
(v0-0 none)
+1 -1
View File
@@ -872,7 +872,7 @@
;; definition for function race-time-save
;; ERROR: Type Propagation failed: Failed type prop at op 6 ((set! v1 (l.wu (+ a0 -4)))): Could not get type of load: (set! v1 (l.wu (+ a0 -4))).
;; ERROR: Type Propagation failed: Type analysis failed
;; WARN: Type analysis failed
;; ERROR: Function may read a register that is not set: a2
(defun race-time-save ((a0-0 race-time) (a1-0 task-control))
(local-vars
+141
View File
@@ -554,4 +554,145 @@
(defmacro .mfc0 (&rest stuff)
`(empty)
)
;; pad
(defmacro cpad-pressed (pad-idx)
`(-> *cpad-list* cpads ,pad-idx button0-rel 0)
)
(defmacro cpad-hold (pad-idx)
`(-> *cpad-list* cpads ,pad-idx button0-abs 0)
)
(defmacro cpad-pressed? (pad-idx &rest buttons)
`(logtest? (cpad-pressed ,pad-idx) (pad-buttons ,@buttons))
)
(defmacro cpad-hold? (pad-idx &rest buttons)
`(logtest? (cpad-hold ,pad-idx) (pad-buttons ,@buttons))
)
(defmacro res-lump-data (lump name type &key (tag-ptr (the-as (pointer res-tag) #f)) &key (time -1000000000.0))
"Helper macro to get data from a res-lump without interpolation."
`(the-as ,type ((method-of-type res-lump get-property-data)
,lump
,name
'interp
,time
(the-as pointer #f)
,tag-ptr
*res-static-buf*
)
)
)
(defmacro res-lump-data-exact (lump name type &key (tag-ptr (the-as (pointer res-tag) #f)) &key (time 0.0))
"Helper macro to get start of data from a res-lump."
`(the-as ,type ((method-of-type res-lump get-property-data)
,lump
,name
'exact
,time
(the-as pointer #f)
,tag-ptr
*res-static-buf*
)
)
)
(defmacro res-lump-struct (lump name type &key (tag-ptr (the-as (pointer res-tag) #f)) &key (time -1000000000.0))
`(the-as ,type ((method-of-type res-lump get-property-struct)
,lump
,name
'interp
,time
(the-as structure #f)
,tag-ptr
*res-static-buf*
)
)
)
(defmacro res-lump-struct-exact (lump name type &key (tag-ptr (the-as (pointer res-tag) #f)) &key (time 0.0))
`(the-as ,type ((method-of-type res-lump get-property-struct)
,lump
,name
'exact
,time
(the-as structure #f)
,tag-ptr
*res-static-buf*
)
)
)
(defmacro res-lump-value (lump name type &key (tag-ptr (the-as (pointer res-tag) #f)) &key (default (the-as uint128 0)) &key (time -1000000000.0))
"Helper macro to get a value from a res-lump with no interpolation."
`(the-as ,type ((method-of-type res-lump get-property-value)
,lump
,name
'interp
,time
,default
,tag-ptr
*res-static-buf*
)
)
)
(defmacro res-lump-float (lump name &key (tag-ptr (the-as (pointer res-tag) #f)) &key (default 0.0) &key (time -1000000000.0))
"Helper macro to get a float from a res-lump with no interpolation."
`((method-of-type res-lump get-property-value-float)
,lump
,name
'interp
,time
,default
,tag-ptr
*res-static-buf*
)
)
(defmacro seek! (place target rate)
"Macro to use seek in-place. place is the base, and where the result is stored."
`(set! ,place (seek ,place ,target ,rate))
)
(defmacro seekl! (place target rate)
"Macro to use seekl in-place. place is the base, and where the result is stored."
`(set! ,place (seekl ,place ,target ,rate))
)
;; run the given function in a process right now.
;; will return to here when:
;; - you return
;; - you deactivate
;; - you go
;; - you throw to 'initialize
(defmacro run-now-in-process (proc func &rest args)
`((the (function _varargs_ object) run-function-in-process)
,proc ,func ,@args
)
)
;; sets the main thread of the given process to run the given thing.
;; this resets the main thread stack back to the top
(defmacro run-next-time-in-process (proc func &rest args)
`((the (function _varargs_ object) set-to-run)
(-> ,proc main-thread) ,func ,@args
)
)
(defmacro send-event (proc msg &key (from (with-pp pp)) &rest params)
"Send an event to a process. This should be used over send-event-function"
`(let ((event-data (new 'stack-no-clear 'event-message-block)))
(set! (-> event-data from) (process->ppointer ,from))
(set! (-> event-data num-params) ,(length params))
(set! (-> event-data message) ,msg)
,@(apply-i (lambda (x i) `(set! (-> event-data param ,i) (the-as uint ,x))) params)
(send-event-function ,proc event-data)
)
)
+1 -1
View File
@@ -47,7 +47,7 @@
;; ERROR: Unsupported inline assembly instruction kind - [jr t9]
;; ERROR: Unsupported inline assembly instruction kind - [sw v1, 0(sp)]
(defun enter-state ((arg0 object) (arg1 object) (arg2 object) (arg3 object) (arg4 object) (arg5 object))
(local-vars (s7-0 none) (sp-0 int) (ra-0 int) (sv-0 none))
(local-vars (s7-0 none) (sp-0 int) (ra-0 int))
(with-pp
(logclear! (-> pp mask) (process-mask sleep sleep-code))
(logior! (-> pp mask) (process-mask going))
+4 -4
View File
@@ -7,7 +7,7 @@ using namespace decompiler;
TEST(InstructionDecode, VDIV) {
init_opcode_info();
LinkedObjectFile file;
LinkedObjectFile file(GameVersion::Jak1);
u32 vdiv_test = 0b010010'1'10'01'10100'01010'01110111100;
// ...... z y 20 10
LinkedWord vdiv_word(vdiv_test);
@@ -17,7 +17,7 @@ TEST(InstructionDecode, VDIV) {
TEST(InstructionDecode, VRSQRT) {
init_opcode_info();
LinkedObjectFile file;
LinkedObjectFile file(GameVersion::Jak1);
u32 vdiv_test = 0b010010'1'10'01'10100'01010'01110111110;
// ...... z y 20 10
LinkedWord vdiv_word(vdiv_test);
@@ -27,7 +27,7 @@ TEST(InstructionDecode, VRSQRT) {
TEST(InstructionDecode, VSQRT) {
init_opcode_info();
LinkedObjectFile file;
LinkedObjectFile file(GameVersion::Jak1);
u32 vdiv_test = 0b010010'1'10'00'10100'00000'01110111101;
// ...... z X 20 X
LinkedWord vdiv_word(vdiv_test);
@@ -37,7 +37,7 @@ TEST(InstructionDecode, VSQRT) {
TEST(Instruction, IntelMaskMove) {
init_opcode_info();
LinkedObjectFile file;
LinkedObjectFile file(GameVersion::Jak1);
u32 vmove_instr = 0b010010'1'1100'00001'00010'01100111100;
LinkedWord vmove_word(vmove_instr);
auto instr = decode_instruction(vmove_word, file, 0, 0);
+20 -1
View File
@@ -15,7 +15,26 @@
"(method 10 process)",
"(method 14 dead-pool)",
/// GSTATE
"enter-state" // stack pointer asm
"enter-state", // stack pointer asm
// MATH
"logf", "log2f", "cube-root", "lerp-scale", "rand-vu-init", "rand-vu", "rand-vu-nostep",
// MATRIX
"matrix-axis-sin-cos-vu!", "matrix-axis-sin-cos!", "matrix-3x3-normalize!",
// DMA
"disasm-dma-list",
// PAD (bug)
"service-cpads",
// GEOMETRY asm
"closest-pt-in-triangle", "circle-circle-xz-intersect", "calculate-basis-functions-vector!", "curve-evaluate!",
// TIMER asm
"(method 9 clock)",
// math camera stupid broken gif crap and CLIP
"update-math-camera", "transform-point-vector!", "transform-point-qword!", "transform-point-vector-scale!",
// quad thing
"(method 3 connection-minimap)",
"(method 3 sky-vertex)",
// cache asm
"invalidate-cache-line"
],
"skip_compile_states": {}
+3 -2
View File
@@ -406,8 +406,8 @@ int main(int argc, char* argv[]) {
app.add_flag("-d,--dump_current_output", dump_current_output,
"Output the current output to a folder, use in conjunction with the reference test "
"files update script");
app.add_flag("-m,--max_files", max_files,
"Limit the amount of files ran in a single test, picks the first N");
app.add_option("-m,--max_files", max_files,
"Limit the amount of files ran in a single test, picks the first N");
app.validate_positionals();
CLI11_PARSE(app, argc, argv);
@@ -446,6 +446,7 @@ int main(int argc, char* argv[]) {
auto compare_result = compare(decompiler, files, dump_current_output);
lg::info("Compared {} lines. {}/{} files passed.", compare_result.total_lines,
compare_result.ok_files, compare_result.total_files);
lg::info("Dump? {}\n", dump_current_output);
if (!compare_result.failing_files.empty()) {
lg::error("Failing files:");