diff --git a/crates/xenia-memory/src/heap.rs b/crates/xenia-memory/src/heap.rs index 18cd16f..c951a8d 100644 --- a/crates/xenia-memory/src/heap.rs +++ b/crates/xenia-memory/src/heap.rs @@ -318,11 +318,33 @@ impl GuestMemory { /// different threads. Used by the XEX loader (init, single-thread) /// and `NtReadFile` (mid-execution; the file's destination buffer is /// guest-thread-private by construction). + /// + /// XMODBUG-002: bumps `page_versions` for every page the write + /// touches. Pre-fix, callers like `NtReadFile` could rewrite a page + /// containing texture or shader bytes that a downstream cache had + /// already keyed on the prior version — the cache would happily + /// hand back the stale decoded bytes. The per-byte `write_*` methods + /// already bump the version after their store; this is the bulk + /// equivalent. Reservation-table invalidation for `lwarx`/`stwcx.` + /// remains the caller's responsibility (the table isn't reachable + /// from `GuestMemory` without a wider plumbing change). pub fn write_bulk(&self, addr: u32, buf: &[u8]) { let ptr = self.translate_virtual_mut(addr); unsafe { std::ptr::copy_nonoverlapping(buf.as_ptr(), ptr, buf.len()); } + if buf.is_empty() { + return; + } + let last_byte = addr.saturating_add(buf.len() as u32).saturating_sub(1); + let first_page = addr / PAGE_SIZE; + let last_page = last_byte / PAGE_SIZE; + for page in first_page..=last_page { + // Use the page-aligned address; bump_page_version computes + // the slot index by `addr / PAGE_SIZE` so any address within + // the page works. + self.bump_page_version(page * PAGE_SIZE); + } } /// Check if a guest address has been allocated/committed. Acquire load