diff --git a/crates/xenia-cpu/src/interpreter.rs b/crates/xenia-cpu/src/interpreter.rs index 8143d7d..a606b4d 100644 --- a/crates/xenia-cpu/src/interpreter.rs +++ b/crates/xenia-cpu/src/interpreter.rs @@ -1647,7 +1647,18 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) - ctx.pc += 4; } PpcOpcode::mtmsr | PpcOpcode::mtmsrd => { - ctx.msr = ctx.gpr[instr.rs()]; + // PPCBUG-078: mtmsrd L=1 is a partial-MSR-write — only MSR[EE] + // (u64 bit 15) and MSR[RI] (u64 bit 0) are modified; all other + // MSR bits preserved. Used by kernel code to re-enable external + // interrupts without disturbing the rest of the MSR. + let l = (instr.raw >> (31 - 15)) & 1; + let rs = ctx.gpr[instr.rs()]; + if matches!(instr.opcode, PpcOpcode::mtmsrd) && l == 1 { + let mask: u64 = (1u64 << 15) | 1u64; + ctx.msr = (ctx.msr & !mask) | (rs & mask); + } else { + ctx.msr = rs; + } ctx.pc += 4; } PpcOpcode::mftb => { @@ -2493,7 +2504,11 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) - // SAT (bit 31) are defined. Canary stores the full Vec128 so we do // the same: mfvscr copies the register, mtvscr overwrites it. PpcOpcode::mfvscr => { - ctx.vr[instr.rd()] = ctx.vscr; + // PPCBUG-080: ISA places VSCR in the rightmost word of VD with + // bytes 0-11 zeroed. Previously the full 128-bit ctx.vscr was + // copied (leaking stale upper data to guest). + let vscr_word = ctx.vscr.as_u32x4()[3]; + ctx.vr[instr.rd()] = xenia_types::Vec128::from_u32x4_array([0, 0, 0, vscr_word]); ctx.pc += 4; } PpcOpcode::mtvscr => { @@ -4717,6 +4732,19 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) - (1 << (31 - 21)) | (1 << (31 - 22)) | (1 << (31 - 23)); let nibble_mask = 0xFu32 << shift; ctx.fpscr &= !(nibble_mask & CLEARABLE_MASK); + // PPCBUG-068: recompute the VX summary bit. If any VX* exception + // bit remains set, VX must remain set; if all are cleared, VX + // must clear. (FEX recomputation omitted — xenia doesn't model + // enabled-exception dispatch.) + const VX_ALL_MASK: u32 = + fpscr::VXSNAN | fpscr::VXISI | fpscr::VXIDI | + fpscr::VXZDZ | fpscr::VXIMZ | fpscr::VXVC | + fpscr::VXSOFT | fpscr::VXSQRT | fpscr::VXCVI; + if ctx.fpscr & VX_ALL_MASK != 0 { + ctx.fpscr |= fpscr::VX; + } else { + ctx.fpscr &= !fpscr::VX; + } ctx.pc += 4; }