mirror of
https://github.com/open-goal/jak-project
synced 2026-07-10 15:14:15 -04:00
[decompiler] as-type and font method support (#3855)
Add support for `as-type` macro, and detecting inline font methods. This works in all three games but I've only updated jak 3's goal_src for now. Eventually I will go back and work through the others, but I want to get more decompiler features in first.  --------- Co-authored-by: water111 <awaterford1111445@gmail.com>
This commit is contained in:
@@ -4638,6 +4638,67 @@ Form* try_rewrite_as_process_to_ppointer(CondNoElseElement* value,
|
||||
GenericOperator::make_fixed(FixedOperatorKind::PROCESS_TO_PPOINTER), repopped);
|
||||
}
|
||||
|
||||
/*!
|
||||
* (if (type? foo bar)
|
||||
* foo
|
||||
* )
|
||||
*/
|
||||
Form* try_rewrite_as_as_type(CondNoElseElement* value,
|
||||
FormStack& stack,
|
||||
FormPool& pool,
|
||||
const Env& env,
|
||||
const TypeSpec& resulting_type) {
|
||||
if (value->entries.size() != 1) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto condition = value->entries.at(0).condition;
|
||||
auto body = value->entries[0].body;
|
||||
|
||||
auto condition_matcher = Matcher::op(
|
||||
GenericOpMatcher::condition(IR2_Condition::Kind::TRUTHY),
|
||||
{Matcher::func(Matcher::symbol("type?"), {Matcher::any_reg(0), Matcher::any_symbol(1)})});
|
||||
|
||||
auto condition_mr = match(condition_matcher, condition);
|
||||
if (!condition_mr.matched) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto body_matcher = Matcher::any_reg(0);
|
||||
auto body_mr = match(body_matcher, body);
|
||||
if (!body_mr.matched) {
|
||||
body_mr = match(Matcher::cast_to_any(1, body_matcher), body);
|
||||
if (!body_mr.matched) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
auto body_var = body_mr.maps.regs.at(0).value();
|
||||
auto condition_var = condition_mr.maps.regs.at(0).value();
|
||||
auto* menv = const_cast<Env*>(&env);
|
||||
menv->disable_use(body_var);
|
||||
auto repopped = stack.pop_reg(condition_var, {}, env, true);
|
||||
if (!repopped) {
|
||||
repopped = var_to_form(condition_var, pool);
|
||||
}
|
||||
auto new_type = condition_mr.maps.strings.at(1);
|
||||
|
||||
auto result = pool.form<GenericElement>(
|
||||
GenericOperator::make_function(pool.form<ConstantTokenElement>("as-type")),
|
||||
std::vector<Form*>{repopped, pool.form<ConstantTokenElement>(new_type)});
|
||||
|
||||
// silly cast situation:
|
||||
// sometimes there is something dumb like (the specific (as-type foo general))
|
||||
// we have to make sure that we keep the leading cast.
|
||||
// HACK: inserting casts more aggressively in Jak 2 because I am too lazy to fix up all the
|
||||
// slightly wrong casts that matter now.
|
||||
if (resulting_type != TypeSpec(new_type) &&
|
||||
(env.version == GameVersion::Jak2 || env.dts->ts.tc(TypeSpec(new_type), resulting_type))) {
|
||||
return pool.form<CastElement>(resulting_type, result);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// (if x (-> x 0 self)) -> (ppointer->process x)
|
||||
Form* try_rewrite_as_pppointer_to_process(CondNoElseElement* value,
|
||||
FormStack& stack,
|
||||
@@ -4810,11 +4871,18 @@ void CondNoElseElement::push_to_stack(const Env& env, FormPool& pool, FormStack&
|
||||
stack.push_value_to_reg(write_as_value, as_ja_group, true,
|
||||
env.get_variable_type(final_destination, false));
|
||||
} else {
|
||||
// lg::print("func {} final destination {} type {}\n", env.func->name(),
|
||||
// final_destination.to_string(env),
|
||||
// env.get_variable_type(final_destination, false).print());
|
||||
stack.push_value_to_reg(write_as_value, pool.alloc_single_form(nullptr, this), true,
|
||||
env.get_variable_type(final_destination, false));
|
||||
auto as_as_type = try_rewrite_as_as_type(this, stack, pool, env,
|
||||
env.get_variable_type(final_destination, true));
|
||||
if (as_as_type) {
|
||||
stack.push_value_to_reg(write_as_value, as_as_type, true,
|
||||
env.get_variable_type(final_destination, false));
|
||||
} else {
|
||||
// lg::print("func {} final destination {} type {}\n", env.func->name(),
|
||||
// final_destination.to_string(env),
|
||||
// env.get_variable_type(final_destination, false).print());
|
||||
stack.push_value_to_reg(write_as_value, pool.alloc_single_form(nullptr, this), true,
|
||||
env.get_variable_type(final_destination, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,14 @@ Matcher Matcher::cast(const std::string& type, Matcher value) {
|
||||
return m;
|
||||
}
|
||||
|
||||
Matcher Matcher::numeric_cast(const std::string& type, Matcher value) {
|
||||
Matcher m;
|
||||
m.m_kind = Kind::NUMERIC_CAST;
|
||||
m.m_str = type;
|
||||
m.m_sub_matchers = {value};
|
||||
return m;
|
||||
}
|
||||
|
||||
Matcher Matcher::cast_to_any(int type_out, Matcher value) {
|
||||
Matcher m;
|
||||
m.m_kind = Kind::CAST_TO_ANY;
|
||||
@@ -412,6 +420,16 @@ bool Matcher::do_match(Form* input, MatchResult::Maps* maps_out, const Env* cons
|
||||
return false;
|
||||
} break;
|
||||
|
||||
case Kind::NUMERIC_CAST: {
|
||||
auto as_cast = dynamic_cast<CastElement*>(input->try_as_single_active_element());
|
||||
if (as_cast && as_cast->numeric()) {
|
||||
if (as_cast->type().print() == m_str) {
|
||||
return m_sub_matchers.at(0).do_match(as_cast->source(), maps_out, env);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} break;
|
||||
|
||||
case Kind::CAST_TO_ANY: {
|
||||
auto as_cast = dynamic_cast<CastElement*>(input->try_as_single_active_element());
|
||||
if (as_cast) {
|
||||
|
||||
@@ -52,6 +52,7 @@ class Matcher {
|
||||
static Matcher set_var(const Matcher& src, int dst_match_id); // var-form
|
||||
static Matcher match_or(const std::vector<Matcher>& args);
|
||||
static Matcher cast(const std::string& type, Matcher value);
|
||||
static Matcher numeric_cast(const std::string& type, Matcher value);
|
||||
static Matcher cast_to_any(int type_out, Matcher value);
|
||||
static Matcher any(int match_id = -1);
|
||||
static Matcher integer(std::optional<int> value);
|
||||
@@ -88,6 +89,7 @@ class Matcher {
|
||||
GENERIC_OP_WITH_REST,
|
||||
OR,
|
||||
CAST,
|
||||
NUMERIC_CAST,
|
||||
CAST_TO_ANY,
|
||||
ANY,
|
||||
INT,
|
||||
|
||||
@@ -81,12 +81,13 @@ struct LetRewriteStats {
|
||||
int launch_particles = 0;
|
||||
int call_parent_state_handler = 0;
|
||||
int suspend_for = 0;
|
||||
int font_method = 0;
|
||||
|
||||
int total() const {
|
||||
return dotimes + countdown + abs + abs2 + unused + ja + case_no_else + case_with_else +
|
||||
set_vector + set_vector2 + send_event + font_context_meth + proc_new + attack_info +
|
||||
vector_dot + rand_float_gen + set_let + with_dma_buf_add_bucket + dma_buffer_add_gs_set +
|
||||
launch_particles + call_parent_state_handler + suspend_for;
|
||||
launch_particles + call_parent_state_handler + suspend_for + font_method;
|
||||
}
|
||||
|
||||
std::string print() const {
|
||||
@@ -115,6 +116,7 @@ struct LetRewriteStats {
|
||||
out += fmt::format(" launch_particles: {}\n", launch_particles);
|
||||
out += fmt::format(" call_parent_state_handler: {}\n", call_parent_state_handler);
|
||||
out += fmt::format(" suspend_for: {}\n", suspend_for);
|
||||
out += fmt::format(" font_method: {}\n", font_method);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -2212,6 +2212,50 @@ FormElement* rewrite_attack_info(LetElement* in, const Env& env, FormPool& pool)
|
||||
return elt;
|
||||
}
|
||||
|
||||
FormElement* rewrite_set_font_single(LetElement* in,
|
||||
const Env& env,
|
||||
FormPool& pool,
|
||||
const char* deref_name,
|
||||
const char* op_name,
|
||||
bool cast_to_float) {
|
||||
/*
|
||||
(let ((v1-10 gp-0))
|
||||
(set! (-> v1-10 scale) 0.6)
|
||||
)
|
||||
*/
|
||||
|
||||
if (in->entries().size() != 1) {
|
||||
return nullptr;
|
||||
}
|
||||
if (in->body()->elts().size() != 1) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Form* font_obj_expr = in->entries().at(0).src;
|
||||
RegisterAccess font_obj_reg = in->entries().at(0).dest;
|
||||
|
||||
if (env.get_variable_type(font_obj_reg, true) != TypeSpec("font-context")) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto src_matcher =
|
||||
cast_to_float ? Matcher::numeric_cast("float", Matcher::any(0)) : Matcher::any(0);
|
||||
Matcher set_matcher = Matcher::set(Matcher::deref(Matcher::reg(font_obj_reg.reg()), false,
|
||||
{DerefTokenMatcher::string(deref_name)}),
|
||||
src_matcher);
|
||||
auto set_mr = match(set_matcher, in->body()->at(0));
|
||||
|
||||
if (!set_mr.matched) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto elt = pool.alloc_element<GenericElement>(
|
||||
GenericOperator::make_function(pool.form<ConstantTokenElement>(op_name)),
|
||||
std::vector<Form*>{font_obj_expr, set_mr.maps.forms.at(0)});
|
||||
elt->parent_form = in->parent_form;
|
||||
return elt;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Attempt to rewrite a let as another form. If it cannot be rewritten, this will return nullptr.
|
||||
*/
|
||||
@@ -2320,6 +2364,24 @@ FormElement* rewrite_let(LetElement* in, const Env& env, FormPool& pool, LetRewr
|
||||
return as_call_parent_state;
|
||||
}
|
||||
|
||||
auto as_font_scale = rewrite_set_font_single(in, env, pool, "scale", "set-scale!", false);
|
||||
if (as_font_scale) {
|
||||
stats.font_method++;
|
||||
return as_font_scale;
|
||||
}
|
||||
|
||||
auto as_font_width = rewrite_set_font_single(in, env, pool, "width", "set-width!", true);
|
||||
if (as_font_width) {
|
||||
stats.font_method++;
|
||||
return as_font_width;
|
||||
}
|
||||
|
||||
auto as_font_height = rewrite_set_font_single(in, env, pool, "height", "set-height!", true);
|
||||
if (as_font_height) {
|
||||
stats.font_method++;
|
||||
return as_font_height;
|
||||
}
|
||||
|
||||
// nothing matched.
|
||||
return nullptr;
|
||||
}
|
||||
@@ -2687,6 +2749,59 @@ FormElement* rewrite_with_dma_buf_add_bucket(LetElement* in, const Env& env, For
|
||||
return elt;
|
||||
}
|
||||
|
||||
FormElement* rewrite_set_font_origin(LetElement* in, const Env& env, FormPool& pool) {
|
||||
/*
|
||||
(let ((v1-9 gp-0)
|
||||
(a1-1 36)
|
||||
(a0-4 140)
|
||||
)
|
||||
(set! (-> v1-9 origin x) (the float a1-1))
|
||||
(set! (-> v1-9 origin y) (the float a0-4))
|
||||
)
|
||||
*/
|
||||
if (in->entries().size() != 3) {
|
||||
return nullptr;
|
||||
}
|
||||
if (in->body()->elts().size() != 2) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Form* font_obj_expr = in->entries().at(0).src;
|
||||
RegisterAccess font_obj_reg = in->entries().at(0).dest;
|
||||
|
||||
if (env.get_variable_type(font_obj_reg, true) != TypeSpec("font-context")) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Form* x_val = in->entries().at(1).src;
|
||||
Form* y_val = in->entries().at(2).src;
|
||||
|
||||
Matcher x_matcher = Matcher::set(
|
||||
Matcher::deref(Matcher::reg(font_obj_reg.reg()), false,
|
||||
{DerefTokenMatcher::string("origin"), DerefTokenMatcher::string("x")}),
|
||||
Matcher::numeric_cast("float", Matcher::reg(in->entries().at(1).dest.reg())));
|
||||
auto x_mr = match(x_matcher, in->body()->at(0));
|
||||
|
||||
Matcher y_matcher = Matcher::set(
|
||||
Matcher::deref(Matcher::reg(font_obj_reg.reg()), false,
|
||||
{DerefTokenMatcher::string("origin"), DerefTokenMatcher::string("y")}),
|
||||
Matcher::numeric_cast("float", Matcher::reg(in->entries().at(2).dest.reg())));
|
||||
auto y_mr = match(y_matcher, in->body()->at(1));
|
||||
|
||||
if (!x_mr.matched) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!y_mr.matched) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto elt = pool.alloc_element<GenericElement>(
|
||||
GenericOperator::make_function(pool.form<ConstantTokenElement>("set-origin!")),
|
||||
std::vector<Form*>{font_obj_expr, x_val, y_val});
|
||||
elt->parent_form = in->parent_form;
|
||||
return elt;
|
||||
}
|
||||
|
||||
FormElement* rewrite_launch_particles(LetElement* in, const Env& env, FormPool& pool) {
|
||||
/*
|
||||
* (let ((t9-0 sp-launch-particles-var)
|
||||
@@ -2846,6 +2961,12 @@ FormElement* rewrite_multi_let(LetElement* in,
|
||||
}
|
||||
}
|
||||
|
||||
auto as_font_set_origin = rewrite_set_font_origin(in, env, pool);
|
||||
if (as_font_set_origin) {
|
||||
stats.font_method++;
|
||||
return as_font_set_origin;
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
|
||||
@@ -3194,6 +3194,16 @@
|
||||
[93, "a1", "process-focusable"],
|
||||
[140, "a1", "process-focusable"]
|
||||
],
|
||||
"(method 106 bot)": [
|
||||
[[16, 31], "v1", "process-focusable"],
|
||||
[[40, 58], "v1", "process-focusable"],
|
||||
[[92, 95], "v1", "process-focusable"]
|
||||
],
|
||||
"(method 106 ashelin)": [
|
||||
[[16, 21], "v1", "process-focusable"],
|
||||
[[40, 57], "v1", "process-focusable"],
|
||||
[[92, 95], "v1", "process-focusable"]
|
||||
],
|
||||
"(method 140 enemy)": [[18, "a1", "process-focusable"]],
|
||||
"(method 142 enemy)": [[[0, 40], "v0", "knocked-type"]],
|
||||
"get-penetrate-using-from-attack-event": [
|
||||
@@ -6607,8 +6617,7 @@
|
||||
[40, "a0", "process-focusable"]
|
||||
],
|
||||
"(method 26 task-manager-temple-climb)": [
|
||||
[126, "s5", "process-focusable"],
|
||||
[130, "s5", "process-focusable"]
|
||||
[[0, 148], "s5", "process-focusable"]
|
||||
],
|
||||
"(method 26 task-manager-desert-beast-battle)": [
|
||||
[39, "a0", "process-focusable"]
|
||||
@@ -9790,12 +9799,7 @@
|
||||
[319, "a1", "ff-squad-control"]
|
||||
],
|
||||
"(method 252 crimson-guard)": [
|
||||
[74, "s5", "process-focusable"],
|
||||
[69, "s5", "process-focusable"],
|
||||
[126, "s5", "process-focusable"],
|
||||
[146, "s5", "process-focusable"],
|
||||
[202, "s5", "process-focusable"],
|
||||
[205, "s5", "process-focusable"]
|
||||
[[0, 223], "s5", "process-focusable"]
|
||||
],
|
||||
"(method 51 ff-squad-control)": [
|
||||
[13, "v1", "connection"],
|
||||
|
||||
@@ -191,6 +191,9 @@
|
||||
"(method 10 process-tree)": {
|
||||
"args": ["this", "ent"]
|
||||
},
|
||||
"(method 106 bot)": {
|
||||
"vars": {"v1-3": ["v1-3", "process-focusable"]}
|
||||
},
|
||||
"(method 0 clock)": {
|
||||
"args": ["allocation", "type-to-make", "index"]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user