The screen object holds a SECOND list of its elements, a reordering built at load time, and that list is the paint order. It is 24 pointers at +0x30, each the +0x00 field of one of the 48-byte element records, so both arrays hold the same objects in different orders. Checked against the draw capture rather than asserted: the seven elements the capture can name sit at child slots 0, 6, 7, 13, 16, 17, 22 — strictly ascending, in exactly the captured submission order. It also resolves the one sub-order no static field could explain, the pair that decodes to the same 1133x280: slot 6 is element 20 and slot 7 is element 19, so they paint 20-then-19, DESCENDING in declaration terms. And the kind=0x4 repeat instances sit immediately after their template, where the declaration table interleaves them. Stated as unsolved, because the port cannot read a runtime array: deriving this order from the bundle. The order is clearly structured rather than arbitrary — elements sharing a sprite are adjacent and the full-screen effects lead — so it is worth attacking, but it is not attacked here.
149 lines
6.8 KiB
Markdown
149 lines
6.8 KiB
Markdown
# The UI screen object at runtime — found in live guest memory
|
||
|
||
**Status:** ✅ `CONFIRMED` for the object's identity and its element array (five
|
||
independent field matches against the file, on a running game). 🟡 the remaining
|
||
arrays are unidentified. ❔ nothing here settles the paint order — see the
|
||
"what this does not answer" section, which is the reason the search happened.
|
||
|
||
## How it was found, in one step
|
||
|
||
The item class from the emulator-era splash work has vtable **`0x820b30b4`**, and
|
||
`sub_823C2990` allocates its objects at **244 bytes** each
|
||
([`ui-quad-class-foothold.md`](../ui-quad-class-foothold.md)). A vtable pointer
|
||
is a fixed 4-byte value at offset 0 of every instance, so the objects are
|
||
findable in the live guest image without a debugger:
|
||
|
||
```bash
|
||
tools/re-capture/gmem.py find hex:820b30b4 # while the title screen is up
|
||
```
|
||
|
||
Seven live objects, at `0xBCD24D88`, `0xBCD25188`, `0xBCD25388`, `0xBCD25488`,
|
||
`0xBCD25588`, `0xBCD25688`, `0xBCD26488` — spaced 0x100 apart, consistent with a
|
||
244-byte object plus allocator overhead.
|
||
|
||
## What they are
|
||
|
||
Each object begins with **two** vtable pointers (`0x820b30b4`, `0x820b31a4` —
|
||
multiple inheritance) and then a run of `{pointer, count, capacity}` triplets.
|
||
The counts identify the objects immediately:
|
||
|
||
| object | first triplet's count | which `GP_TITLE` build |
|
||
|---|---|---|
|
||
| `0xBCD25188` | **7** | builds 0/1 — the loading overlay (7 elements) |
|
||
| `0xBCD25388` | **24** | **build 4 — the title (24 elements)** |
|
||
| `0xBCD24D88` | **1** | builds 2/3 — the `PRESS Ⓐ BUTTON` bundle (1 element) |
|
||
|
||
So the three bundles this project decoded statically are all resident, with the
|
||
element counts the files declare — including the two-bundle composition of the
|
||
title screen that the draw capture inferred.
|
||
|
||
## The element array
|
||
|
||
`+0x08` of the screen object is `{ptr, count, capacity}` pointing at an array of
|
||
**48-byte entries, one per element, in declaration order**. Verified field-by-file
|
||
at five positions on the title build, with no misses:
|
||
|
||
| index | file says (build 4) | live memory at `+0x10`/`+0x14` |
|
||
|---|---|---|
|
||
| 0 | `ptlogo1.t32` pivot (451,50) | 451.0, 50.0 |
|
||
| 1 | `ptlogo2.t32` pivot (449,46) | 449.0, 46.0 |
|
||
| 6 | `pteff01.t32` pivot (320,160) | 320.0, 160.0 |
|
||
| 9 | `ptbase2.t32` pivot (320,180) | 320.0, 180.0 |
|
||
| 21 | `ptcopyright.t32` pivot (309,10) | 309.0, 10.0 |
|
||
|
||
Entry layout as far as it is read:
|
||
|
||
```
|
||
+0x00 u32 pointer (per-element data)
|
||
+0x04 u32 0
|
||
+0x08 u32 flags (0x10081021 on element 0)
|
||
+0x0C u32 ?
|
||
+0x10 f32 pivot X ← the declaration entry's pivot, as a FLOAT
|
||
+0x14 f32 pivot Y
|
||
+0x18 u32 0
|
||
+0x1C u32 ?
|
||
+0x20 u32 pointer }
|
||
+0x24 u32 count } the element's KEYFRAMES — 8/8 on elements 0 and 1,
|
||
+0x28 u32 capacity } which is exactly their keyframe count in the file
|
||
+0x2C u32 ?
|
||
```
|
||
|
||
So the pivot and the keyframe group, which this project reads out of the bundle,
|
||
are present at runtime in the same order and with the same values.
|
||
|
||
## What this does not answer
|
||
|
||
**The paint order.** The array is in *declaration* order, and the capture proves
|
||
the screen is not painted in that order, so the renderer either walks something
|
||
else or sorts. Three more `{ptr,count,capacity}` triplets are present in the
|
||
screen object and unidentified — `+0x14` (24 entries of 12 bytes, each
|
||
`{ptr,1,1}`, pointing into a densely packed region), `+0x24` (2 entries) and
|
||
`+0x30` (24). The `+0x14` one is *not* an index list (its entries are pointers,
|
||
not small integers), so the obvious "draw order table" reading is already out.
|
||
|
||
**What is better than before:** the question is now a *data* question on a live
|
||
structure that can be dumped in seconds, rather than a code-reading exercise. The
|
||
next probe is to dump `+0x24` and `+0x30` on the title build and see whether
|
||
either is 24 entries in a non-declaration order.
|
||
|
||
## ✅ The paint order is the screen object's CHILD array at `+0x30`
|
||
|
||
**Status: `CONFIRMED`.** The question this project has carried since the first
|
||
capture — *what orders the elements, given that the declaration table does not* —
|
||
is answered: the screen object holds a **second list**, a reordering of the
|
||
elements built at load time, and that list is the paint order.
|
||
|
||
`+0x30` is `{ptr, count, capacity}` → an array of **24 pointers** (not 12-byte
|
||
records like `+0x14`). Each pointer is the `+0x00` field of one of the 48-byte
|
||
element records, so the two arrays hold the same objects in different orders. The
|
||
permutation, read live off the title screen:
|
||
|
||
| child slot | element | | child slot | element |
|
||
|---|---|---|---|---|
|
||
| 0 | 9 `ptbase2` | | 12 | 17 `back2eff4` |
|
||
| 1 | 11 `ptloop01` | | 13 | **0 `ptlogo1`** |
|
||
| 2 | 12 `ptloop02` | | 14 | 2 `ptlogo1` (copy) |
|
||
| 3 | 10 `pteff04` | | 15 | 4 `ptlogo1` (copy) |
|
||
| 4 | 13 `pteff02.prm` | | 16 | **7 `ptlogo_tm`** |
|
||
| 5 | 6 `pteff01` | | 17 | **1 `ptlogo2`** |
|
||
| 6 | **20 `ptlogo_back2eff`** | | 18 | 3 `ptlogo2` (copy) |
|
||
| 7 | **19 `ptlogo_back2`** | | 19 | 5 `ptlogo2` (copy) |
|
||
| 8 | 14 `back2eff1` | | 20 | 22 `ptlogoall_eff` |
|
||
| 9 | 15 `back2eff2` | | 21 | 23 `ptlogoall_eff2` |
|
||
| 10 | 18 `back2eff5` | | 22 | **21 `ptcopyright`** |
|
||
| 11 | 16 `back2eff3` | | 23 | 8 `pteff00.prm` |
|
||
|
||
### Checked against the capture, not merely plausible
|
||
|
||
The seven elements the draw capture can name occupy child slots
|
||
|
||
```
|
||
ptbase2 0 back2eff 6 back2 7 ptlogo1 13 tm 16 ptlogo2 17 copyright 22
|
||
```
|
||
|
||
— **strictly ascending, in exactly the captured submission order**. Two details
|
||
make this more than a coincidence of a short list:
|
||
|
||
* it **resolves the ambiguity** that no static field could: slot 6 is element
|
||
**20** (`ptlogo_back2eff`), slot 7 is element 19, so the pair paints
|
||
20-then-19 — *descending* in declaration terms. Nothing in the file predicts
|
||
that, and the runtime list states it;
|
||
* the three `kind = 0x4` repeat instances of each logo sit **immediately after
|
||
their template** (slots 13,14,15 and 17,18,19), which the declaration table
|
||
interleaves (0,1,2,3,4,5). They draw at α=0 and so never appeared in the
|
||
capture, but their placement in the list is consistent with the grouping.
|
||
|
||
### What this means for the port, stated carefully
|
||
|
||
The reimplementation cannot read a runtime array — it has to *derive* this order
|
||
from the bundle. **That derivation is not solved.** What the order shows is
|
||
structure worth attacking: elements sharing a sprite are adjacent
|
||
(`ptlogo1`×3 together, `ptlogo2`×3 together, the `back2eff*` family together),
|
||
and the full-screen/effect elements lead. So the load-time build is doing some
|
||
grouping, not an arbitrary shuffle.
|
||
|
||
Until it is derived, a reimplementation has two honest options: hard-code the
|
||
captured order for the screens that have been captured, or paint in declaration
|
||
order and accept that the title screen composites wrongly. The first is what the
|
||
evidence supports; the second is what the viewer does today.
|