decompiler: detect light-trail-tracker-spawn macro (#4317)

This commit is contained in:
Hat Kid
2026-06-17 18:57:59 +02:00
committed by GitHub
parent 0b799821ed
commit 0f9beb4610
33 changed files with 729 additions and 706 deletions
+308
View File
@@ -1810,6 +1810,308 @@ FormElement* rewrite_part_tracker_new_jak2(const std::string& type,
->try_as_single_element();
}
FormElement* rewrite_light_trail_tracker_spawn(LetElement* in, const Env& env, FormPool& pool) {
// (let ((s5-0 (new 'stack-no-clear 'light-trail-tracker-spawn-params)))
// (set! (-> s5-0 tracked-obj) (process->handle this))
// (set! (-> s5-0 appearance) *blue-shot-trail*)
// (set! (-> s5-0 max-num-crumbs) (the int (* 0.2 (the float (-> s5-0 appearance max-age)))))
// (set! (-> s5-0 track-immediately?) #t)
// (let* ((v1-30 (estimate-light-trail-mem-usage
// (the-as uint (-> s5-0 max-num-crumbs))
// (the-as uint (= (-> s5-0 appearance lie-mode) (lie-mode use-two-strips)))))
// (s4-0 (get-process *default-dead-pool* light-trail-tracker-blue-3 (+ v1-30 8192) 1)))
// (when s4-0
// (let ((t9-4 (method-of-type process activate)))
// (t9-4 s4-0 this "light-trail" (the-as pointer #x70004000)))
// (run-now-in-process s4-0 light-trail-tracker-init-by-other s5-0)
// (-> s4-0 ppointer))))
// ->
// (light-trail-tracker-spawn light-trail-tracker-blue-3 :to this
// :params ((tracked-obj (process->handle this))
// (appearance *blue-shot-trail*)
// (max-num-crumbs (the int (* 0.2 (the float (-> *blue-shot-trail* max-age)))))
// (track-immediately? #t)))
if (env.version < GameVersion::Jak3) {
return nullptr;
}
if (in->entries().size() != 1) {
return nullptr;
}
auto params_var = in->entries().at(0).dest;
const auto& params_reg = params_var.reg();
auto params_src = in->entries().at(0).src->try_as_element<StackStructureDefElement>();
if (!params_src) {
return nullptr;
}
const auto& params_type = params_src->entry().ref_type;
if (!env.dts->ts.tc(TypeSpec("light-trail-tracker-spawn-params"), params_type)) {
return nullptr;
}
if (params_src->entry().hint.container_type != StackStructureHint::ContainerType::NONE) {
return nullptr;
}
auto body = in->body();
if (body->size() < 2) {
return nullptr;
}
// grab all the field setters
std::vector<std::pair<std::string, Form*>> fields;
std::unordered_map<std::string, Form*> field_values;
for (size_t idx = 0; idx + 1 < body->size(); ++idx) {
auto set_mr = match(Matcher::set(Matcher::deref(Matcher::reg(params_reg), false,
{DerefTokenMatcher::any_string(0)}),
Matcher::any(1)),
body->at(idx));
if (!set_mr.matched) {
return nullptr;
}
const auto& field_name = set_mr.maps.strings.at(0);
auto field_val = set_mr.maps.forms.at(1);
fields.emplace_back(field_name, field_val);
field_values[field_name] = field_val;
}
// (let ((v1-30 (estimate...))) (let ((s4-0 (get-process...))) (when s4-0 ...)))
auto mem_let = dynamic_cast<LetElement*>(body->at(body->size() - 1));
if (!mem_let || mem_let->entries().size() != 1 || mem_let->body()->size() != 1) {
return nullptr;
}
// (v1-30 (estimate-light-trail-mem-usage ...))
auto mem_var = mem_let->entries().at(0).dest;
auto est = mem_let->entries().at(0).src->try_as_element<GenericElement>();
if (!est || est->op().kind() != GenericOperator::Kind::FUNCTION_EXPR || !est->op().func() ||
!est->op().func()->to_form(env).is_symbol("estimate-light-trail-mem-usage")) {
return nullptr;
}
auto proc_let = dynamic_cast<LetElement*>(mem_let->body()->at(0));
if (!proc_let || proc_let->entries().size() != 1 || proc_let->body()->size() != 1) {
return nullptr;
}
// (s4-0 (get-process *default-dead-pool* light-trail-tracker-blue-3 (+ v1-30 8192) 1))
auto proc_var = proc_let->entries().at(0).dest;
const auto& proc_reg = proc_var.reg();
auto gp_mr = match(Matcher::func("get-process", {Matcher::any(0), Matcher::any_symbol(1),
Matcher::any(2), Matcher::any_integer(3)}),
proc_let->entries().at(0).src);
if (!gp_mr.matched) {
return nullptr;
}
const auto& proc_type = gp_mr.maps.strings.at(1);
if (!env.dts->ts.tc(TypeSpec("light-trail-tracker"), TypeSpec(proc_type))) {
return nullptr;
}
auto from_form = gp_mr.maps.forms.at(0);
auto unk = gp_mr.maps.ints.at(3);
// stack size is always (+ <estimated-mem> 8192)
if (!match(Matcher::op(GenericOpMatcher::or_match(
{GenericOpMatcher::fixed(FixedOperatorKind::ADDITION),
GenericOpMatcher::fixed(FixedOperatorKind::ADDITION_PTR)}),
{Matcher::reg(mem_var.reg()), Matcher::integer(8192)}),
gp_mr.maps.forms.at(2))
.matched) {
return nullptr;
}
// check the when
auto cast_type = TypeSpec("pointer", {TypeSpec(proc_type)});
auto when_matcher = Matcher::if_no_else(
Matcher::op(GenericOpMatcher::condition(IR2_Condition::Kind::TRUTHY),
{Matcher::reg(proc_reg)}),
Matcher::begin({Matcher::any(0),
Matcher::func_with_rest(Matcher::constant_token("run-now-in-process"),
{Matcher::reg(proc_reg), Matcher::any(1)}),
Matcher::any(2)}));
auto mr_with_shell = rewrite_shelled_return_form(
when_matcher, proc_let->body()->at(0), env, pool,
[&](FormElement* s_in, const MatchResult& /*mr*/, const Env& env_, FormPool& pool_) -> Form* {
auto as_when = dynamic_cast<CondNoElseElement*>(s_in);
if (!as_when) {
return nullptr;
}
const auto& when_body = as_when->entries.front().body->elts();
if (when_body.size() != 3) {
return nullptr;
}
// (let ((t9-4 (method-of-type process activate)))
// (t9-4 s4-0 this "light-trail" (the-as pointer #x70004000)))
auto activate_let = dynamic_cast<LetElement*>(when_body.at(0));
if (!activate_let || activate_let->entries().size() != 1 ||
activate_let->body()->size() != 1) {
return nullptr;
}
if (!match(Matcher::op_fixed(
FixedOperatorKind::METHOD_OF_TYPE,
{Matcher::symbol("process"), Matcher::constant_token("activate")}),
activate_let->entries().at(0).src)
.matched) {
return nullptr;
}
auto act_fn_reg = activate_let->entries().at(0).dest;
auto act_call_mr = match(
Matcher::func(Matcher::reg(act_fn_reg.reg()), {Matcher::reg(proc_reg), Matcher::any(0),
Matcher::any(1), Matcher::any(2)}),
activate_let->body()->at(0));
if (!act_call_mr.matched) {
return nullptr;
}
auto to_form = act_call_mr.maps.forms.at(0);
auto name_form = act_call_mr.maps.forms.at(1);
auto stack_form = act_call_mr.maps.forms.at(2);
// (run-now-in-process s4-0 light-trail-tracker-init-by-other s5-0)
auto run_gen = dynamic_cast<GenericElement*>(when_body.at(1));
if (!run_gen || !run_gen->op().func() ||
!run_gen->op().func()->to_form(env_).is_symbol("run-now-in-process")) {
return nullptr;
}
if (run_gen->elts().size() != 3) {
return nullptr;
}
auto init_form = run_gen->elts().at(1);
if (!match(Matcher::reg(params_reg), run_gen->elts().at(2)).matched) {
return nullptr;
}
// (-> s4-0 ppointer)
auto pptr_matcher =
Matcher::deref(Matcher::reg(proc_reg), false, {DerefTokenMatcher::string("ppointer")});
auto pptr_let = dynamic_cast<LetElement*>(when_body.at(2));
if (pptr_let) {
auto new_pptr = rewrite_empty_let(pptr_let, env, pool);
if (!new_pptr || !match(pptr_matcher, new_pptr).matched) {
return nullptr;
}
} else if (!match(pptr_matcher, when_body.at(2)).matched) {
return nullptr;
}
auto is_simple_base = [](Form* f) {
auto single = f->try_as_single_element();
if (!single) {
return false;
}
return dynamic_cast<DerefElement*>(single) || dynamic_cast<SimpleAtomElement*>(single) ||
dynamic_cast<SimpleExpressionElement*>(single) ||
dynamic_cast<ConstantTokenElement*>(single);
};
// dry run to check we can substitute all the derefs
for (auto& field : fields) {
Form* fval = field.second;
RegAccessSet uses;
fval->collect_vars(uses, true);
int var_uses = 0;
for (const auto& u : uses) {
if (u.reg() == params_reg) {
var_uses++;
}
}
if (var_uses == 0) {
continue;
}
int subst_derefs = 0;
bool bad = false;
fval->apply_form([&](Form* f) {
for (auto* e : f->elts()) {
auto deref = dynamic_cast<DerefElement*>(e);
if (!deref || !match(Matcher::reg(params_reg), deref->base()).matched) {
continue;
}
subst_derefs++;
if (deref->tokens().empty() ||
deref->tokens().at(0).kind() != DerefToken::Kind::FIELD_NAME) {
bad = true;
continue;
}
auto it = field_values.find(deref->tokens().at(0).field_name());
if (it == field_values.end() || !is_simple_base(it->second)) {
bad = true;
}
}
});
if (bad || subst_derefs != var_uses) {
return nullptr;
}
}
// do the inlining
for (auto& field : fields) {
Form* fval = field.second;
fval->apply_form([&](Form* f) {
for (auto*& e : f->elts()) {
auto deref = dynamic_cast<DerefElement*>(e);
if (!deref || !match(Matcher::reg(params_reg), deref->base()).matched) {
continue;
}
auto repl_base = field_values.at(deref->tokens().at(0).field_name());
std::vector rest(deref->tokens().begin() + 1, deref->tokens().end());
if (rest.empty()) {
e = repl_base->try_as_single_element();
} else {
e = pool_.alloc_element<DerefElement>(repl_base, deref->is_addr_of(), rest);
}
}
});
}
std::vector<Form*> args;
args.push_back(pool_.form<ConstantTokenElement>(proc_type));
if (!init_form->to_form(env_).is_symbol("light-trail-tracker-init-by-other")) {
ja_push_form_to_args(pool_, args, init_form, "init");
}
if (params_type != TypeSpec("light-trail-tracker-spawn-params")) {
args.push_back(pool_.form<ConstantTokenElement>(":params-type"));
args.push_back(pool_.form<ConstantTokenElement>(params_type.base_type()));
}
if (!from_form->to_form(env_).is_symbol("*default-dead-pool*")) {
ja_push_form_to_args(pool_, args, from_form, "from");
}
ja_push_form_to_args(pool_, args, to_form, "to");
auto name_str = dynamic_cast<StringConstantElement*>(name_form->try_as_single_element());
if (!name_str || name_str->value() != "light-trail") {
ja_push_form_to_args(pool_, args, name_form, "name");
}
if (stack_form->to_string(env_) != "(the-as pointer #x70004000)") {
ja_push_form_to_args(pool_, args, stack_form, "stack");
}
if (unk != 1) {
args.push_back(pool_.form<ConstantTokenElement>(":unk"));
args.push_back(pool_.form<ConstantTokenElement>(fmt::format("{}", unk)));
}
std::vector<Form*> param_pairs;
for (auto& [fname, fval] : fields) {
param_pairs.push_back(pool_.form<GenericElement>(
GenericOperator::make_function(pool_.form<ConstantTokenElement>(fname)), fval));
}
args.push_back(pool_.form<ConstantTokenElement>(":param-list"));
auto params_head = GenericOperator::make_function(param_pairs.at(0));
std::vector params_rest(param_pairs.begin() + 1, param_pairs.end());
args.push_back(pool_.form<GenericElement>(params_head, params_rest));
return pool_.form<GenericElement>(
GenericOperator::make_function(
pool_.form<ConstantTokenElement>("light-trail-tracker-spawn")),
args);
},
&cast_type);
if (!std::get<0>(mr_with_shell).matched) {
return nullptr;
}
return std::get<1>(mr_with_shell)->try_as_single_element();
}
FormElement* rewrite_call_parent_state_handler(LetElement* in, const Env& env, FormPool& pool) {
// (let ((t9-3 (-> (find-parent-state) code)))
// (if t9-3
@@ -2501,6 +2803,12 @@ FormElement* rewrite_let(LetElement* in, const Env& env, FormPool& pool, LetRewr
return as_abs;
}
auto as_light_trail_tracker_spawn = rewrite_light_trail_tracker_spawn(in, env, pool);
if (as_light_trail_tracker_spawn) {
stats.light_trail_tracker_spawn++;
return as_light_trail_tracker_spawn;
}
auto as_proc_new = rewrite_proc_new(in, env, pool);
if (as_proc_new) {
stats.proc_new++;