Source changes (dormant parity infra, retained from iterate 2.AI/2.AO): - xenia-kernel/exports.rs: nt_create_event manual_reset polarity + related event wiring - xenia-gpu/mmio_region.rs: D1MODE_VBLANK_VLINE_STATUS hardcode parity Also lands the audit-runs/ analysis notes (.md/.txt/.json digests) for the iterate 2.x VSync/0x10e8/0x1004 wedge investigation. Raw trace dumps (.jsonl/.gz/.csv/.stdout) and agent worktrees (.claude/) are gitignored as regenerable local artifacts — see memory + HANDOFF for the running findings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
147 lines
5.5 KiB
Markdown
147 lines
5.5 KiB
Markdown
# Phase C+9 — XamContentCreateEnumerator investigation
|
|
|
|
## Phase A oracle (predecessor C+8 baseline)
|
|
|
|
`canary tid=6 → ours tid=1` main chain, `tid_event_idx = 102197`:
|
|
|
|
```
|
|
canary: kernel.return XamContentCreateEnumerator return_value=1317 status=0x00000525 side_effects=[]
|
|
ours: kernel.return XamContentCreateEnumerator return_value=0 status=0x00000000 side_effects=[]
|
|
```
|
|
|
|
`args` and `args_resolved` are empty per the current Phase A emitter
|
|
scope (kernel.call args are not yet wired for dword-only signatures).
|
|
|
|
## Framing verification (reading-error #28 — canary source supersedes
|
|
NT-doc semantics or value-name guesses)
|
|
|
|
**The plan's framing was wrong on the constant identity**:
|
|
`0x525 = 1317` is `X_ERROR_NO_SUCH_USER`, NOT `X_ERROR_NO_CONTENT`
|
|
(`xbox.h:113`):
|
|
|
|
```cpp
|
|
#define X_ERROR_NO_SUCH_USER X_RESULT_FROM_WIN32(0x00000525L)
|
|
```
|
|
|
|
`X_ERROR_NO_CONTENT` is not even defined in canary's xbox.h. Always
|
|
verify constants against canary source, never the plan's inline
|
|
annotations.
|
|
|
|
## Canary body (`xenia-canary/src/xenia/kernel/xam/xam_content.cc:129-220`)
|
|
|
|
```cpp
|
|
dword_result_t XamContentCreateEnumerator_entry(
|
|
dword_t user_index, dword_t device_id, dword_t content_type,
|
|
dword_t content_flags, dword_t items_per_enumerate,
|
|
lpdword_t buffer_size_ptr, lpdword_t handle_out) {
|
|
assert_not_null(handle_out);
|
|
|
|
auto device_info = device_id == 0 ? nullptr : GetDummyDeviceInfo(device_id);
|
|
if ((device_id && device_info == nullptr) || !handle_out) {
|
|
if (buffer_size_ptr) *buffer_size_ptr = 0;
|
|
return X_E_INVALIDARG; // 0x80070057
|
|
}
|
|
|
|
if (buffer_size_ptr) {
|
|
*buffer_size_ptr = sizeof(XCONTENT_DATA) * items_per_enumerate;
|
|
} // sizeof(XCONTENT_DATA) = 0x134
|
|
|
|
uint64_t xuid = 0;
|
|
if (user_index != XUserIndexNone /* 0xFE */) {
|
|
const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);
|
|
if (!user) return X_ERROR_NO_SUCH_USER; // 0x525 ← THIS BRANCH
|
|
xuid = user->xuid();
|
|
}
|
|
|
|
// ... enumerator init, ListContent over HDD/ODD, fill items ...
|
|
*handle_out = e->handle();
|
|
return X_ERROR_SUCCESS; // 0
|
|
}
|
|
```
|
|
|
|
Only ONE control-flow path returns `0x525`:
|
|
|
|
1. `user_index != 0xFE` (i.e. a specific signed-in user is being queried)
|
|
2. `XamState::GetUserProfile(user_index)` returns `nullptr` — i.e. no
|
|
profile is installed at that slot.
|
|
|
|
Under canary's default `--mute=true` config (no `--profile_slot_*`
|
|
flags), `ProfileManager` has zero profiles, so `GetProfile(N)` returns
|
|
`nullptr` for every `N`.
|
|
|
|
## Side-effects check (rule out classification C)
|
|
|
|
Canary's `kernel.return` payload has `side_effects=[]`. Combined with
|
|
the body shape: the `*handle_out` write and the `*buffer_size_ptr`
|
|
write happen **inside the function** but are not handle-creation
|
|
side effects (no Phase A `handle.create` emit because no `XEnumerator`
|
|
was minted). The empty `side_effects` confirms canary reached the
|
|
`X_ERROR_NO_SUCH_USER` early-return at line 153-155, **before** the
|
|
`new ContentEnumerator(...)` allocation at line 160-161. So:
|
|
|
|
* No handle-table mutation to mirror.
|
|
* The `*buffer_size_ptr` write happens BEFORE the user check (line
|
|
145-147), so it does fire on the 0x525 path. Ours must mirror.
|
|
|
|
## Classification
|
|
|
|
**Class A — Unconditional (under ours's state model).**
|
|
|
|
Ours has no profile manager; all `user_index < 4` queries return
|
|
"no such user". This is effectively unconditional from Sylpheed's
|
|
perspective. Mirror canary's early-return.
|
|
|
|
**NOT class B** (state-dependent over real disk content) — we don't
|
|
reach the `ListContent()` enumeration path, so we don't need a
|
|
filesystem scan. No escalation.
|
|
|
|
**NOT class C** (side-effect mismatch) — both engines have empty
|
|
`side_effects` for this idx (above).
|
|
|
|
**NOT class D** (Phase A coverage asymmetry) — both engines emit the
|
|
same 3-event sequence (import.call, kernel.call, kernel.return).
|
|
|
|
## Ours pre-fix state
|
|
|
|
`crates/xenia-kernel/src/xam.rs:59`:
|
|
```rust
|
|
state.register_export(Xam, 0x025C, "XamContentCreateEnumerator", stub_success);
|
|
```
|
|
|
|
`stub_success` sets `r3 = 0`. Hence `return_value=0`, `status=0x00000000`,
|
|
no buffer_size_ptr write, no handle_out write.
|
|
|
|
## Fix shape
|
|
|
|
Replace the `stub_success` registration with a dedicated handler that:
|
|
|
|
1. Reads r3..r9 (user_index, device_id, content_type, content_flags,
|
|
items_per_enumerate, buffer_size_ptr, handle_out).
|
|
2. Returns `X_E_INVALIDARG` (0x80070057) when device_id is unknown
|
|
(>2; canary's GetDummyDeviceInfo accepts HDD=1, ODD=2) or
|
|
handle_out is NULL; writes `*buffer_size_ptr = 0` in that arm.
|
|
3. Otherwise writes `*buffer_size_ptr = 0x134 * items_per_enumerate`
|
|
(the sizeof(XCONTENT_DATA) computation).
|
|
4. If `user_index != 0xFE`: return `X_ERROR_NO_SUCH_USER` (0x525) —
|
|
ours has no profile manager.
|
|
5. Otherwise (user_index == 0xFE, XUserIndexNone): proceed to a
|
|
stubbed enumerator-success path — write `*handle_out = 0` and
|
|
return 0. Defensive; not exercised by current Phase A.
|
|
|
|
LOC: ~85 body + ~125 tests = ~210 additive in xam.rs only.
|
|
|
|
## Internal-consistency caveat (deferred)
|
|
|
|
Ours's `xam_user_get_signin_state` (xam.rs:380-384) returns 1 for
|
|
`user_index == 0`, claiming user 0 is signed in. This contradicts the
|
|
"no profile manager" model used by the C+9 fix. The conflict surfaces
|
|
at Phase A idx 107996 (canary returns 0 for `XamUserGetSigninState`)
|
|
and will be a separate fix in a future session. Single-fix discipline:
|
|
do not widen scope here.
|
|
|
|
## Internal-consistency caveat #2 (cosmetic)
|
|
|
|
The doc comment on `xam_user_get_signin_state` says "user0 signed in
|
|
locally" but canary returns 0 here. That's evidence of an older fix
|
|
that needs reconciliation. Out of scope for C+9.
|