Skip to content

Commit 48cb9bb

Browse files
water111water111
andauthored
[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. ![image](https://github.com/user-attachments/assets/5c31bf85-97b4-437c-bc4b-dc054e60551e) --------- Co-authored-by: water111 <[email protected]>
1 parent d5590ab commit 48cb9bb

File tree

645 files changed

+5391
-16694
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

645 files changed

+5391
-16694
lines changed

decompiler/IR2/FormExpressionAnalysis.cpp

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4638,6 +4638,67 @@ Form* try_rewrite_as_process_to_ppointer(CondNoElseElement* value,
46384638
GenericOperator::make_fixed(FixedOperatorKind::PROCESS_TO_PPOINTER), repopped);
46394639
}
46404640

4641+
/*!
4642+
* (if (type? foo bar)
4643+
* foo
4644+
* )
4645+
*/
4646+
Form* try_rewrite_as_as_type(CondNoElseElement* value,
4647+
FormStack& stack,
4648+
FormPool& pool,
4649+
const Env& env,
4650+
const TypeSpec& resulting_type) {
4651+
if (value->entries.size() != 1) {
4652+
return nullptr;
4653+
}
4654+
4655+
auto condition = value->entries.at(0).condition;
4656+
auto body = value->entries[0].body;
4657+
4658+
auto condition_matcher = Matcher::op(
4659+
GenericOpMatcher::condition(IR2_Condition::Kind::TRUTHY),
4660+
{Matcher::func(Matcher::symbol("type?"), {Matcher::any_reg(0), Matcher::any_symbol(1)})});
4661+
4662+
auto condition_mr = match(condition_matcher, condition);
4663+
if (!condition_mr.matched) {
4664+
return nullptr;
4665+
}
4666+
4667+
auto body_matcher = Matcher::any_reg(0);
4668+
auto body_mr = match(body_matcher, body);
4669+
if (!body_mr.matched) {
4670+
body_mr = match(Matcher::cast_to_any(1, body_matcher), body);
4671+
if (!body_mr.matched) {
4672+
return nullptr;
4673+
}
4674+
}
4675+
auto body_var = body_mr.maps.regs.at(0).value();
4676+
auto condition_var = condition_mr.maps.regs.at(0).value();
4677+
auto* menv = const_cast<Env*>(&env);
4678+
menv->disable_use(body_var);
4679+
auto repopped = stack.pop_reg(condition_var, {}, env, true);
4680+
if (!repopped) {
4681+
repopped = var_to_form(condition_var, pool);
4682+
}
4683+
auto new_type = condition_mr.maps.strings.at(1);
4684+
4685+
auto result = pool.form<GenericElement>(
4686+
GenericOperator::make_function(pool.form<ConstantTokenElement>("as-type")),
4687+
std::vector<Form*>{repopped, pool.form<ConstantTokenElement>(new_type)});
4688+
4689+
// silly cast situation:
4690+
// sometimes there is something dumb like (the specific (as-type foo general))
4691+
// we have to make sure that we keep the leading cast.
4692+
// HACK: inserting casts more aggressively in Jak 2 because I am too lazy to fix up all the
4693+
// slightly wrong casts that matter now.
4694+
if (resulting_type != TypeSpec(new_type) &&
4695+
(env.version == GameVersion::Jak2 || env.dts->ts.tc(TypeSpec(new_type), resulting_type))) {
4696+
return pool.form<CastElement>(resulting_type, result);
4697+
} else {
4698+
return result;
4699+
}
4700+
}
4701+
46414702
// (if x (-> x 0 self)) -> (ppointer->process x)
46424703
Form* try_rewrite_as_pppointer_to_process(CondNoElseElement* value,
46434704
FormStack& stack,
@@ -4810,11 +4871,18 @@ void CondNoElseElement::push_to_stack(const Env& env, FormPool& pool, FormStack&
48104871
stack.push_value_to_reg(write_as_value, as_ja_group, true,
48114872
env.get_variable_type(final_destination, false));
48124873
} else {
4813-
// lg::print("func {} final destination {} type {}\n", env.func->name(),
4814-
// final_destination.to_string(env),
4815-
// env.get_variable_type(final_destination, false).print());
4816-
stack.push_value_to_reg(write_as_value, pool.alloc_single_form(nullptr, this), true,
4817-
env.get_variable_type(final_destination, false));
4874+
auto as_as_type = try_rewrite_as_as_type(this, stack, pool, env,
4875+
env.get_variable_type(final_destination, true));
4876+
if (as_as_type) {
4877+
stack.push_value_to_reg(write_as_value, as_as_type, true,
4878+
env.get_variable_type(final_destination, false));
4879+
} else {
4880+
// lg::print("func {} final destination {} type {}\n", env.func->name(),
4881+
// final_destination.to_string(env),
4882+
// env.get_variable_type(final_destination, false).print());
4883+
stack.push_value_to_reg(write_as_value, pool.alloc_single_form(nullptr, this), true,
4884+
env.get_variable_type(final_destination, false));
4885+
}
48184886
}
48194887
}
48204888
}

decompiler/IR2/GenericElementMatcher.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,14 @@ Matcher Matcher::cast(const std::string& type, Matcher value) {
9191
return m;
9292
}
9393

94+
Matcher Matcher::numeric_cast(const std::string& type, Matcher value) {
95+
Matcher m;
96+
m.m_kind = Kind::NUMERIC_CAST;
97+
m.m_str = type;
98+
m.m_sub_matchers = {value};
99+
return m;
100+
}
101+
94102
Matcher Matcher::cast_to_any(int type_out, Matcher value) {
95103
Matcher m;
96104
m.m_kind = Kind::CAST_TO_ANY;
@@ -412,6 +420,16 @@ bool Matcher::do_match(Form* input, MatchResult::Maps* maps_out, const Env* cons
412420
return false;
413421
} break;
414422

423+
case Kind::NUMERIC_CAST: {
424+
auto as_cast = dynamic_cast<CastElement*>(input->try_as_single_active_element());
425+
if (as_cast && as_cast->numeric()) {
426+
if (as_cast->type().print() == m_str) {
427+
return m_sub_matchers.at(0).do_match(as_cast->source(), maps_out, env);
428+
}
429+
}
430+
return false;
431+
} break;
432+
415433
case Kind::CAST_TO_ANY: {
416434
auto as_cast = dynamic_cast<CastElement*>(input->try_as_single_active_element());
417435
if (as_cast) {

decompiler/IR2/GenericElementMatcher.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class Matcher {
5252
static Matcher set_var(const Matcher& src, int dst_match_id); // var-form
5353
static Matcher match_or(const std::vector<Matcher>& args);
5454
static Matcher cast(const std::string& type, Matcher value);
55+
static Matcher numeric_cast(const std::string& type, Matcher value);
5556
static Matcher cast_to_any(int type_out, Matcher value);
5657
static Matcher any(int match_id = -1);
5758
static Matcher integer(std::optional<int> value);
@@ -88,6 +89,7 @@ class Matcher {
8889
GENERIC_OP_WITH_REST,
8990
OR,
9091
CAST,
92+
NUMERIC_CAST,
9193
CAST_TO_ANY,
9294
ANY,
9395
INT,

decompiler/ObjectFile/ObjectFileDB.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,13 @@ struct LetRewriteStats {
8181
int launch_particles = 0;
8282
int call_parent_state_handler = 0;
8383
int suspend_for = 0;
84+
int font_method = 0;
8485

8586
int total() const {
8687
return dotimes + countdown + abs + abs2 + unused + ja + case_no_else + case_with_else +
8788
set_vector + set_vector2 + send_event + font_context_meth + proc_new + attack_info +
8889
vector_dot + rand_float_gen + set_let + with_dma_buf_add_bucket + dma_buffer_add_gs_set +
89-
launch_particles + call_parent_state_handler + suspend_for;
90+
launch_particles + call_parent_state_handler + suspend_for + font_method;
9091
}
9192

9293
std::string print() const {
@@ -115,6 +116,7 @@ struct LetRewriteStats {
115116
out += fmt::format(" launch_particles: {}\n", launch_particles);
116117
out += fmt::format(" call_parent_state_handler: {}\n", call_parent_state_handler);
117118
out += fmt::format(" suspend_for: {}\n", suspend_for);
119+
out += fmt::format(" font_method: {}\n", font_method);
118120
return out;
119121
}
120122

decompiler/analysis/insert_lets.cpp

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2212,6 +2212,50 @@ FormElement* rewrite_attack_info(LetElement* in, const Env& env, FormPool& pool)
22122212
return elt;
22132213
}
22142214

2215+
FormElement* rewrite_set_font_single(LetElement* in,
2216+
const Env& env,
2217+
FormPool& pool,
2218+
const char* deref_name,
2219+
const char* op_name,
2220+
bool cast_to_float) {
2221+
/*
2222+
(let ((v1-10 gp-0))
2223+
(set! (-> v1-10 scale) 0.6)
2224+
)
2225+
*/
2226+
2227+
if (in->entries().size() != 1) {
2228+
return nullptr;
2229+
}
2230+
if (in->body()->elts().size() != 1) {
2231+
return nullptr;
2232+
}
2233+
2234+
Form* font_obj_expr = in->entries().at(0).src;
2235+
RegisterAccess font_obj_reg = in->entries().at(0).dest;
2236+
2237+
if (env.get_variable_type(font_obj_reg, true) != TypeSpec("font-context")) {
2238+
return nullptr;
2239+
}
2240+
2241+
auto src_matcher =
2242+
cast_to_float ? Matcher::numeric_cast("float", Matcher::any(0)) : Matcher::any(0);
2243+
Matcher set_matcher = Matcher::set(Matcher::deref(Matcher::reg(font_obj_reg.reg()), false,
2244+
{DerefTokenMatcher::string(deref_name)}),
2245+
src_matcher);
2246+
auto set_mr = match(set_matcher, in->body()->at(0));
2247+
2248+
if (!set_mr.matched) {
2249+
return nullptr;
2250+
}
2251+
2252+
auto elt = pool.alloc_element<GenericElement>(
2253+
GenericOperator::make_function(pool.form<ConstantTokenElement>(op_name)),
2254+
std::vector<Form*>{font_obj_expr, set_mr.maps.forms.at(0)});
2255+
elt->parent_form = in->parent_form;
2256+
return elt;
2257+
}
2258+
22152259
/*!
22162260
* Attempt to rewrite a let as another form. If it cannot be rewritten, this will return nullptr.
22172261
*/
@@ -2320,6 +2364,24 @@ FormElement* rewrite_let(LetElement* in, const Env& env, FormPool& pool, LetRewr
23202364
return as_call_parent_state;
23212365
}
23222366

2367+
auto as_font_scale = rewrite_set_font_single(in, env, pool, "scale", "set-scale!", false);
2368+
if (as_font_scale) {
2369+
stats.font_method++;
2370+
return as_font_scale;
2371+
}
2372+
2373+
auto as_font_width = rewrite_set_font_single(in, env, pool, "width", "set-width!", true);
2374+
if (as_font_width) {
2375+
stats.font_method++;
2376+
return as_font_width;
2377+
}
2378+
2379+
auto as_font_height = rewrite_set_font_single(in, env, pool, "height", "set-height!", true);
2380+
if (as_font_height) {
2381+
stats.font_method++;
2382+
return as_font_height;
2383+
}
2384+
23232385
// nothing matched.
23242386
return nullptr;
23252387
}
@@ -2687,6 +2749,59 @@ FormElement* rewrite_with_dma_buf_add_bucket(LetElement* in, const Env& env, For
26872749
return elt;
26882750
}
26892751

2752+
FormElement* rewrite_set_font_origin(LetElement* in, const Env& env, FormPool& pool) {
2753+
/*
2754+
(let ((v1-9 gp-0)
2755+
(a1-1 36)
2756+
(a0-4 140)
2757+
)
2758+
(set! (-> v1-9 origin x) (the float a1-1))
2759+
(set! (-> v1-9 origin y) (the float a0-4))
2760+
)
2761+
*/
2762+
if (in->entries().size() != 3) {
2763+
return nullptr;
2764+
}
2765+
if (in->body()->elts().size() != 2) {
2766+
return nullptr;
2767+
}
2768+
2769+
Form* font_obj_expr = in->entries().at(0).src;
2770+
RegisterAccess font_obj_reg = in->entries().at(0).dest;
2771+
2772+
if (env.get_variable_type(font_obj_reg, true) != TypeSpec("font-context")) {
2773+
return nullptr;
2774+
}
2775+
2776+
Form* x_val = in->entries().at(1).src;
2777+
Form* y_val = in->entries().at(2).src;
2778+
2779+
Matcher x_matcher = Matcher::set(
2780+
Matcher::deref(Matcher::reg(font_obj_reg.reg()), false,
2781+
{DerefTokenMatcher::string("origin"), DerefTokenMatcher::string("x")}),
2782+
Matcher::numeric_cast("float", Matcher::reg(in->entries().at(1).dest.reg())));
2783+
auto x_mr = match(x_matcher, in->body()->at(0));
2784+
2785+
Matcher y_matcher = Matcher::set(
2786+
Matcher::deref(Matcher::reg(font_obj_reg.reg()), false,
2787+
{DerefTokenMatcher::string("origin"), DerefTokenMatcher::string("y")}),
2788+
Matcher::numeric_cast("float", Matcher::reg(in->entries().at(2).dest.reg())));
2789+
auto y_mr = match(y_matcher, in->body()->at(1));
2790+
2791+
if (!x_mr.matched) {
2792+
return nullptr;
2793+
}
2794+
if (!y_mr.matched) {
2795+
return nullptr;
2796+
}
2797+
2798+
auto elt = pool.alloc_element<GenericElement>(
2799+
GenericOperator::make_function(pool.form<ConstantTokenElement>("set-origin!")),
2800+
std::vector<Form*>{font_obj_expr, x_val, y_val});
2801+
elt->parent_form = in->parent_form;
2802+
return elt;
2803+
}
2804+
26902805
FormElement* rewrite_launch_particles(LetElement* in, const Env& env, FormPool& pool) {
26912806
/*
26922807
* (let ((t9-0 sp-launch-particles-var)
@@ -2846,6 +2961,12 @@ FormElement* rewrite_multi_let(LetElement* in,
28462961
}
28472962
}
28482963

2964+
auto as_font_set_origin = rewrite_set_font_origin(in, env, pool);
2965+
if (as_font_set_origin) {
2966+
stats.font_method++;
2967+
return as_font_set_origin;
2968+
}
2969+
28492970
return in;
28502971
}
28512972

decompiler/config/jak3/ntsc_v1/type_casts.jsonc

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3194,6 +3194,16 @@
31943194
[93, "a1", "process-focusable"],
31953195
[140, "a1", "process-focusable"]
31963196
],
3197+
"(method 106 bot)": [
3198+
[[16, 31], "v1", "process-focusable"],
3199+
[[40, 58], "v1", "process-focusable"],
3200+
[[92, 95], "v1", "process-focusable"]
3201+
],
3202+
"(method 106 ashelin)": [
3203+
[[16, 21], "v1", "process-focusable"],
3204+
[[40, 57], "v1", "process-focusable"],
3205+
[[92, 95], "v1", "process-focusable"]
3206+
],
31973207
"(method 140 enemy)": [[18, "a1", "process-focusable"]],
31983208
"(method 142 enemy)": [[[0, 40], "v0", "knocked-type"]],
31993209
"get-penetrate-using-from-attack-event": [
@@ -6607,8 +6617,7 @@
66076617
[40, "a0", "process-focusable"]
66086618
],
66096619
"(method 26 task-manager-temple-climb)": [
6610-
[126, "s5", "process-focusable"],
6611-
[130, "s5", "process-focusable"]
6620+
[[0, 148], "s5", "process-focusable"]
66126621
],
66136622
"(method 26 task-manager-desert-beast-battle)": [
66146623
[39, "a0", "process-focusable"]
@@ -9790,12 +9799,7 @@
97909799
[319, "a1", "ff-squad-control"]
97919800
],
97929801
"(method 252 crimson-guard)": [
9793-
[74, "s5", "process-focusable"],
9794-
[69, "s5", "process-focusable"],
9795-
[126, "s5", "process-focusable"],
9796-
[146, "s5", "process-focusable"],
9797-
[202, "s5", "process-focusable"],
9798-
[205, "s5", "process-focusable"]
9802+
[[0, 223], "s5", "process-focusable"]
97999803
],
98009804
"(method 51 ff-squad-control)": [
98019805
[13, "v1", "connection"],

decompiler/config/jak3/ntsc_v1/var_names.jsonc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@
191191
"(method 10 process-tree)": {
192192
"args": ["this", "ent"]
193193
},
194+
"(method 106 bot)": {
195+
"vars": {"v1-3": ["v1-3", "process-focusable"]}
196+
},
194197
"(method 0 clock)": {
195198
"args": ["allocation", "type-to-make", "index"]
196199
},

goal_src/jak2/kernel/gcommon.gc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,19 @@
323323
,@args)
324324
)
325325

326+
(defmacro as-type (this type)
327+
"If this is the specified type, return this cast to that type. Otherwise, return #f."
328+
(with-gensyms (obj)
329+
`(the ,type (let ((,obj ,this))
330+
(if (type? ,obj ,type)
331+
,obj
332+
)
333+
)
334+
)
335+
)
336+
)
337+
338+
326339
(defun ref ((arg0 object) (arg1 int))
327340
"Get the n-th item in a linked list. No range checking."
328341
(dotimes (v1-0 arg1)

0 commit comments

Comments
 (0)