diff --git a/crates/xenia-gpu/src/draw_capture.rs b/crates/xenia-gpu/src/draw_capture.rs index 2453e01..df4df75 100644 --- a/crates/xenia-gpu/src/draw_capture.rs +++ b/crates/xenia-gpu/src/draw_capture.rs @@ -115,18 +115,35 @@ fn resolve_vertex_window( let fc = fetch_const? as u32; let dword0 = rf.read(CONST_BASE_FETCH + fc * 6); let dword1 = rf.read(CONST_BASE_FETCH + fc * 6 + 1); - // address:30 at bits[31:2] of dword0 (in bytes once masked). + // address:30 at bits[31:2] of dword0 (in bytes once masked). The fetch + // constant carries a guest *physical* dword address — canary reads the + // vertex buffer via `Memory::TranslatePhysical(fetch.address * 4)` + // (`draw_util.cc:961`). On the Xbox 360 the physical range is mirrored at + // several virtual windows; ours only maps the cached-physical window at + // `0x4000_0000` (`gpu_system::physical_to_backing`). Reading the bare low + // address (`0x0adf_xxxx`) hits an unmapped VA and returns zeros, so rebase + // a low physical base onto the mapped `0x4000_0000` alias when the raw VA + // is not itself mapped. `window_base_dwords` keeps the *original* base so + // the shader's rebase against the (unmodified) fetch-constant address still + // indexes the uploaded window correctly. let base_bytes = dword0 & 0xFFFF_FFFC; if base_bytes == 0 { return None; } + let read_base = if mem.translate(base_bytes).is_some() { + base_bytes + } else if base_bytes < 0x2000_0000 && mem.translate(base_bytes | 0x4000_0000).is_some() { + base_bytes | 0x4000_0000 + } else { + base_bytes + }; // size:24 at bits[25:2] of dword1, in dwords. Clamp to our window cap. let size_dwords = ((dword1 >> 2) & 0x00FF_FFFF).clamp(1, MAX_WINDOW_DWORDS); let window_base_dwords = base_bytes >> 2; let mut dwords = Vec::with_capacity(size_dwords as usize); for i in 0..size_dwords { - let addr = base_bytes.wrapping_add(i * 4); - if addr < base_bytes { + let addr = read_base.wrapping_add(i * 4); + if addr < read_base { break; // wrap guard } // `read_u32` composes big-endian bytes into the u32 value; the WGSL's