mirror of
https://github.com/open-goal/jak-project
synced 2026-08-02 00:34:13 -04:00
[decompiler] support for jak 2 (#1781)
* [decompiler] suppport jak 2 * cleanpu * remove brief from gtest options * fix test
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -104,6 +104,5 @@ class DecompWarnings {
|
||||
}
|
||||
|
||||
std::vector<Warning> m_warnings;
|
||||
bool m_used_lq_sq = false;
|
||||
};
|
||||
} // namespace decompiler
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+5247
-4167
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)"]
|
||||
]
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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]
|
||||
]
|
||||
}
|
||||
@@ -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!": []
|
||||
}
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"],
|
||||
|
||||
////////////////////////////
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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 {}",
|
||||
|
||||
@@ -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};
|
||||
@@ -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))
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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*"
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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": {}
|
||||
|
||||
@@ -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:");
|
||||
|
||||
Reference in New Issue
Block a user