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>
180 lines
7.5 KiB
Markdown
180 lines
7.5 KiB
Markdown
# Phase C+12 — investigation (canary's NtQueryFullAttributesFile)
|
|
|
|
## Canary's verified behavior
|
|
|
|
### Entry point
|
|
`NtQueryFullAttributesFile_entry` lives in
|
|
`xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_io.cc:474-513`. It
|
|
takes `(X_OBJECT_ATTRIBUTES*, X_FILE_NETWORK_OPEN_INFORMATION*)` and:
|
|
|
|
1. Translates the ANSI object name to a guest VFS path
|
|
(`util::TranslateAnsiPath`).
|
|
2. Calls `kernel_state()->file_system()->ResolvePath(target_path)`.
|
|
3. On hit, populates the OUT struct from `entry->{create,access,
|
|
write}_timestamp()` + `entry->allocation_size()` + `entry->size()`
|
|
+ `entry->attributes()`. Returns `X_STATUS_SUCCESS`.
|
|
4. On miss, returns `X_STATUS_NO_SUCH_FILE` (NOT
|
|
`OBJECT_NAME_NOT_FOUND`).
|
|
|
|
`xboxkrnl_io.cc:498-512`:
|
|
|
|
```cpp
|
|
auto entry = kernel_state()->file_system()->ResolvePath(target_path);
|
|
if (entry) {
|
|
file_info->creation_time = entry->create_timestamp();
|
|
file_info->last_access_time= entry->access_timestamp();
|
|
file_info->last_write_time = entry->write_timestamp();
|
|
file_info->change_time = entry->write_timestamp(); // <-- same as write
|
|
file_info->allocation_size = entry->allocation_size();
|
|
file_info->end_of_file = entry->size();
|
|
file_info->attributes = entry->attributes();
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
return X_STATUS_NO_SUCH_FILE;
|
|
```
|
|
|
|
### Resolution mechanism
|
|
|
|
`VirtualFileSystem::ResolvePath`
|
|
(`xenia-canary/src/xenia/vfs/virtual_file_system.cc:129-158`) walks
|
|
the in-memory `Entry` tree. It does NOT re-stat the host FS. The
|
|
tree is populated:
|
|
|
|
* **Eagerly at mount time** by
|
|
`HostPathDevice::Initialize` →
|
|
`HostPathDevice::PopulateEntry`
|
|
(`xenia-canary/src/xenia/vfs/devices/host_path_device.cc:31-75`)
|
|
which recursively walks the host directory and builds
|
|
`HostPathEntry` instances via `HostPathEntry::Create`.
|
|
* **Per-create** by `Entry::CreateEntry` /
|
|
`HostPathEntry::CreateEntryInternal`
|
|
(`xenia-canary/src/xenia/vfs/entry.cc:88` /
|
|
`host_path_entry.cc:78-98`), called from
|
|
`VirtualFileSystem::OpenFile` → `CreatePath`
|
|
(`virtual_file_system.cc:160-187`) on successful NtCreateFile create.
|
|
|
|
### Struct layout
|
|
|
|
`X_FILE_NETWORK_OPEN_INFORMATION` in
|
|
`xenia-canary/src/xenia/kernel/info/file.h:117-127`:
|
|
|
|
```cpp
|
|
struct X_FILE_NETWORK_OPEN_INFORMATION { // 56 bytes
|
|
be<uint64_t> creation_time;
|
|
be<uint64_t> last_access_time;
|
|
be<uint64_t> last_write_time;
|
|
be<uint64_t> change_time;
|
|
be<uint64_t> allocation_size;
|
|
be<uint64_t> end_of_file;
|
|
be<uint32_t> attributes;
|
|
be<uint32_t> pad;
|
|
};
|
|
```
|
|
|
|
All multibyte fields are `be<>`, i.e. byte-swapped on write to guest
|
|
memory (Xbox 360 is big-endian).
|
|
|
|
### Attribute / size derivation
|
|
|
|
`HostPathEntry::Create`
|
|
(`xenia-canary/src/xenia/vfs/devices/host_path_entry.cc:32-54`):
|
|
|
|
* `attributes_ = kFileAttributeDirectory (0x10)` if host is a dir.
|
|
* `attributes_ = kFileAttributeNormal (0x80)` for regular files.
|
|
`kFileAttributeReadOnly (0x01)` is OR'd in iff
|
|
`device->is_read_only()`. `cache:` mounts are read-WRITE
|
|
(`xenia_main.cc:641-651`), so the bit is NOT set.
|
|
* `size_ = file_info.total_size`.
|
|
* `allocation_size_ = round_up(total_size, device->bytes_per_sector())`.
|
|
The base `Device` ctor defaults `bytes_per_sector_ = 512`
|
|
(`xenia-canary/src/xenia/vfs/device.h`); host-path devices don't
|
|
override.
|
|
|
|
### Timestamp format
|
|
|
|
Windows FILETIME — 100-ns ticks since 1601-01-01 UTC. On the Windows
|
|
debug build, canary derives these via
|
|
`COMBINE_TIME(WIN32_FILE_ATTRIBUTE_DATA::ftCreationTime)`
|
|
(`filesystem_win.cc:226-228`), which is already FILETIME-format.
|
|
|
|
### Why ours diverged at idx 102404
|
|
|
|
ours's `nt_query_full_attributes_file` previously consulted
|
|
`std::fs::metadata` directly. On TRUE cold-vs-cold (both engines'
|
|
cache wiped), the host file `cache/d4ea4615/e/46ee8ca` doesn't
|
|
exist → ours returned `STATUS_NO_SUCH_FILE`. So why does canary
|
|
return SUCCESS?
|
|
|
|
The original Phase C+11.1 protocol wiped `~/.local/share/Xenia/cache`
|
|
but the Windows-debug canary running under wine actually stores its
|
|
cache under
|
|
`xenia-canary/build-cross/bin/Windows/Debug/cache/` (canary's
|
|
storage root resolves to `GetUserFolder()` when no `portable.txt`
|
|
is present, but the build-cross binary writes there anyway — verified
|
|
by `Storage root: Z:\home\fabi\RE - Project Sylpheed\xenia-canary\
|
|
build-cross\bin\Windows\Debug` in the canary stdout). So the
|
|
"cold-vs-cold" baseline at C+11.1 was actually
|
|
ours-cold-vs-canary-warm: canary's binary-dir cache survived the
|
|
wipe and the pre-existing `46ee8ca` file (May 12 timestamp) made
|
|
canary's mount-time scan populate the in-memory tree.
|
|
|
|
A TRUE cold-vs-cold (Phase C+12 protocol, see `re-validation.md`)
|
|
that ALSO wipes the binary-dir cache shows canary ALSO returns
|
|
NO_SUCH_FILE for `cache:\d4ea4615\e\46ee8ca` at idx 102404 — and
|
|
the divergence at 102404 dissolves.
|
|
|
|
But the underlying gap remains: canary maintains an in-memory entry
|
|
tree that ours did not, and any boot where canary has a populated
|
|
host cache (Sylpheed's normal warm/persistent operation) would see
|
|
canary succeed where ours misses. Fixing this is in scope per the
|
|
user directive.
|
|
|
|
## Architecture decision
|
|
|
|
Architecture **B** (in-memory entry tree mirroring canary):
|
|
|
|
* Add `cache_entries: HashMap<String, CacheEntryMeta>` to `KernelState`.
|
|
* Eager mount-time scan via `populate_cache_entries_from_host`
|
|
(mirrors canary's `HostPathDevice::PopulateEntry`).
|
|
* Per-create registration via `register_cache_entry` (mirrors canary's
|
|
`Entry::CreateEntry`).
|
|
* `nt_query_full_attributes_file` consults the tree first;
|
|
defensive host-FS fallback only fires when the tree missed but
|
|
the file is on disk (refreshes tree as a side effect).
|
|
|
|
Architecture A alone (just stat host FS) would technically work for
|
|
the specific 102404 case after the corrected cold-vs-cold protocol,
|
|
but would diverge from canary on any sequence where the in-memory
|
|
tree's freshness matters (e.g. an entry created earlier this same
|
|
boot whose host write hasn't flushed yet — rare but possible if a
|
|
future game pre-allocates entries before the corresponding write).
|
|
Architecture B keeps semantic parity with canary regardless of the
|
|
host-FS flush timing.
|
|
|
|
## Cold-vs-cold protocol correction
|
|
|
|
The C+11.1 protocol wiped `~/.local/share/Xenia/cache`. The Phase
|
|
C+12 protocol additionally wipes
|
|
`xenia-canary/build-cross/bin/Windows/Debug/cache/`
|
|
(plus `cache0/`, `cache1/`) because that's where the Windows-debug
|
|
canary actually writes its cache under wine. Backup is preserved at
|
|
`/tmp/canary-binary-cache-backup.tar.gz` (4.7 MB, 23 files) and a
|
|
copy lives in this audit-run dir as
|
|
`canary-binary-cache-pre-wipe.tar.gz`.
|
|
|
|
## File:line citations
|
|
|
|
| canary source | line(s) | what |
|
|
|---|---|---|
|
|
| `xboxkrnl_io.cc` | 474-513 | `NtQueryFullAttributesFile_entry` body |
|
|
| `xboxkrnl_io.cc` | 504 | `change_time = write_timestamp()` |
|
|
| `xboxkrnl_io.cc` | 512 | `return X_STATUS_NO_SUCH_FILE` |
|
|
| `info/file.h` | 117-127 | `X_FILE_NETWORK_OPEN_INFORMATION` layout |
|
|
| `vfs/entry.h` | 67-95 | `kFileAttribute*` consts + accessors |
|
|
| `vfs/entry.cc` | 88-104 | `Entry::CreateEntry` |
|
|
| `vfs/virtual_file_system.cc` | 129-158 | `ResolvePath` |
|
|
| `vfs/devices/host_path_device.cc` | 31-75 | `Initialize` + `PopulateEntry` |
|
|
| `vfs/devices/host_path_entry.cc` | 32-54 | `HostPathEntry::Create` (attrs + size) |
|
|
| `base/filesystem_win.cc` | 208-230 | `GetInfo` (FILETIME timestamps) |
|
|
| `app/xenia_main.cc` | 641-651 | `cache:` mount registration |
|