diff --git a/crates/sylpheed-ppc/examples/decode_table_check.rs b/crates/sylpheed-ppc/examples/decode_table_check.rs index 1328eb27..b7928310 100644 --- a/crates/sylpheed-ppc/examples/decode_table_check.rs +++ b/crates/sylpheed-ppc/examples/decode_table_check.rs @@ -12,13 +12,17 @@ use std::io::BufRead; fn main() -> Result<(), Box> { - let path = std::env::args().nth(1).ok_or("usage: decode_table_check ")?; + let path = std::env::args() + .nth(1) + .ok_or("usage: decode_table_check
")?; let f = std::io::BufReader::new(std::fs::File::open(path)?); let (mut ok, mut bad, mut invalid) = (0u32, 0u32, 0u32); for line in f.lines() { let line = line?; let mut it = line.split_whitespace(); - let (Some(w), Some(name)) = (it.next(), it.next()) else { continue }; + let (Some(w), Some(name)) = (it.next(), it.next()) else { + continue; + }; let word = u32::from_str_radix(w.trim_start_matches("0x"), 16)?; let d = sylpheed_ppc::decoder::decode(word, 0x8200_0000); let got = format!("{:?}", d.opcode); diff --git a/crates/sylpheed-ppc/src/decoder.rs b/crates/sylpheed-ppc/src/decoder.rs index e29b6d2a..adc60fc3 100644 --- a/crates/sylpheed-ppc/src/decoder.rs +++ b/crates/sylpheed-ppc/src/decoder.rs @@ -18,34 +18,68 @@ impl DecodedInstr { // Common field extractors (PPC bit numbering) /// Primary opcode (bits 0-5) - #[inline] pub fn op(&self) -> u32 { extract_bits(self.raw, 0, 5) } + #[inline] + pub fn op(&self) -> u32 { + extract_bits(self.raw, 0, 5) + } /// rD/rS/rT (bits 6-10) - destination/source register - #[inline] pub fn rd(&self) -> usize { extract_bits(self.raw, 6, 10) as usize } - #[inline] pub fn rs(&self) -> usize { self.rd() } - #[inline] pub fn rt(&self) -> usize { self.rd() } + #[inline] + pub fn rd(&self) -> usize { + extract_bits(self.raw, 6, 10) as usize + } + #[inline] + pub fn rs(&self) -> usize { + self.rd() + } + #[inline] + pub fn rt(&self) -> usize { + self.rd() + } /// rA (bits 11-15) - #[inline] pub fn ra(&self) -> usize { extract_bits(self.raw, 11, 15) as usize } + #[inline] + pub fn ra(&self) -> usize { + extract_bits(self.raw, 11, 15) as usize + } /// rB (bits 16-20) - #[inline] pub fn rb(&self) -> usize { extract_bits(self.raw, 16, 20) as usize } + #[inline] + pub fn rb(&self) -> usize { + extract_bits(self.raw, 16, 20) as usize + } /// rC (bits 21-25) - for 4-operand instructions - #[inline] pub fn rc(&self) -> usize { extract_bits(self.raw, 21, 25) as usize } + #[inline] + pub fn rc(&self) -> usize { + extract_bits(self.raw, 21, 25) as usize + } /// SIMM/UIMM (bits 16-31) - signed/unsigned immediate - #[inline] pub fn simm16(&self) -> i16 { (self.raw & 0xFFFF) as i16 } - #[inline] pub fn uimm16(&self) -> u16 { (self.raw & 0xFFFF) as u16 } + #[inline] + pub fn simm16(&self) -> i16 { + (self.raw & 0xFFFF) as i16 + } + #[inline] + pub fn uimm16(&self) -> u16 { + (self.raw & 0xFFFF) as u16 + } /// D-form displacement (signed, bits 16-31) - #[inline] pub fn d(&self) -> i32 { self.simm16() as i32 } + #[inline] + pub fn d(&self) -> i32 { + self.simm16() as i32 + } /// DS-form displacement (signed, bits 16-29, shifted left 2) - #[inline] pub fn ds(&self) -> i32 { (self.raw & 0xFFFC) as i16 as i32 } + #[inline] + pub fn ds(&self) -> i32 { + (self.raw & 0xFFFC) as i16 as i32 + } /// LI field for branch (bits 6-29, sign-extended, shifted left 2) - #[inline] pub fn li(&self) -> i32 { + #[inline] + pub fn li(&self) -> i32 { let li = extract_bits(self.raw, 6, 29); // Sign-extend from 24 bits, then shift left 2 let sign_extended = ((li as i32) << 8) >> 8; @@ -53,87 +87,154 @@ impl DecodedInstr { } /// BD field for conditional branch (bits 16-29, sign-extended, shifted left 2) - #[inline] pub fn bd(&self) -> i32 { + #[inline] + pub fn bd(&self) -> i32 { let bd = extract_bits(self.raw, 16, 29); let sign_extended = ((bd as i32) << 18) >> 18; sign_extended << 2 } /// BO field (bits 6-10) - branch options - #[inline] pub fn bo(&self) -> u32 { extract_bits(self.raw, 6, 10) } + #[inline] + pub fn bo(&self) -> u32 { + extract_bits(self.raw, 6, 10) + } /// BI field (bits 11-15) - branch condition - #[inline] pub fn bi(&self) -> u32 { extract_bits(self.raw, 11, 15) } + #[inline] + pub fn bi(&self) -> u32 { + extract_bits(self.raw, 11, 15) + } /// AA bit (bit 30) - absolute address - #[inline] pub fn aa(&self) -> bool { (self.raw >> 1) & 1 != 0 } + #[inline] + pub fn aa(&self) -> bool { + (self.raw >> 1) & 1 != 0 + } /// LK bit (bit 31) - link (update LR) - #[inline] pub fn lk(&self) -> bool { self.raw & 1 != 0 } + #[inline] + pub fn lk(&self) -> bool { + self.raw & 1 != 0 + } /// Rc bit (bit 31) - record CR0 - #[inline] pub fn rc_bit(&self) -> bool { self.raw & 1 != 0 } + #[inline] + pub fn rc_bit(&self) -> bool { + self.raw & 1 != 0 + } /// Rc for VC-form vector compare instructions — PPC bit 21 = host bit 10. - #[inline] pub fn vc_rc_bit(&self) -> bool { (self.raw >> 10) & 1 != 0 } + #[inline] + pub fn vc_rc_bit(&self) -> bool { + (self.raw >> 10) & 1 != 0 + } /// Rc for VX128_R-form vector compare instructions — PPC bit 27 = host bit 4. /// VX128_R Rc bit — PPC bit 25 (host bit 6) per canary's FormatVX128_R /// bitfield layout. PPCBUG-700. - #[inline] pub fn vx128r_rc_bit(&self) -> bool { (self.raw >> 6) & 1 != 0 } + #[inline] + pub fn vx128r_rc_bit(&self) -> bool { + (self.raw >> 6) & 1 != 0 + } /// IMM field for VX128_4-form instructions (vrlimi128) — 5-bit blend mask at PPC bits 11-15. - #[inline] pub fn vx128_4_imm(&self) -> u32 { extract_bits(self.raw, 11, 15) } + #[inline] + pub fn vx128_4_imm(&self) -> u32 { + extract_bits(self.raw, 11, 15) + } /// z field for VX128_4-form instructions (vrlimi128) — 2-bit rotation index at PPC bits 24-25. - #[inline] pub fn vx128_4_z(&self) -> u32 { extract_bits(self.raw, 24, 25) } + #[inline] + pub fn vx128_4_z(&self) -> u32 { + extract_bits(self.raw, 24, 25) + } /// OE bit (bit 21) - overflow enable - #[inline] pub fn oe(&self) -> bool { extract_bits(self.raw, 21, 21) != 0 } + #[inline] + pub fn oe(&self) -> bool { + extract_bits(self.raw, 21, 21) != 0 + } /// TO field (bits 6-10) for tw/twi/td/tdi trap instructions. - #[inline] pub fn to(&self) -> u32 { extract_bits(self.raw, 6, 10) } + #[inline] + pub fn to(&self) -> u32 { + extract_bits(self.raw, 6, 10) + } /// MB, ME fields for rotate instructions - #[inline] pub fn mb(&self) -> u32 { extract_bits(self.raw, 21, 25) } - #[inline] pub fn me(&self) -> u32 { extract_bits(self.raw, 26, 30) } + #[inline] + pub fn mb(&self) -> u32 { + extract_bits(self.raw, 21, 25) + } + #[inline] + pub fn me(&self) -> u32 { + extract_bits(self.raw, 26, 30) + } /// SH field (bits 16-20) for shift instructions - #[inline] pub fn sh(&self) -> u32 { extract_bits(self.raw, 16, 20) } + #[inline] + pub fn sh(&self) -> u32 { + extract_bits(self.raw, 16, 20) + } /// SH field for 64-bit shifts (bits 16-20 + bit 30) - #[inline] pub fn sh64(&self) -> u32 { + #[inline] + pub fn sh64(&self) -> u32 { (extract_bits(self.raw, 30, 30) << 5) | extract_bits(self.raw, 16, 20) } /// MB/ME field for MD-form and MDS-form instructions (6-bit field, split encoding). /// MB[4:0] at PPC bits 21-25; MB[5] at PPC bit 26. - #[inline] pub fn mb_md(&self) -> u32 { + #[inline] + pub fn mb_md(&self) -> u32 { extract_bits(self.raw, 21, 25) | (extract_bits(self.raw, 26, 26) << 5) } /// SPR field (bits 11-20, swapped halves) - #[inline] pub fn spr(&self) -> u32 { + #[inline] + pub fn spr(&self) -> u32 { let spr_raw = extract_bits(self.raw, 11, 20); ((spr_raw & 0x1F) << 5) | ((spr_raw >> 5) & 0x1F) } /// CRM field (bits 12-19) for mtcrf - #[inline] pub fn crm(&self) -> u32 { extract_bits(self.raw, 12, 19) } + #[inline] + pub fn crm(&self) -> u32 { + extract_bits(self.raw, 12, 19) + } /// crfD (bits 6-8) - condition register field destination - #[inline] pub fn crfd(&self) -> usize { extract_bits(self.raw, 6, 8) as usize } + #[inline] + pub fn crfd(&self) -> usize { + extract_bits(self.raw, 6, 8) as usize + } /// crfS (bits 11-13) - #[inline] pub fn crfs(&self) -> usize { extract_bits(self.raw, 11, 13) as usize } + #[inline] + pub fn crfs(&self) -> usize { + extract_bits(self.raw, 11, 13) as usize + } /// L bit (bit 10) - 64-bit compare - #[inline] pub fn l(&self) -> bool { extract_bits(self.raw, 10, 10) != 0 } + #[inline] + pub fn l(&self) -> bool { + extract_bits(self.raw, 10, 10) != 0 + } /// crbD (bits 6-10) - #[inline] pub fn crbd(&self) -> u32 { extract_bits(self.raw, 6, 10) } + #[inline] + pub fn crbd(&self) -> u32 { + extract_bits(self.raw, 6, 10) + } /// crbA (bits 11-15) - #[inline] pub fn crba(&self) -> u32 { extract_bits(self.raw, 11, 15) } + #[inline] + pub fn crba(&self) -> u32 { + extract_bits(self.raw, 11, 15) + } /// crbB (bits 16-20) - #[inline] pub fn crbb(&self) -> u32 { extract_bits(self.raw, 16, 20) } + #[inline] + pub fn crbb(&self) -> u32 { + extract_bits(self.raw, 16, 20) + } // VMX128 field extractors — bit positions match canary's // FormatVX128/VX128_2/VX128_4/VX128_5/VX128_R bitfield layout @@ -141,7 +242,8 @@ impl DecodedInstr { /// VA128 = VA128l(5) | VA128h(1) << 5 | VA128H(1) << 6. /// Canonical 7-bit register selector: PPC 11-15 (low), PPC 26 (mid), PPC 21 (high). - #[inline] pub fn va128(&self) -> usize { + #[inline] + pub fn va128(&self) -> usize { (extract_bits(self.raw, 11, 15) | (extract_bits(self.raw, 26, 26) << 5) | (extract_bits(self.raw, 21, 21) << 6)) as usize @@ -149,35 +251,48 @@ impl DecodedInstr { /// VB128 = VB128l(5) | VB128h(2) << 5. Canary's VB128h is a 2-bit /// contiguous field at PPC 30-31 (host bits 0-1). - #[inline] pub fn vb128(&self) -> usize { - (extract_bits(self.raw, 16, 20) - | (extract_bits(self.raw, 30, 31) << 5)) as usize + #[inline] + pub fn vb128(&self) -> usize { + (extract_bits(self.raw, 16, 20) | (extract_bits(self.raw, 30, 31) << 5)) as usize } /// VD128 = VD128l(5) | VD128h(2) << 5. Canary's VD128h is a 2-bit /// contiguous field at PPC 28-29 (host bits 2-3). - #[inline] pub fn vd128(&self) -> usize { - (extract_bits(self.raw, 6, 10) - | (extract_bits(self.raw, 28, 29) << 5)) as usize + #[inline] + pub fn vd128(&self) -> usize { + (extract_bits(self.raw, 6, 10) | (extract_bits(self.raw, 28, 29) << 5)) as usize } /// VS128 - same encoding as VD128 - #[inline] pub fn vs128(&self) -> usize { self.vd128() } + #[inline] + pub fn vs128(&self) -> usize { + self.vd128() + } /// VC register for VX128_2-form instructions (vperm128) — 3-bit at PPC bits 23-25. - #[inline] pub fn vc128_2(&self) -> usize { extract_bits(self.raw, 23, 25) as usize } + #[inline] + pub fn vc128_2(&self) -> usize { + extract_bits(self.raw, 23, 25) as usize + } /// NB field (bits 16-20) for lswi/stswi - #[inline] pub fn nb(&self) -> u32 { extract_bits(self.raw, 16, 20) } + #[inline] + pub fn nb(&self) -> u32 { + extract_bits(self.raw, 16, 20) + } /// PERM field for VX128_P-form instructions (vpermwi128) — 8-bit split encoding. /// PERMl (5 bits) at PPC bits 11-15; PERMh (3 bits) at PPC bits 23-25. - #[inline] pub fn vx128_p_perm(&self) -> u32 { + #[inline] + pub fn vx128_p_perm(&self) -> u32 { extract_bits(self.raw, 11, 15) | (extract_bits(self.raw, 23, 25) << 5) } /// SH field for VX128_5-form instructions (vsldoi128) — 4-bit shift at PPC bits 22-25. - #[inline] pub fn vx128_5_sh(&self) -> u32 { extract_bits(self.raw, 22, 25) } + #[inline] + pub fn vx128_5_sh(&self) -> u32 { + extract_bits(self.raw, 22, 25) + } } /// Extract the 5-bit `UIMM` (`VX128_3`) / `IMM` (`VX128_4`) field. Canary @@ -1057,8 +1172,15 @@ mod tests { /// vd_hi is 2 bits (PPC 28-29). Same shape for vb128 (vb_lo at PPC 16-20, /// vb_hi 2 bits at PPC 30-31). va128 = va_lo | (va_h26<<5) | (va_h21<<6) /// per canary's 7-bit VA selector. - fn vmx128_test_word(vd_lo: u32, vd_hi: u32, va_lo: u32, va_h26: u32, va_h21: u32, - vb_lo: u32, vb_hi: u32) -> u32 { + fn vmx128_test_word( + vd_lo: u32, + vd_hi: u32, + va_lo: u32, + va_h26: u32, + va_h21: u32, + vb_lo: u32, + vb_hi: u32, + ) -> u32 { // PPC bit i -> host bit (31-i). (vd_lo << (31 - 10)) // VD128l: PPC 6-10 = host 21-25 | (vd_hi << (31 - 29)) // VD128h: PPC 28-29 = host 2-3 (LSB at host 2) @@ -1066,15 +1188,19 @@ mod tests { | (va_h26 << (31 - 26)) // VA128h: PPC 26 = host 5 | (va_h21 << (31 - 21)) // VA128H: PPC 21 = host 10 | (vb_lo << (31 - 20)) // VB128l: PPC 16-20 = host 11-15 - | (vb_hi << (31 - 31)) // VB128h: PPC 30-31 = host 0-1 (LSB at host 0) + | vb_hi // VB128h: PPC 30-31 = host 0-1 (LSB at host 0) } #[test] fn vmx128_vd128_low_5_bits_only() { // vd_lo = 0..31, vd_hi = 0 → vd128 = vd_lo for r in 0..32u32 { - let raw = (r as u32) << (31 - 10); - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let raw = r << (31 - 10); + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.vd128(), r as usize, "vd_lo={r}"); } } @@ -1082,26 +1208,36 @@ mod tests { #[test] fn vmx128_vd128_high_low_bit_adds_32() { // vd_lo = 0, VD128h = 0b01 (LSB only at host bit 2 = PPC 29) → vd128 = 32 - let raw = (1u32 << (31 - 29)); - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let raw = 1u32 << (31 - 29); + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.vd128(), 32); } #[test] fn vmx128_vd128_high_high_bit_adds_64() { // vd_lo = 0, VD128h = 0b10 (MSB only at host bit 3 = PPC 28) → vd128 = 64 - let raw = (1u32 << (31 - 28)); - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let raw = 1u32 << (31 - 28); + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.vd128(), 64); } #[test] fn vmx128_vd128_full_127() { // vd_lo = 31, VD128h = 0b11 → vd128 = 127 - let raw = (31u32 << (31 - 10)) - | (1u32 << (31 - 28)) - | (1u32 << (31 - 29)); - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let raw = (31u32 << (31 - 10)) | (1u32 << (31 - 28)) | (1u32 << (31 - 29)); + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.vd128(), 127); } @@ -1109,11 +1245,19 @@ mod tests { fn vmx128_va128_canary_layout() { // va_lo = 7 at PPC 11-15, VA128h = 1 at PPC 26 → va128 = 7 | 32 = 39 let raw = (7u32 << (31 - 15)) | (1u32 << (31 - 26)); - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.va128(), 39); // VA128H = 1 at PPC 21 → va128 += 64 = 103 let raw = raw | (1u32 << (31 - 21)); - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.va128(), 7 | 32 | 64); } @@ -1122,10 +1266,18 @@ mod tests { // vb_lo = 5 at PPC 16-20. VB128h = 0b01 (LSB at PPC 31 = host 0) → +32. // VB128h = 0b11 → +96. let raw = (5u32 << (31 - 20)) | (1u32 << (31 - 31)); - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.vb128(), 5 | 32); let raw = raw | (1u32 << (31 - 30)); - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.vb128(), 5 | 32 | 64); } @@ -1135,9 +1287,12 @@ mod tests { for r in [0u32, 31, 32, 64, 96, 127] { let lo = r & 0x1F; let hi = (r >> 5) & 0x3; - let raw = (lo << (31 - 10)) - | (hi << (31 - 29)); - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let raw = (lo << (31 - 10)) | (hi << (31 - 29)); + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.vd128(), r as usize, "vd128 mismatch for r={r}"); assert_eq!(d.vs128(), r as usize, "vs128 mismatch for r={r}"); assert_eq!(d.vd128(), d.vs128()); @@ -1150,7 +1305,11 @@ mod tests { // Keep the helper validated against the real accessor. // vd_lo=5, vd_hi=0b11 → vd128 = 5 | 96 = 101 let raw = vmx128_test_word(5, 3, 0, 0, 0, 0, 0); - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.vd128(), 5 | 32 | 64); } @@ -1160,21 +1319,37 @@ mod tests { // Host bit 9 = 1 (PPC bit 22), host bits 6-8 = 0. // So raw bit 9 set = raw |= 1 << 9 = 0x200 let raw = 0x200u32; // host bit 9 set only - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.vx128_5_sh(), 8, "SH=8: MSB at PPC bit 22"); // SH=1 (binary 0001): host bit 6 set = raw |= 1 << 6 = 0x40 let raw = 0x40u32; - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.vx128_5_sh(), 1, "SH=1: LSB at PPC bit 25"); // SH=15 (binary 1111): host bits 6-9 all set = raw |= 0xF << 6 = 0x3C0 let raw = 0x3C0u32; - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.vx128_5_sh(), 15, "SH=15: all 4 bits set"); // SH=0: raw=0 - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw: 0, addr: 0 }; + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw: 0, + addr: 0, + }; assert_eq!(d.vx128_5_sh(), 0, "SH=0"); } @@ -1182,23 +1357,39 @@ mod tests { fn vx128_4_accessors_correct_bit_positions() { // z=3 (binary 11) at PPC bits 24-25 = host bits 6-7 let raw = 0b11u32 << 6; - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.vx128_4_z(), 3, "z=3 from host bits 6-7"); // IMM=0x15 (binary 10101) at PPC bits 11-15 = host bits 16-20 let raw2 = 0x15u32 << 16; - let d2 = DecodedInstr { opcode: PpcOpcode::Invalid, raw: raw2, addr: 0 }; + let d2 = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw: raw2, + addr: 0, + }; assert_eq!(d2.vx128_4_imm(), 0x15, "IMM=0x15 from host bits 16-20"); // Combined: z=1, IMM=0xA — fields must not bleed into each other let raw3 = (0x1u32 << 6) | (0xAu32 << 16); - let d3 = DecodedInstr { opcode: PpcOpcode::Invalid, raw: raw3, addr: 0 }; + let d3 = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw: raw3, + addr: 0, + }; assert_eq!(d3.vx128_4_z(), 1, "z=1 combined"); assert_eq!(d3.vx128_4_imm(), 0xA, "IMM=0xA combined"); // z=2, IMM=0xF — max 4-bit blend mask, exercises the full lower nibble let raw4 = (0b10u32 << 6) | (0xFu32 << 16); - let d4 = DecodedInstr { opcode: PpcOpcode::Invalid, raw: raw4, addr: 0 }; + let d4 = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw: raw4, + addr: 0, + }; assert_eq!(d4.vx128_4_z(), 2, "z=2 from binary 10"); assert_eq!(d4.vx128_4_imm(), 0xF, "IMM=0xF all-ones nibble"); } @@ -1208,16 +1399,32 @@ mod tests { // VC=5 (binary 101) at PPC bits 23-25 = host bits 6-8 // extract_bits(raw, 23, 25) = (raw >> (31-25)) & 0x7 = (raw >> 6) & 0x7 let raw = 5u32 << 6; // host bits 6-8 = 5 - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.vc128_2(), 5); - let d0 = DecodedInstr { opcode: PpcOpcode::Invalid, raw: 0, addr: 0 }; + let d0 = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw: 0, + addr: 0, + }; assert_eq!(d0.vc128_2(), 0); - let d7 = DecodedInstr { opcode: PpcOpcode::Invalid, raw: 7u32 << 6, addr: 0 }; + let d7 = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw: 7u32 << 6, + addr: 0, + }; assert_eq!(d7.vc128_2(), 7); - let d1 = DecodedInstr { opcode: PpcOpcode::Invalid, raw: 1u32 << 6, addr: 0 }; + let d1 = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw: 1u32 << 6, + addr: 0, + }; assert_eq!(d1.vc128_2(), 1); } @@ -1225,21 +1432,37 @@ mod tests { fn vx128_p_perm_assembles_correctly() { // PERMl=0x1F (all 5 bits set) at host bits 16-20: raw = 0x1F << 16 let raw = 0x1Fu32 << 16; - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.vx128_p_perm(), 0x1F, "PERMl only"); // PERMh=0x7 (all 3 bits set) at host bits 6-8: raw = 0x7 << 6 = 0x1C0 let raw = 0x7u32 << 6; - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.vx128_p_perm(), 0x7 << 5, "PERMh only: bits 5-7"); // PERMl=0xA, PERMh=0x5: raw = (0xA << 16) | (0x5 << 6) let raw = (0xAu32 << 16) | (0x5u32 << 6); - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw, addr: 0 }; + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw, + addr: 0, + }; assert_eq!(d.vx128_p_perm(), 0xA | (0x5 << 5)); // PERMl and PERMh bits must not bleed into each other - let d = DecodedInstr { opcode: PpcOpcode::Invalid, raw: 0, addr: 0 }; + let d = DecodedInstr { + opcode: PpcOpcode::Invalid, + raw: 0, + addr: 0, + }; assert_eq!(d.vx128_p_perm(), 0); } } diff --git a/crates/sylpheed-ppc/src/disasm.rs b/crates/sylpheed-ppc/src/disasm.rs index 90b0052b..1868dfb2 100644 --- a/crates/sylpheed-ppc/src/disasm.rs +++ b/crates/sylpheed-ppc/src/disasm.rs @@ -46,8 +46,11 @@ impl DisasmText { fn pad_into(mnem: &str, operands: &str, width: usize) -> String { if width <= mnem.len() + 1 { // No padding fits — fall back to single-space join. - if operands.is_empty() { mnem.to_string() } - else { format!("{mnem} {operands}") } + if operands.is_empty() { + mnem.to_string() + } else { + format!("{mnem} {operands}") + } } else { format!("{: DisasmText { } fn with_ext( - base_mnem: &str, base_ops: String, base_pad: usize, - ext_mnem: &str, ext_ops: String, ext_pad: usize, + base_mnem: &str, + base_ops: String, + base_pad: usize, + ext_mnem: &str, + ext_ops: String, + ext_pad: usize, ) -> DisasmText { let disasm = pad_into(base_mnem, &base_ops, base_pad); let ext_disasm = pad_into(ext_mnem, &ext_ops, ext_pad); @@ -95,15 +102,28 @@ fn long_word(raw: u32) -> DisasmText { // ── Helpers (register names, sign extension, condition decoding) ──────────── -#[inline] fn gpr(r: usize) -> String { format!("r{r}") } -#[inline] fn fpr(r: usize) -> String { format!("f{r}") } -#[inline] fn vr(r: usize) -> String { format!("v{r}") } +#[inline] +fn gpr(r: usize) -> String { + format!("r{r}") +} +#[inline] +fn fpr(r: usize) -> String { + format!("f{r}") +} +#[inline] +fn vr(r: usize) -> String { + format!("v{r}") +} fn crb(b: u32) -> String { let cr = b / 4; let bit = b % 4; let bit_name = ["lt", "gt", "eq", "so"][bit as usize]; - if cr == 0 { bit_name.to_string() } else { format!("4*cr{cr}+{bit_name}") } + if cr == 0 { + bit_name.to_string() + } else { + format!("4*cr{cr}+{bit_name}") + } } fn spr_name(spr: u32) -> String { @@ -115,7 +135,8 @@ fn spr_name(spr: u32) -> String { } } -#[inline] fn sign_ext(val: u32, bits: u32) -> i32 { +#[inline] +fn sign_ext(val: u32, bits: u32) -> i32 { let shift = 32 - bits; ((val << shift) as i32) >> shift } @@ -125,19 +146,19 @@ fn spr_name(spr: u32) -> String { /// encoding which `tw`/`td` use for logical-compare conditions. fn trap_cond(to: u32) -> Option<&'static str> { match to { - 1 => Some("lgt"), - 2 => Some("llt"), - 3 => Some("lne"), - 4 => Some("eq"), - 5 => Some("lge"), - 6 => Some("lle"), - 8 => Some("gt"), + 1 => Some("lgt"), + 2 => Some("llt"), + 3 => Some("lne"), + 4 => Some("eq"), + 5 => Some("lge"), + 6 => Some("lle"), + 8 => Some("gt"), 12 => Some("ge"), 16 => Some("lt"), 20 => Some("le"), 24 => Some("ne"), - 31 => Some(""), // unconditional - _ => None, + 31 => Some(""), // unconditional + _ => None, } } @@ -147,22 +168,33 @@ fn cond_branch_ext(bo: u32, bi: u32) -> Option<(&'static str, String)> { let cond_true = bo & 0x08 != 0; let no_cond = bo & 0x10 != 0; let decr = bo & 0x04 == 0; - if no_cond || decr { return None; } + if no_cond || decr { + return None; + } let cr_field = bi / 4; let cr_bit = bi % 4; let cond_name = match (cr_bit, cond_true) { - (0, true) => "lt", (0, false) => "ge", - (1, true) => "gt", (1, false) => "le", - (2, true) => "eq", (2, false) => "ne", - (3, true) => "so", (3, false) => "ns", + (0, true) => "lt", + (0, false) => "ge", + (1, true) => "gt", + (1, false) => "le", + (2, true) => "eq", + (2, false) => "ne", + (3, true) => "so", + (3, false) => "ns", _ => return None, }; - let cr = if cr_field == 0 { String::new() } else { format!("cr{cr_field}, ") }; + let cr = if cr_field == 0 { + String::new() + } else { + format!("cr{cr_field}, ") + }; Some((cond_name, cr)) } -#[inline] fn rc_dot(instr: &DecodedInstr) -> &'static str { +#[inline] +fn rc_dot(instr: &DecodedInstr) -> &'static str { if instr.rc_bit() { "." } else { "" } } @@ -172,114 +204,114 @@ fn cond_branch_ext(bo: u32, bi: u32) -> Option<(&'static str, String)> { pub fn format(instr: &DecodedInstr) -> DisasmText { match instr.opcode { // ── Branch ────────────────────────────────────────────────────────── - PpcOpcode::bx => fmt_b(instr), - PpcOpcode::bcx => fmt_bc(instr), - PpcOpcode::bclrx => fmt_bclr(instr), + PpcOpcode::bx => fmt_b(instr), + PpcOpcode::bcx => fmt_bc(instr), + PpcOpcode::bclrx => fmt_bclr(instr), PpcOpcode::bcctrx => fmt_bcctr(instr), - PpcOpcode::sc => base("sc", String::new(), 0), + PpcOpcode::sc => base("sc", String::new(), 0), // ── Trap ──────────────────────────────────────────────────────────── PpcOpcode::tdi => fmt_trap_imm(instr, "tdi", "td"), PpcOpcode::twi => fmt_trap_imm(instr, "twi", "tw"), - PpcOpcode::td => fmt_trap_reg(instr, "td"), - PpcOpcode::tw => fmt_trap_reg(instr, "tw"), + PpcOpcode::td => fmt_trap_reg(instr, "td"), + PpcOpcode::tw => fmt_trap_reg(instr, "tw"), // ── D-form ALU/logical ────────────────────────────────────────────── - PpcOpcode::addi => fmt_addi(instr), - PpcOpcode::addis => fmt_addis(instr), - PpcOpcode::addic => fmt_d_add(instr, "addic"), - PpcOpcode::addicx => fmt_d_add(instr, "addic."), + PpcOpcode::addi => fmt_addi(instr), + PpcOpcode::addis => fmt_addis(instr), + PpcOpcode::addic => fmt_d_add(instr, "addic"), + PpcOpcode::addicx => fmt_d_add(instr, "addic."), PpcOpcode::subficx => fmt_d_imm_simple(instr, "subfic"), - PpcOpcode::mulli => fmt_d_imm_simple(instr, "mulli"), - PpcOpcode::cmpi => fmt_cmp_imm(instr, "cmpi", true), - PpcOpcode::cmpli => fmt_cmp_imm(instr, "cmpli", false), - PpcOpcode::ori => fmt_ori(instr), - PpcOpcode::oris => fmt_d_logic(instr, "oris"), - PpcOpcode::xori => fmt_d_logic(instr, "xori"), - PpcOpcode::xoris => fmt_d_logic(instr, "xoris"), - PpcOpcode::andix => fmt_d_logic(instr, "andi."), - PpcOpcode::andisx => fmt_d_logic(instr, "andis."), + PpcOpcode::mulli => fmt_d_imm_simple(instr, "mulli"), + PpcOpcode::cmpi => fmt_cmp_imm(instr, "cmpi", true), + PpcOpcode::cmpli => fmt_cmp_imm(instr, "cmpli", false), + PpcOpcode::ori => fmt_ori(instr), + PpcOpcode::oris => fmt_d_logic(instr, "oris"), + PpcOpcode::xori => fmt_d_logic(instr, "xori"), + PpcOpcode::xoris => fmt_d_logic(instr, "xoris"), + PpcOpcode::andix => fmt_d_logic(instr, "andi."), + PpcOpcode::andisx => fmt_d_logic(instr, "andis."), // ── D-form load/store ─────────────────────────────────────────────── - PpcOpcode::lwz => fmt_ld(instr, "lwz", false), - PpcOpcode::lwzu => fmt_ld(instr, "lwzu", false), - PpcOpcode::lbz => fmt_ld(instr, "lbz", false), - PpcOpcode::lbzu => fmt_ld(instr, "lbzu", false), - PpcOpcode::lhz => fmt_ld(instr, "lhz", false), - PpcOpcode::lhzu => fmt_ld(instr, "lhzu", false), - PpcOpcode::lha => fmt_ld(instr, "lha", false), - PpcOpcode::lhau => fmt_ld(instr, "lhau", false), - PpcOpcode::lmw => fmt_ld(instr, "lmw", false), - PpcOpcode::lfs => fmt_ld(instr, "lfs", true), - PpcOpcode::lfsu => fmt_ld(instr, "lfsu", true), - PpcOpcode::lfd => fmt_ld(instr, "lfd", true), - PpcOpcode::lfdu => fmt_ld(instr, "lfdu", true), - PpcOpcode::stw => fmt_st(instr, "stw", false), - PpcOpcode::stwu => fmt_st(instr, "stwu", false), - PpcOpcode::stb => fmt_st(instr, "stb", false), - PpcOpcode::stbu => fmt_st(instr, "stbu", false), - PpcOpcode::sth => fmt_st(instr, "sth", false), - PpcOpcode::sthu => fmt_st(instr, "sthu", false), - PpcOpcode::stmw => fmt_st(instr, "stmw", false), - PpcOpcode::stfs => fmt_st(instr, "stfs", true), + PpcOpcode::lwz => fmt_ld(instr, "lwz", false), + PpcOpcode::lwzu => fmt_ld(instr, "lwzu", false), + PpcOpcode::lbz => fmt_ld(instr, "lbz", false), + PpcOpcode::lbzu => fmt_ld(instr, "lbzu", false), + PpcOpcode::lhz => fmt_ld(instr, "lhz", false), + PpcOpcode::lhzu => fmt_ld(instr, "lhzu", false), + PpcOpcode::lha => fmt_ld(instr, "lha", false), + PpcOpcode::lhau => fmt_ld(instr, "lhau", false), + PpcOpcode::lmw => fmt_ld(instr, "lmw", false), + PpcOpcode::lfs => fmt_ld(instr, "lfs", true), + PpcOpcode::lfsu => fmt_ld(instr, "lfsu", true), + PpcOpcode::lfd => fmt_ld(instr, "lfd", true), + PpcOpcode::lfdu => fmt_ld(instr, "lfdu", true), + PpcOpcode::stw => fmt_st(instr, "stw", false), + PpcOpcode::stwu => fmt_st(instr, "stwu", false), + PpcOpcode::stb => fmt_st(instr, "stb", false), + PpcOpcode::stbu => fmt_st(instr, "stbu", false), + PpcOpcode::sth => fmt_st(instr, "sth", false), + PpcOpcode::sthu => fmt_st(instr, "sthu", false), + PpcOpcode::stmw => fmt_st(instr, "stmw", false), + PpcOpcode::stfs => fmt_st(instr, "stfs", true), PpcOpcode::stfsu => fmt_st(instr, "stfsu", true), - PpcOpcode::stfd => fmt_st(instr, "stfd", true), + PpcOpcode::stfd => fmt_st(instr, "stfd", true), PpcOpcode::stfdu => fmt_st(instr, "stfdu", true), // ── DS-form load/store ────────────────────────────────────────────── - PpcOpcode::ld => fmt_ds(instr, "ld"), - PpcOpcode::ldu => fmt_ds(instr, "ldu"), - PpcOpcode::lwa => fmt_ds(instr, "lwa"), - PpcOpcode::std => fmt_ds(instr, "std"), + PpcOpcode::ld => fmt_ds(instr, "ld"), + PpcOpcode::ldu => fmt_ds(instr, "ldu"), + PpcOpcode::lwa => fmt_ds(instr, "lwa"), + PpcOpcode::std => fmt_ds(instr, "std"), PpcOpcode::stdu => fmt_ds(instr, "stdu"), // ── Rotate ───────────────────────────────────────────────────────── PpcOpcode::rlwimix => fmt_rlwimi(instr), PpcOpcode::rlwinmx => fmt_rlwinm(instr), - PpcOpcode::rlwnmx => fmt_rlwnm(instr), + PpcOpcode::rlwnmx => fmt_rlwnm(instr), PpcOpcode::rldiclx => fmt_rldicl(instr), PpcOpcode::rldicrx => fmt_rldicr(instr), - PpcOpcode::rldicx => fmt_rldic(instr), + PpcOpcode::rldicx => fmt_rldic(instr), PpcOpcode::rldimix => fmt_rldimi(instr), - PpcOpcode::rldclx => fmt_rldcl(instr), - PpcOpcode::rldcrx => fmt_rldcr(instr), + PpcOpcode::rldclx => fmt_rldcl(instr), + PpcOpcode::rldcrx => fmt_rldcr(instr), // ── Compare (X-form) ─────────────────────────────────────────────── - PpcOpcode::cmp => fmt_cmp_reg(instr, "cmp"), + PpcOpcode::cmp => fmt_cmp_reg(instr, "cmp"), PpcOpcode::cmpl => fmt_cmp_reg(instr, "cmpl"), // ── X-form ALU (3-register) with OE/Rc ───────────────────────────── - PpcOpcode::addx => fmt_xo_3op(instr, "add"), - PpcOpcode::addcx => fmt_xo_3op(instr, "addc"), - PpcOpcode::addex => fmt_xo_3op(instr, "adde"), - PpcOpcode::addmex => fmt_xo_2op(instr, "addme"), - PpcOpcode::addzex => fmt_xo_2op(instr, "addze"), - PpcOpcode::subfx => fmt_subf(instr, "subf", "sub"), - PpcOpcode::subfcx => fmt_subf(instr, "subfc", "subc"), - PpcOpcode::subfex => fmt_xo_3op(instr, "subfe"), + PpcOpcode::addx => fmt_xo_3op(instr, "add"), + PpcOpcode::addcx => fmt_xo_3op(instr, "addc"), + PpcOpcode::addex => fmt_xo_3op(instr, "adde"), + PpcOpcode::addmex => fmt_xo_2op(instr, "addme"), + PpcOpcode::addzex => fmt_xo_2op(instr, "addze"), + PpcOpcode::subfx => fmt_subf(instr, "subf", "sub"), + PpcOpcode::subfcx => fmt_subf(instr, "subfc", "subc"), + PpcOpcode::subfex => fmt_xo_3op(instr, "subfe"), PpcOpcode::subfmex => fmt_xo_2op(instr, "subfme"), PpcOpcode::subfzex => fmt_xo_2op(instr, "subfze"), - PpcOpcode::negx => fmt_xo_2op(instr, "neg"), - PpcOpcode::mullwx => fmt_xo_3op(instr, "mullw"), - PpcOpcode::mulhwx => fmt_xo_3op_no_oe(instr, "mulhw"), + PpcOpcode::negx => fmt_xo_2op(instr, "neg"), + PpcOpcode::mullwx => fmt_xo_3op(instr, "mullw"), + PpcOpcode::mulhwx => fmt_xo_3op_no_oe(instr, "mulhw"), PpcOpcode::mulhwux => fmt_xo_3op_rc_only(instr, "mulhwu"), - PpcOpcode::divwx => fmt_xo_3op(instr, "divw"), - PpcOpcode::divwux => fmt_xo_3op(instr, "divwu"), - PpcOpcode::mulldx => fmt_xo_3op(instr, "mulld"), - PpcOpcode::mulhdx => fmt_xo_3op_rc_only(instr, "mulhd"), + PpcOpcode::divwx => fmt_xo_3op(instr, "divw"), + PpcOpcode::divwux => fmt_xo_3op(instr, "divwu"), + PpcOpcode::mulldx => fmt_xo_3op(instr, "mulld"), + PpcOpcode::mulhdx => fmt_xo_3op_rc_only(instr, "mulhd"), PpcOpcode::mulhdux => fmt_xo_3op_rc_only(instr, "mulhdu"), - PpcOpcode::divdx => fmt_xo_3op(instr, "divd"), - PpcOpcode::divdux => fmt_xo_3op(instr, "divdu"), + PpcOpcode::divdx => fmt_xo_3op(instr, "divd"), + PpcOpcode::divdux => fmt_xo_3op(instr, "divdu"), // ── X-form logical (Rc) ──────────────────────────────────────────── - PpcOpcode::andx => fmt_logic_and(instr), - PpcOpcode::andcx => fmt_x_logic(instr, "andc"), - PpcOpcode::orx => fmt_logic_or(instr), - PpcOpcode::orcx => fmt_x_logic(instr, "orc"), - PpcOpcode::xorx => fmt_x_logic(instr, "xor"), - PpcOpcode::norx => fmt_logic_nor(instr), - PpcOpcode::nandx => fmt_x_logic(instr, "nand"), - PpcOpcode::eqvx => fmt_x_logic(instr, "eqv"), + PpcOpcode::andx => fmt_logic_and(instr), + PpcOpcode::andcx => fmt_x_logic(instr, "andc"), + PpcOpcode::orx => fmt_logic_or(instr), + PpcOpcode::orcx => fmt_x_logic(instr, "orc"), + PpcOpcode::xorx => fmt_x_logic(instr, "xor"), + PpcOpcode::norx => fmt_logic_nor(instr), + PpcOpcode::nandx => fmt_x_logic(instr, "nand"), + PpcOpcode::eqvx => fmt_x_logic(instr, "eqv"), PpcOpcode::extsbx => fmt_x_unary_rc(instr, "extsb"), PpcOpcode::extshx => fmt_x_unary_rc(instr, "extsh"), PpcOpcode::extswx => fmt_x_unary_rc(instr, "extsw"), @@ -287,82 +319,82 @@ pub fn format(instr: &DecodedInstr) -> DisasmText { PpcOpcode::cntlzdx => fmt_x_unary_rc(instr, "cntlzd"), // ── Shift (32 / 64) ───────────────────────────────────────────────── - PpcOpcode::slwx => fmt_x_logic(instr, "slw"), - PpcOpcode::srwx => fmt_x_logic(instr, "srw"), - PpcOpcode::srawx => fmt_x_logic(instr, "sraw"), - PpcOpcode::sldx => fmt_x_logic(instr, "sld"), - PpcOpcode::srdx => fmt_x_logic(instr, "srd"), - PpcOpcode::sradx => fmt_x_logic(instr, "srad"), - PpcOpcode::srawix => fmt_srawi(instr), - PpcOpcode::sradix => fmt_sradi(instr), + PpcOpcode::slwx => fmt_x_logic(instr, "slw"), + PpcOpcode::srwx => fmt_x_logic(instr, "srw"), + PpcOpcode::srawx => fmt_x_logic(instr, "sraw"), + PpcOpcode::sldx => fmt_x_logic(instr, "sld"), + PpcOpcode::srdx => fmt_x_logic(instr, "srd"), + PpcOpcode::sradx => fmt_x_logic(instr, "srad"), + PpcOpcode::srawix => fmt_srawi(instr), + PpcOpcode::sradix => fmt_sradi(instr), // ── Special register moves ───────────────────────────────────────── PpcOpcode::mfspr => fmt_mfspr(instr), PpcOpcode::mtspr => fmt_mtspr(instr), - PpcOpcode::mfcr => fmt_mfcr(instr), + PpcOpcode::mfcr => fmt_mfcr(instr), PpcOpcode::mtcrf => fmt_mtcrf(instr), PpcOpcode::mfmsr => base("mfmsr", gpr(instr.rd()), 8), PpcOpcode::mtmsr => base("mtmsr", gpr(instr.rs()), 8), PpcOpcode::mtmsrd => base("mtmsrd", gpr(instr.rs()), 8), - PpcOpcode::mftb => fmt_mftb(instr), + PpcOpcode::mftb => fmt_mftb(instr), PpcOpcode::mcrxr => base("mcrxr", format!("cr{}", instr.crfd()), 8), - PpcOpcode::mcrf => base("mcrf", format!("cr{}, cr{}", instr.crfd(), instr.crfs()), 8), + PpcOpcode::mcrf => base("mcrf", format!("cr{}, cr{}", instr.crfd(), instr.crfs()), 8), // ── X-form indexed load/store ────────────────────────────────────── - PpcOpcode::lwzx => fmt_x_load(instr, "lwzx", false), - PpcOpcode::lwzux => fmt_x_load(instr, "lwzux", false), - PpcOpcode::lbzx => fmt_x_load(instr, "lbzx", false), - PpcOpcode::lbzux => fmt_x_load(instr, "lbzux", false), - PpcOpcode::lhzx => fmt_x_load(instr, "lhzx", false), - PpcOpcode::lhzux => fmt_x_load(instr, "lhzux", false), - PpcOpcode::lhax => fmt_x_load(instr, "lhax", false), - PpcOpcode::lhaux => fmt_x_load(instr, "lhaux", false), - PpcOpcode::lwax => fmt_x_load(instr, "lwax", false), - PpcOpcode::lwaux => fmt_x_load(instr, "lwaux", false), - PpcOpcode::ldx => fmt_x_load(instr, "ldx", false), - PpcOpcode::ldux => fmt_x_load(instr, "ldux", false), - PpcOpcode::lwbrx => fmt_x_load(instr, "lwbrx", false), - PpcOpcode::lhbrx => fmt_x_load(instr, "lhbrx", false), - PpcOpcode::ldbrx => fmt_x_load(instr, "ldbrx", false), - PpcOpcode::lwarx => fmt_x_load(instr, "lwarx", false), - PpcOpcode::ldarx => fmt_x_load(instr, "ldarx", false), - PpcOpcode::lswx => fmt_x_load(instr, "lswx", false), - PpcOpcode::lswi => fmt_lswi_stswi(instr, "lswi"), - PpcOpcode::lfsx => fmt_x_load(instr, "lfsx", true), - PpcOpcode::lfsux => fmt_x_load(instr, "lfsux", true), - PpcOpcode::lfdx => fmt_x_load(instr, "lfdx", true), - PpcOpcode::lfdux => fmt_x_load(instr, "lfdux", true), - PpcOpcode::stwx => fmt_x_store(instr, "stwx", false), - PpcOpcode::stwux => fmt_x_store(instr, "stwux", false), - PpcOpcode::stbx => fmt_x_store(instr, "stbx", false), - PpcOpcode::stbux => fmt_x_store(instr, "stbux", false), - PpcOpcode::sthx => fmt_x_store(instr, "sthx", false), - PpcOpcode::sthux => fmt_x_store(instr, "sthux", false), - PpcOpcode::stdx => fmt_x_store(instr, "stdx", false), - PpcOpcode::stdux => fmt_x_store(instr, "stdux", false), + PpcOpcode::lwzx => fmt_x_load(instr, "lwzx", false), + PpcOpcode::lwzux => fmt_x_load(instr, "lwzux", false), + PpcOpcode::lbzx => fmt_x_load(instr, "lbzx", false), + PpcOpcode::lbzux => fmt_x_load(instr, "lbzux", false), + PpcOpcode::lhzx => fmt_x_load(instr, "lhzx", false), + PpcOpcode::lhzux => fmt_x_load(instr, "lhzux", false), + PpcOpcode::lhax => fmt_x_load(instr, "lhax", false), + PpcOpcode::lhaux => fmt_x_load(instr, "lhaux", false), + PpcOpcode::lwax => fmt_x_load(instr, "lwax", false), + PpcOpcode::lwaux => fmt_x_load(instr, "lwaux", false), + PpcOpcode::ldx => fmt_x_load(instr, "ldx", false), + PpcOpcode::ldux => fmt_x_load(instr, "ldux", false), + PpcOpcode::lwbrx => fmt_x_load(instr, "lwbrx", false), + PpcOpcode::lhbrx => fmt_x_load(instr, "lhbrx", false), + PpcOpcode::ldbrx => fmt_x_load(instr, "ldbrx", false), + PpcOpcode::lwarx => fmt_x_load(instr, "lwarx", false), + PpcOpcode::ldarx => fmt_x_load(instr, "ldarx", false), + PpcOpcode::lswx => fmt_x_load(instr, "lswx", false), + PpcOpcode::lswi => fmt_lswi_stswi(instr, "lswi"), + PpcOpcode::lfsx => fmt_x_load(instr, "lfsx", true), + PpcOpcode::lfsux => fmt_x_load(instr, "lfsux", true), + PpcOpcode::lfdx => fmt_x_load(instr, "lfdx", true), + PpcOpcode::lfdux => fmt_x_load(instr, "lfdux", true), + PpcOpcode::stwx => fmt_x_store(instr, "stwx", false), + PpcOpcode::stwux => fmt_x_store(instr, "stwux", false), + PpcOpcode::stbx => fmt_x_store(instr, "stbx", false), + PpcOpcode::stbux => fmt_x_store(instr, "stbux", false), + PpcOpcode::sthx => fmt_x_store(instr, "sthx", false), + PpcOpcode::sthux => fmt_x_store(instr, "sthux", false), + PpcOpcode::stdx => fmt_x_store(instr, "stdx", false), + PpcOpcode::stdux => fmt_x_store(instr, "stdux", false), PpcOpcode::stwbrx => fmt_x_store(instr, "stwbrx", false), PpcOpcode::sthbrx => fmt_x_store(instr, "sthbrx", false), PpcOpcode::stdbrx => fmt_x_store(instr, "stdbrx", false), - PpcOpcode::stwcx => fmt_x_store(instr, "stwcx.", false), - PpcOpcode::stdcx => fmt_x_store(instr, "stdcx.", false), - PpcOpcode::stswx => fmt_x_store(instr, "stswx", false), - PpcOpcode::stswi => fmt_lswi_stswi(instr, "stswi"), - PpcOpcode::stfsx => fmt_x_store(instr, "stfsx", true), + PpcOpcode::stwcx => fmt_x_store(instr, "stwcx.", false), + PpcOpcode::stdcx => fmt_x_store(instr, "stdcx.", false), + PpcOpcode::stswx => fmt_x_store(instr, "stswx", false), + PpcOpcode::stswi => fmt_lswi_stswi(instr, "stswi"), + PpcOpcode::stfsx => fmt_x_store(instr, "stfsx", true), PpcOpcode::stfsux => fmt_x_store(instr, "stfsux", true), - PpcOpcode::stfdx => fmt_x_store(instr, "stfdx", true), + PpcOpcode::stfdx => fmt_x_store(instr, "stfdx", true), PpcOpcode::stfdux => fmt_x_store(instr, "stfdux", true), PpcOpcode::stfiwx => fmt_x_store(instr, "stfiwx", true), // ── Cache / sync ──────────────────────────────────────────────────── - PpcOpcode::dcbf => fmt_cache(instr, "dcbf"), - PpcOpcode::dcbi => fmt_cache(instr, "dcbi"), + PpcOpcode::dcbf => fmt_cache(instr, "dcbf"), + PpcOpcode::dcbi => fmt_cache(instr, "dcbi"), PpcOpcode::dcbst => fmt_cache(instr, "dcbst"), - PpcOpcode::dcbt => fmt_cache(instr, "dcbt"), + PpcOpcode::dcbt => fmt_cache(instr, "dcbt"), PpcOpcode::dcbtst => fmt_cache(instr, "dcbtst"), - PpcOpcode::dcbz => fmt_cache(instr, "dcbz"), + PpcOpcode::dcbz => fmt_cache(instr, "dcbz"), PpcOpcode::dcbz128 => fmt_cache(instr, "dcbz128"), - PpcOpcode::icbi => fmt_cache(instr, "icbi"), - PpcOpcode::sync => { + PpcOpcode::icbi => fmt_cache(instr, "icbi"), + PpcOpcode::sync => { // L-field at PPC bit 10 (host bit 21) selects lwsync (L=1), the // acquire barrier in every Xbox 360 spinlock. PPCBUG-641. if (instr.raw >> 21) & 1 == 1 { @@ -375,126 +407,203 @@ pub fn format(instr: &DecodedInstr) -> DisasmText { PpcOpcode::isync => base("isync", String::new(), 0), // ── CR logical ────────────────────────────────────────────────────── - PpcOpcode::crand => fmt_cr_logic(instr, "crand"), - PpcOpcode::crandc => fmt_cr_logic(instr, "crandc"), - PpcOpcode::creqv => fmt_creqv(instr), - PpcOpcode::crnand => fmt_cr_logic(instr, "crnand"), - PpcOpcode::crnor => fmt_crnor(instr), - PpcOpcode::cror => fmt_cror(instr), - PpcOpcode::crorc => fmt_cr_logic(instr, "crorc"), - PpcOpcode::crxor => fmt_crxor(instr), + PpcOpcode::crand => fmt_cr_logic(instr, "crand"), + PpcOpcode::crandc => fmt_cr_logic(instr, "crandc"), + PpcOpcode::creqv => fmt_creqv(instr), + PpcOpcode::crnand => fmt_cr_logic(instr, "crnand"), + PpcOpcode::crnor => fmt_crnor(instr), + PpcOpcode::cror => fmt_cror(instr), + PpcOpcode::crorc => fmt_cr_logic(instr, "crorc"), + PpcOpcode::crxor => fmt_crxor(instr), // ── FPU (op59 / op63) ────────────────────────────────────────────── - PpcOpcode::fdivsx => fmt_a_3op(instr, "fdivs", false), - PpcOpcode::fsubsx => fmt_a_3op(instr, "fsubs", false), - PpcOpcode::faddsx => fmt_a_3op(instr, "fadds", false), - PpcOpcode::fsqrtsx => fmt_a_unary(instr, "fsqrts"), - PpcOpcode::fresx => fmt_a_unary(instr, "fres"), - PpcOpcode::fmulsx => fmt_a_3op(instr, "fmuls", true), - PpcOpcode::fmsubsx => fmt_a_4op(instr, "fmsubs"), - PpcOpcode::fmaddsx => fmt_a_4op(instr, "fmadds"), + PpcOpcode::fdivsx => fmt_a_3op(instr, "fdivs", false), + PpcOpcode::fsubsx => fmt_a_3op(instr, "fsubs", false), + PpcOpcode::faddsx => fmt_a_3op(instr, "fadds", false), + PpcOpcode::fsqrtsx => fmt_a_unary(instr, "fsqrts"), + PpcOpcode::fresx => fmt_a_unary(instr, "fres"), + PpcOpcode::fmulsx => fmt_a_3op(instr, "fmuls", true), + PpcOpcode::fmsubsx => fmt_a_4op(instr, "fmsubs"), + PpcOpcode::fmaddsx => fmt_a_4op(instr, "fmadds"), PpcOpcode::fnmsubsx => fmt_a_4op(instr, "fnmsubs"), PpcOpcode::fnmaddsx => fmt_a_4op(instr, "fnmadds"), - PpcOpcode::fdivx => fmt_a_3op(instr, "fdiv", false), - PpcOpcode::fsubx => fmt_a_3op(instr, "fsub", false), - PpcOpcode::faddx => fmt_a_3op(instr, "fadd", false), - PpcOpcode::fsqrtx => fmt_a_unary(instr, "fsqrt"), - PpcOpcode::fselx => fmt_a_4op(instr, "fsel"), - PpcOpcode::fmulx => fmt_a_3op(instr, "fmul", true), + PpcOpcode::fdivx => fmt_a_3op(instr, "fdiv", false), + PpcOpcode::fsubx => fmt_a_3op(instr, "fsub", false), + PpcOpcode::faddx => fmt_a_3op(instr, "fadd", false), + PpcOpcode::fsqrtx => fmt_a_unary(instr, "fsqrt"), + PpcOpcode::fselx => fmt_a_4op(instr, "fsel"), + PpcOpcode::fmulx => fmt_a_3op(instr, "fmul", true), PpcOpcode::frsqrtex => fmt_a_unary(instr, "frsqrte"), - PpcOpcode::fmsubx => fmt_a_4op(instr, "fmsub"), - PpcOpcode::fmaddx => fmt_a_4op(instr, "fmadd"), - PpcOpcode::fnmsubx => fmt_a_4op(instr, "fnmsub"), - PpcOpcode::fnmaddx => fmt_a_4op(instr, "fnmadd"), + PpcOpcode::fmsubx => fmt_a_4op(instr, "fmsub"), + PpcOpcode::fmaddx => fmt_a_4op(instr, "fmadd"), + PpcOpcode::fnmsubx => fmt_a_4op(instr, "fnmsub"), + PpcOpcode::fnmaddx => fmt_a_4op(instr, "fnmadd"), - PpcOpcode::fcmpu => fmt_fcmp(instr, "fcmpu"), - PpcOpcode::fcmpo => fmt_fcmp(instr, "fcmpo"), - PpcOpcode::frspx => fmt_x_fpu_unary(instr, "frsp"), - PpcOpcode::fctiwx => fmt_x_fpu_unary(instr, "fctiw"), - PpcOpcode::fctiwzx => fmt_x_fpu_unary(instr, "fctiwz"), - PpcOpcode::fnegx => fmt_x_fpu_unary(instr, "fneg"), - PpcOpcode::fmrx => fmt_x_fpu_unary(instr, "fmr"), - PpcOpcode::fnabsx => fmt_x_fpu_unary(instr, "fnabs"), - PpcOpcode::fabsx => fmt_x_fpu_unary(instr, "fabs"), - PpcOpcode::fctidx => fmt_x_fpu_unary(instr, "fctid"), - PpcOpcode::fctidzx => fmt_x_fpu_unary(instr, "fctidz"), - PpcOpcode::fcfidx => fmt_x_fpu_unary(instr, "fcfid"), - PpcOpcode::mffsx => { + PpcOpcode::fcmpu => fmt_fcmp(instr, "fcmpu"), + PpcOpcode::fcmpo => fmt_fcmp(instr, "fcmpo"), + PpcOpcode::frspx => fmt_x_fpu_unary(instr, "frsp"), + PpcOpcode::fctiwx => fmt_x_fpu_unary(instr, "fctiw"), + PpcOpcode::fctiwzx => fmt_x_fpu_unary(instr, "fctiwz"), + PpcOpcode::fnegx => fmt_x_fpu_unary(instr, "fneg"), + PpcOpcode::fmrx => fmt_x_fpu_unary(instr, "fmr"), + PpcOpcode::fnabsx => fmt_x_fpu_unary(instr, "fnabs"), + PpcOpcode::fabsx => fmt_x_fpu_unary(instr, "fabs"), + PpcOpcode::fctidx => fmt_x_fpu_unary(instr, "fctid"), + PpcOpcode::fctidzx => fmt_x_fpu_unary(instr, "fctidz"), + PpcOpcode::fcfidx => fmt_x_fpu_unary(instr, "fcfid"), + PpcOpcode::mffsx => { let rc = rc_dot(instr); base(&format!("mffs{rc}"), fpr(instr.rd()), 8) } - PpcOpcode::mtfsfx => { + PpcOpcode::mtfsfx => { let rc = rc_dot(instr); let fxm = (instr.raw >> 17) & 0xFF; let frb = (instr.raw >> 11) & 0x1F; - base(&format!("mtfsf{rc}"), format!("0x{fxm:02X}, {}", fpr(frb as usize)), 8) + base( + &format!("mtfsf{rc}"), + format!("0x{fxm:02X}, {}", fpr(frb as usize)), + 8, + ) } - PpcOpcode::mtfsb1x => fmt_mtfsb(instr, "mtfsb1"), - PpcOpcode::mtfsb0x => fmt_mtfsb(instr, "mtfsb0"), - PpcOpcode::mtfsfix => { + PpcOpcode::mtfsb1x => fmt_mtfsb(instr, "mtfsb1"), + PpcOpcode::mtfsb0x => fmt_mtfsb(instr, "mtfsb0"), + PpcOpcode::mtfsfix => { let rc = rc_dot(instr); let bf = instr.crfd(); let imm = (instr.raw >> 12) & 0xF; base(&format!("mtfsfi{rc}"), format!("cr{bf}, {imm}"), 8) } - PpcOpcode::mcrfs => base("mcrfs", format!("cr{}, cr{}", instr.crfd(), instr.crfs()), 8), + PpcOpcode::mcrfs => base( + "mcrfs", + format!("cr{}, cr{}", instr.crfd(), instr.crfs()), + 8, + ), // ── Standard VMX (5-bit registers) ──────────────────────────────── // 3-operand VD, VA, VB // `vor vD,vA,vA` is the canonical vector register move, and // `vnor vD,vA,vA` the canonical vector complement. Both are extremely // common (1,535 and 9 sites here) and both read as noise in base form. - PpcOpcode::vor if instr.ra() == instr.rb() => { - fmt_vmx_move(instr, "vor", "vmr") - } - PpcOpcode::vnor if instr.ra() == instr.rb() => { - fmt_vmx_move(instr, "vnor", "vnot") - } - - PpcOpcode::vaddubm | PpcOpcode::vmaxub | PpcOpcode::vrlb | PpcOpcode::vmuloub | - PpcOpcode::vaddfp | PpcOpcode::vmrghb | PpcOpcode::vpkuhum | - PpcOpcode::vadduhm | PpcOpcode::vmaxuh | PpcOpcode::vrlh | PpcOpcode::vmulouh | - PpcOpcode::vsubfp | PpcOpcode::vmrghh | PpcOpcode::vpkuwum | - PpcOpcode::vadduwm | PpcOpcode::vmaxuw | PpcOpcode::vrlw | PpcOpcode::vmrghw | - PpcOpcode::vpkuhus | PpcOpcode::vpkuwus | - PpcOpcode::vmaxsb | PpcOpcode::vslb | PpcOpcode::vmulosb | PpcOpcode::vmrglb | - PpcOpcode::vpkshus | PpcOpcode::vmaxsh | PpcOpcode::vslh | PpcOpcode::vmulosh | - PpcOpcode::vmrglh | PpcOpcode::vpkswus | PpcOpcode::vaddcuw | PpcOpcode::vmaxsw | - PpcOpcode::vslw | PpcOpcode::vmrglw | PpcOpcode::vpkshss | PpcOpcode::vsl | - PpcOpcode::vpkswss | PpcOpcode::vaddubs | PpcOpcode::vminub | PpcOpcode::vsrb | - PpcOpcode::vmuleub | PpcOpcode::vadduhs | PpcOpcode::vminuh | PpcOpcode::vsrh | - PpcOpcode::vmuleuh | PpcOpcode::vadduws | PpcOpcode::vminuw | PpcOpcode::vsrw | - PpcOpcode::vsr | PpcOpcode::vaddsbs | PpcOpcode::vminsb | PpcOpcode::vsrab | - PpcOpcode::vmulesb | PpcOpcode::vpkpx | PpcOpcode::vaddshs | PpcOpcode::vminsh | - PpcOpcode::vsrah | PpcOpcode::vmulesh | PpcOpcode::vaddsws | PpcOpcode::vminsw | - PpcOpcode::vsraw | PpcOpcode::vsububm | PpcOpcode::vavgub | PpcOpcode::vand | - PpcOpcode::vmaxfp | PpcOpcode::vslo | PpcOpcode::vsubuhm | PpcOpcode::vavguh | - PpcOpcode::vandc | PpcOpcode::vminfp | PpcOpcode::vsro | PpcOpcode::vsubuwm | - PpcOpcode::vavguw | PpcOpcode::vor | PpcOpcode::vxor | PpcOpcode::vavgsb | - PpcOpcode::vnor | PpcOpcode::vavgsh | PpcOpcode::vsubcuw | PpcOpcode::vavgsw | - PpcOpcode::vsububs | PpcOpcode::vsum4ubs| PpcOpcode::vsubuhs | PpcOpcode::vsum4shs | - PpcOpcode::vsubuws | PpcOpcode::vsum2sws| PpcOpcode::vsubsbs | PpcOpcode::vsum4sbs | - PpcOpcode::vsubshs | PpcOpcode::vsubsws | PpcOpcode::vsumsws => { - fmt_vmx_3op(instr, opcode_name(instr.opcode)) - } + PpcOpcode::vor if instr.ra() == instr.rb() => fmt_vmx_move(instr, "vor", "vmr"), + PpcOpcode::vnor if instr.ra() == instr.rb() => fmt_vmx_move(instr, "vnor", "vnot"), + PpcOpcode::vaddubm + | PpcOpcode::vmaxub + | PpcOpcode::vrlb + | PpcOpcode::vmuloub + | PpcOpcode::vaddfp + | PpcOpcode::vmrghb + | PpcOpcode::vpkuhum + | PpcOpcode::vadduhm + | PpcOpcode::vmaxuh + | PpcOpcode::vrlh + | PpcOpcode::vmulouh + | PpcOpcode::vsubfp + | PpcOpcode::vmrghh + | PpcOpcode::vpkuwum + | PpcOpcode::vadduwm + | PpcOpcode::vmaxuw + | PpcOpcode::vrlw + | PpcOpcode::vmrghw + | PpcOpcode::vpkuhus + | PpcOpcode::vpkuwus + | PpcOpcode::vmaxsb + | PpcOpcode::vslb + | PpcOpcode::vmulosb + | PpcOpcode::vmrglb + | PpcOpcode::vpkshus + | PpcOpcode::vmaxsh + | PpcOpcode::vslh + | PpcOpcode::vmulosh + | PpcOpcode::vmrglh + | PpcOpcode::vpkswus + | PpcOpcode::vaddcuw + | PpcOpcode::vmaxsw + | PpcOpcode::vslw + | PpcOpcode::vmrglw + | PpcOpcode::vpkshss + | PpcOpcode::vsl + | PpcOpcode::vpkswss + | PpcOpcode::vaddubs + | PpcOpcode::vminub + | PpcOpcode::vsrb + | PpcOpcode::vmuleub + | PpcOpcode::vadduhs + | PpcOpcode::vminuh + | PpcOpcode::vsrh + | PpcOpcode::vmuleuh + | PpcOpcode::vadduws + | PpcOpcode::vminuw + | PpcOpcode::vsrw + | PpcOpcode::vsr + | PpcOpcode::vaddsbs + | PpcOpcode::vminsb + | PpcOpcode::vsrab + | PpcOpcode::vmulesb + | PpcOpcode::vpkpx + | PpcOpcode::vaddshs + | PpcOpcode::vminsh + | PpcOpcode::vsrah + | PpcOpcode::vmulesh + | PpcOpcode::vaddsws + | PpcOpcode::vminsw + | PpcOpcode::vsraw + | PpcOpcode::vsububm + | PpcOpcode::vavgub + | PpcOpcode::vand + | PpcOpcode::vmaxfp + | PpcOpcode::vslo + | PpcOpcode::vsubuhm + | PpcOpcode::vavguh + | PpcOpcode::vandc + | PpcOpcode::vminfp + | PpcOpcode::vsro + | PpcOpcode::vsubuwm + | PpcOpcode::vavguw + | PpcOpcode::vor + | PpcOpcode::vxor + | PpcOpcode::vavgsb + | PpcOpcode::vnor + | PpcOpcode::vavgsh + | PpcOpcode::vsubcuw + | PpcOpcode::vavgsw + | PpcOpcode::vsububs + | PpcOpcode::vsum4ubs + | PpcOpcode::vsubuhs + | PpcOpcode::vsum4shs + | PpcOpcode::vsubuws + | PpcOpcode::vsum2sws + | PpcOpcode::vsubsbs + | PpcOpcode::vsum4sbs + | PpcOpcode::vsubshs + | PpcOpcode::vsubsws + | PpcOpcode::vsumsws => fmt_vmx_3op(instr, opcode_name(instr.opcode)), // VMX unary VD, VB - PpcOpcode::vrefp | PpcOpcode::vrsqrtefp | PpcOpcode::vexptefp | - PpcOpcode::vlogefp | PpcOpcode::vrfin | PpcOpcode::vrfiz | - PpcOpcode::vrfip | PpcOpcode::vrfim | PpcOpcode::vupkhsb | - PpcOpcode::vupkhsh | PpcOpcode::vupklsb | PpcOpcode::vupklsh | - PpcOpcode::vupkhpx | PpcOpcode::vupklpx => { - fmt_vmx_unary(instr, opcode_name(instr.opcode)) - } + PpcOpcode::vrefp + | PpcOpcode::vrsqrtefp + | PpcOpcode::vexptefp + | PpcOpcode::vlogefp + | PpcOpcode::vrfin + | PpcOpcode::vrfiz + | PpcOpcode::vrfip + | PpcOpcode::vrfim + | PpcOpcode::vupkhsb + | PpcOpcode::vupkhsh + | PpcOpcode::vupklsb + | PpcOpcode::vupklsh + | PpcOpcode::vupkhpx + | PpcOpcode::vupklpx => fmt_vmx_unary(instr, opcode_name(instr.opcode)), // VMX VD, VB, UIMM (VA = uimm field) - PpcOpcode::vspltb | PpcOpcode::vsplth | PpcOpcode::vspltw | - PpcOpcode::vcfux | PpcOpcode::vcfsx | - PpcOpcode::vctuxs | PpcOpcode::vctsxs => { - fmt_vmx_uimm(instr, opcode_name(instr.opcode)) - } + PpcOpcode::vspltb + | PpcOpcode::vsplth + | PpcOpcode::vspltw + | PpcOpcode::vcfux + | PpcOpcode::vcfsx + | PpcOpcode::vctuxs + | PpcOpcode::vctsxs => fmt_vmx_uimm(instr, opcode_name(instr.opcode)), // VMX VD, SIMM (VA field as 5-bit signed immediate) PpcOpcode::vspltisb => fmt_vmx_simm(instr, "vspltisb"), @@ -505,112 +614,125 @@ pub fn format(instr: &DecodedInstr) -> DisasmText { PpcOpcode::mtvscr => base("mtvscr", vr(instr.rb()), 8), // VMX compare (Rc bit at bit 21) - PpcOpcode::vcmpequb | PpcOpcode::vcmpequh | PpcOpcode::vcmpequw | - PpcOpcode::vcmpeqfp | PpcOpcode::vcmpgefp | PpcOpcode::vcmpgtub | - PpcOpcode::vcmpgtuh | PpcOpcode::vcmpgtuw | PpcOpcode::vcmpgtfp | - PpcOpcode::vcmpgtsb | PpcOpcode::vcmpgtsh | PpcOpcode::vcmpgtsw | - PpcOpcode::vcmpbfp => fmt_vmx_cmp(instr, opcode_name(instr.opcode)), + PpcOpcode::vcmpequb + | PpcOpcode::vcmpequh + | PpcOpcode::vcmpequw + | PpcOpcode::vcmpeqfp + | PpcOpcode::vcmpgefp + | PpcOpcode::vcmpgtub + | PpcOpcode::vcmpgtuh + | PpcOpcode::vcmpgtuw + | PpcOpcode::vcmpgtfp + | PpcOpcode::vcmpgtsb + | PpcOpcode::vcmpgtsh + | PpcOpcode::vcmpgtsw + | PpcOpcode::vcmpbfp => fmt_vmx_cmp(instr, opcode_name(instr.opcode)), // VMX 4-operand VD, VA, VB, VC - PpcOpcode::vmhaddshs | PpcOpcode::vmhraddshs | PpcOpcode::vmladduhm | - PpcOpcode::vmsumubm | PpcOpcode::vmsummbm | PpcOpcode::vmsumuhm | - PpcOpcode::vmsumuhs | PpcOpcode::vmsumshm | PpcOpcode::vmsumshs | - PpcOpcode::vsel | PpcOpcode::vperm => { - fmt_vmx_4op(instr, opcode_name(instr.opcode)) - } + PpcOpcode::vmhaddshs + | PpcOpcode::vmhraddshs + | PpcOpcode::vmladduhm + | PpcOpcode::vmsumubm + | PpcOpcode::vmsummbm + | PpcOpcode::vmsumuhm + | PpcOpcode::vmsumuhs + | PpcOpcode::vmsumshm + | PpcOpcode::vmsumshs + | PpcOpcode::vsel + | PpcOpcode::vperm => fmt_vmx_4op(instr, opcode_name(instr.opcode)), - PpcOpcode::vsldoi => fmt_vsldoi(instr), - PpcOpcode::vmaddfp => fmt_vmx_4op_swap(instr, "vmaddfp"), + PpcOpcode::vsldoi => fmt_vsldoi(instr), + PpcOpcode::vmaddfp => fmt_vmx_4op_swap(instr, "vmaddfp"), PpcOpcode::vnmsubfp => fmt_vmx_4op_swap(instr, "vnmsubfp"), // ── VMX128 load/store (uses GPR addressing + vd128 dest) ─────────── - PpcOpcode::lvsl128 => fmt_vmx128_ls(instr, "lvsl128"), - PpcOpcode::lvsr128 => fmt_vmx128_ls(instr, "lvsr128"), - PpcOpcode::lvewx128 => fmt_vmx128_ls(instr, "lvewx128"), - PpcOpcode::lvx128 => fmt_vmx128_ls(instr, "lvx128"), - PpcOpcode::lvxl128 => fmt_vmx128_ls(instr, "lvxl128"), - PpcOpcode::lvlx128 => fmt_vmx128_ls(instr, "lvlx128"), - PpcOpcode::lvrx128 => fmt_vmx128_ls(instr, "lvrx128"), - PpcOpcode::lvlxl128 => fmt_vmx128_ls(instr, "lvlxl128"), - PpcOpcode::lvrxl128 => fmt_vmx128_ls(instr, "lvrxl128"), + PpcOpcode::lvsl128 => fmt_vmx128_ls(instr, "lvsl128"), + PpcOpcode::lvsr128 => fmt_vmx128_ls(instr, "lvsr128"), + PpcOpcode::lvewx128 => fmt_vmx128_ls(instr, "lvewx128"), + PpcOpcode::lvx128 => fmt_vmx128_ls(instr, "lvx128"), + PpcOpcode::lvxl128 => fmt_vmx128_ls(instr, "lvxl128"), + PpcOpcode::lvlx128 => fmt_vmx128_ls(instr, "lvlx128"), + PpcOpcode::lvrx128 => fmt_vmx128_ls(instr, "lvrx128"), + PpcOpcode::lvlxl128 => fmt_vmx128_ls(instr, "lvlxl128"), + PpcOpcode::lvrxl128 => fmt_vmx128_ls(instr, "lvrxl128"), PpcOpcode::stvewx128 => fmt_vmx128_ls(instr, "stvewx128"), - PpcOpcode::stvx128 => fmt_vmx128_ls(instr, "stvx128"), - PpcOpcode::stvxl128 => fmt_vmx128_ls(instr, "stvxl128"), - PpcOpcode::stvlx128 => fmt_vmx128_ls(instr, "stvlx128"), - PpcOpcode::stvrx128 => fmt_vmx128_ls(instr, "stvrx128"), + PpcOpcode::stvx128 => fmt_vmx128_ls(instr, "stvx128"), + PpcOpcode::stvxl128 => fmt_vmx128_ls(instr, "stvxl128"), + PpcOpcode::stvlx128 => fmt_vmx128_ls(instr, "stvlx128"), + PpcOpcode::stvrx128 => fmt_vmx128_ls(instr, "stvrx128"), PpcOpcode::stvlxl128 => fmt_vmx128_ls(instr, "stvlxl128"), PpcOpcode::stvrxl128 => fmt_vmx128_ls(instr, "stvrxl128"), // Standard AltiVec load/store indexed (5-bit vr0-vr31) - PpcOpcode::lvsl => fmt_vmx_ls(instr, "lvsl"), - PpcOpcode::lvsr => fmt_vmx_ls(instr, "lvsr"), - PpcOpcode::lvebx => fmt_vmx_ls(instr, "lvebx"), - PpcOpcode::lvehx => fmt_vmx_ls(instr, "lvehx"), - PpcOpcode::lvewx => fmt_vmx_ls(instr, "lvewx"), - PpcOpcode::lvx => fmt_vmx_ls(instr, "lvx"), - PpcOpcode::lvxl => fmt_vmx_ls(instr, "lvxl"), - PpcOpcode::lvlx => fmt_vmx_ls(instr, "lvlx"), - PpcOpcode::lvrx => fmt_vmx_ls(instr, "lvrx"), - PpcOpcode::lvlxl => fmt_vmx_ls(instr, "lvlxl"), - PpcOpcode::lvrxl => fmt_vmx_ls(instr, "lvrxl"), - PpcOpcode::stvebx => fmt_vmx_ls(instr, "stvebx"), - PpcOpcode::stvehx => fmt_vmx_ls(instr, "stvehx"), - PpcOpcode::stvewx => fmt_vmx_ls(instr, "stvewx"), - PpcOpcode::stvx => fmt_vmx_ls(instr, "stvx"), - PpcOpcode::stvxl => fmt_vmx_ls(instr, "stvxl"), - PpcOpcode::stvlx => fmt_vmx_ls(instr, "stvlx"), - PpcOpcode::stvrx => fmt_vmx_ls(instr, "stvrx"), - PpcOpcode::stvlxl => fmt_vmx_ls(instr, "stvlxl"), - PpcOpcode::stvrxl => fmt_vmx_ls(instr, "stvrxl"), + PpcOpcode::lvsl => fmt_vmx_ls(instr, "lvsl"), + PpcOpcode::lvsr => fmt_vmx_ls(instr, "lvsr"), + PpcOpcode::lvebx => fmt_vmx_ls(instr, "lvebx"), + PpcOpcode::lvehx => fmt_vmx_ls(instr, "lvehx"), + PpcOpcode::lvewx => fmt_vmx_ls(instr, "lvewx"), + PpcOpcode::lvx => fmt_vmx_ls(instr, "lvx"), + PpcOpcode::lvxl => fmt_vmx_ls(instr, "lvxl"), + PpcOpcode::lvlx => fmt_vmx_ls(instr, "lvlx"), + PpcOpcode::lvrx => fmt_vmx_ls(instr, "lvrx"), + PpcOpcode::lvlxl => fmt_vmx_ls(instr, "lvlxl"), + PpcOpcode::lvrxl => fmt_vmx_ls(instr, "lvrxl"), + PpcOpcode::stvebx => fmt_vmx_ls(instr, "stvebx"), + PpcOpcode::stvehx => fmt_vmx_ls(instr, "stvehx"), + PpcOpcode::stvewx => fmt_vmx_ls(instr, "stvewx"), + PpcOpcode::stvx => fmt_vmx_ls(instr, "stvx"), + PpcOpcode::stvxl => fmt_vmx_ls(instr, "stvxl"), + PpcOpcode::stvlx => fmt_vmx_ls(instr, "stvlx"), + PpcOpcode::stvrx => fmt_vmx_ls(instr, "stvrx"), + PpcOpcode::stvlxl => fmt_vmx_ls(instr, "stvlxl"), + PpcOpcode::stvrxl => fmt_vmx_ls(instr, "stvrxl"), // ── VMX128 op5 (3-op and 4-op fp/pack/logic) ─────────────────────── - PpcOpcode::vaddfp128 => fmt_vmx128_3op(instr, "vaddfp128"), - PpcOpcode::vsubfp128 => fmt_vmx128_3op(instr, "vsubfp128"), - PpcOpcode::vmulfp128 => fmt_vmx128_3op(instr, "vmulfp128"), + PpcOpcode::vaddfp128 => fmt_vmx128_3op(instr, "vaddfp128"), + PpcOpcode::vsubfp128 => fmt_vmx128_3op(instr, "vsubfp128"), + PpcOpcode::vmulfp128 => fmt_vmx128_3op(instr, "vmulfp128"), PpcOpcode::vmsum3fp128 => fmt_vmx128_3op(instr, "vmsum3fp128"), PpcOpcode::vmsum4fp128 => fmt_vmx128_3op(instr, "vmsum4fp128"), - PpcOpcode::vpkshss128 => fmt_vmx128_3op(instr, "vpkshss128"), - PpcOpcode::vpkshus128 => fmt_vmx128_3op(instr, "vpkshus128"), - PpcOpcode::vpkswss128 => fmt_vmx128_3op(instr, "vpkswss128"), - PpcOpcode::vpkswus128 => fmt_vmx128_3op(instr, "vpkswus128"), - PpcOpcode::vpkuhum128 => fmt_vmx128_3op(instr, "vpkuhum128"), - PpcOpcode::vpkuhus128 => fmt_vmx128_3op(instr, "vpkuhus128"), - PpcOpcode::vpkuwum128 => fmt_vmx128_3op(instr, "vpkuwum128"), - PpcOpcode::vpkuwus128 => fmt_vmx128_3op(instr, "vpkuwus128"), - PpcOpcode::vand128 => fmt_vmx128_3op(instr, "vand128"), - PpcOpcode::vandc128 => fmt_vmx128_3op(instr, "vandc128"), - PpcOpcode::vnor128 => fmt_vmx128_3op(instr, "vnor128"), - PpcOpcode::vor128 => fmt_vmx128_3op(instr, "vor128"), - PpcOpcode::vxor128 => fmt_vmx128_3op(instr, "vxor128"), - PpcOpcode::vsel128 => fmt_vmx128_3op(instr, "vsel128"), - PpcOpcode::vslo128 => fmt_vmx128_3op(instr, "vslo128"), - PpcOpcode::vsro128 => fmt_vmx128_3op(instr, "vsro128"), + PpcOpcode::vpkshss128 => fmt_vmx128_3op(instr, "vpkshss128"), + PpcOpcode::vpkshus128 => fmt_vmx128_3op(instr, "vpkshus128"), + PpcOpcode::vpkswss128 => fmt_vmx128_3op(instr, "vpkswss128"), + PpcOpcode::vpkswus128 => fmt_vmx128_3op(instr, "vpkswus128"), + PpcOpcode::vpkuhum128 => fmt_vmx128_3op(instr, "vpkuhum128"), + PpcOpcode::vpkuhus128 => fmt_vmx128_3op(instr, "vpkuhus128"), + PpcOpcode::vpkuwum128 => fmt_vmx128_3op(instr, "vpkuwum128"), + PpcOpcode::vpkuwus128 => fmt_vmx128_3op(instr, "vpkuwus128"), + PpcOpcode::vand128 => fmt_vmx128_3op(instr, "vand128"), + PpcOpcode::vandc128 => fmt_vmx128_3op(instr, "vandc128"), + PpcOpcode::vnor128 => fmt_vmx128_3op(instr, "vnor128"), + PpcOpcode::vor128 => fmt_vmx128_3op(instr, "vor128"), + PpcOpcode::vxor128 => fmt_vmx128_3op(instr, "vxor128"), + PpcOpcode::vsel128 => fmt_vmx128_3op(instr, "vsel128"), + PpcOpcode::vslo128 => fmt_vmx128_3op(instr, "vslo128"), + PpcOpcode::vsro128 => fmt_vmx128_3op(instr, "vsro128"), - PpcOpcode::vmaddfp128 => fmt_vmaddfp128(instr), - PpcOpcode::vmaddcfp128 => fmt_vmx128_madd_vd_vb(instr, "vmaddcfp128"), - PpcOpcode::vnmsubfp128 => fmt_vmx128_madd_vd_vb(instr, "vnmsubfp128"), + PpcOpcode::vmaddfp128 => fmt_vmaddfp128(instr), + PpcOpcode::vmaddcfp128 => fmt_vmx128_madd_vd_vb(instr, "vmaddcfp128"), + PpcOpcode::vnmsubfp128 => fmt_vmx128_madd_vd_vb(instr, "vnmsubfp128"), - PpcOpcode::vperm128 => fmt_vperm128(instr), - PpcOpcode::vsldoi128 => fmt_vsldoi128(instr), - PpcOpcode::vpermwi128 => fmt_vpermwi128(instr), + PpcOpcode::vperm128 => fmt_vperm128(instr), + PpcOpcode::vsldoi128 => fmt_vsldoi128(instr), + PpcOpcode::vpermwi128 => fmt_vpermwi128(instr), // ── VMX128 op6 special ───────────────────────────────────────────── - PpcOpcode::vpkd3d128 => fmt_vmx128_pack_d3d(instr, "vpkd3d128"), - PpcOpcode::vrlimi128 => fmt_vmx128_pack_d3d(instr, "vrlimi128"), - PpcOpcode::vrfim128 => fmt_vmx128_unary(instr, "vrfim128"), - PpcOpcode::vrfin128 => fmt_vmx128_unary(instr, "vrfin128"), - PpcOpcode::vrfip128 => fmt_vmx128_unary(instr, "vrfip128"), - PpcOpcode::vrfiz128 => fmt_vmx128_unary(instr, "vrfiz128"), - PpcOpcode::vrefp128 => fmt_vmx128_unary(instr, "vrefp128"), + PpcOpcode::vpkd3d128 => fmt_vmx128_pack_d3d(instr, "vpkd3d128"), + PpcOpcode::vrlimi128 => fmt_vmx128_pack_d3d(instr, "vrlimi128"), + PpcOpcode::vrfim128 => fmt_vmx128_unary(instr, "vrfim128"), + PpcOpcode::vrfin128 => fmt_vmx128_unary(instr, "vrfin128"), + PpcOpcode::vrfip128 => fmt_vmx128_unary(instr, "vrfip128"), + PpcOpcode::vrfiz128 => fmt_vmx128_unary(instr, "vrfiz128"), + PpcOpcode::vrefp128 => fmt_vmx128_unary(instr, "vrefp128"), PpcOpcode::vrsqrtefp128 => fmt_vmx128_unary(instr, "vrsqrtefp128"), PpcOpcode::vexptefp128 => fmt_vmx128_unary(instr, "vexptefp128"), - PpcOpcode::vlogefp128 => fmt_vmx128_unary(instr, "vlogefp128"), + PpcOpcode::vlogefp128 => fmt_vmx128_unary(instr, "vlogefp128"), PpcOpcode::vcfpsxws128 => fmt_vmx128_uimm(instr, "vcfpsxws128"), PpcOpcode::vcfpuxws128 => fmt_vmx128_uimm(instr, "vcfpuxws128"), - PpcOpcode::vcsxwfp128 => fmt_vmx128_uimm(instr, "vcsxwfp128"), - PpcOpcode::vcuxwfp128 => fmt_vmx128_uimm(instr, "vcuxwfp128"), - PpcOpcode::vspltw128 => fmt_vmx128_uimm(instr, "vspltw128"), - PpcOpcode::vupkd3d128 => fmt_vmx128_uimm(instr, "vupkd3d128"), + PpcOpcode::vcsxwfp128 => fmt_vmx128_uimm(instr, "vcsxwfp128"), + PpcOpcode::vcuxwfp128 => fmt_vmx128_uimm(instr, "vcuxwfp128"), + PpcOpcode::vspltw128 => fmt_vmx128_uimm(instr, "vspltw128"), + PpcOpcode::vupkd3d128 => fmt_vmx128_uimm(instr, "vupkd3d128"), PpcOpcode::vspltisw128 => { let vd = instr.vd128(); let simm = sign_ext(extract_vx128_uimm5(instr.raw), 5); @@ -619,18 +741,18 @@ pub fn format(instr: &DecodedInstr) -> DisasmText { PpcOpcode::vcmpeqfp128 => fmt_vmx128_cmp(instr, "vcmpeqfp128"), PpcOpcode::vcmpgefp128 => fmt_vmx128_cmp(instr, "vcmpgefp128"), PpcOpcode::vcmpgtfp128 => fmt_vmx128_cmp(instr, "vcmpgtfp128"), - PpcOpcode::vcmpbfp128 => fmt_vmx128_cmp(instr, "vcmpbfp128"), + PpcOpcode::vcmpbfp128 => fmt_vmx128_cmp(instr, "vcmpbfp128"), PpcOpcode::vcmpequw128 => fmt_vmx128_cmp(instr, "vcmpequw128"), - PpcOpcode::vrlw128 => fmt_vmx128_3op(instr, "vrlw128"), - PpcOpcode::vslw128 => fmt_vmx128_3op(instr, "vslw128"), - PpcOpcode::vsraw128 => fmt_vmx128_3op(instr, "vsraw128"), - PpcOpcode::vsrw128 => fmt_vmx128_3op(instr, "vsrw128"), - PpcOpcode::vmaxfp128 => fmt_vmx128_3op(instr, "vmaxfp128"), - PpcOpcode::vminfp128 => fmt_vmx128_3op(instr, "vminfp128"), - PpcOpcode::vmrghw128 => fmt_vmx128_3op(instr, "vmrghw128"), - PpcOpcode::vmrglw128 => fmt_vmx128_3op(instr, "vmrglw128"), - PpcOpcode::vupkhsb128 => fmt_vmx128_3op(instr, "vupkhsb128"), - PpcOpcode::vupklsb128 => fmt_vmx128_3op(instr, "vupklsb128"), + PpcOpcode::vrlw128 => fmt_vmx128_3op(instr, "vrlw128"), + PpcOpcode::vslw128 => fmt_vmx128_3op(instr, "vslw128"), + PpcOpcode::vsraw128 => fmt_vmx128_3op(instr, "vsraw128"), + PpcOpcode::vsrw128 => fmt_vmx128_3op(instr, "vsrw128"), + PpcOpcode::vmaxfp128 => fmt_vmx128_3op(instr, "vmaxfp128"), + PpcOpcode::vminfp128 => fmt_vmx128_3op(instr, "vminfp128"), + PpcOpcode::vmrghw128 => fmt_vmx128_3op(instr, "vmrghw128"), + PpcOpcode::vmrglw128 => fmt_vmx128_3op(instr, "vmrglw128"), + PpcOpcode::vupkhsb128 => fmt_vmx128_3op(instr, "vupkhsb128"), + PpcOpcode::vupklsb128 => fmt_vmx128_3op(instr, "vupklsb128"), PpcOpcode::Invalid => long_word(instr.raw), } @@ -653,7 +775,10 @@ pub fn disassemble_block(data: &[u8], base_addr: u32, count: usize) -> Vec<(u32, break; } let raw = u32::from_be_bytes([ - data[offset], data[offset + 1], data[offset + 2], data[offset + 3], + data[offset], + data[offset + 1], + data[offset + 2], + data[offset + 3], ]); let addr = base_addr + offset as u32; let instr = crate::decoder::decode(raw, addr); @@ -687,7 +812,12 @@ pub fn iter_disasm( va_start: u32, va_end: u32, ) -> impl Iterator + '_ { - DisasmIter { image, image_base, va: va_start, end: va_end } + DisasmIter { + image, + image_base, + va: va_start, + end: va_end, + } } struct DisasmIter<'a> { @@ -718,7 +848,12 @@ impl Iterator for DisasmIter<'_> { let decoded = crate::decoder::decode(raw, abs); let text = format(&decoded); self.va = self.va.wrapping_add(4); - Some(DisasmItem { addr: abs, raw, opcode: decoded.opcode, text }) + Some(DisasmItem { + addr: abs, + raw, + opcode: decoded.opcode, + text, + }) } } @@ -728,69 +863,146 @@ fn opcode_name(op: PpcOpcode) -> &'static str { // Used for VMX where the enum variant name matches the canonical mnemonic. // For ALU/FPU variants ending in "x", use hardcoded strings instead. match op { - PpcOpcode::vaddubm => "vaddubm", PpcOpcode::vmaxub => "vmaxub", PpcOpcode::vrlb => "vrlb", - PpcOpcode::vmuloub => "vmuloub", PpcOpcode::vaddfp => "vaddfp", PpcOpcode::vmrghb => "vmrghb", - PpcOpcode::vpkuhum => "vpkuhum", PpcOpcode::vadduhm => "vadduhm", PpcOpcode::vmaxuh => "vmaxuh", - PpcOpcode::vrlh => "vrlh", PpcOpcode::vmulouh => "vmulouh", PpcOpcode::vsubfp => "vsubfp", - PpcOpcode::vmrghh => "vmrghh", PpcOpcode::vpkuwum => "vpkuwum", - PpcOpcode::vadduwm => "vadduwm", PpcOpcode::vmaxuw => "vmaxuw", PpcOpcode::vrlw => "vrlw", - PpcOpcode::vmrghw => "vmrghw", PpcOpcode::vpkuhus => "vpkuhus", PpcOpcode::vpkuwus => "vpkuwus", - PpcOpcode::vmaxsb => "vmaxsb", PpcOpcode::vslb => "vslb", PpcOpcode::vmulosb => "vmulosb", - PpcOpcode::vmrglb => "vmrglb", PpcOpcode::vpkshus => "vpkshus", PpcOpcode::vmaxsh => "vmaxsh", - PpcOpcode::vslh => "vslh", PpcOpcode::vmulosh => "vmulosh", PpcOpcode::vmrglh => "vmrglh", - PpcOpcode::vpkswus => "vpkswus", PpcOpcode::vaddcuw => "vaddcuw", PpcOpcode::vmaxsw => "vmaxsw", - PpcOpcode::vslw => "vslw", PpcOpcode::vmrglw => "vmrglw", PpcOpcode::vpkshss => "vpkshss", - PpcOpcode::vsl => "vsl", PpcOpcode::vpkswss => "vpkswss", - PpcOpcode::vaddubs => "vaddubs", PpcOpcode::vminub => "vminub", PpcOpcode::vsrb => "vsrb", - PpcOpcode::vmuleub => "vmuleub", PpcOpcode::vadduhs => "vadduhs", PpcOpcode::vminuh => "vminuh", - PpcOpcode::vsrh => "vsrh", PpcOpcode::vmuleuh => "vmuleuh", - PpcOpcode::vadduws => "vadduws", PpcOpcode::vminuw => "vminuw", PpcOpcode::vsrw => "vsrw", + PpcOpcode::vaddubm => "vaddubm", + PpcOpcode::vmaxub => "vmaxub", + PpcOpcode::vrlb => "vrlb", + PpcOpcode::vmuloub => "vmuloub", + PpcOpcode::vaddfp => "vaddfp", + PpcOpcode::vmrghb => "vmrghb", + PpcOpcode::vpkuhum => "vpkuhum", + PpcOpcode::vadduhm => "vadduhm", + PpcOpcode::vmaxuh => "vmaxuh", + PpcOpcode::vrlh => "vrlh", + PpcOpcode::vmulouh => "vmulouh", + PpcOpcode::vsubfp => "vsubfp", + PpcOpcode::vmrghh => "vmrghh", + PpcOpcode::vpkuwum => "vpkuwum", + PpcOpcode::vadduwm => "vadduwm", + PpcOpcode::vmaxuw => "vmaxuw", + PpcOpcode::vrlw => "vrlw", + PpcOpcode::vmrghw => "vmrghw", + PpcOpcode::vpkuhus => "vpkuhus", + PpcOpcode::vpkuwus => "vpkuwus", + PpcOpcode::vmaxsb => "vmaxsb", + PpcOpcode::vslb => "vslb", + PpcOpcode::vmulosb => "vmulosb", + PpcOpcode::vmrglb => "vmrglb", + PpcOpcode::vpkshus => "vpkshus", + PpcOpcode::vmaxsh => "vmaxsh", + PpcOpcode::vslh => "vslh", + PpcOpcode::vmulosh => "vmulosh", + PpcOpcode::vmrglh => "vmrglh", + PpcOpcode::vpkswus => "vpkswus", + PpcOpcode::vaddcuw => "vaddcuw", + PpcOpcode::vmaxsw => "vmaxsw", + PpcOpcode::vslw => "vslw", + PpcOpcode::vmrglw => "vmrglw", + PpcOpcode::vpkshss => "vpkshss", + PpcOpcode::vsl => "vsl", + PpcOpcode::vpkswss => "vpkswss", + PpcOpcode::vaddubs => "vaddubs", + PpcOpcode::vminub => "vminub", + PpcOpcode::vsrb => "vsrb", + PpcOpcode::vmuleub => "vmuleub", + PpcOpcode::vadduhs => "vadduhs", + PpcOpcode::vminuh => "vminuh", + PpcOpcode::vsrh => "vsrh", + PpcOpcode::vmuleuh => "vmuleuh", + PpcOpcode::vadduws => "vadduws", + PpcOpcode::vminuw => "vminuw", + PpcOpcode::vsrw => "vsrw", PpcOpcode::vsr => "vsr", - PpcOpcode::vaddsbs => "vaddsbs", PpcOpcode::vminsb => "vminsb", PpcOpcode::vsrab => "vsrab", - PpcOpcode::vmulesb => "vmulesb", PpcOpcode::vpkpx => "vpkpx", - PpcOpcode::vaddshs => "vaddshs", PpcOpcode::vminsh => "vminsh", PpcOpcode::vsrah => "vsrah", + PpcOpcode::vaddsbs => "vaddsbs", + PpcOpcode::vminsb => "vminsb", + PpcOpcode::vsrab => "vsrab", + PpcOpcode::vmulesb => "vmulesb", + PpcOpcode::vpkpx => "vpkpx", + PpcOpcode::vaddshs => "vaddshs", + PpcOpcode::vminsh => "vminsh", + PpcOpcode::vsrah => "vsrah", PpcOpcode::vmulesh => "vmulesh", - PpcOpcode::vaddsws => "vaddsws", PpcOpcode::vminsw => "vminsw", PpcOpcode::vsraw => "vsraw", - PpcOpcode::vsububm => "vsububm", PpcOpcode::vavgub => "vavgub", PpcOpcode::vand => "vand", - PpcOpcode::vmaxfp => "vmaxfp", PpcOpcode::vslo => "vslo", - PpcOpcode::vsubuhm => "vsubuhm", PpcOpcode::vavguh => "vavguh", PpcOpcode::vandc => "vandc", - PpcOpcode::vminfp => "vminfp", PpcOpcode::vsro => "vsro", - PpcOpcode::vsubuwm => "vsubuwm", PpcOpcode::vavguw => "vavguw", PpcOpcode::vor => "vor", - PpcOpcode::vxor => "vxor", PpcOpcode::vavgsb => "vavgsb", PpcOpcode::vnor => "vnor", - PpcOpcode::vavgsh => "vavgsh", PpcOpcode::vsubcuw => "vsubcuw", PpcOpcode::vavgsw => "vavgsw", - PpcOpcode::vsububs => "vsububs", PpcOpcode::vsum4ubs => "vsum4ubs", - PpcOpcode::vsubuhs => "vsubuhs", PpcOpcode::vsum4shs => "vsum4shs", - PpcOpcode::vsubuws => "vsubuws", PpcOpcode::vsum2sws => "vsum2sws", - PpcOpcode::vsubsbs => "vsubsbs", PpcOpcode::vsum4sbs => "vsum4sbs", - PpcOpcode::vsubshs => "vsubshs", PpcOpcode::vsubsws => "vsubsws", + PpcOpcode::vaddsws => "vaddsws", + PpcOpcode::vminsw => "vminsw", + PpcOpcode::vsraw => "vsraw", + PpcOpcode::vsububm => "vsububm", + PpcOpcode::vavgub => "vavgub", + PpcOpcode::vand => "vand", + PpcOpcode::vmaxfp => "vmaxfp", + PpcOpcode::vslo => "vslo", + PpcOpcode::vsubuhm => "vsubuhm", + PpcOpcode::vavguh => "vavguh", + PpcOpcode::vandc => "vandc", + PpcOpcode::vminfp => "vminfp", + PpcOpcode::vsro => "vsro", + PpcOpcode::vsubuwm => "vsubuwm", + PpcOpcode::vavguw => "vavguw", + PpcOpcode::vor => "vor", + PpcOpcode::vxor => "vxor", + PpcOpcode::vavgsb => "vavgsb", + PpcOpcode::vnor => "vnor", + PpcOpcode::vavgsh => "vavgsh", + PpcOpcode::vsubcuw => "vsubcuw", + PpcOpcode::vavgsw => "vavgsw", + PpcOpcode::vsububs => "vsububs", + PpcOpcode::vsum4ubs => "vsum4ubs", + PpcOpcode::vsubuhs => "vsubuhs", + PpcOpcode::vsum4shs => "vsum4shs", + PpcOpcode::vsubuws => "vsubuws", + PpcOpcode::vsum2sws => "vsum2sws", + PpcOpcode::vsubsbs => "vsubsbs", + PpcOpcode::vsum4sbs => "vsum4sbs", + PpcOpcode::vsubshs => "vsubshs", + PpcOpcode::vsubsws => "vsubsws", PpcOpcode::vsumsws => "vsumsws", - PpcOpcode::vrefp => "vrefp", PpcOpcode::vrsqrtefp => "vrsqrtefp", - PpcOpcode::vexptefp => "vexptefp", PpcOpcode::vlogefp => "vlogefp", - PpcOpcode::vrfin => "vrfin", PpcOpcode::vrfiz => "vrfiz", - PpcOpcode::vrfip => "vrfip", PpcOpcode::vrfim => "vrfim", - PpcOpcode::vupkhsb => "vupkhsb", PpcOpcode::vupkhsh => "vupkhsh", - PpcOpcode::vupklsb => "vupklsb", PpcOpcode::vupklsh => "vupklsh", - PpcOpcode::vupkhpx => "vupkhpx", PpcOpcode::vupklpx => "vupklpx", + PpcOpcode::vrefp => "vrefp", + PpcOpcode::vrsqrtefp => "vrsqrtefp", + PpcOpcode::vexptefp => "vexptefp", + PpcOpcode::vlogefp => "vlogefp", + PpcOpcode::vrfin => "vrfin", + PpcOpcode::vrfiz => "vrfiz", + PpcOpcode::vrfip => "vrfip", + PpcOpcode::vrfim => "vrfim", + PpcOpcode::vupkhsb => "vupkhsb", + PpcOpcode::vupkhsh => "vupkhsh", + PpcOpcode::vupklsb => "vupklsb", + PpcOpcode::vupklsh => "vupklsh", + PpcOpcode::vupkhpx => "vupkhpx", + PpcOpcode::vupklpx => "vupklpx", - PpcOpcode::vspltb => "vspltb", PpcOpcode::vsplth => "vsplth", PpcOpcode::vspltw => "vspltw", - PpcOpcode::vcfux => "vcfux", PpcOpcode::vcfsx => "vcfsx", - PpcOpcode::vctuxs => "vctuxs", PpcOpcode::vctsxs => "vctsxs", + PpcOpcode::vspltb => "vspltb", + PpcOpcode::vsplth => "vsplth", + PpcOpcode::vspltw => "vspltw", + PpcOpcode::vcfux => "vcfux", + PpcOpcode::vcfsx => "vcfsx", + PpcOpcode::vctuxs => "vctuxs", + PpcOpcode::vctsxs => "vctsxs", - PpcOpcode::vcmpequb => "vcmpequb", PpcOpcode::vcmpequh => "vcmpequh", - PpcOpcode::vcmpequw => "vcmpequw", PpcOpcode::vcmpeqfp => "vcmpeqfp", - PpcOpcode::vcmpgefp => "vcmpgefp", PpcOpcode::vcmpgtub => "vcmpgtub", - PpcOpcode::vcmpgtuh => "vcmpgtuh", PpcOpcode::vcmpgtuw => "vcmpgtuw", - PpcOpcode::vcmpgtfp => "vcmpgtfp", PpcOpcode::vcmpgtsb => "vcmpgtsb", - PpcOpcode::vcmpgtsh => "vcmpgtsh", PpcOpcode::vcmpgtsw => "vcmpgtsw", + PpcOpcode::vcmpequb => "vcmpequb", + PpcOpcode::vcmpequh => "vcmpequh", + PpcOpcode::vcmpequw => "vcmpequw", + PpcOpcode::vcmpeqfp => "vcmpeqfp", + PpcOpcode::vcmpgefp => "vcmpgefp", + PpcOpcode::vcmpgtub => "vcmpgtub", + PpcOpcode::vcmpgtuh => "vcmpgtuh", + PpcOpcode::vcmpgtuw => "vcmpgtuw", + PpcOpcode::vcmpgtfp => "vcmpgtfp", + PpcOpcode::vcmpgtsb => "vcmpgtsb", + PpcOpcode::vcmpgtsh => "vcmpgtsh", + PpcOpcode::vcmpgtsw => "vcmpgtsw", PpcOpcode::vcmpbfp => "vcmpbfp", - PpcOpcode::vmhaddshs => "vmhaddshs", PpcOpcode::vmhraddshs => "vmhraddshs", + PpcOpcode::vmhaddshs => "vmhaddshs", + PpcOpcode::vmhraddshs => "vmhraddshs", PpcOpcode::vmladduhm => "vmladduhm", - PpcOpcode::vmsumubm => "vmsumubm", PpcOpcode::vmsummbm => "vmsummbm", - PpcOpcode::vmsumuhm => "vmsumuhm", PpcOpcode::vmsumuhs => "vmsumuhs", - PpcOpcode::vmsumshm => "vmsumshm", PpcOpcode::vmsumshs => "vmsumshs", - PpcOpcode::vsel => "vsel", PpcOpcode::vperm => "vperm", + PpcOpcode::vmsumubm => "vmsumubm", + PpcOpcode::vmsummbm => "vmsummbm", + PpcOpcode::vmsumuhm => "vmsumuhm", + PpcOpcode::vmsumuhs => "vmsumuhs", + PpcOpcode::vmsumshm => "vmsumshm", + PpcOpcode::vmsumshs => "vmsumshs", + PpcOpcode::vsel => "vsel", + PpcOpcode::vperm => "vperm", _ => "?", } } @@ -799,13 +1011,16 @@ fn opcode_name(op: PpcOpcode) -> &'static str { fn fmt_b(instr: &DecodedInstr) -> DisasmText { let aa = instr.aa(); let lk = instr.lk(); - let target = if aa { instr.li() as u32 } - else { instr.addr.wrapping_add(instr.li() as u32) }; + let target = if aa { + instr.li() as u32 + } else { + instr.addr.wrapping_add(instr.li() as u32) + }; let mnem = match (aa, lk) { (false, false) => "b", - (false, true) => "bl", - (true, false) => "ba", - (true, true) => "bla", + (false, true) => "bl", + (true, false) => "ba", + (true, true) => "bla", }; let ops = format!("0x{target:08X}"); with_target(base(mnem, ops, 8), target) @@ -843,8 +1058,11 @@ fn fmt_bc(instr: &DecodedInstr) -> DisasmText { let bi = instr.bi(); let aa = instr.aa(); let lk = instr.lk(); - let target = if aa { instr.bd() as u32 } - else { instr.addr.wrapping_add(instr.bd() as u32) }; + let target = if aa { + instr.bd() as u32 + } else { + instr.addr.wrapping_add(instr.bd() as u32) + }; let a = if aa { "a" } else { "" }; let l = if lk { "l" } else { "" }; @@ -866,13 +1084,21 @@ fn fmt_bc(instr: &DecodedInstr) -> DisasmText { } else { let cond_true = bo & 0x08 != 0; let cond_name_opt: Option<&'static str> = match (cr_bit, cond_true) { - (0, true) => Some("lt"), (0, false) => Some("ge"), - (1, true) => Some("gt"), (1, false) => Some("le"), - (2, true) => Some("eq"), (2, false) => Some("ne"), - (3, true) => Some("so"), (3, false) => Some("ns"), + (0, true) => Some("lt"), + (0, false) => Some("ge"), + (1, true) => Some("gt"), + (1, false) => Some("le"), + (2, true) => Some("eq"), + (2, false) => Some("ne"), + (3, true) => Some("so"), + (3, false) => Some("ns"), _ => None, }; - let cr = if cr_field == 0 { String::new() } else { format!("cr{cr_field}, ") }; + let cr = if cr_field == 0 { + String::new() + } else { + format!("cr{cr_field}, ") + }; if decr { let z = if bo & 0x02 != 0 { "z" } else { "nz" }; @@ -927,7 +1153,14 @@ fn fmt_bclr(instr: &DecodedInstr) -> DisasmText { if cr_no_comma.is_empty() { return with_ext(&base_mnem, base_ops, 8, &ext_mnem, String::new(), 0); } else { - return with_ext(&base_mnem, base_ops, 8, &ext_mnem, cr_no_comma.to_string(), 8); + return with_ext( + &base_mnem, + base_ops, + 8, + &ext_mnem, + cr_no_comma.to_string(), + 8, + ); } } let decr = bo & 0x04 == 0; @@ -960,7 +1193,14 @@ fn fmt_bcctr(instr: &DecodedInstr) -> DisasmText { if cr_no_comma.is_empty() { return with_ext(&base_mnem, base_ops, 8, &ext_mnem, String::new(), 0); } else { - return with_ext(&base_mnem, base_ops, 8, &ext_mnem, cr_no_comma.to_string(), 8); + return with_ext( + &base_mnem, + base_ops, + 8, + &ext_mnem, + cr_no_comma.to_string(), + 8, + ); } } base(&base_mnem, base_ops, 8) @@ -1017,7 +1257,14 @@ fn fmt_addi(instr: &DecodedInstr) -> DisasmText { if ra == 0 { with_ext("addi", base_ops, 8, "li", format!("{}, {imm}", gpr(rt)), 8) } else if imm < 0 { - with_ext("addi", base_ops, 8, "subi", format!("{}, {}, {}", gpr(rt), gpr(ra), -imm), 8) + with_ext( + "addi", + base_ops, + 8, + "subi", + format!("{}, {}, {}", gpr(rt), gpr(ra), -imm), + 8, + ) } else { base("addi", base_ops, 8) } @@ -1030,10 +1277,24 @@ fn fmt_addis(instr: &DecodedInstr) -> DisasmText { let imm_u = imm as u16 as u32; let base_ops = format!("{}, {}, 0x{imm_u:X}", gpr(rt), gpr(ra)); if ra == 0 { - with_ext("addis", base_ops, 8, "lis", format!("{}, 0x{imm_u:X}", gpr(rt)), 8) + with_ext( + "addis", + base_ops, + 8, + "lis", + format!("{}, 0x{imm_u:X}", gpr(rt)), + 8, + ) } else if imm < 0 { let neg = (-imm) as u16 as u32; - with_ext("addis", base_ops, 8, "subis", format!("{}, {}, 0x{neg:X}", gpr(rt), gpr(ra)), 8) + with_ext( + "addis", + base_ops, + 8, + "subis", + format!("{}, {}, 0x{neg:X}", gpr(rt), gpr(ra)), + 8, + ) } else { base("addis", base_ops, 8) } @@ -1046,7 +1307,14 @@ fn fmt_d_add(instr: &DecodedInstr, mnem: &str) -> DisasmText { let base_ops = format!("{}, {}, {imm}", gpr(rt), gpr(ra)); if imm < 0 { let ext_mnem = mnem.replace("addic", "subic"); - with_ext(mnem, base_ops, 8, &ext_mnem, format!("{}, {}, {}", gpr(rt), gpr(ra), -imm), 8) + with_ext( + mnem, + base_ops, + 8, + &ext_mnem, + format!("{}, {}, {}", gpr(rt), gpr(ra), -imm), + 8, + ) } else { base(mnem, base_ops, 8) } @@ -1068,7 +1336,11 @@ fn fmt_cmp_imm(instr: &DecodedInstr, mnem: &str, signed: bool) -> DisasmText { } else { format!("0x{:X}", instr.uimm16()) }; - let cr = if bf == 0 { String::new() } else { format!("cr{bf}, ") }; + let cr = if bf == 0 { + String::new() + } else { + format!("cr{bf}, ") + }; let base_ops = format!("{cr}{l_bit}, {}, {imm_str}", gpr(ra)); let size = if l_bit == 0 { "w" } else { "d" }; @@ -1086,7 +1358,11 @@ fn fmt_cmp_reg(instr: &DecodedInstr, mnem: &str) -> DisasmText { let l_bit = if instr.l() { 1 } else { 0 }; let ra = instr.ra(); let rb = instr.rb(); - let cr = if bf == 0 { String::new() } else { format!("cr{bf}, ") }; + let cr = if bf == 0 { + String::new() + } else { + format!("cr{bf}, ") + }; let base_ops = format!("{cr}{l_bit}, {}, {}", gpr(ra), gpr(rb)); let size = if l_bit == 0 { "w" } else { "d" }; let ext_mnem = format!("{mnem}{size}"); @@ -1153,14 +1429,28 @@ fn fmt_rlwimi(instr: &DecodedInstr) -> DisasmText { let n = me - mb + 1; let b = mb; let ext_mnem = format!("inslwi{rc}"); - return with_ext(&mnem, base_ops, 8, &ext_mnem, format!("{}, {}, {n}, {b}", gpr(ra), gpr(rs)), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext_mnem, + format!("{}, {}, {n}, {b}", gpr(ra), gpr(rs)), + 8, + ); } // insrwi rA, rS, n, b = rlwimi rA, rS, 32-(b+n), b, b+n-1 if mb <= me && sh == 31u32.wrapping_sub(me) % 32 { let n = me - mb + 1; let b = mb; let ext_mnem = format!("insrwi{rc}"); - return with_ext(&mnem, base_ops, 8, &ext_mnem, format!("{}, {}, {n}, {b}", gpr(ra), gpr(rs)), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext_mnem, + format!("{}, {}, {n}, {b}", gpr(ra), gpr(rs)), + 8, + ); } base(&mnem, base_ops, 8) } @@ -1184,34 +1474,83 @@ fn fmt_rlwinm(instr: &DecodedInstr) -> DisasmText { // away left the largest simplified-mnemonic gap we had. if mb == 0 && me == 31 - sh { let ext = format!("slwi{rc}"); - return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {sh}", gpr(ra), gpr(rs)), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext, + format!("{}, {}, {sh}", gpr(ra), gpr(rs)), + 8, + ); } if sh > 0 && me == 31 && sh + mb == 32 { let ext = format!("srwi{rc}"); - return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {}", gpr(ra), gpr(rs), 32 - sh), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext, + format!("{}, {}, {}", gpr(ra), gpr(rs), 32 - sh), + 8, + ); } if sh > 0 && mb == 0 && me == 31 { let ext = format!("rotlwi{rc}"); - return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {sh}", gpr(ra), gpr(rs)), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext, + format!("{}, {}, {sh}", gpr(ra), gpr(rs)), + 8, + ); } if sh == 0 && me == 31 && mb > 0 { let ext = format!("clrlwi{rc}"); - return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {mb}", gpr(ra), gpr(rs)), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext, + format!("{}, {}, {mb}", gpr(ra), gpr(rs)), + 8, + ); } if sh == 0 && mb == 0 && me < 31 { let ext = format!("clrrwi{rc}"); - return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {}", gpr(ra), gpr(rs), 31 - me), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext, + format!("{}, {}, {}", gpr(ra), gpr(rs), 31 - me), + 8, + ); } if mb == 0 && sh > 0 && me < 31 { let n = me + 1; let ext = format!("extlwi{rc}"); - return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {n}, {sh}", gpr(ra), gpr(rs)), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext, + format!("{}, {}, {n}, {sh}", gpr(ra), gpr(rs)), + 8, + ); } if me == 31 && mb > 0 && sh > 0 { let n = 32 - mb; let b = sh.wrapping_sub(n) % 32; let ext = format!("extrwi{rc}"); - return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {n}, {b}", gpr(ra), gpr(rs)), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext, + format!("{}, {}, {n}, {b}", gpr(ra), gpr(rs)), + 8, + ); } base(&mnem, base_ops, 8) } @@ -1227,7 +1566,14 @@ fn fmt_rlwnm(instr: &DecodedInstr) -> DisasmText { let base_ops = format!("{}, {}, {}, {mb}, {me}", gpr(ra), gpr(rs), gpr(rb)); if mb == 0 && me == 31 { let ext = format!("rotlw{rc}"); - return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {}", gpr(ra), gpr(rs), gpr(rb)), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext, + format!("{}, {}, {}", gpr(ra), gpr(rs), gpr(rb)), + 8, + ); } base(&mnem, base_ops, 8) } @@ -1243,15 +1589,36 @@ fn fmt_rldicl(instr: &DecodedInstr) -> DisasmText { let base_ops = format!("{}, {}, {sh}, {mb}", gpr(ra), gpr(rs)); if sh == 0 && mb > 0 { let ext = format!("clrldi{rc}"); - return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {mb}", gpr(ra), gpr(rs)), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext, + format!("{}, {}, {mb}", gpr(ra), gpr(rs)), + 8, + ); } if mb > 0 && sh == (64u32.wrapping_sub(mb)) & 63 { let ext = format!("srdi{rc}"); - return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {mb}", gpr(ra), gpr(rs)), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext, + format!("{}, {}, {mb}", gpr(ra), gpr(rs)), + 8, + ); } if sh > 0 && mb == 0 { let ext = format!("rotldi{rc}"); - return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {sh}", gpr(ra), gpr(rs)), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext, + format!("{}, {}, {sh}", gpr(ra), gpr(rs)), + 8, + ); } base(&mnem, base_ops, 8) } @@ -1266,11 +1633,25 @@ fn fmt_rldicr(instr: &DecodedInstr) -> DisasmText { let base_ops = format!("{}, {}, {sh}, {me}", gpr(ra), gpr(rs)); if sh > 0 && me == (63u32.wrapping_sub(sh)) & 63 { let ext = format!("sldi{rc}"); - return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {sh}", gpr(ra), gpr(rs)), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext, + format!("{}, {}, {sh}", gpr(ra), gpr(rs)), + 8, + ); } if sh == 0 && me < 63 { let ext = format!("clrrdi{rc}"); - return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {}", gpr(ra), gpr(rs), 63 - me), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext, + format!("{}, {}, {}", gpr(ra), gpr(rs), 63 - me), + 8, + ); } base(&mnem, base_ops, 8) } @@ -1281,7 +1662,11 @@ fn fmt_rldic(instr: &DecodedInstr) -> DisasmText { let rc = rc_dot(instr); let sh = instr.sh64(); let mb = mb_md(instr.raw); - base(&format!("rldic{rc}"), format!("{}, {}, {sh}, {mb}", gpr(ra), gpr(rs)), 8) + base( + &format!("rldic{rc}"), + format!("{}, {}, {sh}, {mb}", gpr(ra), gpr(rs)), + 8, + ) } fn fmt_rldimi(instr: &DecodedInstr) -> DisasmText { @@ -1296,7 +1681,14 @@ fn fmt_rldimi(instr: &DecodedInstr) -> DisasmText { let n = (64u32.wrapping_sub(sh).wrapping_sub(mb)) & 63; if n > 0 { let ext = format!("insrdi{rc}"); - return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {n}, {mb}", gpr(ra), gpr(rs)), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext, + format!("{}, {}, {n}, {mb}", gpr(ra), gpr(rs)), + 8, + ); } } base(&mnem, base_ops, 8) @@ -1312,7 +1704,14 @@ fn fmt_rldcl(instr: &DecodedInstr) -> DisasmText { let base_ops = format!("{}, {}, {}, {mb}", gpr(ra), gpr(rs), gpr(rb)); if mb == 0 { let ext = format!("rotld{rc}"); - return with_ext(&mnem, base_ops, 8, &ext, format!("{}, {}, {}", gpr(ra), gpr(rs), gpr(rb)), 8); + return with_ext( + &mnem, + base_ops, + 8, + &ext, + format!("{}, {}, {}", gpr(ra), gpr(rs), gpr(rb)), + 8, + ); } base(&mnem, base_ops, 8) } @@ -1323,14 +1722,18 @@ fn fmt_rldcr(instr: &DecodedInstr) -> DisasmText { let rb = instr.rb(); let rc = rc_dot(instr); let me = mb_md(instr.raw); - base(&format!("rldcr{rc}"), format!("{}, {}, {}, {me}", gpr(ra), gpr(rs), gpr(rb)), 8) + base( + &format!("rldcr{rc}"), + format!("{}, {}, {}, {me}", gpr(ra), gpr(rs), gpr(rb)), + 8, + ) } /// MD/MDS-form mb/me field: 6 bits packed as bits 21-25 + bit 26 (low bit). #[inline] fn mb_md(raw: u32) -> u32 { - let lo5 = (raw >> 6) & 0x1F; // bits 21-25 - let hi = (raw >> 5) & 0x1; // bit 26 + let lo5 = (raw >> 6) & 0x1F; // bits 21-25 + let hi = (raw >> 5) & 0x1; // bit 26 lo5 | (hi << 5) } @@ -1455,7 +1858,11 @@ fn fmt_srawi(instr: &DecodedInstr) -> DisasmText { let ra = instr.ra(); let sh = instr.sh(); let rc = rc_dot(instr); - base(&format!("srawi{rc}"), format!("{}, {}, {sh}", gpr(ra), gpr(rs)), 8) + base( + &format!("srawi{rc}"), + format!("{}, {}, {sh}", gpr(ra), gpr(rs)), + 8, + ) } fn fmt_sradi(instr: &DecodedInstr) -> DisasmText { @@ -1463,7 +1870,11 @@ fn fmt_sradi(instr: &DecodedInstr) -> DisasmText { let ra = instr.ra(); let sh = instr.sh64(); let rc = rc_dot(instr); - base(&format!("sradi{rc}"), format!("{}, {}, {sh}", gpr(ra), gpr(rs)), 8) + base( + &format!("sradi{rc}"), + format!("{}, {}, {sh}", gpr(ra), gpr(rs)), + 8, + ) } // Special-purpose register moves @@ -1472,9 +1883,9 @@ fn fmt_mfspr(instr: &DecodedInstr) -> DisasmText { let spr = instr.spr(); let base_ops = format!("{}, {}", gpr(rd), spr_name(spr)); let ext = match spr { - 8 => Some(("mflr", format!("{}", gpr(rd)))), - 9 => Some(("mfctr", format!("{}", gpr(rd)))), - 1 => Some(("mfxer", format!("{}", gpr(rd)))), + 8 => Some(("mflr", gpr(rd).to_string())), + 9 => Some(("mfctr", gpr(rd).to_string())), + 1 => Some(("mfxer", gpr(rd).to_string())), _ => None, }; match ext { @@ -1488,9 +1899,9 @@ fn fmt_mtspr(instr: &DecodedInstr) -> DisasmText { let spr = instr.spr(); let base_ops = format!("{}, {}", spr_name(spr), gpr(rs)); let ext = match spr { - 8 => Some(("mtlr", format!("{}", gpr(rs)))), - 9 => Some(("mtctr", format!("{}", gpr(rs)))), - 1 => Some(("mtxer", format!("{}", gpr(rs)))), + 8 => Some(("mtlr", gpr(rs).to_string())), + 9 => Some(("mtctr", gpr(rs).to_string())), + 1 => Some(("mtxer", gpr(rs).to_string())), _ => None, }; match ext { @@ -1588,7 +1999,14 @@ fn fmt_crnor(instr: &DecodedInstr) -> DisasmText { let bb = instr.crbb(); let bo = format!("{}, {}, {}", crb(bt), crb(ba), crb(bb)); if ba == bb { - with_ext("crnor", bo, 8, "crnot", format!("{}, {}", crb(bt), crb(ba)), 8) + with_ext( + "crnor", + bo, + 8, + "crnot", + format!("{}, {}", crb(bt), crb(ba)), + 8, + ) } else { base("crnor", bo, 8) } @@ -1624,7 +2042,14 @@ fn fmt_cror(instr: &DecodedInstr) -> DisasmText { let bb = instr.crbb(); let bo = format!("{}, {}, {}", crb(bt), crb(ba), crb(bb)); if ba == bb { - with_ext("cror", bo, 8, "crmove", format!("{}, {}", crb(bt), crb(ba)), 8) + with_ext( + "cror", + bo, + 8, + "crmove", + format!("{}, {}", crb(bt), crb(ba)), + 8, + ) } else { base("cror", bo, 8) } @@ -1650,7 +2075,11 @@ fn fmt_a_unary(instr: &DecodedInstr, mnem: &str) -> DisasmText { let frt = instr.rd(); let frb = instr.rb(); let rc = rc_dot(instr); - base(&format!("{mnem}{rc}"), format!("{}, {}", fpr(frt), fpr(frb)), 8) + base( + &format!("{mnem}{rc}"), + format!("{}, {}", fpr(frt), fpr(frb)), + 8, + ) } fn fmt_a_4op(instr: &DecodedInstr, mnem: &str) -> DisasmText { @@ -1659,8 +2088,11 @@ fn fmt_a_4op(instr: &DecodedInstr, mnem: &str) -> DisasmText { let frb = instr.rb(); let frc = instr.rc(); let rc = rc_dot(instr); - base(&format!("{mnem}{rc}"), - format!("{}, {}, {}, {}", fpr(frt), fpr(fra), fpr(frc), fpr(frb)), 8) + base( + &format!("{mnem}{rc}"), + format!("{}, {}, {}, {}", fpr(frt), fpr(fra), fpr(frc), fpr(frb)), + 8, + ) } fn fmt_fcmp(instr: &DecodedInstr, mnem: &str) -> DisasmText { @@ -1674,7 +2106,11 @@ fn fmt_x_fpu_unary(instr: &DecodedInstr, mnem: &str) -> DisasmText { let frt = instr.rd(); let frb = instr.rb(); let rc = rc_dot(instr); - base(&format!("{mnem}{rc}"), format!("{}, {}", fpr(frt), fpr(frb)), 8) + base( + &format!("{mnem}{rc}"), + format!("{}, {}", fpr(frt), fpr(frb)), + 8, + ) } fn fmt_mtfsb(instr: &DecodedInstr, mnem: &str) -> DisasmText { @@ -1692,9 +2128,11 @@ fn fmt_vmx_move(instr: &DecodedInstr, base_mnem: &str, ext_mnem: &str) -> Disasm let vb = instr.rb(); with_ext( base_mnem, - format!("{}, {}, {}", vr(vd), vr(va), vr(vb)), 8, + format!("{}, {}, {}", vr(vd), vr(va), vr(vb)), + 8, ext_mnem, - format!("{}, {}", vr(vd), vr(va)), 8, + format!("{}, {}", vr(vd), vr(va)), + 8, ) } @@ -1739,7 +2177,11 @@ fn fmt_vmx_4op(instr: &DecodedInstr, mnem: &str) -> DisasmText { let va = instr.ra(); let vb = instr.rb(); let vc = instr.rc(); - base(mnem, format!("{}, {}, {}, {}", vr(vd), vr(va), vr(vb), vr(vc)), 12) + base( + mnem, + format!("{}, {}, {}, {}", vr(vd), vr(va), vr(vb), vr(vc)), + 12, + ) } fn fmt_vmx_4op_swap(instr: &DecodedInstr, mnem: &str) -> DisasmText { @@ -1747,7 +2189,11 @@ fn fmt_vmx_4op_swap(instr: &DecodedInstr, mnem: &str) -> DisasmText { let va = instr.ra(); let vb = instr.rb(); let vc = instr.rc(); - base(mnem, format!("{}, {}, {}, {}", vr(vd), vr(va), vr(vc), vr(vb)), 9) + base( + mnem, + format!("{}, {}, {}, {}", vr(vd), vr(va), vr(vc), vr(vb)), + 9, + ) } fn fmt_vsldoi(instr: &DecodedInstr) -> DisasmText { @@ -1755,7 +2201,11 @@ fn fmt_vsldoi(instr: &DecodedInstr) -> DisasmText { let va = instr.ra(); let vb = instr.rb(); let sh = (instr.raw >> 6) & 0xF; - base("vsldoi", format!("{}, {}, {}, {sh}", vr(vd), vr(va), vr(vb)), 8) + base( + "vsldoi", + format!("{}, {}, {}, {sh}", vr(vd), vr(va), vr(vb)), + 8, + ) } fn fmt_vmx_ls(instr: &DecodedInstr, mnem: &str) -> DisasmText { @@ -1813,7 +2263,11 @@ fn fmt_vperm128(instr: &DecodedInstr) -> DisasmText { let va = instr.va128(); let vb = instr.vb128(); let vc = (instr.raw >> 6) & 0x7; - base("vperm128", format!("{}, {}, {}, {vc}", vr(vd), vr(va), vr(vb)), 9) + base( + "vperm128", + format!("{}, {}, {}, {vc}", vr(vd), vr(va), vr(vb)), + 9, + ) } fn fmt_vsldoi128(instr: &DecodedInstr) -> DisasmText { @@ -1821,7 +2275,11 @@ fn fmt_vsldoi128(instr: &DecodedInstr) -> DisasmText { let va = instr.va128(); let vb = instr.vb128(); let sh = (instr.raw >> 6) & 0xF; - base("vsldoi128", format!("{}, {}, {}, {sh}", vr(vd), vr(va), vr(vb)), 10) + base( + "vsldoi128", + format!("{}, {}, {}, {sh}", vr(vd), vr(va), vr(vb)), + 10, + ) } fn fmt_vpermwi128(instr: &DecodedInstr) -> DisasmText { @@ -1831,7 +2289,11 @@ fn fmt_vpermwi128(instr: &DecodedInstr) -> DisasmText { let lo = (instr.raw >> 16) & 0x1F; let hi = (instr.raw >> 6) & 0x7; let uimm = lo | (hi << 5); - base("vpermwi128", format!("{}, {}, 0x{uimm:X}", vr(vd), vr(vb)), 11) + base( + "vpermwi128", + format!("{}, {}, 0x{uimm:X}", vr(vd), vr(vb)), + 11, + ) } fn fmt_vmx128_pack_d3d(instr: &DecodedInstr, mnem: &str) -> DisasmText { @@ -1882,7 +2344,7 @@ mod tests { #[test] fn addi_to_li_when_ra_zero() { // addi r3, r0, 16 - let raw = (14u32 << 26) | (3 << 21) | (0 << 16) | 16; + let raw = ((14u32 << 26) | (3 << 21)) | 16; let instr = decode(raw, 0); let t = format(&instr); assert_eq!(t.mnemonic, "addi"); @@ -1894,8 +2356,7 @@ mod tests { fn rlwinm_dot_preserves_record_bit() { // Same pattern as the Sylpheed graphics-callback test: // rlwinm. r11, r11, 0, 31, 31 with Rc=1 - let raw = (21u32 << 26) | (11 << 21) | (11 << 16) - | (0 << 11) | (31 << 6) | (31 << 1) | 1; + let raw = ((21u32 << 26) | (11 << 21) | (11 << 16)) | (31 << 6) | (31 << 1) | 1; let instr = decode(raw, 0); let t = format(&instr); assert!(t.disasm.starts_with("rlwinm."), "got: {}", t.disasm); @@ -1903,8 +2364,7 @@ mod tests { #[test] fn rlwinm_no_dot_when_rc_unset() { - let raw = (21u32 << 26) | (11 << 21) | (11 << 16) - | (0 << 11) | (31 << 6) | (31 << 1); + let raw = ((21u32 << 26) | (11 << 21) | (11 << 16)) | (31 << 6) | (31 << 1); let instr = decode(raw, 0); let t = format(&instr); assert_eq!(t.mnemonic, "rlwinm"); @@ -1935,7 +2395,7 @@ mod tests { #[test] fn bclr_unconditional_is_blr() { // bclr 20, 0 - let raw = (19u32 << 26) | (20 << 21) | (0 << 16) | (16 << 1); + let raw = ((19u32 << 26) | (20 << 21)) | (16 << 1); let instr = decode(raw, 0); let t = format(&instr); assert_eq!(t.ext_mnemonic.as_deref(), Some("blr")); @@ -1952,11 +2412,11 @@ mod tests { // Three instructions at 0x82000000: nop, addi r3,r0,16, b +0x100. let mut bytes = Vec::new(); bytes.extend_from_slice(&0x60000000u32.to_be_bytes()); // nop - bytes.extend_from_slice(&((14u32 << 26) | (3 << 21) | (0 << 16) | 16).to_be_bytes()); // addi + bytes.extend_from_slice(&(((14u32 << 26) | (3 << 21)) | 16).to_be_bytes()); // addi bytes.extend_from_slice(&((18u32 << 26) | (0x40 << 2)).to_be_bytes()); // b +0x100 - let items: Vec<_> = super::iter_disasm(&bytes, 0x82000000, 0x82000000, 0x82000000 + 12) - .collect(); + let items: Vec<_> = + super::iter_disasm(&bytes, 0x82000000, 0x82000000, 0x82000000 + 12).collect(); assert_eq!(items.len(), 3); assert_eq!(items[0].addr, 0x82000000); assert_eq!(items[0].text.ext_mnemonic.as_deref(), Some("nop")); @@ -1971,7 +2431,8 @@ mod tests { // 6 bytes — one full instruction + 2 dangling. Iterator must yield exactly 1. let mut bytes = Vec::new(); bytes.extend_from_slice(&0x60000000u32.to_be_bytes()); - bytes.push(0x60); bytes.push(0x00); + bytes.push(0x60); + bytes.push(0x00); let items: Vec<_> = super::iter_disasm(&bytes, 0, 0, 6).collect(); assert_eq!(items.len(), 1); @@ -2026,7 +2487,7 @@ mod tests { #[test] fn rlwinm_bit_extract_has_no_simplified_form() { // rlwinm rA,rS,0,30,30 — extracts one bit; not a shift or clear. - let word = 0x5548_0000 | (0 << 11) | (30 << 6) | (30 << 1); + let word = 0x5548_0000 | (30 << 6) | (30 << 1); let t = format(&crate::decoder::decode(word, 0x8200_0000)); assert_eq!(t.mnemonic, "rlwinm"); assert_eq!(t.ext_mnemonic, None); @@ -2070,17 +2531,23 @@ mod tests { fn branch_prediction_hints_are_preserved() { // 0x4320fff0: bdnz with at=0b11 -> "+" assert_eq!( - format(&crate::decoder::decode(0x4320_fff0, 0x8200_0000)).ext_mnemonic.as_deref(), + format(&crate::decoder::decode(0x4320_fff0, 0x8200_0000)) + .ext_mnemonic + .as_deref(), Some("bdnz+") ); // 0x41c20024: beq with at=0b10 -> "-" assert_eq!( - format(&crate::decoder::decode(0x41c2_0024, 0x8200_0000)).ext_mnemonic.as_deref(), + format(&crate::decoder::decode(0x41c2_0024, 0x8200_0000)) + .ext_mnemonic + .as_deref(), Some("beq-") ); // 0x4de20020: beqlr with at=0b11 -> "+" assert_eq!( - format(&crate::decoder::decode(0x4de2_0020, 0x8200_0000)).ext_mnemonic.as_deref(), + format(&crate::decoder::decode(0x4de2_0020, 0x8200_0000)) + .ext_mnemonic + .as_deref(), Some("beqlr+") ); } @@ -2109,20 +2576,23 @@ mod tests { // TO=6 is "logically less than or equal" — the divide-by-zero guard // MSVC emits, and the most common trap in the reference title. - let word = 0x0c00_0000 | (6 << 21) | (3 << 16) | 0; + let word = 0x0c00_0000 | (6 << 21) | (3 << 16); assert_eq!( - format(&crate::decoder::decode(word, 0x8200_0000)).ext_mnemonic.as_deref(), + format(&crate::decoder::decode(word, 0x8200_0000)) + .ext_mnemonic + .as_deref(), Some("twllei") ); // TO=5 is "logically greater than or equal". - let word = 0x0c00_0000 | (5 << 21) | (3 << 16) | 0; + let word = 0x0c00_0000 | (5 << 21) | (3 << 16); assert_eq!( - format(&crate::decoder::decode(word, 0x8200_0000)).ext_mnemonic.as_deref(), + format(&crate::decoder::decode(word, 0x8200_0000)) + .ext_mnemonic + .as_deref(), Some("twlgei") ); // tw 31,0,0 stays the register-form `trap`. let t = format(&crate::decoder::decode(0x7fe0_0008, 0x8200_0000)); assert_eq!(t.ext_mnemonic.as_deref(), Some("trap")); } - } diff --git a/crates/sylpheed-ppc/src/opcode.rs b/crates/sylpheed-ppc/src/opcode.rs index 7ccf1205..92497165 100644 --- a/crates/sylpheed-ppc/src/opcode.rs +++ b/crates/sylpheed-ppc/src/opcode.rs @@ -5,131 +5,498 @@ #[allow(non_camel_case_types)] pub enum PpcOpcode { // ALU - addcx, addex, addi, addic, addicx, addis, addmex, addx, addzex, - andcx, andisx, andix, andx, + addcx, + addex, + addi, + addic, + addicx, + addis, + addmex, + addx, + addzex, + andcx, + andisx, + andix, + andx, // Branch - bcctrx, bclrx, bcx, bx, + bcctrx, + bclrx, + bcx, + bx, // Compare - cmp, cmpi, cmpl, cmpli, + cmp, + cmpi, + cmpl, + cmpli, // Count leading zeros - cntlzdx, cntlzwx, + cntlzdx, + cntlzwx, // Condition register - crand, crandc, creqv, crnand, crnor, cror, crorc, crxor, + crand, + crandc, + creqv, + crnand, + crnor, + cror, + crorc, + crxor, // Data cache - dcbf, dcbi, dcbst, dcbt, dcbtst, dcbz, dcbz128, + dcbf, + dcbi, + dcbst, + dcbt, + dcbtst, + dcbz, + dcbz128, // Division - divdux, divdx, divwux, divwx, + divdux, + divdx, + divwux, + divwx, // Sync/barrier eieio, // Logical - eqvx, extsbx, extshx, extswx, + eqvx, + extsbx, + extshx, + extswx, // FPU - fabsx, faddsx, faddx, fcfidx, fcmpo, fcmpu, fctidx, fctidzx, fctiwx, fctiwzx, - fdivsx, fdivx, fmaddsx, fmaddx, fmrx, fmsubsx, fmsubx, fmulsx, fmulx, - fnabsx, fnegx, fnmaddsx, fnmaddx, fnmsubsx, fnmsubx, fresx, frspx, frsqrtex, - fselx, fsqrtsx, fsqrtx, fsubsx, fsubx, + fabsx, + faddsx, + faddx, + fcfidx, + fcmpo, + fcmpu, + fctidx, + fctidzx, + fctiwx, + fctiwzx, + fdivsx, + fdivx, + fmaddsx, + fmaddx, + fmrx, + fmsubsx, + fmsubx, + fmulsx, + fmulx, + fnabsx, + fnegx, + fnmaddsx, + fnmaddx, + fnmsubsx, + fnmsubx, + fresx, + frspx, + frsqrtex, + fselx, + fsqrtsx, + fsqrtx, + fsubsx, + fsubx, // Instruction cache - icbi, isync, + icbi, + isync, // Load byte - lbz, lbzu, lbzux, lbzx, + lbz, + lbzu, + lbzux, + lbzx, // Load doubleword - ld, ldarx, ldbrx, ldu, ldux, ldx, + ld, + ldarx, + ldbrx, + ldu, + ldux, + ldx, // Load float - lfd, lfdu, lfdux, lfdx, lfs, lfsu, lfsux, lfsx, + lfd, + lfdu, + lfdux, + lfdx, + lfs, + lfsu, + lfsux, + lfsx, // Load halfword - lha, lhau, lhaux, lhax, lhbrx, lhz, lhzu, lhzux, lhzx, + lha, + lhau, + lhaux, + lhax, + lhbrx, + lhz, + lhzu, + lhzux, + lhzx, // Load multiple/string - lmw, lswi, lswx, + lmw, + lswi, + lswx, // Load vector - lvebx, lvehx, lvewx, lvewx128, lvlx, lvlx128, lvlxl, lvlxl128, - lvrx, lvrx128, lvrxl, lvrxl128, - lvsl, lvsl128, lvsr, lvsr128, - lvx, lvx128, lvxl, lvxl128, + lvebx, + lvehx, + lvewx, + lvewx128, + lvlx, + lvlx128, + lvlxl, + lvlxl128, + lvrx, + lvrx128, + lvrxl, + lvrxl128, + lvsl, + lvsl128, + lvsr, + lvsr128, + lvx, + lvx128, + lvxl, + lvxl128, // Load word - lwa, lwarx, lwaux, lwax, lwbrx, lwz, lwzu, lwzux, lwzx, + lwa, + lwarx, + lwaux, + lwax, + lwbrx, + lwz, + lwzu, + lwzux, + lwzx, // Move CR - mcrf, mcrfs, mcrxr, + mcrf, + mcrfs, + mcrxr, // Move from special - mfcr, mffsx, mfmsr, mfspr, mftb, mfvscr, + mfcr, + mffsx, + mfmsr, + mfspr, + mftb, + mfvscr, // Move to special - mtcrf, mtfsb0x, mtfsb1x, mtfsfix, mtfsfx, mtmsr, mtmsrd, mtspr, mtvscr, + mtcrf, + mtfsb0x, + mtfsb1x, + mtfsfix, + mtfsfx, + mtmsr, + mtmsrd, + mtspr, + mtvscr, // Multiply - mulhdux, mulhdx, mulhwux, mulhwx, mulldx, mulli, mullwx, + mulhdux, + mulhdx, + mulhwux, + mulhwx, + mulldx, + mulli, + mullwx, // Logical - nandx, negx, norx, orcx, ori, oris, orx, + nandx, + negx, + norx, + orcx, + ori, + oris, + orx, // Rotate - rldclx, rldcrx, rldiclx, rldicrx, rldicx, rldimix, rlwimix, rlwinmx, rlwnmx, + rldclx, + rldcrx, + rldiclx, + rldicrx, + rldicx, + rldimix, + rlwimix, + rlwinmx, + rlwnmx, // System call sc, // Shift - sldx, slwx, sradix, sradx, srawix, srawx, srdx, srwx, + sldx, + slwx, + sradix, + sradx, + srawix, + srawx, + srdx, + srwx, // Store byte - stb, stbu, stbux, stbx, + stb, + stbu, + stbux, + stbx, // Store doubleword - std, stdbrx, stdcx, stdu, stdux, stdx, + std, + stdbrx, + stdcx, + stdu, + stdux, + stdx, // Store float - stfd, stfdu, stfdux, stfdx, stfiwx, stfs, stfsu, stfsux, stfsx, + stfd, + stfdu, + stfdux, + stfdx, + stfiwx, + stfs, + stfsu, + stfsux, + stfsx, // Store halfword - sth, sthbrx, sthu, sthux, sthx, + sth, + sthbrx, + sthu, + sthux, + sthx, // Store multiple/string - stmw, stswi, stswx, + stmw, + stswi, + stswx, // Store vector - stvebx, stvehx, stvewx, stvewx128, stvlx, stvlx128, stvlxl, stvlxl128, - stvrx, stvrx128, stvrxl, stvrxl128, - stvx, stvx128, stvxl, stvxl128, + stvebx, + stvehx, + stvewx, + stvewx128, + stvlx, + stvlx128, + stvlxl, + stvlxl128, + stvrx, + stvrx128, + stvrxl, + stvrxl128, + stvx, + stvx128, + stvxl, + stvxl128, // Store word - stw, stwbrx, stwcx, stwu, stwux, stwx, + stw, + stwbrx, + stwcx, + stwu, + stwux, + stwx, // Subtract - subfcx, subfex, subficx, subfmex, subfx, subfzex, + subfcx, + subfex, + subficx, + subfmex, + subfx, + subfzex, // Sync sync, // Trap - td, tdi, tw, twi, + td, + tdi, + tw, + twi, // VMX integer - vaddcuw, vaddfp, vaddfp128, vaddsbs, vaddshs, vaddsws, - vaddubm, vaddubs, vadduhm, vadduhs, vadduwm, vadduws, - vand, vand128, vandc, vandc128, - vavgsb, vavgsh, vavgsw, vavgub, vavguh, vavguw, - vcfpsxws128, vcfpuxws128, vcfsx, vcfux, - vcmpbfp, vcmpbfp128, vcmpeqfp, vcmpeqfp128, - vcmpequb, vcmpequh, vcmpequw, vcmpequw128, - vcmpgefp, vcmpgefp128, vcmpgtfp, vcmpgtfp128, - vcmpgtsb, vcmpgtsh, vcmpgtsw, vcmpgtub, vcmpgtuh, vcmpgtuw, - vcsxwfp128, vctsxs, vctuxs, vcuxwfp128, - vexptefp, vexptefp128, vlogefp, vlogefp128, - vmaddcfp128, vmaddfp, vmaddfp128, - vmaxfp, vmaxfp128, vmaxsb, vmaxsh, vmaxsw, vmaxub, vmaxuh, vmaxuw, - vmhaddshs, vmhraddshs, - vminfp, vminfp128, vminsb, vminsh, vminsw, vminub, vminuh, vminuw, + vaddcuw, + vaddfp, + vaddfp128, + vaddsbs, + vaddshs, + vaddsws, + vaddubm, + vaddubs, + vadduhm, + vadduhs, + vadduwm, + vadduws, + vand, + vand128, + vandc, + vandc128, + vavgsb, + vavgsh, + vavgsw, + vavgub, + vavguh, + vavguw, + vcfpsxws128, + vcfpuxws128, + vcfsx, + vcfux, + vcmpbfp, + vcmpbfp128, + vcmpeqfp, + vcmpeqfp128, + vcmpequb, + vcmpequh, + vcmpequw, + vcmpequw128, + vcmpgefp, + vcmpgefp128, + vcmpgtfp, + vcmpgtfp128, + vcmpgtsb, + vcmpgtsh, + vcmpgtsw, + vcmpgtub, + vcmpgtuh, + vcmpgtuw, + vcsxwfp128, + vctsxs, + vctuxs, + vcuxwfp128, + vexptefp, + vexptefp128, + vlogefp, + vlogefp128, + vmaddcfp128, + vmaddfp, + vmaddfp128, + vmaxfp, + vmaxfp128, + vmaxsb, + vmaxsh, + vmaxsw, + vmaxub, + vmaxuh, + vmaxuw, + vmhaddshs, + vmhraddshs, + vminfp, + vminfp128, + vminsb, + vminsh, + vminsw, + vminub, + vminuh, + vminuw, vmladduhm, - vmrghb, vmrghh, vmrghw, vmrghw128, vmrglb, vmrglh, vmrglw, vmrglw128, - vmsum3fp128, vmsum4fp128, - vmsummbm, vmsumshm, vmsumshs, vmsumubm, vmsumuhm, vmsumuhs, - vmulesb, vmulesh, vmuleub, vmuleuh, vmulfp128, - vmulosb, vmulosh, vmuloub, vmulouh, - vnmsubfp, vnmsubfp128, vnor, vnor128, - vor, vor128, - vperm, vperm128, vpermwi128, vpkd3d128, - vpkpx, vpkshss, vpkshss128, vpkshus, vpkshus128, - vpkswss, vpkswss128, vpkswus, vpkswus128, - vpkuhum, vpkuhum128, vpkuhus, vpkuhus128, - vpkuwum, vpkuwum128, vpkuwus, vpkuwus128, - vrefp, vrefp128, - vrfim, vrfim128, vrfin, vrfin128, vrfip, vrfip128, vrfiz, vrfiz128, - vrlb, vrlh, vrlimi128, vrlw, vrlw128, - vrsqrtefp, vrsqrtefp128, - vsel, vsel128, - vsl, vslb, vsldoi, vsldoi128, vslh, vslo, vslo128, vslw, vslw128, - vspltb, vsplth, vspltisb, vspltish, vspltisw, vspltisw128, vspltw, vspltw128, - vsr, vsrab, vsrah, vsraw, vsraw128, vsrb, vsrh, vsro, vsro128, vsrw, vsrw128, - vsubcuw, vsubfp, vsubfp128, vsubsbs, vsubshs, vsubsws, - vsububm, vsububs, vsubuhm, vsubuhs, vsubuwm, vsubuws, - vsum2sws, vsum4sbs, vsum4shs, vsum4ubs, vsumsws, - vupkd3d128, vupkhpx, vupkhsb, vupkhsb128, vupkhsh, - vupklpx, vupklsb, vupklsb128, vupklsh, - vxor, vxor128, + vmrghb, + vmrghh, + vmrghw, + vmrghw128, + vmrglb, + vmrglh, + vmrglw, + vmrglw128, + vmsum3fp128, + vmsum4fp128, + vmsummbm, + vmsumshm, + vmsumshs, + vmsumubm, + vmsumuhm, + vmsumuhs, + vmulesb, + vmulesh, + vmuleub, + vmuleuh, + vmulfp128, + vmulosb, + vmulosh, + vmuloub, + vmulouh, + vnmsubfp, + vnmsubfp128, + vnor, + vnor128, + vor, + vor128, + vperm, + vperm128, + vpermwi128, + vpkd3d128, + vpkpx, + vpkshss, + vpkshss128, + vpkshus, + vpkshus128, + vpkswss, + vpkswss128, + vpkswus, + vpkswus128, + vpkuhum, + vpkuhum128, + vpkuhus, + vpkuhus128, + vpkuwum, + vpkuwum128, + vpkuwus, + vpkuwus128, + vrefp, + vrefp128, + vrfim, + vrfim128, + vrfin, + vrfin128, + vrfip, + vrfip128, + vrfiz, + vrfiz128, + vrlb, + vrlh, + vrlimi128, + vrlw, + vrlw128, + vrsqrtefp, + vrsqrtefp128, + vsel, + vsel128, + vsl, + vslb, + vsldoi, + vsldoi128, + vslh, + vslo, + vslo128, + vslw, + vslw128, + vspltb, + vsplth, + vspltisb, + vspltish, + vspltisw, + vspltisw128, + vspltw, + vspltw128, + vsr, + vsrab, + vsrah, + vsraw, + vsraw128, + vsrb, + vsrh, + vsro, + vsro128, + vsrw, + vsrw128, + vsubcuw, + vsubfp, + vsubfp128, + vsubsbs, + vsubshs, + vsubsws, + vsububm, + vsububs, + vsubuhm, + vsubuhs, + vsubuwm, + vsubuws, + vsum2sws, + vsum4sbs, + vsum4shs, + vsum4ubs, + vsumsws, + vupkd3d128, + vupkhpx, + vupkhsb, + vupkhsb128, + vupkhsh, + vupklpx, + vupklsb, + vupklsb128, + vupklsh, + vxor, + vxor128, // XOR immediate - xori, xoris, xorx, + xori, + xoris, + xorx, // Invalid Invalid, } @@ -165,42 +532,102 @@ impl PpcOpcode { pub fn terminates_block(&self) -> bool { matches!( self, - Self::bx | Self::bcx | Self::bclrx | Self::bcctrx + Self::bx + | Self::bcx + | Self::bclrx + | Self::bcctrx | Self::sc - | Self::td | Self::tdi | Self::tw | Self::twi + | Self::td + | Self::tdi + | Self::tw + | Self::twi | Self::Invalid ) } /// Returns true if this is a load instruction. pub fn is_load(&self) -> bool { - matches!(self, - Self::lbz | Self::lbzu | Self::lbzux | Self::lbzx | - Self::lhz | Self::lhzu | Self::lhzux | Self::lhzx | - Self::lha | Self::lhau | Self::lhaux | Self::lhax | - Self::lwz | Self::lwzu | Self::lwzux | Self::lwzx | - Self::lwa | Self::lwax | Self::lwaux | - Self::ld | Self::ldu | Self::ldux | Self::ldx | - Self::lfs | Self::lfsu | Self::lfsux | Self::lfsx | - Self::lfd | Self::lfdu | Self::lfdux | Self::lfdx | - Self::lhbrx | Self::lwbrx | Self::ldbrx | - Self::lmw | Self::lswi | Self::lswx | - Self::lwarx | Self::ldarx + matches!( + self, + Self::lbz + | Self::lbzu + | Self::lbzux + | Self::lbzx + | Self::lhz + | Self::lhzu + | Self::lhzux + | Self::lhzx + | Self::lha + | Self::lhau + | Self::lhaux + | Self::lhax + | Self::lwz + | Self::lwzu + | Self::lwzux + | Self::lwzx + | Self::lwa + | Self::lwax + | Self::lwaux + | Self::ld + | Self::ldu + | Self::ldux + | Self::ldx + | Self::lfs + | Self::lfsu + | Self::lfsux + | Self::lfsx + | Self::lfd + | Self::lfdu + | Self::lfdux + | Self::lfdx + | Self::lhbrx + | Self::lwbrx + | Self::ldbrx + | Self::lmw + | Self::lswi + | Self::lswx + | Self::lwarx + | Self::ldarx ) } /// Returns true if this is a store instruction. pub fn is_store(&self) -> bool { - matches!(self, - Self::stb | Self::stbu | Self::stbux | Self::stbx | - Self::sth | Self::sthu | Self::sthux | Self::sthx | - Self::stw | Self::stwu | Self::stwux | Self::stwx | - Self::std | Self::stdu | Self::stdux | Self::stdx | - Self::stfs | Self::stfsu | Self::stfsux | Self::stfsx | - Self::stfd | Self::stfdu | Self::stfdux | Self::stfdx | - Self::sthbrx | Self::stwbrx | Self::stdbrx | - Self::stmw | Self::stswi | Self::stswx | - Self::stwcx | Self::stdcx | Self::stfiwx + matches!( + self, + Self::stb + | Self::stbu + | Self::stbux + | Self::stbx + | Self::sth + | Self::sthu + | Self::sthux + | Self::sthx + | Self::stw + | Self::stwu + | Self::stwux + | Self::stwx + | Self::std + | Self::stdu + | Self::stdux + | Self::stdx + | Self::stfs + | Self::stfsu + | Self::stfsux + | Self::stfsx + | Self::stfd + | Self::stfdu + | Self::stfdux + | Self::stfdx + | Self::sthbrx + | Self::stwbrx + | Self::stdbrx + | Self::stmw + | Self::stswi + | Self::stswx + | Self::stwcx + | Self::stdcx + | Self::stfiwx ) } @@ -227,8 +654,13 @@ impl PpcOpcode { pub fn is_sync_sensitive(&self) -> bool { matches!( self, - Self::lwarx | Self::ldarx | Self::stwcx | Self::stdcx - | Self::sync | Self::eieio | Self::isync + Self::lwarx + | Self::ldarx + | Self::stwcx + | Self::stdcx + | Self::sync + | Self::eieio + | Self::isync ) } diff --git a/crates/sylpheed-xex/src/lib.rs b/crates/sylpheed-xex/src/lib.rs index ed9f46c7..0a23b346 100644 --- a/crates/sylpheed-xex/src/lib.rs +++ b/crates/sylpheed-xex/src/lib.rs @@ -4,13 +4,13 @@ //! From `xenia-rs`'s `xenia-xex` + `xenia-vfs` when that emulator was retired. //! `docs/agents/CONSOLIDATION.md` Phase 3. -pub mod vfs; pub mod header; pub mod loader; pub mod lzx; -pub mod pe; pub mod pdata; +pub mod pe; pub mod resources; pub mod tls; +pub mod vfs; pub use header::Xex2Header; diff --git a/crates/sylpheed-xex/src/loader.rs b/crates/sylpheed-xex/src/loader.rs index 17d6bc14..4d86afbc 100644 --- a/crates/sylpheed-xex/src/loader.rs +++ b/crates/sylpheed-xex/src/loader.rs @@ -1,6 +1,6 @@ use crate::header::*; -use aes::cipher::{BlockDecrypt, KeyInit}; use aes::Aes128; +use aes::cipher::{BlockDecrypt, KeyInit}; use byteorder::{BigEndian, ReadBytesExt}; use std::io::{self, Cursor, Read, Seek, SeekFrom}; @@ -12,7 +12,10 @@ pub fn parse_xex2_header(data: &[u8]) -> io::Result { if magic != XEX2_MAGIC { return Err(io::Error::new( io::ErrorKind::InvalidData, - format!("Invalid XEX2 magic: {:#010x} (expected {:#010x})", magic, XEX2_MAGIC), + format!( + "Invalid XEX2 magic: {:#010x} (expected {:#010x})", + magic, XEX2_MAGIC + ), )); } @@ -84,41 +87,41 @@ fn parse_security_info(cursor: &mut Cursor<&[u8]>) -> io::Result()?; // 0x000 - let image_size = cursor.read_u32::()?; // 0x004 + let _header_size = cursor.read_u32::()?; // 0x000 + let image_size = cursor.read_u32::()?; // 0x004 // Skip RSA signature (0x100 bytes) let mut rsa_sig = [0u8; 0x100]; - cursor.read_exact(&mut rsa_sig)?; // 0x008 + cursor.read_exact(&mut rsa_sig)?; // 0x008 - let _unk_108 = cursor.read_u32::()?; // 0x108 - let image_flags = cursor.read_u32::()?; // 0x10C - let load_address = cursor.read_u32::()?; // 0x110 + let _unk_108 = cursor.read_u32::()?; // 0x108 + let image_flags = cursor.read_u32::()?; // 0x10C + let load_address = cursor.read_u32::()?; // 0x110 // Skip section_digest (0x14 bytes) let mut digest = [0u8; 0x14]; - cursor.read_exact(&mut digest)?; // 0x114 + cursor.read_exact(&mut digest)?; // 0x114 let _import_table_count = cursor.read_u32::()?; // 0x128 // Skip import_table_digest (0x14 bytes) - cursor.read_exact(&mut digest)?; // 0x12C + cursor.read_exact(&mut digest)?; // 0x12C // Skip xgd2_media_id (0x10 bytes) let mut media_id = [0u8; 0x10]; - cursor.read_exact(&mut media_id)?; // 0x140 + cursor.read_exact(&mut media_id)?; // 0x140 // Read aes_key (0x10 bytes) let mut aes_key = [0u8; 0x10]; - cursor.read_exact(&mut aes_key)?; // 0x150 + cursor.read_exact(&mut aes_key)?; // 0x150 let export_table_address = cursor.read_u32::()?; // 0x160 // Skip header_digest (0x14 bytes) - cursor.read_exact(&mut digest)?; // 0x164 + cursor.read_exact(&mut digest)?; // 0x164 - let _region = cursor.read_u32::()?; // 0x178 - let _allowed_media = cursor.read_u32::()?; // 0x17C + let _region = cursor.read_u32::()?; // 0x178 + let _allowed_media = cursor.read_u32::()?; // 0x17C let page_descriptor_count = cursor.read_u32::()?; // 0x180 @@ -144,7 +147,9 @@ fn parse_security_info(cursor: &mut Cursor<&[u8]>) -> io::Result Option { // The key format: low 8 bits indicate the data size category // 0xFF = data offset is a pointer to variable-size data in the header area - let header = headers.iter().find(|h| h.key == header_keys::FILE_FORMAT_INFO)?; + let header = headers + .iter() + .find(|h| h.key == header_keys::FILE_FORMAT_INFO)?; let offset = header.value as usize; if offset + 8 > data.len() { return None; @@ -166,11 +171,18 @@ fn parse_file_format_info(data: &[u8], headers: &[Xex2OptionalHeader]) -> Option COMPRESSION_BASIC => { // Basic compression blocks: (data_size, zero_size) pairs // Number of blocks = (info_size - 8) / 8 - let block_count = if info_size > 8 { (info_size - 8) / 8 } else { 0 }; + let block_count = if info_size > 8 { + (info_size - 8) / 8 + } else { + 0 + }; for _ in 0..block_count { let data_size = cursor.read_u32::().ok()?; let zero_size = cursor.read_u32::().ok()?; - basic_blocks.push(BasicCompressionBlock { data_size, zero_size }); + basic_blocks.push(BasicCompressionBlock { + data_size, + zero_size, + }); } } COMPRESSION_NORMAL => { @@ -197,7 +209,10 @@ fn parse_file_format_info(data: &[u8], headers: &[Xex2OptionalHeader]) -> Option /// At this stage, only record addresses are read; ordinals and record types /// are resolved later by `resolve_imports` once the PE image is decompressed. fn parse_import_libraries(data: &[u8], headers: &[Xex2OptionalHeader]) -> Vec { - let header = match headers.iter().find(|h| h.key == header_keys::IMPORT_LIBRARIES) { + let header = match headers + .iter() + .find(|h| h.key == header_keys::IMPORT_LIBRARIES) + { Some(h) => h, None => return Vec::new(), }; @@ -208,10 +223,10 @@ fn parse_import_libraries(data: &[u8], headers: &[Xex2OptionalHeader]) -> Vec u32 { - u32::from_be_bytes([data[off], data[off+1], data[off+2], data[off+3]]) + u32::from_be_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) } fn be_u16(data: &[u8], off: usize) -> u16 { - u16::from_be_bytes([data[off], data[off+1]]) + u16::from_be_bytes([data[off], data[off + 1]]) } let total_size = be_u32(data, offset) as usize; @@ -225,11 +240,17 @@ fn parse_import_libraries(data: &[u8], headers: &[Xex2OptionalHeader]) -> Vec Vec Vec> 24) & 0xFF) as u8; imp.ordinal = (val & 0xFFFF) as u16; @@ -299,14 +327,21 @@ fn parse_execution_info(data: &[u8], headers: &[Xex2OptionalHeader]) -> Option data.len() { return None; } - let media_id = u32::from_be_bytes([data[off], data[off+1], data[off+2], data[off+3]]); - let title_id = u32::from_be_bytes([data[off+12], data[off+13], data[off+14], data[off+15]]); + let media_id = u32::from_be_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]); + let title_id = u32::from_be_bytes([ + data[off + 12], + data[off + 13], + data[off + 14], + data[off + 15], + ]); let disc_number = data[off + 18]; let disc_count = data[off + 19]; @@ -320,24 +355,33 @@ fn parse_execution_info(data: &[u8], headers: &[Xex2OptionalHeader]) -> Option Option { - let header = headers.iter().find(|h| h.key == header_keys::ORIGINAL_PE_NAME)?; + let header = headers + .iter() + .find(|h| h.key == header_keys::ORIGINAL_PE_NAME)?; let off = header.value as usize; if off + 4 > data.len() { return None; } - let size = u32::from_be_bytes([data[off], data[off+1], data[off+2], data[off+3]]) as usize; + let size = + u32::from_be_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) as usize; if off + size > data.len() || size <= 4 { return None; } let name_bytes = &data[off + 4..off + size]; - Some(String::from_utf8_lossy(name_bytes).trim_end_matches('\0').to_string()) + Some( + String::from_utf8_lossy(name_bytes) + .trim_end_matches('\0') + .to_string(), + ) } /// Get an optional header value by key. pub fn get_opt_header(header: &Xex2Header, key: u32) -> Option { - header.optional_headers.iter() + header + .optional_headers + .iter() .find(|h| h.key == key) .map(|h| h.value) } @@ -389,15 +433,27 @@ pub fn load_image(data: &[u8], header: &Xex2Header) -> io::Result> { metrics::histogram!("xex.load_image_ms").record(elapsed_ms); metrics::counter!("xex.bytes_in").increment(bytes_in as u64); metrics::counter!("xex.bytes_out").increment(output.len() as u64); - let ratio = if bytes_in == 0 { 0.0 } else { output.len() as f64 / bytes_in as f64 }; - tracing::info!(bytes_in, bytes_out = output.len(), ratio, elapsed_ms, "image loaded"); + let ratio = if bytes_in == 0 { + 0.0 + } else { + output.len() as f64 / bytes_in as f64 + }; + tracing::info!( + bytes_in, + bytes_out = output.len(), + ratio, + elapsed_ms, + "image loaded" + ); Ok(output) } /// Load basic compressed image data. fn load_basic_compressed(source: &[u8], info: &FileFormatInfo) -> io::Result> { // Calculate total uncompressed size - let total_size: u64 = info.basic_blocks.iter() + let total_size: u64 = info + .basic_blocks + .iter() .map(|b| b.data_size as u64 + b.zero_size as u64) .sum(); @@ -412,8 +468,12 @@ fn load_basic_compressed(source: &[u8], info: &FileFormatInfo) -> io::Result source.len() { return Err(io::Error::new( io::ErrorKind::UnexpectedEof, - format!("Basic compression block data extends past end of file (src_offset={:#x}, data_size={:#x}, source_len={:#x})", - src_offset, data_size, source.len()), + format!( + "Basic compression block data extends past end of file (src_offset={:#x}, data_size={:#x}, source_len={:#x})", + src_offset, + data_size, + source.len() + ), )); } @@ -434,8 +494,7 @@ fn load_basic_compressed(source: &[u8], info: &FileFormatInfo) -> io::Result [u8; 16] { /// /// The first block's size comes from the file format header (first_block_size). /// Each block in the data starts with a block_info struct for the NEXT block: -/// - block_size: u32 BE (size of the next block) -/// - block_hash: [u8; 20] (SHA1 of the next block) +/// +/// - block_size: u32 BE (size of the next block) +/// - block_hash: [u8; 20] (SHA1 of the next block) +/// /// Followed by chunks: { chunk_size: u16 BE, data: [u8; chunk_size] }, terminated by chunk_size=0 fn deblock(input: &[u8], first_block_size: u32) -> io::Result> { let mut output = Vec::new(); @@ -499,9 +560,8 @@ fn deblock(input: &[u8], first_block_size: u32) -> io::Result> { // Read next block's info from start of current block data let next_block_size = if pos + 4 <= input.len() { - u32::from_be_bytes([ - input[pos], input[pos + 1], input[pos + 2], input[pos + 3], - ]) as usize + u32::from_be_bytes([input[pos], input[pos + 1], input[pos + 2], input[pos + 3]]) + as usize } else { 0 }; @@ -522,8 +582,12 @@ fn deblock(input: &[u8], first_block_size: u32) -> io::Result> { if p + chunk_size > input.len() { return Err(io::Error::new( io::ErrorKind::UnexpectedEof, - format!("De-block chunk extends past input (pos={:#x}, chunk_size={:#x}, input_len={:#x})", - p, chunk_size, input.len()), + format!( + "De-block chunk extends past input (pos={:#x}, chunk_size={:#x}, input_len={:#x})", + p, + chunk_size, + input.len() + ), )); } output.extend_from_slice(&input[p..p + chunk_size]); @@ -543,8 +607,14 @@ fn deblock(input: &[u8], first_block_size: u32) -> io::Result> { /// Load normal (LZX) compressed image data. /// Pipeline: decrypt → de-block → LZX decompress (pure Rust) #[tracing::instrument(skip_all, fields(bytes_in = source.len()))] -fn load_normal_compressed(source: &[u8], info: &FileFormatInfo, header: &Xex2Header) -> io::Result> { - let uncompressed_size = header.security_info.as_ref() +fn load_normal_compressed( + source: &[u8], + info: &FileFormatInfo, + header: &Xex2Header, +) -> io::Result> { + let uncompressed_size = header + .security_info + .as_ref() .map(|s| s.image_size as usize) .unwrap_or(0); @@ -577,15 +647,25 @@ fn load_normal_compressed(source: &[u8], info: &FileFormatInfo, header: &Xex2Hea // Step 3: LZX decompress using pure Rust decoder let window_bits = match info.normal_window_size { - s if s == 0 => 15, // default + 0 => 15, // default s => (s as f64).log2() as u32, }; let mut decoder = crate::lzx::LzxDecoder::new(window_bits); - let output = decoder.decompress(&deblocked, uncompressed_size) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("LZX decompression failed: {e}")))?; + let output = decoder + .decompress(&deblocked, uncompressed_size) + .map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("LZX decompression failed: {e}"), + ) + })?; - tracing::info!("LZX decompressed: {} -> {} bytes", deblocked.len(), uncompressed_size); + tracing::info!( + "LZX decompressed: {} -> {} bytes", + deblocked.len(), + uncompressed_size + ); Ok(output) } diff --git a/crates/sylpheed-xex/src/lzx.rs b/crates/sylpheed-xex/src/lzx.rs index 2a8bf2c1..50850bed 100644 --- a/crates/sylpheed-xex/src/lzx.rs +++ b/crates/sylpheed-xex/src/lzx.rs @@ -1,3 +1,10 @@ +// 🔴 INDEX ARITHMETIC IS THE ALGORITHM HERE, so the range-loop lints are off for +// this file and this file only. LZX decoding is defined over symbol indices, +// Huffman table slots and window positions; rewriting those loops as iterators +// would hide the correspondence with the format, and a decompressor that is +// merely idiomatic is worth nothing if it is not bit-exact. +#![allow(clippy::needless_range_loop, clippy::explicit_counter_loop)] + //! LZX decompressor for Xbox 360 XEX2 "normal compression". //! Ported from libmspack lzxd.c (C) 2003-2013 Stuart Caie, LGPL 2.1. @@ -32,9 +39,8 @@ const BITBUF_WIDTH: u32 = 32; static POSITION_SLOTS: [u32; 11] = [30, 32, 34, 36, 38, 42, 50, 66, 98, 162, 290]; static EXTRA_BITS: [u8; 36] = [ - 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, - 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, - 15, 15, 16, 16, + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, + 13, 14, 14, 15, 15, 16, 16, ]; #[rustfmt::skip] @@ -109,17 +115,30 @@ struct BitReader<'a> { impl<'a> BitReader<'a> { fn new(data: &'a [u8]) -> Self { - Self { data, pos: 0, buf: 0, left: 0 } + Self { + data, + pos: 0, + buf: 0, + left: 0, + } } /// Inject one 16-bit little-endian pair into MSB bit buffer. fn fill(&mut self) { let b0 = if self.pos < self.data.len() { - let b = self.data[self.pos]; self.pos += 1; b as u32 - } else { 0 }; + let b = self.data[self.pos]; + self.pos += 1; + b as u32 + } else { + 0 + }; let b1 = if self.pos < self.data.len() { - let b = self.data[self.pos]; self.pos += 1; b as u32 - } else { 0 }; + let b = self.data[self.pos]; + self.pos += 1; + b as u32 + } else { + 0 + }; let word = (b1 << 8) | b0; self.buf |= word << (16 - self.left as u32); self.left += 16; @@ -127,7 +146,9 @@ impl<'a> BitReader<'a> { #[inline] fn ensure(&mut self, n: i32) { - while self.left < n { self.fill(); } + while self.left < n { + self.fill(); + } } #[inline] @@ -152,26 +173,29 @@ impl<'a> BitReader<'a> { /// Read a raw byte directly (for UNCOMPRESSED blocks). fn raw_byte(&mut self) -> u8 { if self.pos < self.data.len() { - let b = self.data[self.pos]; self.pos += 1; b - } else { 0 } + let b = self.data[self.pos]; + self.pos += 1; + b + } else { + 0 + } } /// Re-align the bitstream at a frame boundary. fn align_frame(&mut self) { - if self.left > 0 { self.ensure(16); } + if self.left > 0 { + self.ensure(16); + } let r = self.left & 15; - if r != 0 { self.remove(r as u32); } + if r != 0 { + self.remove(r as u32); + } } } // ── Huffman table builder (MSB order) ─────────────────────────────────────── -fn make_decode_table( - nsyms: usize, - nbits: usize, - length: &[u8], - table: &mut [u16], -) -> bool { +fn make_decode_table(nsyms: usize, nbits: usize, length: &[u8], table: &mut [u16]) -> bool { let mut pos: usize = 0; let table_mask = 1usize << nbits; let mut bit_mask = table_mask >> 1; @@ -179,10 +203,14 @@ fn make_decode_table( // Short codes: direct mapping for bit_num in 1..=nbits { for sym in 0..nsyms { - if length[sym] as usize != bit_num { continue; } + if length[sym] as usize != bit_num { + continue; + } let leaf = pos; pos += bit_mask; - if pos > table_mask { return true; } + if pos > table_mask { + return true; + } for i in leaf..leaf + bit_mask { table[i] = sym as u16; } @@ -190,14 +218,20 @@ fn make_decode_table( bit_mask >>= 1; } - if pos == table_mask { return false; } + if pos == table_mask { + return false; + } // Mark remaining entries as unused for i in pos..table_mask { table[i] = 0xFFFF; } - let mut next_symbol = if (table_mask >> 1) < nsyms { nsyms } else { table_mask >> 1 }; + let mut next_symbol = if (table_mask >> 1) < nsyms { + nsyms + } else { + table_mask >> 1 + }; let mut pos32 = (pos as u32) << 16; let table_mask32 = (table_mask as u32) << 16; @@ -206,8 +240,12 @@ fn make_decode_table( // Long codes: tree traversal for bit_num in (nbits + 1)..=HUFF_MAXBITS { for sym in 0..nsyms { - if length[sym] as usize != bit_num { continue; } - if pos32 >= table_mask32 { return true; } + if length[sym] as usize != bit_num { + continue; + } + if pos32 >= table_mask32 { + return true; + } let mut leaf = (pos32 >> 16) as usize; @@ -247,9 +285,13 @@ fn read_huffsym( let mut i: u32 = 1 << (BITBUF_WIDTH - tablebits as u32); loop { i >>= 1; - if i == 0 { return Err(LzxError::BadHuffmanTable); } + if i == 0 { + return Err(LzxError::BadHuffmanTable); + } sym = table[(sym << 1) | if br.buf & i != 0 { 1 } else { 0 }] as usize; - if sym < maxsyms { break; } + if sym < maxsyms { + break; + } } } br.remove(lens[sym] as u32); @@ -307,7 +349,9 @@ impl LzxDecoder { frame_posn: 0, frame: 0, num_offsets, - r0: 1, r1: 1, r2: 1, + r0: 1, + r1: 1, + r2: 1, block_type: 0, block_length: 0, block_remaining: 0, @@ -315,20 +359,23 @@ impl LzxDecoder { intel_filesize: 0, intel_curpos: 0, intel_started: false, - pretree_len: vec![0u8; PRETREE_MAXSYMS + LENTABLE_SAFETY], - maintree_len: vec![0u8; MAINTREE_MAXSYMS + LENTABLE_SAFETY], - length_len: vec![0u8; LENGTH_MAXSYMS + LENTABLE_SAFETY], - aligned_len: vec![0u8; ALIGNED_MAXSYMS + LENTABLE_SAFETY], - pretree_table: vec![0u16; (1 << PRETREE_TABLEBITS) + PRETREE_MAXSYMS * 2], + pretree_len: vec![0u8; PRETREE_MAXSYMS + LENTABLE_SAFETY], + maintree_len: vec![0u8; MAINTREE_MAXSYMS + LENTABLE_SAFETY], + length_len: vec![0u8; LENGTH_MAXSYMS + LENTABLE_SAFETY], + aligned_len: vec![0u8; ALIGNED_MAXSYMS + LENTABLE_SAFETY], + pretree_table: vec![0u16; (1 << PRETREE_TABLEBITS) + PRETREE_MAXSYMS * 2], maintree_table: vec![0u16; (1 << MAINTREE_TABLEBITS) + MAINTREE_MAXSYMS * 2], - length_table: vec![0u16; (1 << LENGTH_TABLEBITS) + LENGTH_MAXSYMS * 2], - aligned_table: vec![0u16; (1 << ALIGNED_TABLEBITS) + ALIGNED_MAXSYMS * 2], + length_table: vec![0u16; (1 << LENGTH_TABLEBITS) + LENGTH_MAXSYMS * 2], + aligned_table: vec![0u16; (1 << ALIGNED_TABLEBITS) + ALIGNED_MAXSYMS * 2], length_empty: false, } } fn build_table( - lens: &[u8], table: &mut [u16], maxsyms: usize, tablebits: usize, + lens: &[u8], + table: &mut [u16], + maxsyms: usize, + tablebits: usize, ) -> Result<(), LzxError> { if make_decode_table(maxsyms, tablebits, lens, table) { Err(LzxError::BadHuffmanTable) @@ -338,7 +385,10 @@ impl LzxDecoder { } fn build_table_maybe_empty( - lens: &[u8], table: &mut [u16], maxsyms: usize, tablebits: usize, + lens: &[u8], + table: &mut [u16], + maxsyms: usize, + tablebits: usize, ) -> Result { if make_decode_table(maxsyms, tablebits, lens, table) { // Check if table is simply empty (all lengths zero) @@ -366,30 +416,63 @@ impl LzxDecoder { for i in 0..20 { pretree_len[i] = br.read(4) as u8; } - Self::build_table(pretree_len, pretree_table, PRETREE_MAXSYMS, PRETREE_TABLEBITS)?; + Self::build_table( + pretree_len, + pretree_table, + PRETREE_MAXSYMS, + PRETREE_TABLEBITS, + )?; let mut x = first; while x < last { - let z = read_huffsym(br, pretree_table, pretree_len, PRETREE_TABLEBITS, PRETREE_MAXSYMS)?; + let z = read_huffsym( + br, + pretree_table, + pretree_len, + PRETREE_TABLEBITS, + PRETREE_MAXSYMS, + )?; if z == 17 { // Run of zeros: [read 4 bits] + 4 let mut y = br.read(4) as usize + 4; - while y > 0 && x < last { lens[x] = 0; x += 1; y -= 1; } + while y > 0 && x < last { + lens[x] = 0; + x += 1; + y -= 1; + } } else if z == 18 { // Run of zeros: [read 5 bits] + 20 let mut y = br.read(5) as usize + 20; - while y > 0 && x < last { lens[x] = 0; x += 1; y -= 1; } + while y > 0 && x < last { + lens[x] = 0; + x += 1; + y -= 1; + } } else if z == 19 { // Run of same: [read 1 bit] + 4, then read symbol let mut y = br.read(1) as usize + 4; - let z2 = read_huffsym(br, pretree_table, pretree_len, PRETREE_TABLEBITS, PRETREE_MAXSYMS)?; + let z2 = read_huffsym( + br, + pretree_table, + pretree_len, + PRETREE_TABLEBITS, + PRETREE_MAXSYMS, + )?; let mut val = lens[x] as i32 - z2 as i32; - if val < 0 { val += 17; } - while y > 0 && x < last { lens[x] = val as u8; x += 1; y -= 1; } + if val < 0 { + val += 17; + } + while y > 0 && x < last { + lens[x] = val as u8; + x += 1; + y -= 1; + } } else { // Delta: code 0..16 let mut val = lens[x] as i32 - z as i32; - if val < 0 { val += 17; } + if val < 0 { + val += 17; + } lens[x] = val as u8; x += 1; } @@ -425,13 +508,15 @@ impl LzxDecoder { LZX_FRAME_SIZE }; - let mut bytes_todo = (self.frame_posn + frame_size).wrapping_sub(self.window_posn) as i32; + let mut bytes_todo = + (self.frame_posn + frame_size).wrapping_sub(self.window_posn) as i32; while bytes_todo > 0 { // New block? if self.block_remaining == 0 { // Realign after odd UNCOMPRESSED block - if self.block_type == LZX_BLOCKTYPE_UNCOMPRESSED && (self.block_length & 1) != 0 { + if self.block_type == LZX_BLOCKTYPE_UNCOMPRESSED && (self.block_length & 1) != 0 + { br.raw_byte(); } // Read block type (3 bits) and length (24 bits) @@ -443,33 +528,110 @@ impl LzxDecoder { match self.block_type { LZX_BLOCKTYPE_ALIGNED => { - for i in 0..8 { self.aligned_len[i] = br.read(3) as u8; } - Self::build_table(&self.aligned_len, &mut self.aligned_table, ALIGNED_MAXSYMS, ALIGNED_TABLEBITS)?; + for i in 0..8 { + self.aligned_len[i] = br.read(3) as u8; + } + Self::build_table( + &self.aligned_len, + &mut self.aligned_table, + ALIGNED_MAXSYMS, + ALIGNED_TABLEBITS, + )?; // Fall through to verbatim tree reading - Self::read_lens(&mut br, &mut self.maintree_len, &mut self.pretree_len, &mut self.pretree_table, 0, 256)?; - Self::read_lens(&mut br, &mut self.maintree_len, &mut self.pretree_len, &mut self.pretree_table, 256, LZX_NUM_CHARS + self.num_offsets)?; - Self::build_table(&self.maintree_len, &mut self.maintree_table, MAINTREE_MAXSYMS, MAINTREE_TABLEBITS)?; - if self.maintree_len[0xE8] != 0 { self.intel_started = true; } - Self::read_lens(&mut br, &mut self.length_len, &mut self.pretree_len, &mut self.pretree_table, 0, LZX_NUM_SECONDARY_LENGTHS)?; - self.length_empty = Self::build_table_maybe_empty(&self.length_len, &mut self.length_table, LENGTH_MAXSYMS, LENGTH_TABLEBITS)?; + Self::read_lens( + &mut br, + &mut self.maintree_len, + &mut self.pretree_len, + &mut self.pretree_table, + 0, + 256, + )?; + Self::read_lens( + &mut br, + &mut self.maintree_len, + &mut self.pretree_len, + &mut self.pretree_table, + 256, + LZX_NUM_CHARS + self.num_offsets, + )?; + Self::build_table( + &self.maintree_len, + &mut self.maintree_table, + MAINTREE_MAXSYMS, + MAINTREE_TABLEBITS, + )?; + if self.maintree_len[0xE8] != 0 { + self.intel_started = true; + } + Self::read_lens( + &mut br, + &mut self.length_len, + &mut self.pretree_len, + &mut self.pretree_table, + 0, + LZX_NUM_SECONDARY_LENGTHS, + )?; + self.length_empty = Self::build_table_maybe_empty( + &self.length_len, + &mut self.length_table, + LENGTH_MAXSYMS, + LENGTH_TABLEBITS, + )?; } LZX_BLOCKTYPE_VERBATIM => { - Self::read_lens(&mut br, &mut self.maintree_len, &mut self.pretree_len, &mut self.pretree_table, 0, 256)?; - Self::read_lens(&mut br, &mut self.maintree_len, &mut self.pretree_len, &mut self.pretree_table, 256, LZX_NUM_CHARS + self.num_offsets)?; - Self::build_table(&self.maintree_len, &mut self.maintree_table, MAINTREE_MAXSYMS, MAINTREE_TABLEBITS)?; - if self.maintree_len[0xE8] != 0 { self.intel_started = true; } - Self::read_lens(&mut br, &mut self.length_len, &mut self.pretree_len, &mut self.pretree_table, 0, LZX_NUM_SECONDARY_LENGTHS)?; - self.length_empty = Self::build_table_maybe_empty(&self.length_len, &mut self.length_table, LENGTH_MAXSYMS, LENGTH_TABLEBITS)?; + Self::read_lens( + &mut br, + &mut self.maintree_len, + &mut self.pretree_len, + &mut self.pretree_table, + 0, + 256, + )?; + Self::read_lens( + &mut br, + &mut self.maintree_len, + &mut self.pretree_len, + &mut self.pretree_table, + 256, + LZX_NUM_CHARS + self.num_offsets, + )?; + Self::build_table( + &self.maintree_len, + &mut self.maintree_table, + MAINTREE_MAXSYMS, + MAINTREE_TABLEBITS, + )?; + if self.maintree_len[0xE8] != 0 { + self.intel_started = true; + } + Self::read_lens( + &mut br, + &mut self.length_len, + &mut self.pretree_len, + &mut self.pretree_table, + 0, + LZX_NUM_SECONDARY_LENGTHS, + )?; + self.length_empty = Self::build_table_maybe_empty( + &self.length_len, + &mut self.length_table, + LENGTH_MAXSYMS, + LENGTH_TABLEBITS, + )?; } LZX_BLOCKTYPE_UNCOMPRESSED => { self.intel_started = true; // Align to byte boundary - if br.left == 0 { br.ensure(16); } + if br.left == 0 { + br.ensure(16); + } br.left = 0; br.buf = 0; // Read R0, R1, R2 (12 bytes, little-endian u32s) let mut buf = [0u8; 12]; - for b in &mut buf { *b = br.raw_byte(); } + for b in &mut buf { + *b = br.raw_byte(); + } self.r0 = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]); self.r1 = u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]); self.r2 = u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]); @@ -479,7 +641,9 @@ impl LzxDecoder { } let mut this_run = self.block_remaining as i32; - if this_run > bytes_todo { this_run = bytes_todo; } + if this_run > bytes_todo { + this_run = bytes_todo; + } bytes_todo -= this_run; self.block_remaining -= this_run as usize; @@ -488,7 +652,13 @@ impl LzxDecoder { match self.block_type { LZX_BLOCKTYPE_VERBATIM => { while this_run > 0 { - let main_element = read_huffsym(&mut br, &self.maintree_table, &self.maintree_len, MAINTREE_TABLEBITS, MAINTREE_MAXSYMS)?; + let main_element = read_huffsym( + &mut br, + &self.maintree_table, + &self.maintree_len, + MAINTREE_TABLEBITS, + MAINTREE_MAXSYMS, + )?; if main_element < LZX_NUM_CHARS { self.window[self.window_posn] = main_element as u8; self.window_posn += 1; @@ -497,8 +667,16 @@ impl LzxDecoder { let me = main_element - LZX_NUM_CHARS; let mut match_length = me & LZX_NUM_PRIMARY_LENGTHS; if match_length == LZX_NUM_PRIMARY_LENGTHS { - if self.length_empty { return Err(LzxError::Decrunch("LENGTH tree empty".into())); } - let footer = read_huffsym(&mut br, &self.length_table, &self.length_len, LENGTH_TABLEBITS, LENGTH_MAXSYMS)?; + if self.length_empty { + return Err(LzxError::Decrunch("LENGTH tree empty".into())); + } + let footer = read_huffsym( + &mut br, + &self.length_table, + &self.length_len, + LENGTH_TABLEBITS, + LENGTH_MAXSYMS, + )?; match_length += footer; } match_length += LZX_MIN_MATCH; @@ -506,14 +684,34 @@ impl LzxDecoder { let mut match_offset = (me >> 3) as u32; match match_offset { 0 => match_offset = self.r0, - 1 => { match_offset = self.r1; self.r1 = self.r0; self.r0 = match_offset; } - 2 => { match_offset = self.r2; self.r2 = self.r0; self.r0 = match_offset; } - 3 => { match_offset = 1; self.r2 = self.r1; self.r1 = self.r0; self.r0 = match_offset; } + 1 => { + match_offset = self.r1; + self.r1 = self.r0; + self.r0 = match_offset; + } + 2 => { + match_offset = self.r2; + self.r2 = self.r0; + self.r0 = match_offset; + } + 3 => { + match_offset = 1; + self.r2 = self.r1; + self.r1 = self.r0; + self.r0 = match_offset; + } _ => { - let extra = if match_offset >= 36 { 17 } else { EXTRA_BITS[match_offset as usize] as u32 }; + let extra = if match_offset >= 36 { + 17 + } else { + EXTRA_BITS[match_offset as usize] as u32 + }; let verbatim_bits = br.read(extra); - match_offset = POSITION_BASE[match_offset as usize] - 2 + verbatim_bits; - self.r2 = self.r1; self.r1 = self.r0; self.r0 = match_offset; + match_offset = POSITION_BASE[match_offset as usize] - 2 + + verbatim_bits; + self.r2 = self.r1; + self.r1 = self.r0; + self.r0 = match_offset; } } @@ -527,7 +725,13 @@ impl LzxDecoder { } LZX_BLOCKTYPE_ALIGNED => { while this_run > 0 { - let main_element = read_huffsym(&mut br, &self.maintree_table, &self.maintree_len, MAINTREE_TABLEBITS, MAINTREE_MAXSYMS)?; + let main_element = read_huffsym( + &mut br, + &self.maintree_table, + &self.maintree_len, + MAINTREE_TABLEBITS, + MAINTREE_MAXSYMS, + )?; if main_element < LZX_NUM_CHARS { self.window[self.window_posn] = main_element as u8; self.window_posn += 1; @@ -536,8 +740,16 @@ impl LzxDecoder { let me = main_element - LZX_NUM_CHARS; let mut match_length = me & LZX_NUM_PRIMARY_LENGTHS; if match_length == LZX_NUM_PRIMARY_LENGTHS { - if self.length_empty { return Err(LzxError::Decrunch("LENGTH tree empty".into())); } - let footer = read_huffsym(&mut br, &self.length_table, &self.length_len, LENGTH_TABLEBITS, LENGTH_MAXSYMS)?; + if self.length_empty { + return Err(LzxError::Decrunch("LENGTH tree empty".into())); + } + let footer = read_huffsym( + &mut br, + &self.length_table, + &self.length_len, + LENGTH_TABLEBITS, + LENGTH_MAXSYMS, + )?; match_length += footer; } match_length += LZX_MIN_MATCH; @@ -545,18 +757,42 @@ impl LzxDecoder { let mut match_offset = (me >> 3) as u32; match match_offset { 0 => match_offset = self.r0, - 1 => { match_offset = self.r1; self.r1 = self.r0; self.r0 = match_offset; } - 2 => { match_offset = self.r2; self.r2 = self.r0; self.r0 = match_offset; } + 1 => { + match_offset = self.r1; + self.r1 = self.r0; + self.r0 = match_offset; + } + 2 => { + match_offset = self.r2; + self.r2 = self.r0; + self.r0 = match_offset; + } _ => { - let extra = if match_offset >= 36 { 17 } else { EXTRA_BITS[match_offset as usize] as u32 }; + let extra = if match_offset >= 36 { + 17 + } else { + EXTRA_BITS[match_offset as usize] as u32 + }; match_offset = POSITION_BASE[match_offset as usize] - 2; if extra > 3 { let verbatim_bits = br.read(extra - 3); match_offset += verbatim_bits << 3; - let aligned = read_huffsym(&mut br, &self.aligned_table, &self.aligned_len, ALIGNED_TABLEBITS, ALIGNED_MAXSYMS)?; + let aligned = read_huffsym( + &mut br, + &self.aligned_table, + &self.aligned_len, + ALIGNED_TABLEBITS, + ALIGNED_MAXSYMS, + )?; match_offset += aligned as u32; } else if extra == 3 { - let aligned = read_huffsym(&mut br, &self.aligned_table, &self.aligned_len, ALIGNED_TABLEBITS, ALIGNED_MAXSYMS)?; + let aligned = read_huffsym( + &mut br, + &self.aligned_table, + &self.aligned_len, + ALIGNED_TABLEBITS, + ALIGNED_MAXSYMS, + )?; match_offset += aligned as u32; } else if extra > 0 { let verbatim_bits = br.read(extra); @@ -564,7 +800,9 @@ impl LzxDecoder { } else { match_offset = 1; } - self.r2 = self.r1; self.r1 = self.r0; self.r0 = match_offset; + self.r2 = self.r1; + self.r1 = self.r0; + self.r0 = match_offset; } } @@ -599,7 +837,9 @@ impl LzxDecoder { // Frame boundary check if (self.window_posn.wrapping_sub(self.frame_posn)) != frame_size { return Err(LzxError::Decrunch(format!( - "decode beyond frame: {} != {}", self.window_posn - self.frame_posn, frame_size + "decode beyond frame: {} != {}", + self.window_posn - self.frame_posn, + frame_size ))); } @@ -607,8 +847,10 @@ impl LzxDecoder { br.align_frame(); // Intel E8 postprocessing - if self.intel_started && self.intel_filesize != 0 - && self.frame <= 32768 && frame_size > 10 + if self.intel_started + && self.intel_filesize != 0 + && self.frame <= 32768 + && frame_size > 10 { let mut e8_buf = vec![0u8; frame_size]; e8_buf.copy_from_slice(&self.window[self.frame_posn..self.frame_posn + frame_size]); @@ -619,18 +861,26 @@ impl LzxDecoder { let filesize = self.intel_filesize; while i < limit { - if e8_buf[i] != 0xE8 { i += 1; curpos += 1; continue; } - let abs_off = e8_buf[i+1] as i32 - | (e8_buf[i+2] as i32) << 8 - | (e8_buf[i+3] as i32) << 16 - | (e8_buf[i+4] as i32) << 24; + if e8_buf[i] != 0xE8 { + i += 1; + curpos += 1; + continue; + } + let abs_off = e8_buf[i + 1] as i32 + | (e8_buf[i + 2] as i32) << 8 + | (e8_buf[i + 3] as i32) << 16 + | (e8_buf[i + 4] as i32) << 24; if abs_off >= -curpos && abs_off < filesize { - let rel_off = if abs_off >= 0 { abs_off - curpos } else { abs_off + filesize }; - e8_buf[i+1] = rel_off as u8; - e8_buf[i+2] = (rel_off >> 8) as u8; - e8_buf[i+3] = (rel_off >> 16) as u8; - e8_buf[i+4] = (rel_off >> 24) as u8; + let rel_off = if abs_off >= 0 { + abs_off - curpos + } else { + abs_off + filesize + }; + e8_buf[i + 1] = rel_off as u8; + e8_buf[i + 2] = (rel_off >> 8) as u8; + e8_buf[i + 3] = (rel_off >> 16) as u8; + e8_buf[i + 4] = (rel_off >> 24) as u8; } i += 5; curpos += 5; @@ -641,7 +891,9 @@ impl LzxDecoder { output.extend_from_slice(&e8_buf[..to_write]); offset += to_write; } else { - if self.intel_filesize != 0 { self.intel_curpos += frame_size as i32; } + if self.intel_filesize != 0 { + self.intel_curpos += frame_size as i32; + } let to_write = frame_size.min(output_len - offset); output.extend_from_slice(&self.window[self.frame_posn..self.frame_posn + to_write]); offset += to_write; @@ -650,8 +902,12 @@ impl LzxDecoder { // Advance frame self.frame_posn += frame_size; self.frame += 1; - if self.window_posn == self.window_size { self.window_posn = 0; } - if self.frame_posn == self.window_size { self.frame_posn = 0; } + if self.window_posn == self.window_size { + self.window_posn = 0; + } + if self.frame_posn == self.window_size { + self.frame_posn = 0; + } } Ok(output) diff --git a/crates/sylpheed-xex/src/pdata.rs b/crates/sylpheed-xex/src/pdata.rs index 1a0c08f8..e3cf6036 100644 --- a/crates/sylpheed-xex/src/pdata.rs +++ b/crates/sylpheed-xex/src/pdata.rs @@ -91,7 +91,11 @@ pub fn parse_pdata(pe: &[u8], image_base: u32, sections: &[PeSection]) -> Vec= image_base && e.begin_address < high); @@ -104,7 +108,12 @@ mod tests { use super::*; use crate::pe::PeSection; - fn mk_pe(image_base: u32, text_va: u32, text_size: u32, pdata: &[(u32, u32)]) -> (Vec, Vec) { + fn mk_pe( + image_base: u32, + text_va: u32, + text_size: u32, + pdata: &[(u32, u32)], + ) -> (Vec, Vec) { // Build a synthetic PE image with .text and .pdata. // Layout: pdata at RVA 0x1000, .text at RVA 0x2000. let pdata_rva = 0x1000u32; diff --git a/crates/sylpheed-xex/src/pe.rs b/crates/sylpheed-xex/src/pe.rs index e7ec272f..9c6f8960 100644 --- a/crates/sylpheed-xex/src/pe.rs +++ b/crates/sylpheed-xex/src/pe.rs @@ -46,7 +46,9 @@ pub fn parse_sections(pe: &[u8]) -> anyhow::Result> { let mut sections = Vec::new(); for i in 0..num_sections { let s = section_table_off + i * 40; - if s + 40 > pe.len() { break; } + if s + 40 > pe.len() { + break; + } let name_bytes = &pe[s..s + 8]; let name = std::str::from_utf8(name_bytes) diff --git a/crates/sylpheed-xex/src/resources.rs b/crates/sylpheed-xex/src/resources.rs index 38223a6c..f794e6cc 100644 --- a/crates/sylpheed-xex/src/resources.rs +++ b/crates/sylpheed-xex/src/resources.rs @@ -53,7 +53,8 @@ pub fn parse_resources(data: &[u8], header: &Xex2Header) -> Vec { if off + 4 > data.len() { return Vec::new(); } - let size = u32::from_be_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) as usize; + let size = + u32::from_be_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]) as usize; // The size field counts itself; anything smaller than one record is junk. if size < 4 + 16 || off + size > data.len() { return Vec::new(); @@ -67,7 +68,11 @@ pub fn parse_resources(data: &[u8], header: &Xex2Header) -> Vec { .to_string(); let address = u32::from_be_bytes([data[p + 8], data[p + 9], data[p + 10], data[p + 11]]); let rsize = u32::from_be_bytes([data[p + 12], data[p + 13], data[p + 14], data[p + 15]]); - out.push(XexResource { name, address, size: rsize }); + out.push(XexResource { + name, + address, + size: rsize, + }); } out } @@ -94,7 +99,10 @@ mod tests { } fn with_resource(value: u32) -> Xex2Header { - mk_header(vec![Xex2OptionalHeader { key: header_keys::RESOURCE_INFO, value }]) + mk_header(vec![Xex2OptionalHeader { + key: header_keys::RESOURCE_INFO, + value, + }]) } #[test] diff --git a/crates/sylpheed-xex/src/tls.rs b/crates/sylpheed-xex/src/tls.rs index c9e1e3c0..fdb7e47d 100644 --- a/crates/sylpheed-xex/src/tls.rs +++ b/crates/sylpheed-xex/src/tls.rs @@ -60,7 +60,9 @@ pub fn parse_tls(pe: &[u8], image_base: u32, sections: &[PeSection]) -> Option pe.len() { return None; } + if off + 24 > pe.len() { + return None; + } // Xbox 360 PE bodies are big-endian; this is consistent with how we // parse the PE elsewhere (e.g. xref scanning reads BE u32 from PE). @@ -69,8 +71,8 @@ pub fn parse_tls(pe: &[u8], image_base: u32, sections: &[PeSection]) -> Option Option= 64 { break; } // sanity cap + if callbacks.len() >= 64 { + break; + } // sanity cap } } @@ -137,15 +143,22 @@ mod tests { let cb_va: u32 = 0x200; // Directory fields: let raw_start = 0x800u32; - let raw_end = 0x900u32; - let idx = 0x1000u32; + let raw_end = 0x900u32; + let idx = 0x1000u32; let zero_fill = 0x40u32; - let chars = 0x0u32; - let cb_array = image_base + cb_va; + let chars = 0x0u32; + let cb_array = image_base + cb_va; for (i, v) in [ - image_base + raw_start, image_base + raw_end, - image_base + idx, cb_array, zero_fill, chars, - ].iter().enumerate() { + image_base + raw_start, + image_base + raw_end, + image_base + idx, + cb_array, + zero_fill, + chars, + ] + .iter() + .enumerate() + { pe[tls_va as usize + i * 4..tls_va as usize + i * 4 + 4] .copy_from_slice(&v.to_be_bytes()); } diff --git a/crates/sylpheed-xex/src/vfs/disc_image.rs b/crates/sylpheed-xex/src/vfs/disc_image.rs index 0254d106..90142aa4 100644 --- a/crates/sylpheed-xex/src/vfs/disc_image.rs +++ b/crates/sylpheed-xex/src/vfs/disc_image.rs @@ -121,8 +121,11 @@ impl DiscImageDevice { let node_l = u16::from_le_bytes([buffer[p], buffer[p + 1]]); let node_r = u16::from_le_bytes([buffer[p + 2], buffer[p + 3]]); - let sector = u32::from_le_bytes([buffer[p + 4], buffer[p + 5], buffer[p + 6], buffer[p + 7]]) as u64; - let length = u32::from_le_bytes([buffer[p + 8], buffer[p + 9], buffer[p + 10], buffer[p + 11]]) as u64; + let sector = + u32::from_le_bytes([buffer[p + 4], buffer[p + 5], buffer[p + 6], buffer[p + 7]]) as u64; + let length = + u32::from_le_bytes([buffer[p + 8], buffer[p + 9], buffer[p + 10], buffer[p + 11]]) + as u64; let attributes = buffer[p + 12]; let name_length = buffer[p + 13] as usize; diff --git a/crates/sylpheed-xexdb/build.rs b/crates/sylpheed-xexdb/build.rs index da7a8002..9e0e887a 100644 --- a/crates/sylpheed-xexdb/build.rs +++ b/crates/sylpheed-xexdb/build.rs @@ -34,11 +34,16 @@ fn main() { json_path.display() ) }); - let doc: serde_json::Value = serde_json::from_str(&raw).expect("export table is not valid JSON"); + let doc: serde_json::Value = + serde_json::from_str(&raw).expect("export table is not valid JSON"); let out = Path::new(&env::var("OUT_DIR").unwrap()).join("ordinals.rs"); let mut f = fs::File::create(&out).unwrap(); - writeln!(f, "/// Auto-generated from `docs/reference/xbox360-exports.json`.").unwrap(); + writeln!( + f, + "/// Auto-generated from `docs/reference/xbox360-exports.json`." + ) + .unwrap(); writeln!( f, "pub fn resolve_ordinal(lib: &str, ordinal: u16) -> Option<&'static str> {{" diff --git a/crates/sylpheed-xexdb/src/bin/sylph-xexdb.rs b/crates/sylpheed-xexdb/src/bin/sylph-xexdb.rs index 8d8bc894..af6b5db4 100644 --- a/crates/sylpheed-xexdb/src/bin/sylph-xexdb.rs +++ b/crates/sylpheed-xexdb/src/bin/sylph-xexdb.rs @@ -12,10 +12,13 @@ use std::time::Instant; use anyhow::Result; use clap::{Parser, Subcommand, ValueEnum}; -use tracing::{debug, info, instrument, warn}; +use tracing::{info, instrument, warn}; #[derive(Parser)] -#[command(name = "sylph-xexdb", about = "XEX static analysis: extract, disassemble, and build the analysis DB")] +#[command( + name = "sylph-xexdb", + about = "XEX static analysis: extract, disassemble, and build the analysis DB" +)] struct Cli { #[command(subcommand)] command: Commands, @@ -39,7 +42,6 @@ enum AnalyzeMode { #[derive(Subcommand)] enum Commands { - /// Display XEX header information Info { /// Path to XEX file @@ -130,8 +132,11 @@ fn load_xex_data(path: &str) -> Result> { if lower.ends_with(".iso") || lower.ends_with(".xiso") { use sylpheed_xex::vfs::VfsDevice; info!("detected disc image, extracting default.xex"); - let disc = sylpheed_xex::vfs::disc_image::DiscImageDevice::open("disc", std::path::Path::new(path)) - .map_err(|e| anyhow::anyhow!("Failed to open disc image: {}", e))?; + let disc = sylpheed_xex::vfs::disc_image::DiscImageDevice::open( + "disc", + std::path::Path::new(path), + ) + .map_err(|e| anyhow::anyhow!("Failed to open disc image: {}", e))?; disc.read_file("default.xex") .map_err(|e| anyhow::anyhow!("Failed to extract default.xex from disc image: {}", e)) } else { @@ -149,9 +154,26 @@ fn main() -> Result<()> { Commands::Info { path } => cmd_info(&path), Commands::Disasm { path, count, at } => cmd_disasm(&path, count, at), Commands::Browse { path } => cmd_browse(&path), - Commands::Extract { path, output, db } => cmd_extract(&path, output.as_deref(), db.as_deref()), - Commands::Dis { path, output, db, json, analyze, max_indirect_candidates, quiet } => - cmd_dis(&path, output.as_deref(), db.as_deref(), json.as_deref(), analyze, max_indirect_candidates, quiet), + Commands::Extract { path, output, db } => { + cmd_extract(&path, output.as_deref(), db.as_deref()) + } + Commands::Dis { + path, + output, + db, + json, + analyze, + max_indirect_candidates, + quiet, + } => cmd_dis( + &path, + output.as_deref(), + db.as_deref(), + json.as_deref(), + analyze, + max_indirect_candidates, + quiet, + ), } } @@ -188,12 +210,23 @@ fn cmd_info(path: &str) -> Result<()> { if let Some(ref ffi) = header.file_format_info { println!("\n=== File Format ==="); - println!("Encryption: {}", match ffi.encryption_type { - 0 => "None", 1 => "Normal (AES)", _ => "Unknown" - }); - println!("Compression: {}", match ffi.compression_type { - 0 => "None", 1 => "Basic", 2 => "Normal (LZX)", _ => "Unknown" - }); + println!( + "Encryption: {}", + match ffi.encryption_type { + 0 => "None", + 1 => "Normal (AES)", + _ => "Unknown", + } + ); + println!( + "Compression: {}", + match ffi.compression_type { + 0 => "None", + 1 => "Basic", + 2 => "Normal (LZX)", + _ => "Unknown", + } + ); if !ffi.basic_blocks.is_empty() { println!("Basic blocks: {}", ffi.basic_blocks.len()); } @@ -216,18 +249,27 @@ fn cmd_info(path: &str) -> Result<()> { if !header.import_libraries.is_empty() { println!("\n=== Import Libraries ==="); for lib in &header.import_libraries { - println!(" {} (v{:#010x}, {} imports)", lib.name, lib.version_cur, lib.imports.len()); + println!( + " {} (v{:#010x}, {} imports)", + lib.name, + lib.version_cur, + lib.imports.len() + ); } } - info!(wall_ms = started.elapsed().as_millis() as u64, "info complete"); + info!( + wall_ms = started.elapsed().as_millis() as u64, + "info complete" + ); Ok(()) } /// Clap parser for `--at` — accepts decimal, 0x-prefixed hex, or bare hex. fn parse_hex_u32(s: &str) -> Result { let t = s.trim(); - let (digits, radix) = if let Some(rest) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) { + let (digits, radix) = if let Some(rest) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) + { (rest, 16) } else if t.chars().all(|c| c.is_ascii_digit()) { (t, 10) @@ -250,14 +292,25 @@ fn cmd_disasm(path: &str, count: usize, at: Option) -> Result<()> { let base = sylpheed_xex::loader::get_image_base(&header) .ok_or_else(|| anyhow::anyhow!("No image base found in XEX2 header"))?; - info!(entry = format_args!("{:#010x}", entry), base = format_args!("{:#010x}", base), "XEX entry/base"); + info!( + entry = format_args!("{:#010x}", entry), + base = format_args!("{:#010x}", base), + "XEX entry/base" + ); let image_data = sylpheed_xex::loader::load_image(&data, &header)?; info!(bytes = image_data.len(), "image decompressed"); let start = at.unwrap_or(entry); - let label = if at.is_some() { "requested address" } else { "entry point" }; - println!("Disassembly from {} {:#010x} ({} instructions):\n", label, start, count); + let label = if at.is_some() { + "requested address" + } else { + "entry point" + }; + println!( + "Disassembly from {} {:#010x} ({} instructions):\n", + label, start, count + ); if start < base { return Err(anyhow::anyhow!( @@ -281,7 +334,10 @@ fn cmd_disasm(path: &str, count: usize, at: Option) -> Result<()> { println!(" {:#010x}: {}", addr, text); } - info!(wall_ms = started.elapsed().as_millis() as u64, "disasm complete"); + info!( + wall_ms = started.elapsed().as_millis() as u64, + "disasm complete" + ); Ok(()) } @@ -290,8 +346,9 @@ fn cmd_disasm(path: &str, count: usize, at: Option) -> Result<()> { fn cmd_browse(path: &str) -> Result<()> { use sylpheed_xex::vfs::VfsDevice; - let disc = sylpheed_xex::vfs::disc_image::DiscImageDevice::open("disc", std::path::Path::new(path)) - .map_err(|e| anyhow::anyhow!("Failed to open disc image: {}", e))?; + let disc = + sylpheed_xex::vfs::disc_image::DiscImageDevice::open("disc", std::path::Path::new(path)) + .map_err(|e| anyhow::anyhow!("Failed to open disc image: {}", e))?; println!("=== XISO Contents: {} ===", path); match disc.list_root() { @@ -307,15 +364,22 @@ fn cmd_browse(path: &str) -> Result<()> { Ok(()) } -/// Helper: load XEX, parse header, decompress PE, resolve imports, parse sections. -#[instrument(skip_all, fields(path = %path))] -/// Load a XEX and prepare it for analysis. +/// Header, decompressed image, PE sections, raw container bytes. +type Prepared = ( + sylpheed_xex::Xex2Header, + Vec, + Vec, + Vec, +); + +/// Load a XEX and prepare it for analysis: parse the header, decompress the PE, +/// resolve imports, parse sections. /// -/// Returns the parsed header, the decompressed image, its sections, and the -/// **raw XEX bytes**. The raw bytes are needed because optional-header values -/// are file offsets into the container, not image VAs — the resource table -/// (and so the embedded XDBF package) is only reachable through them. -fn load_and_prepare(path: &str) -> Result<(sylpheed_xex::Xex2Header, Vec, Vec, Vec)> { +/// The **raw XEX bytes** come back too, because optional-header values are file +/// offsets into the container rather than image VAs — the resource table, and so +/// the embedded XDBF package, is only reachable through them. +#[instrument(skip_all, fields(path = %path))] +fn load_and_prepare(path: &str) -> Result { let data = load_xex_data(path)?; let mut header = sylpheed_xex::loader::parse_xex2_header(&data)?; @@ -352,7 +416,11 @@ fn cmd_extract(path: &str, output_dir: Option<&str>, db_path: Option<&str>) -> R let entry = sylpheed_xex::loader::get_entry_point(&header).unwrap(); let base = sylpheed_xex::loader::get_image_base(&header).unwrap(); - let image_size = header.security_info.as_ref().map(|s| s.image_size).unwrap_or(0); + let image_size = header + .security_info + .as_ref() + .map(|s| s.image_size) + .unwrap_or(0); // Build JSON-serializable info struct #[derive(Serialize)] @@ -382,11 +450,15 @@ fn cmd_extract(path: &str, output_dir: Option<&str>, db_path: Option<&str>) -> R let input_path = std::path::Path::new(path); let out_dir = match output_dir { Some(d) => std::path::PathBuf::from(d), - None => input_path.parent().unwrap_or(std::path::Path::new(".")).to_path_buf(), + None => input_path + .parent() + .unwrap_or(std::path::Path::new(".")) + .to_path_buf(), }; std::fs::create_dir_all(&out_dir)?; - let stem = input_path.file_stem() + let stem = input_path + .file_stem() .and_then(|s| s.to_str()) .unwrap_or("output"); @@ -406,11 +478,22 @@ fn cmd_extract(path: &str, output_dir: Option<&str>, db_path: Option<&str>) -> R info!(path = %json_path.display(), "wrote metadata JSON"); // Print summary - let total_imports: usize = header.import_libraries.iter().map(|l| l.imports.len()).sum(); - println!("Extracted: {} sections, {} import libraries ({} imports)", - sections.len(), header.import_libraries.len(), total_imports); + let total_imports: usize = header + .import_libraries + .iter() + .map(|l| l.imports.len()) + .sum(); + println!( + "Extracted: {} sections, {} import libraries ({} imports)", + sections.len(), + header.import_libraries.len(), + total_imports + ); if let Some(ref ei) = header.execution_info { - println!("Title ID: 0x{:08X} Media ID: 0x{:08X}", ei.title_id, ei.media_id); + println!( + "Title ID: 0x{:08X} Media ID: 0x{:08X}", + ei.title_id, ei.media_id + ); } // Write base tables to SQLite if requested @@ -468,27 +551,38 @@ fn cmd_dis( info!(thunks = import_map.len(), "resolved import thunks"); // Function analysis (with .pdata-validated boundaries when present) - let code_sections: Vec<(u32, u32, u32)> = sections.iter() + let code_sections: Vec<(u32, u32, u32)> = sections + .iter() .filter(|s| s.is_code()) .map(|s| (s.virtual_address, s.virtual_size, s.flags)) .collect(); let pdata_entries = sylpheed_xex::pdata::parse_pdata(&pe_image, base, §ions); - info!(pdata_entries = pdata_entries.len(), "parsed .pdata RUNTIME_FUNCTION entries"); + info!( + pdata_entries = pdata_entries.len(), + "parsed .pdata RUNTIME_FUNCTION entries" + ); let func_analysis = sylpheed_xexdb::func::analyze_with_pdata( - &pe_image, base, entry, &code_sections, &pdata_entries, + &pe_image, + base, + entry, + &code_sections, + &pdata_entries, ); info!( functions = func_analysis.functions.len(), - pdata_validated = func_analysis.functions.values().filter(|f| f.pdata_validated).count(), + pdata_validated = func_analysis + .functions + .values() + .filter(|f| f.pdata_validated) + .count(), "function detection complete", ); // M12 — switch / jump-table recovery. Emits one `jt` xref per distinct // case body so the case bodies stop looking unreachable, and reports the // table extents so the linear disassembler can flag them as data. - let jump_tables = sylpheed_xexdb::jumptables::analyze( - &pe_image, base, §ions, &func_analysis, - ); + let jump_tables = + sylpheed_xexdb::jumptables::analyze(&pe_image, base, §ions, &func_analysis); let jt_data_words = sylpheed_xexdb::jumptables::data_word_addresses(&jump_tables); info!( jump_tables = jump_tables.len(), @@ -499,7 +593,13 @@ fn cmd_dis( // Cross-reference analysis let mut xref_result = sylpheed_xexdb::xref::analyze_xrefs_skipping( - &pe_image, base, entry, §ions, &func_analysis, &import_map, &jt_data_words, + &pe_image, + base, + entry, + §ions, + &func_analysis, + &import_map, + &jt_data_words, ); // Feed the recovered `switch` edges into the xref graph, so case bodies @@ -507,7 +607,8 @@ fn cmd_dis( let mut jt_edges = 0usize; for jt in &jump_tables { for target in jt.distinct_targets() { - xref_result.xrefs + xref_result + .xrefs .entry(target) .or_default() .push(sylpheed_xexdb::xref::Xref { @@ -515,12 +616,14 @@ fn cmd_dis( kind: sylpheed_xexdb::xref::XrefKind::JumpTable, addr_mode: None, }); - xref_result.labels + xref_result + .labels .entry(target) .or_insert_with(|| format!("case_{target:08X}")); jt_edges += 1; } - xref_result.labels + xref_result + .labels .entry(jt.table_address) .or_insert_with(|| format!("jpt_{:08X}", jt.table_address)); } @@ -548,9 +651,16 @@ fn cmd_dis( let vptr_block_boundaries: std::collections::HashSet = xref_result.labels.keys().copied().collect(); let mut vtable_anchors = sylpheed_xexdb::vtables::scan_vptr_write_constants( - &pe_image, base, &vptr_anchor_funcs, §ions, &vptr_block_boundaries, + &pe_image, + base, + &vptr_anchor_funcs, + §ions, + &vptr_block_boundaries, + ); + info!( + vtable_anchors = vtable_anchors.len(), + "vptr-write anchor scan complete" ); - info!(vtable_anchors = vtable_anchors.len(), "vptr-write anchor scan complete"); // M13 — authoritative MSVC RTTI walk. Every `vftable[-1] -> COL` link the // linker emitted is an anchor the heuristic scan must not miss, and the @@ -566,7 +676,11 @@ fn cmd_dis( ); let mut vtables = sylpheed_xexdb::vtables::analyze_with_anchors( - &pe_image, base, §ions, &function_starts, &vtable_anchors, + &pe_image, + base, + §ions, + &function_starts, + &vtable_anchors, ); let named = sylpheed_xexdb::vtables::apply_rtti_names(&mut vtables, &rtti); let vtables = vtables; @@ -584,11 +698,19 @@ fn cmd_dis( // emits one xref edge per resolvable site. Inserted into xrefs as // kind='ind_call'. let indirect_edges = sylpheed_xexdb::indirect::analyze( - &pe_image, base, &func_analysis, &vtables, &xref_result.labels, + &pe_image, + base, + &func_analysis, + &vtables, + &xref_result.labels, + ); + info!( + indirect_edges = indirect_edges.len(), + "indirect-dispatch scan complete" ); - info!(indirect_edges = indirect_edges.len(), "indirect-dispatch scan complete"); for edge in &indirect_edges { - xref_result.xrefs + xref_result + .xrefs .entry(edge.target) .or_default() .push(sylpheed_xexdb::xref::Xref { @@ -613,14 +735,22 @@ fn cmd_dis( // Generic function-pointer-array scan (M8 + M11). Re-emits M3 vtables // plus dispatch tables and static-init tables in `.rdata`. let mut fparrays = sylpheed_xexdb::funcptr_arrays::analyze( - &pe_image, base, §ions, &function_starts, &vtables, + &pe_image, + base, + §ions, + &function_starts, + &vtables, ); // M11.5 — static-init driver chain detection. Replaces M11's prologue // heuristic with a structurally-grounded result where the driver // function shape matches. let static_init = sylpheed_xexdb::static_init::analyze( - &pe_image, base, §ions, &func_analysis, &function_starts, + &pe_image, + base, + §ions, + &func_analysis, + &function_starts, &xref_result.labels, ); info!( @@ -639,7 +769,10 @@ fn cmd_dis( } info!( funcptr_arrays = fparrays.len(), - dispatch_tables = fparrays.iter().filter(|a| a.kind == "dispatch_table").count(), + dispatch_tables = fparrays + .iter() + .filter(|a| a.kind == "dispatch_table") + .count(), static_inits = fparrays.iter().filter(|a| a.kind == "static_init").count(), "function-pointer array set finalised", ); @@ -655,12 +788,24 @@ fn cmd_dis( // M5.5 — typed indirect-dispatch resolution (this->vptr → method). let typed_ind = sylpheed_xexdb::ind_dispatch_typed::analyze( - &pe_image, base, &func_analysis, &vtables, &xref_result.labels, + &pe_image, + base, + &func_analysis, + &vtables, + &xref_result.labels, max_indirect_candidates, ); - let single = typed_ind.dispatches.iter().filter(|d| d.total_candidates == 1).count(); + let single = typed_ind + .dispatches + .iter() + .filter(|d| d.total_candidates == 1) + .count(); let multi = typed_ind.dispatches.len() - single; - let typed_edges: usize = typed_ind.dispatches.iter().map(|d| d.method_pcs.len()).sum(); + let typed_edges: usize = typed_ind + .dispatches + .iter() + .map(|d| d.method_pcs.len()) + .sum(); info!( vptr_writes = typed_ind.vptr_writes.len(), dispatches = typed_ind.dispatches.len(), @@ -675,7 +820,8 @@ fn cmd_dis( // possibilities. for d in &typed_ind.dispatches { for &method_pc in &d.method_pcs { - xref_result.xrefs + xref_result + .xrefs .entry(method_pc) .or_default() .push(sylpheed_xexdb::xref::Xref { @@ -707,7 +853,10 @@ fn cmd_dis( Some(x) }); if xdbf.is_none() && !resources.is_empty() { - info!(resources = resources.len(), "resource table present but no XDBF package"); + info!( + resources = resources.len(), + "resource table present but no XDBF package" + ); } // Build DisasmInfo @@ -728,7 +877,11 @@ fn cmd_dis( let mut w = sylpheed_xexdb::DbWriter::open_fresh(std::path::Path::new(db))?; w.write_base(&disasm_info)?; w.ingest_instructions( - &pe_image, &disasm_info, &func_analysis, &xref_result.labels, &jt_data_words, + &pe_image, + &disasm_info, + &func_analysis, + &xref_result.labels, + &jt_data_words, )?; w.write_analysis_results( &pe_image, @@ -772,12 +925,20 @@ fn cmd_dis( let mut out = std::io::BufWriter::new(std::fs::File::create(json)?); let mut total: u64 = 0; for section in §ions { - if !section.is_code() { continue; } + if !section.is_code() { + continue; + } let abs_start = base + section.virtual_address; let abs_end = abs_start + section.virtual_size; let items = sylpheed_xexdb::enrich_section( - &pe_image, base, §ion.name, abs_start, abs_end, - &func_analysis, &xref_result.labels, &jt_data_words, + &pe_image, + base, + §ion.name, + abs_start, + abs_end, + &func_analysis, + &xref_result.labels, + &jt_data_words, ); total += sylpheed_xexdb::sinks::json::write_jsonl(&mut out, items)?; } @@ -808,11 +969,13 @@ fn cmd_dis( } } - info!(wall_ms = started.elapsed().as_millis() as u64, "dis complete"); + info!( + wall_ms = started.elapsed().as_millis() as u64, + "dis complete" + ); Ok(()) } - #[cfg(test)] mod tests { use super::parse_hex_u32; diff --git a/crates/sylpheed-xexdb/src/db.rs b/crates/sylpheed-xexdb/src/db.rs index f21bd82b..76ea3bd8 100644 --- a/crates/sylpheed-xexdb/src/db.rs +++ b/crates/sylpheed-xexdb/src/db.rs @@ -94,9 +94,9 @@ //! - `function` — VA of the enclosing function; `NULL` if not inside a detected function //! - `label` — Label name at this address; `NULL` if none //! - `is_data` — `true` when this word is data embedded in a code section (a -//! recovered jump table or index map). The decoded `mnemonic` / -//! `disasm` columns are meaningless on such rows; filter them out -//! (`WHERE NOT is_data`) for any instruction-level analysis. +//! recovered jump table or index map). The decoded `mnemonic` / +//! `disasm` columns are meaningless on such rows; filter them out +//! (`WHERE NOT is_data`) for any instruction-level analysis. //! //! ## `jump_tables` / `jump_table_entries` / `data_in_code` //! Recovered `switch` dispatches (see [`crate::jumptables`]). `jump_tables` has @@ -118,12 +118,12 @@ //! - `source` — Absolute VA of the instruction making the reference //! - `target` — Absolute VA of the referenced destination //! - `kind` — Reference type as the short tag from [`crate::xref::XrefKind::tag`]: -//! `call`, `ind_call` (resolved vtable `bcctrl`), -//! `jt` (recovered `switch` case), `j` (jump), -//! `br` (branch), `read` (data_read), -//! `write` (data_write), `ref` (data_ref). -//! Note: this is a different convention from `branch_trace.kind`, -//! which uses the long names (`call` / `return` / `jump` / `branch`). +//! `call`, `ind_call` (resolved vtable `bcctrl`), +//! `jt` (recovered `switch` case), `j` (jump), +//! `br` (branch), `read` (data_read), +//! `write` (data_write), `ref` (data_ref). +//! Note: this is a different convention from `branch_trace.kind`, +//! which uses the long names (`call` / `return` / `jump` / `branch`). //! - `instruction` — Mnemonic of the source instruction; `NULL` if address is not in binary //! - `source_func` — VA of the function containing `source`; `NULL` if unknown //! - `source_label` — Label at `source`; `NULL` if none @@ -158,9 +158,9 @@ use std::path::Path; use duckdb::{Connection, params}; +use crate::formatter::DisasmInfo; use crate::func::FuncAnalysis; use crate::xref::{XrefMap, resolve_source_label}; -use crate::formatter::DisasmInfo; const DEFAULT_BATCH_SIZE: u64 = 100_000; @@ -249,7 +249,8 @@ impl DbWriter { /// Write metadata, sections, imports tables and their indices. #[tracing::instrument(skip_all, name = "db.write_base")] pub fn write_base(&mut self, info: &DisasmInfo) -> anyhow::Result<()> { - self.conn.execute_batch(" + self.conn.execute_batch( + " CREATE TABLE metadata ( key VARCHAR PRIMARY KEY, -- header field name value VARCHAR NOT NULL -- hex-formatted or plain string value @@ -272,16 +273,19 @@ impl DbWriter { record_type BIGINT NOT NULL, -- 0 = function thunk, 1 = variable address BIGINT NOT NULL -- absolute VA of the thunk or variable ); - ")?; + ", + )?; insert_metadata(&self.conn, info)?; insert_sections(&self.conn, info.sections)?; insert_imports(&self.conn, info)?; - self.conn.execute_batch(" + self.conn.execute_batch( + " CREATE INDEX idx_imports_library ON imports(library); CREATE INDEX idx_imports_name ON imports(name); - ")?; + ", + )?; Ok(()) } @@ -325,13 +329,34 @@ impl DbWriter { insert_instructions_streaming(&self.conn, pe, info, func_analysis, labels, data_words)?; let indices = [ - ("idx_instructions_function", "CREATE INDEX idx_instructions_function ON instructions(function)"), - ("idx_instructions_mnemonic", "CREATE INDEX idx_instructions_mnemonic ON instructions(mnemonic)"), - ("idx_instructions_ext_mnemonic", "CREATE INDEX idx_instructions_ext_mnemonic ON instructions(ext_mnemonic)"), - ("idx_instructions_section", "CREATE INDEX idx_instructions_section ON instructions(section)"), - ("idx_instructions_label", "CREATE INDEX idx_instructions_label ON instructions(label)"), - ("idx_instructions_target_hex", "CREATE INDEX idx_instructions_target_hex ON instructions(target_hex)"), - ("idx_instructions_is_data", "CREATE INDEX idx_instructions_is_data ON instructions(is_data)"), + ( + "idx_instructions_function", + "CREATE INDEX idx_instructions_function ON instructions(function)", + ), + ( + "idx_instructions_mnemonic", + "CREATE INDEX idx_instructions_mnemonic ON instructions(mnemonic)", + ), + ( + "idx_instructions_ext_mnemonic", + "CREATE INDEX idx_instructions_ext_mnemonic ON instructions(ext_mnemonic)", + ), + ( + "idx_instructions_section", + "CREATE INDEX idx_instructions_section ON instructions(section)", + ), + ( + "idx_instructions_label", + "CREATE INDEX idx_instructions_label ON instructions(label)", + ), + ( + "idx_instructions_target_hex", + "CREATE INDEX idx_instructions_target_hex ON instructions(target_hex)", + ), + ( + "idx_instructions_is_data", + "CREATE INDEX idx_instructions_is_data ON instructions(is_data)", + ), ]; for (name, sql) in indices { tracing::debug!(index = name, "creating instructions index"); @@ -693,34 +718,116 @@ impl DbWriter { if let Some(t) = typed_ind { insert_typed_ind_dispatch(&self.conn, t)?; } - insert_xrefs_streaming(&self.conn, xrefs, pe, info.image_base, func_analysis, labels)?; + insert_xrefs_streaming( + &self.conn, + xrefs, + pe, + info.image_base, + func_analysis, + labels, + )?; let indices = [ - ("idx_functions_name", "CREATE INDEX idx_functions_name ON functions(name)"), - ("idx_functions_pdata_validated", "CREATE INDEX idx_functions_pdata_validated ON functions(pdata_validated)"), - ("idx_functions_has_eh", "CREATE INDEX idx_functions_has_eh ON functions(has_eh)"), - ("idx_labels_kind", "CREATE INDEX idx_labels_kind ON labels(kind)"), - ("idx_labels_name", "CREATE INDEX idx_labels_name ON labels(name)"), - ("idx_demangled_address", "CREATE INDEX idx_demangled_address ON demangled_names(address)"), - ("idx_demangled_class", "CREATE INDEX idx_demangled_class ON demangled_names(class_name)"), - ("idx_demangled_method", "CREATE INDEX idx_demangled_method ON demangled_names(method_name)"), - ("idx_methods_function", "CREATE INDEX idx_methods_function ON methods(function_address)"), - ("idx_classes_rtti", "CREATE INDEX idx_classes_rtti ON classes(rtti_present)"), - ("idx_strings_encoding", "CREATE INDEX idx_strings_encoding ON strings(encoding)"), - ("idx_xrefs_addr_mode", "CREATE INDEX idx_xrefs_addr_mode ON xrefs(addr_mode)"), - ("idx_fparrays_kind", "CREATE INDEX idx_fparrays_kind ON function_pointer_arrays(kind)"), - ("idx_fpentries_function", "CREATE INDEX idx_fpentries_function ON function_pointer_array_entries(function_address)"), - ("idx_indcand_method", "CREATE INDEX idx_indcand_method ON indirect_dispatch_candidates(method_address)"), - ("idx_indcand_vtable", "CREATE INDEX idx_indcand_vtable ON indirect_dispatch_candidates(vtable_address)"), - ("idx_indsites_offset_slot", "CREATE INDEX idx_indsites_offset_slot ON indirect_dispatch_sites(vptr_offset, slot)"), - ("idx_vptrw_vtable", "CREATE INDEX idx_vptrw_vtable ON vptr_writes(vtable_address)"), - ("idx_vptrw_offset", "CREATE INDEX idx_vptrw_offset ON vptr_writes(vptr_offset)"), - ("idx_xrefs_target", "CREATE INDEX idx_xrefs_target ON xrefs(target)"), - ("idx_xrefs_source", "CREATE INDEX idx_xrefs_source ON xrefs(source)"), - ("idx_xrefs_source_func", "CREATE INDEX idx_xrefs_source_func ON xrefs(source_func)"), - ("idx_xrefs_kind", "CREATE INDEX idx_xrefs_kind ON xrefs(kind)"), - ("idx_xrefs_instruction", "CREATE INDEX idx_xrefs_instruction ON xrefs(instruction)"), - ("idx_xrefs_target_label", "CREATE INDEX idx_xrefs_target_label ON xrefs(target_label)"), + ( + "idx_functions_name", + "CREATE INDEX idx_functions_name ON functions(name)", + ), + ( + "idx_functions_pdata_validated", + "CREATE INDEX idx_functions_pdata_validated ON functions(pdata_validated)", + ), + ( + "idx_functions_has_eh", + "CREATE INDEX idx_functions_has_eh ON functions(has_eh)", + ), + ( + "idx_labels_kind", + "CREATE INDEX idx_labels_kind ON labels(kind)", + ), + ( + "idx_labels_name", + "CREATE INDEX idx_labels_name ON labels(name)", + ), + ( + "idx_demangled_address", + "CREATE INDEX idx_demangled_address ON demangled_names(address)", + ), + ( + "idx_demangled_class", + "CREATE INDEX idx_demangled_class ON demangled_names(class_name)", + ), + ( + "idx_demangled_method", + "CREATE INDEX idx_demangled_method ON demangled_names(method_name)", + ), + ( + "idx_methods_function", + "CREATE INDEX idx_methods_function ON methods(function_address)", + ), + ( + "idx_classes_rtti", + "CREATE INDEX idx_classes_rtti ON classes(rtti_present)", + ), + ( + "idx_strings_encoding", + "CREATE INDEX idx_strings_encoding ON strings(encoding)", + ), + ( + "idx_xrefs_addr_mode", + "CREATE INDEX idx_xrefs_addr_mode ON xrefs(addr_mode)", + ), + ( + "idx_fparrays_kind", + "CREATE INDEX idx_fparrays_kind ON function_pointer_arrays(kind)", + ), + ( + "idx_fpentries_function", + "CREATE INDEX idx_fpentries_function ON function_pointer_array_entries(function_address)", + ), + ( + "idx_indcand_method", + "CREATE INDEX idx_indcand_method ON indirect_dispatch_candidates(method_address)", + ), + ( + "idx_indcand_vtable", + "CREATE INDEX idx_indcand_vtable ON indirect_dispatch_candidates(vtable_address)", + ), + ( + "idx_indsites_offset_slot", + "CREATE INDEX idx_indsites_offset_slot ON indirect_dispatch_sites(vptr_offset, slot)", + ), + ( + "idx_vptrw_vtable", + "CREATE INDEX idx_vptrw_vtable ON vptr_writes(vtable_address)", + ), + ( + "idx_vptrw_offset", + "CREATE INDEX idx_vptrw_offset ON vptr_writes(vptr_offset)", + ), + ( + "idx_xrefs_target", + "CREATE INDEX idx_xrefs_target ON xrefs(target)", + ), + ( + "idx_xrefs_source", + "CREATE INDEX idx_xrefs_source ON xrefs(source)", + ), + ( + "idx_xrefs_source_func", + "CREATE INDEX idx_xrefs_source_func ON xrefs(source_func)", + ), + ( + "idx_xrefs_kind", + "CREATE INDEX idx_xrefs_kind ON xrefs(kind)", + ), + ( + "idx_xrefs_instruction", + "CREATE INDEX idx_xrefs_instruction ON xrefs(instruction)", + ), + ( + "idx_xrefs_target_label", + "CREATE INDEX idx_xrefs_target_label ON xrefs(target_label)", + ), ]; for (name, sql) in indices { tracing::debug!(index = name, "creating analysis index"); @@ -745,8 +852,19 @@ impl DbWriter { let empty = std::collections::BTreeSet::new(); self.ingest_instructions(pe, info, func_analysis, labels, &empty)?; self.write_analysis_results( - pe, info, func_analysis, labels, xrefs, - &[], &[], &[], None, &[], &[], &crate::rtti::RttiResult::default(), None, + pe, + info, + func_analysis, + labels, + xrefs, + &[], + &[], + &[], + None, + &[], + &[], + &crate::rtti::RttiResult::default(), + None, )?; Ok(()) } @@ -754,11 +872,10 @@ impl DbWriter { /// M10 — write the parsed `.tls` directory + callback array. No-op /// when `tls` is `None` (binary has no `.tls` section). #[tracing::instrument(skip_all, name = "db.write_tls")] - pub fn write_tls( - &mut self, - tls: Option<&sylpheed_xex::tls::TlsInfo>, - ) -> anyhow::Result<()> { - let Some(t) = tls else { return Ok(()); }; + pub fn write_tls(&mut self, tls: Option<&sylpheed_xex::tls::TlsInfo>) -> anyhow::Result<()> { + let Some(t) = tls else { + return Ok(()); + }; self.conn.execute( "INSERT INTO tls_info (raw_data_start, raw_data_end, index_address, callback_array, zero_fill_size, characteristics) @@ -772,14 +889,19 @@ impl DbWriter { t.characteristics as i64, ], )?; - let mut stmt = self.conn.prepare( - "INSERT INTO tls_callbacks (slot, address) VALUES (?, ?)" - )?; + let mut stmt = self + .conn + .prepare("INSERT INTO tls_callbacks (slot, address) VALUES (?, ?)")?; for (i, cb) in t.callbacks.iter().enumerate() { stmt.execute(params![i as i64, cb.address as i64])?; } - metrics::counter!("db.rows", "table" => "tls_callbacks").increment(t.callbacks.len() as u64); - tracing::info!(rows = t.callbacks.len(), table = "tls_callbacks", "tls write complete"); + metrics::counter!("db.rows", "table" => "tls_callbacks") + .increment(t.callbacks.len() as u64); + tracing::info!( + rows = t.callbacks.len(), + table = "tls_callbacks", + "tls write complete" + ); Ok(()) } @@ -811,14 +933,16 @@ impl DbWriter { LEFT JOIN xrefs x \ ON x.source = vb.source AND x.target = vb.target AND x.kind = vb.kind \ WHERE x.source IS NULL", - [], |row| row.get(0) + [], + |row| row.get(0), )?; let rust_only: i64 = self.conn.query_row( "SELECT COUNT(*) FROM xrefs x \ LEFT JOIN v_branch_xrefs vb \ ON vb.source = x.source AND vb.target = x.target AND vb.kind = x.kind \ WHERE x.kind IN ('call','j','br') AND vb.source IS NULL", - [], |row| row.get(0) + [], + |row| row.get(0), )?; Ok((sql_only as u64, rust_only as u64)) } @@ -837,7 +961,8 @@ impl DbWriter { self.trace_branches = trace_branches; if trace_instructions { - self.conn.execute_batch(" + self.conn.execute_batch( + " CREATE TABLE exec_trace ( address BIGINT NOT NULL, -- absolute VA of the instruction cycle BIGINT NOT NULL, -- monotonic instruction counter (execution order) @@ -846,11 +971,13 @@ impl DbWriter { lr BIGINT NOT NULL, -- link register sp BIGINT NOT NULL -- stack pointer ); - ")?; + ", + )?; } if trace_imports { - self.conn.execute_batch(" + self.conn.execute_batch( + " CREATE TABLE import_calls ( address BIGINT NOT NULL, -- VA of the import thunk cycle BIGINT NOT NULL, -- instruction counter at interception @@ -863,11 +990,13 @@ impl DbWriter { arg_r6 BIGINT NOT NULL, -- fourth argument (r6) return_value BIGINT NOT NULL -- r3 after the call returns ); - ")?; + ", + )?; } if trace_branches { - self.conn.execute_batch(" + self.conn.execute_batch( + " CREATE TABLE branch_trace ( cycle BIGINT NOT NULL, -- instruction counter source BIGINT NOT NULL, -- VA of the branch instruction @@ -875,14 +1004,17 @@ impl DbWriter { kind VARCHAR NOT NULL, -- call | return | jump | branch lr BIGINT NOT NULL -- link register at time of branch ); - ")?; + ", + )?; } Ok(()) } pub fn log_instruction(&mut self, entry: ExecTraceEntry) { - if !self.trace_instructions { return; } + if !self.trace_instructions { + return; + } self.exec_buffer.push(entry); if self.exec_buffer.len() as u64 >= batch_size() { self.flush_exec(); @@ -890,7 +1022,9 @@ impl DbWriter { } pub fn log_import_call(&mut self, entry: ImportCallEntry) { - if !self.trace_imports { return; } + if !self.trace_imports { + return; + } self.import_buffer.push(entry); if self.import_buffer.len() >= 1000 { self.flush_imports(); @@ -898,7 +1032,9 @@ impl DbWriter { } pub fn log_branch(&mut self, entry: BranchTraceEntry) { - if !self.trace_branches { return; } + if !self.trace_branches { + return; + } self.branch_buffer.push(entry); if self.branch_buffer.len() as u64 >= batch_size() { self.flush_branches(); @@ -906,17 +1042,21 @@ impl DbWriter { } fn flush_exec(&mut self) { - if self.exec_buffer.is_empty() { return; } + if self.exec_buffer.is_empty() { + return; + } let mut appender = self.conn.appender("exec_trace").unwrap(); for e in &self.exec_buffer { - appender.append_row(params![ - e.address as i64, - e.cycle as i64, - e.r3 as i64, - e.r4 as i64, - e.lr as i64, - e.sp as i64, - ]).ok(); + appender + .append_row(params![ + e.address as i64, + e.cycle as i64, + e.r3 as i64, + e.r4 as i64, + e.lr as i64, + e.sp as i64, + ]) + .ok(); } appender.flush().ok(); self.exec_count += self.exec_buffer.len() as u64; @@ -924,21 +1064,25 @@ impl DbWriter { } fn flush_imports(&mut self) { - if self.import_buffer.is_empty() { return; } + if self.import_buffer.is_empty() { + return; + } let mut appender = self.conn.appender("import_calls").unwrap(); for e in &self.import_buffer { - appender.append_row(params![ - e.address as i64, - e.cycle as i64, - e.module.as_str(), - e.ordinal as i64, - e.name.as_str(), - e.arg_r3 as i64, - e.arg_r4 as i64, - e.arg_r5 as i64, - e.arg_r6 as i64, - e.return_value as i64, - ]).ok(); + appender + .append_row(params![ + e.address as i64, + e.cycle as i64, + e.module.as_str(), + e.ordinal as i64, + e.name.as_str(), + e.arg_r3 as i64, + e.arg_r4 as i64, + e.arg_r5 as i64, + e.arg_r6 as i64, + e.return_value as i64, + ]) + .ok(); } appender.flush().ok(); self.import_count += self.import_buffer.len() as u64; @@ -946,16 +1090,20 @@ impl DbWriter { } fn flush_branches(&mut self) { - if self.branch_buffer.is_empty() { return; } + if self.branch_buffer.is_empty() { + return; + } let mut appender = self.conn.appender("branch_trace").unwrap(); for e in &self.branch_buffer { - appender.append_row(params![ - e.cycle as i64, - e.source as i64, - e.target as i64, - e.kind, - e.lr as i64, - ]).ok(); + appender + .append_row(params![ + e.cycle as i64, + e.source as i64, + e.target as i64, + e.kind, + e.lr as i64, + ]) + .ok(); } appender.flush().ok(); self.branch_count += self.branch_buffer.len() as u64; @@ -971,25 +1119,33 @@ impl DbWriter { if self.trace_instructions { tracing::debug!("creating idx_exec_trace_address"); - self.conn.execute_batch("CREATE INDEX idx_exec_trace_address ON exec_trace(address);")?; + self.conn + .execute_batch("CREATE INDEX idx_exec_trace_address ON exec_trace(address);")?; tracing::debug!("creating idx_exec_trace_cycle"); - self.conn.execute_batch("CREATE INDEX idx_exec_trace_cycle ON exec_trace(cycle);")?; + self.conn + .execute_batch("CREATE INDEX idx_exec_trace_cycle ON exec_trace(cycle);")?; } if self.trace_imports { tracing::debug!("creating idx_import_calls_name"); - self.conn.execute_batch("CREATE INDEX idx_import_calls_name ON import_calls(name);")?; + self.conn + .execute_batch("CREATE INDEX idx_import_calls_name ON import_calls(name);")?; tracing::debug!("creating idx_import_calls_cycle"); - self.conn.execute_batch("CREATE INDEX idx_import_calls_cycle ON import_calls(cycle);")?; + self.conn + .execute_batch("CREATE INDEX idx_import_calls_cycle ON import_calls(cycle);")?; } if self.trace_branches { tracing::debug!("creating idx_branch_trace_source"); - self.conn.execute_batch("CREATE INDEX idx_branch_trace_source ON branch_trace(source);")?; + self.conn + .execute_batch("CREATE INDEX idx_branch_trace_source ON branch_trace(source);")?; tracing::debug!("creating idx_branch_trace_target"); - self.conn.execute_batch("CREATE INDEX idx_branch_trace_target ON branch_trace(target);")?; + self.conn + .execute_batch("CREATE INDEX idx_branch_trace_target ON branch_trace(target);")?; tracing::debug!("creating idx_branch_trace_kind"); - self.conn.execute_batch("CREATE INDEX idx_branch_trace_kind ON branch_trace(kind);")?; + self.conn + .execute_batch("CREATE INDEX idx_branch_trace_kind ON branch_trace(kind);")?; tracing::debug!("creating idx_branch_trace_cycle"); - self.conn.execute_batch("CREATE INDEX idx_branch_trace_cycle ON branch_trace(cycle);")?; + self.conn + .execute_batch("CREATE INDEX idx_branch_trace_cycle ON branch_trace(cycle);")?; } metrics::counter!("db.rows", "table" => "exec_trace").increment(self.exec_count); @@ -1044,44 +1200,68 @@ fn insert_metadata(conn: &Connection, info: &DisasmInfo) -> anyhow::Result<()> { // Section geometry is useful enough on its own to be worth denormalising: // a query that just wants "how big is the code" should not have to join. - let code_bytes: u64 = info.sections.iter().filter(|s| s.is_code()) - .map(|s| s.virtual_size as u64).sum(); + let code_bytes: u64 = info + .sections + .iter() + .filter(|s| s.is_code()) + .map(|s| s.virtual_size as u64) + .sum(); put("section_count", info.sections.len().to_string())?; put("code_bytes", code_bytes.to_string())?; - let Some(header) = info.xex_header else { return Ok(()) }; + let Some(header) = info.xex_header else { + return Ok(()); + }; put("xex_module_flags", format!("0x{:08X}", header.module_flags))?; - put("xex_module_flags_decoded", decode_module_flags(header.module_flags))?; + put( + "xex_module_flags_decoded", + decode_module_flags(header.module_flags), + )?; put("xex_header_count", header.header_count.to_string())?; if let Some(sec) = &header.security_info { put("image_size", format!("0x{:08X}", sec.image_size))?; put("load_address", format!("0x{:08X}", sec.load_address))?; put("image_flags", format!("0x{:08X}", sec.image_flags))?; - put("page_descriptor_count", sec.page_descriptors.len().to_string())?; + put( + "page_descriptor_count", + sec.page_descriptors.len().to_string(), + )?; if sec.export_table_address != 0 { - put("export_table_address", format!("0x{:08X}", sec.export_table_address))?; + put( + "export_table_address", + format!("0x{:08X}", sec.export_table_address), + )?; } } if let Some(ff) = &header.file_format_info { - put("encryption_type", match ff.encryption_type { - 0 => "none".into(), - 1 => "normal (AES-128-CBC)".into(), - n => format!("unknown ({n})"), - })?; - put("compression_type", match ff.compression_type { - 0 => "none".into(), - 1 => "basic (raw + zero-fill blocks)".into(), - 2 => "normal (LZX)".into(), - n => format!("unknown ({n})"), - })?; + put( + "encryption_type", + match ff.encryption_type { + 0 => "none".into(), + 1 => "normal (AES-128-CBC)".into(), + n => format!("unknown ({n})"), + }, + )?; + put( + "compression_type", + match ff.compression_type { + 0 => "none".into(), + 1 => "basic (raw + zero-fill blocks)".into(), + 2 => "normal (LZX)".into(), + n => format!("unknown ({n})"), + }, + )?; if ff.compression_type == 1 { put("basic_block_count", ff.basic_blocks.len().to_string())?; } if ff.compression_type == 2 { - put("lzx_window_size", format!("0x{:08X}", ff.normal_window_size))?; + put( + "lzx_window_size", + format!("0x{:08X}", ff.normal_window_size), + )?; } } @@ -1092,17 +1272,32 @@ fn insert_metadata(conn: &Connection, info: &DisasmInfo) -> anyhow::Result<()> { // Import libraries carry the SDK version each module was linked against — // the single most useful "what toolchain built this" signal in the header. - put("import_library_count", header.import_libraries.len().to_string())?; + put( + "import_library_count", + header.import_libraries.len().to_string(), + )?; for lib in &header.import_libraries { - put(&format!("import_lib.{}.version_min", lib.name), format_xex_version(lib.version_min))?; - put(&format!("import_lib.{}.version_cur", lib.name), format_xex_version(lib.version_cur))?; - put(&format!("import_lib.{}.imports", lib.name), lib.imports.len().to_string())?; + put( + &format!("import_lib.{}.version_min", lib.name), + format_xex_version(lib.version_min), + )?; + put( + &format!("import_lib.{}.version_cur", lib.name), + format_xex_version(lib.version_cur), + )?; + put( + &format!("import_lib.{}.imports", lib.name), + lib.imports.len().to_string(), + )?; } // Any optional header we do not model explicitly is still recorded by key, // so nothing in the XEX is silently dropped. for oh in &header.optional_headers { - put(&format!("xex_optional_header.0x{:08X}", oh.key), format!("0x{:08X}", oh.value))?; + put( + &format!("xex_optional_header.0x{:08X}", oh.key), + format!("0x{:08X}", oh.value), + )?; } Ok(()) @@ -1129,11 +1324,22 @@ fn decode_module_flags(flags: u32) -> String { (0x0000_0040, "patch_delta"), (0x0000_0080, "user_mode"), ]; - let set: Vec<&str> = NAMES.iter().filter(|&&(b, _)| flags & b != 0).map(|&(_, n)| n).collect(); - if set.is_empty() { "none".to_string() } else { set.join("|") } + let set: Vec<&str> = NAMES + .iter() + .filter(|&&(b, _)| flags & b != 0) + .map(|&(_, n)| n) + .collect(); + if set.is_empty() { + "none".to_string() + } else { + set.join("|") + } } -fn insert_sections(conn: &Connection, sections: &[sylpheed_xex::pe::PeSection]) -> anyhow::Result<()> { +fn insert_sections( + conn: &Connection, + sections: &[sylpheed_xex::pe::PeSection], +) -> anyhow::Result<()> { let mut stmt = conn.prepare( "INSERT INTO sections (name, virtual_address, virtual_size, raw_offset, raw_size, flags, is_code) VALUES (?, ?, ?, ?, ?, ?, ?)" @@ -1155,7 +1361,7 @@ fn insert_sections(conn: &Connection, sections: &[sylpheed_xex::pe::PeSection]) fn insert_imports(conn: &Connection, info: &DisasmInfo) -> anyhow::Result<()> { let mut stmt = conn.prepare( "INSERT INTO imports (library, ordinal, name, record_type, address) - VALUES (?, ?, ?, ?, ?)" + VALUES (?, ?, ?, ?, ?)", )?; for lib in info.import_libraries { for imp in &lib.imports { @@ -1179,7 +1385,8 @@ fn insert_functions( ) -> anyhow::Result<()> { let mut appender = conn.appender("functions")?; for (&addr, fi) in &func_analysis.functions { - let name = labels.get(&addr) + let name = labels + .get(&addr) .cloned() .unwrap_or_else(|| format!("sub_{addr:08X}")); appender.append_row(params![ @@ -1206,12 +1413,14 @@ fn insert_vtables( _pe: &[u8], _image_base: u32, ) -> anyhow::Result<()> { - if vtables.is_empty() { return Ok(()); } + if vtables.is_empty() { + return Ok(()); + } let mut stmt = conn.prepare( "INSERT INTO vtables (address, length, col_address, class_name, rtti_present, base_classes_json) VALUES (?, ?, ?, ?, ?, ?) - ON CONFLICT DO NOTHING" + ON CONFLICT DO NOTHING", )?; let mut count = 0u64; for v in vtables { @@ -1235,7 +1444,9 @@ fn insert_methods_and_classes( vtables: &[crate::vtables::Vtable], labels: &HashMap, ) -> anyhow::Result<()> { - if vtables.is_empty() { return Ok(()); } + if vtables.is_empty() { + return Ok(()); + } // methods rows — keyed by (vtable_address, slot), which `methods_table` // emits at most once each. @@ -1253,7 +1464,11 @@ fn insert_methods_and_classes( } appender.flush()?; metrics::counter!("db.rows", "table" => "methods").increment(methods.len() as u64); - tracing::info!(rows = methods.len(), table = "methods", "bulk insert complete"); + tracing::info!( + rows = methods.len(), + table = "methods", + "bulk insert complete" + ); } // classes rows (deduped by class_name, first-detected wins) @@ -1263,7 +1478,7 @@ fn insert_methods_and_classes( "INSERT INTO classes (name, vtable_address, rtti_present, base_classes_json) VALUES (?, ?, ?, ?) - ON CONFLICT DO NOTHING" + ON CONFLICT DO NOTHING", )?; for (name, vt_addr, rtti, bases) in &classes { stmt.execute(params![ @@ -1274,7 +1489,11 @@ fn insert_methods_and_classes( ])?; } metrics::counter!("db.rows", "table" => "classes").increment(classes.len() as u64); - tracing::info!(rows = classes.len(), table = "classes", "bulk insert complete"); + tracing::info!( + rows = classes.len(), + table = "classes", + "bulk insert complete" + ); } Ok(()) @@ -1284,7 +1503,9 @@ fn insert_strings( conn: &Connection, strings: &[crate::strings::DetectedString], ) -> anyhow::Result<()> { - if strings.is_empty() { return Ok(()); } + if strings.is_empty() { + return Ok(()); + } // The ascii / shift_jis / utf8 scans all run over the same bytes, so two // of them can report a string at the same address. `address` is the // primary key and the Appender cannot absorb that the way @@ -1293,7 +1514,9 @@ fn insert_strings( let mut appender = conn.appender("strings")?; let mut count = 0u64; for s in strings { - if !seen.insert(s.address) { continue; } + if !seen.insert(s.address) { + continue; + } appender.append_row(params![ s.address as i64, s.encoding, @@ -1313,22 +1536,28 @@ fn insert_eh_records( conn: &Connection, records: &[crate::eh_scope::EhFuncInfo], ) -> anyhow::Result<()> { - if records.is_empty() { return Ok(()); } + if records.is_empty() { + return Ok(()); + } let mut stmt_fi = conn.prepare( "INSERT INTO eh_funcinfo (address, magic, max_state, p_unwind_map, n_try_blocks, p_try_block_map, n_ip_map_entries, p_ip_to_state_map, p_es_type_list, eh_flags) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT DO NOTHING" + ON CONFLICT DO NOTHING", )?; let mut n_fi = 0u64; let mut kept: Vec<&crate::eh_scope::EhFuncInfo> = Vec::with_capacity(records.len()); for r in records { let inserted = stmt_fi.execute(params![ - r.address as i64, r.magic as i64, r.max_state as i64, - r.p_unwind_map as i64, r.n_try_blocks as i64, - r.p_try_block_map as i64, r.n_ip_map_entries as i64, + r.address as i64, + r.magic as i64, + r.max_state as i64, + r.p_unwind_map as i64, + r.n_try_blocks as i64, + r.p_try_block_map as i64, + r.n_ip_map_entries as i64, r.p_ip_to_state_map as i64, r.p_es_type_list.map(|p| p as i64), r.eh_flags.map(|f| f as i64), @@ -1348,7 +1577,10 @@ fn insert_eh_records( for r in &kept { for (i, e) in r.unwind_map.iter().enumerate() { appender.append_row(params![ - r.address as i64, i as i64, e.to_state as i64, e.action_pc as i64, + r.address as i64, + i as i64, + e.to_state as i64, + e.action_pc as i64, ])?; n_unwind += 1; } @@ -1362,9 +1594,13 @@ fn insert_eh_records( for r in &kept { for (i, t) in r.try_blocks.iter().enumerate() { appender.append_row(params![ - r.address as i64, i as i64, - t.try_low as i64, t.try_high as i64, t.catch_high as i64, - t.n_catches as i64, t.p_handler_array as i64, + r.address as i64, + i as i64, + t.try_low as i64, + t.try_high as i64, + t.catch_high as i64, + t.n_catches as i64, + t.p_handler_array as i64, ])?; n_try += 1; } @@ -1376,7 +1612,9 @@ fn insert_eh_records( metrics::counter!("db.rows", "table" => "eh_unwind_map").increment(n_unwind); metrics::counter!("db.rows", "table" => "eh_try_blocks").increment(n_try); tracing::info!( - funcinfo = n_fi, unwind = n_unwind, try_blocks = n_try, + funcinfo = n_fi, + unwind = n_unwind, + try_blocks = n_try, "EH scope-table insert complete" ); Ok(()) @@ -1390,7 +1628,7 @@ fn insert_typed_ind_dispatch( let mut stmt_site = conn.prepare( "INSERT INTO indirect_dispatch_sites (dispatch_pc, vptr_offset, slot, candidate_count, truncated) - VALUES (?, ?, ?, ?, ?) ON CONFLICT DO NOTHING" + VALUES (?, ?, ?, ?, ?) ON CONFLICT DO NOTHING", )?; let mut n_sites = 0u64; for d in &t.dispatches { @@ -1422,9 +1660,7 @@ fn insert_typed_ind_dispatch( for d in &t.dispatches { for (vt, m) in d.candidate_vtables.iter().zip(d.method_pcs.iter()) { if seen.insert((d.dispatch_pc, *vt)) { - appender.append_row(params![ - d.dispatch_pc as i64, *vt as i64, *m as i64, - ])?; + appender.append_row(params![d.dispatch_pc as i64, *vt as i64, *m as i64,])?; n_cand += 1; } } @@ -1433,13 +1669,17 @@ fn insert_typed_ind_dispatch( metrics::counter!("db.rows", "table" => "indirect_dispatch_sites").increment(n_sites); metrics::counter!("db.rows", "table" => "indirect_dispatch_candidates").increment(n_cand); - tracing::info!(sites = n_sites, candidates = n_cand, "typed indirect-dispatch insert complete"); + tracing::info!( + sites = n_sites, + candidates = n_cand, + "typed indirect-dispatch insert complete" + ); } if !t.vptr_writes.is_empty() { let mut stmt = conn.prepare( "INSERT INTO vptr_writes (writer_pc, vtable_address, vptr_offset, writer_function) - VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING" + VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING", )?; let mut n = 0u64; for w in &t.vptr_writes { @@ -1466,7 +1706,7 @@ fn insert_xdbf(conn: &Connection, xdbf: Option<&crate::xdbf::Xdbf>) -> anyhow::R let mut stmt = conn.prepare( "INSERT INTO xdbf_entries (namespace, namespace_name, id, body_offset, size, magic) - VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT DO NOTHING" + VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT DO NOTHING", )?; for e in &x.entries { let ns_name = match e.namespace { @@ -1476,15 +1716,19 @@ fn insert_xdbf(conn: &Connection, xdbf: Option<&crate::xdbf::Xdbf>) -> anyhow::R _ => "unknown", }; stmt.execute(params![ - e.namespace as i64, ns_name, e.id as i64, - e.offset as i64, e.size as i64, e.magic.as_deref(), + e.namespace as i64, + ns_name, + e.id as i64, + e.offset as i64, + e.size as i64, + e.magic.as_deref(), ])?; } drop(stmt); let mut stmt = conn.prepare( "INSERT INTO xdbf_strings (language, language_name, string_id, value) - VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING" + VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING", )?; let mut n_strings = 0u64; for t in &x.string_tables { @@ -1516,7 +1760,7 @@ fn insert_xdbf(conn: &Connection, xdbf: Option<&crate::xdbf::Xdbf>) -> anyhow::R "INSERT INTO xdbf_achievements (id, name, unlocked_desc, locked_desc, label_id, description_id, unachieved_id, image_id, gamerscore, flags) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT DO NOTHING" + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT DO NOTHING", )?; for a in &x.achievements { stmt.execute(params![ @@ -1524,15 +1768,19 @@ fn insert_xdbf(conn: &Connection, xdbf: Option<&crate::xdbf::Xdbf>) -> anyhow::R text(a.label_id), text(a.description_id), text(a.unachieved_id), - a.label_id as i64, a.description_id as i64, a.unachieved_id as i64, - a.image_id as i64, a.gamerscore as i64, a.flags as i64, + a.label_id as i64, + a.description_id as i64, + a.unachieved_id as i64, + a.image_id as i64, + a.gamerscore as i64, + a.flags as i64, ])?; } drop(stmt); let mut stmt = conn.prepare( "INSERT INTO xdbf_images (id, is_title_icon, body_offset, size, format) - VALUES (?, ?, ?, ?, ?) ON CONFLICT DO NOTHING" + VALUES (?, ?, ?, ?, ?) ON CONFLICT DO NOTHING", )?; for i in &x.images { stmt.execute(params![ @@ -1548,7 +1796,10 @@ fn insert_xdbf(conn: &Connection, xdbf: Option<&crate::xdbf::Xdbf>) -> anyhow::R let mut meta = conn.prepare("INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)")?; meta.execute(params!["xdbf.entry_count", x.entries.len().to_string()])?; if let Some(l) = x.default_language { - meta.execute(params!["xdbf.default_language", crate::xdbf::language_name(l)])?; + meta.execute(params![ + "xdbf.default_language", + crate::xdbf::language_name(l) + ])?; } if let Some(t) = x.title { meta.execute(params!["xdbf.title_id", format!("{:#010X}", t.title_id)])?; @@ -1578,20 +1829,20 @@ fn insert_funcptr_arrays( conn: &Connection, arrays: &[crate::funcptr_arrays::FuncPtrArray], ) -> anyhow::Result<()> { - if arrays.is_empty() { return Ok(()); } + if arrays.is_empty() { + return Ok(()); + } // Parents first (few, and `ON CONFLICT` decides which survive), then the // entry rows in one Appender pass — only entries of a parent that was // actually inserted. let mut stmt_arr = conn.prepare( "INSERT INTO function_pointer_arrays (address, length, kind) VALUES (?, ?, ?) - ON CONFLICT DO NOTHING" + ON CONFLICT DO NOTHING", )?; let mut n_arr = 0u64; let mut kept: Vec<&crate::funcptr_arrays::FuncPtrArray> = Vec::with_capacity(arrays.len()); for a in arrays { - let inserted = stmt_arr.execute(params![ - a.address as i64, a.length as i64, a.kind, - ])?; + let inserted = stmt_arr.execute(params![a.address as i64, a.length as i64, a.kind,])?; if inserted > 0 { n_arr += 1; kept.push(a); @@ -1611,7 +1862,11 @@ fn insert_funcptr_arrays( metrics::counter!("db.rows", "table" => "function_pointer_arrays").increment(n_arr); metrics::counter!("db.rows", "table" => "function_pointer_array_entries").increment(n_ent); - tracing::info!(arrays = n_arr, entries = n_ent, "function-pointer arrays insert complete"); + tracing::info!( + arrays = n_arr, + entries = n_ent, + "function-pointer arrays insert complete" + ); Ok(()) } @@ -1624,7 +1879,7 @@ fn insert_demangled_from_labels( "INSERT INTO demangled_names (address, mangled, raw_demangled, namespace_path, class_name, method_name, params_signature) - VALUES (?, ?, ?, ?, ?, ?, ?)" + VALUES (?, ?, ?, ?, ?, ?, ?)", )?; let mut count = 0u64; @@ -1674,7 +1929,11 @@ fn insert_demangled_from_labels( } metrics::counter!("db.rows", "table" => "demangled_names").increment(count); - tracing::info!(rows = count, table = "demangled_names", "demangler complete"); + tracing::info!( + rows = count, + table = "demangled_names", + "demangler complete" + ); Ok(()) } @@ -1701,10 +1960,7 @@ fn insert_pdata_entries( Ok(()) } -fn insert_labels( - conn: &Connection, - labels: &HashMap, -) -> anyhow::Result<()> { +fn insert_labels(conn: &Connection, labels: &HashMap) -> anyhow::Result<()> { // `labels` is keyed by address, so it cannot contain a duplicate primary // key — the Appender is safe here without a dedupe pass. let mut appender = conn.appender("labels")?; @@ -1740,11 +1996,19 @@ fn insert_instructions_streaming( let mut total: u64 = 0; for section in info.sections { - if !section.is_code() { continue; } + if !section.is_code() { + continue; + } let va_start = info.image_base + section.virtual_address; let va_end = info.image_base + section.virtual_address + section.virtual_size; let items = crate::disasm::enrich_section( - pe, info.image_base, §ion.name, va_start, va_end, func_analysis, labels, + pe, + info.image_base, + §ion.name, + va_start, + va_end, + func_analysis, + labels, data_words, ); total += crate::sinks::duckdb::append_instructions(&mut appender, items)?; @@ -1765,7 +2029,7 @@ fn insert_jump_tables( "INSERT INTO jump_tables (bctr_pc, function, table_address, entry_count, table_slots, index_map_address, index_map_count, case_bound, kind) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", )?; for jt in tables { t.execute(params![ @@ -1801,7 +2065,8 @@ fn insert_jump_tables( kinds.insert(a, "jump_index_map"); } } - let mut d = conn.prepare("INSERT INTO data_in_code (address, length, kind) VALUES (?, ?, ?)")?; + let mut d = + conn.prepare("INSERT INTO data_in_code (address, length, kind) VALUES (?, ?, ?)")?; for (addr, len) in crate::jumptables::data_regions(tables) { let kind = kinds.get(&addr).copied().unwrap_or("jump_table"); d.execute(params![addr as i64, len as i64, kind])?; @@ -1821,7 +2086,7 @@ fn insert_jump_tables( fn insert_rtti(conn: &Connection, rtti: &crate::rtti::RttiResult) -> anyhow::Result<()> { let mut td = conn.prepare( "INSERT INTO rtti_type_descriptors (address, mangled_name, demangled_name) - VALUES (?, ?, ?)" + VALUES (?, ?, ?)", )?; // RTTI descriptors are the only mangled names a stripped retail binary // still carries, so they are also the only thing `demangled_names` can be @@ -1830,7 +2095,7 @@ fn insert_rtti(conn: &Connection, rtti: &crate::rtti::RttiResult) -> anyhow::Res "INSERT INTO demangled_names (address, mangled, raw_demangled, namespace_path, class_name, method_name, params_signature) - VALUES (?, ?, ?, ?, ?, ?, ?)" + VALUES (?, ?, ?, ?, ?, ?, ?)", )?; for t in &rtti.type_descriptors { td.execute(params![t.address as i64, t.mangled_name, t.demangled_name])?; @@ -1852,7 +2117,7 @@ fn insert_rtti(conn: &Connection, rtti: &crate::rtti::RttiResult) -> anyhow::Res let mut col = conn.prepare( "INSERT INTO rtti_locators (address, subobject_offset, cd_offset, type_descriptor, class_hierarchy, vtable_address) - VALUES (?, ?, ?, ?, ?, ?)" + VALUES (?, ?, ?, ?, ?, ?)", )?; for c in &rtti.locators { col.execute(params![ @@ -1869,7 +2134,7 @@ fn insert_rtti(conn: &Connection, rtti: &crate::rtti::RttiResult) -> anyhow::Res "INSERT INTO rtti_base_classes (class_hierarchy, base_index, type_descriptor, name, num_contained_bases, mdisp, pdisp, vdisp, attributes) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", )?; for b in &rtti.base_classes { bc.execute(params![ @@ -1914,7 +2179,7 @@ fn insert_xrefs_streaming( let instruction: Option = { let off = xref.source.wrapping_sub(image_base) as usize; if off + 4 <= pe.len() { - let raw = u32::from_be_bytes([pe[off], pe[off+1], pe[off+2], pe[off+3]]); + let raw = u32::from_be_bytes([pe[off], pe[off + 1], pe[off + 2], pe[off + 3]]); let d = sylpheed_ppc::decode(raw, xref.source); let t = sylpheed_ppc::disasm::format(&d); // Prefer the simplified mnemonic when present (matches what @@ -1925,14 +2190,13 @@ fn insert_xrefs_streaming( } }; - let source_func = func_analysis.functions + let source_func = func_analysis + .functions .range(..=xref.source) .next_back() .map(|(&a, _)| a as i64); - let source_label = resolve_source_label( - xref.source, func_analysis, labels, - ); + let source_label = resolve_source_label(xref.source, func_analysis, labels); let addr_mode = xref.addr_mode.map(|m| m.tag()); appender.append_row(params![ diff --git a/crates/sylpheed-xexdb/src/demangle.rs b/crates/sylpheed-xexdb/src/demangle.rs index e2c332a1..6e73ccc8 100644 --- a/crates/sylpheed-xexdb/src/demangle.rs +++ b/crates/sylpheed-xexdb/src/demangle.rs @@ -345,7 +345,10 @@ mod rtti_name_tests { #[test] fn plain_class_in_namespace() { - assert_eq!(demangle_type_descriptor(".?AVSilph@silph@@").as_deref(), Some("silph::Silph")); + assert_eq!( + demangle_type_descriptor(".?AVSilph@silph@@").as_deref(), + Some("silph::Silph") + ); } #[test] @@ -358,12 +361,16 @@ mod rtti_name_tests { #[test] fn global_scope_class() { - assert_eq!(demangle_type_descriptor(".?AVexception@std@@").as_deref(), Some("std::exception")); + assert_eq!( + demangle_type_descriptor(".?AVexception@std@@").as_deref(), + Some("std::exception") + ); } #[test] fn anonymous_namespace_is_named() { - let got = demangle_type_descriptor(".?AVAct_Stop@?A0x5cc05762@unnamed_namespaces@@").unwrap(); + let got = + demangle_type_descriptor(".?AVAct_Stop@?A0x5cc05762@unnamed_namespaces@@").unwrap(); assert!(got.ends_with("Act_Stop"), "got {got}"); assert!(got.starts_with("unnamed_namespaces"), "got {got}"); } diff --git a/crates/sylpheed-xexdb/src/disasm.rs b/crates/sylpheed-xexdb/src/disasm.rs index 03056b16..adcb9436 100644 --- a/crates/sylpheed-xexdb/src/disasm.rs +++ b/crates/sylpheed-xexdb/src/disasm.rs @@ -86,10 +86,16 @@ mod tests { fn fi(start: u32, end: u32) -> FuncInfo { FuncInfo { - start, end, - frame_size: 0, saved_gprs: 0, is_leaf: true, is_saverestore: false, - pdata_validated: true, pdata_length: Some(end - start), - pdata_prolog_length: None, has_eh: false, + start, + end, + frame_size: 0, + saved_gprs: 0, + is_leaf: true, + is_saverestore: false, + pdata_validated: true, + pdata_length: Some(end - start), + pdata_prolog_length: None, + has_eh: false, } } @@ -113,18 +119,29 @@ mod tests { let labels = HashMap::new(); let data_words = BTreeSet::new(); let got: Vec<(u32, Option)> = enrich_section( - &image, image_base, ".text", image_base, image_base + 24, - &fa, &labels, &data_words, - ).map(|r| (r.item.addr, r.function)).collect(); + &image, + image_base, + ".text", + image_base, + image_base + 24, + &fa, + &labels, + &data_words, + ) + .map(|r| (r.item.addr, r.function)) + .collect(); - assert_eq!(got, vec![ - (image_base, Some(image_base)), // inside f0 - (image_base + 4, Some(image_base)), // inside f0 - (image_base + 8, None), // gap — was wrongly f0 - (image_base + 12, None), // gap — was wrongly f0 - (image_base + 16, Some(image_base + 16)), // f1 starts - (image_base + 20, Some(image_base + 16)), - ]); + assert_eq!( + got, + vec![ + (image_base, Some(image_base)), // inside f0 + (image_base + 4, Some(image_base)), // inside f0 + (image_base + 8, None), // gap — was wrongly f0 + (image_base + 12, None), // gap — was wrongly f0 + (image_base + 16, Some(image_base + 16)), // f1 starts + (image_base + 20, Some(image_base + 16)), + ] + ); } /// A function starting exactly at its predecessor's `end_address` must be @@ -137,18 +154,33 @@ mod tests { functions.insert(image_base, fi(image_base, image_base + 8)); functions.insert(image_base + 8, fi(image_base + 8, image_base + 16)); let fa = FuncAnalysis { - functions, save_gpr_base: None, restore_gpr_base: None, + functions, + save_gpr_base: None, + restore_gpr_base: None, pdata_entries: Vec::new(), }; let labels = HashMap::new(); let data_words = BTreeSet::new(); let got: Vec> = enrich_section( - &image, image_base, ".text", image_base, image_base + 16, - &fa, &labels, &data_words, - ).map(|r| r.function).collect(); - assert_eq!(got, vec![ - Some(image_base), Some(image_base), - Some(image_base + 8), Some(image_base + 8), - ]); + &image, + image_base, + ".text", + image_base, + image_base + 16, + &fa, + &labels, + &data_words, + ) + .map(|r| r.function) + .collect(); + assert_eq!( + got, + vec![ + Some(image_base), + Some(image_base), + Some(image_base + 8), + Some(image_base + 8), + ] + ); } } diff --git a/crates/sylpheed-xexdb/src/eh_scope.rs b/crates/sylpheed-xexdb/src/eh_scope.rs index 58c906a1..02aef937 100644 --- a/crates/sylpheed-xexdb/src/eh_scope.rs +++ b/crates/sylpheed-xexdb/src/eh_scope.rs @@ -49,9 +49,9 @@ use sylpheed_xex::pe::PeSection; -const MAGIC_OLD: u32 = 0x1993_0520; -const MAGIC_V21: u32 = 0x1993_0521; -const MAGIC_V22: u32 = 0x1993_0522; +const MAGIC_OLD: u32 = 0x1993_0520; +const MAGIC_V21: u32 = 0x1993_0521; +const MAGIC_V22: u32 = 0x1993_0522; #[derive(Debug, Clone, Copy)] pub struct UnwindMapEntry { @@ -85,41 +85,56 @@ pub struct EhFuncInfo { } #[tracing::instrument(skip_all, fields(image_base = format_args!("{:#010x}", image_base)))] -pub fn analyze( - pe: &[u8], - image_base: u32, - sections: &[PeSection], -) -> Vec { +pub fn analyze(pe: &[u8], image_base: u32, sections: &[PeSection]) -> Vec { let started = std::time::Instant::now(); let mut out: Vec = Vec::new(); // Compute the union of valid VA ranges across all sections — used to // sanity-check internal pointers in the FuncInfo records. - let valid_ranges: Vec<(u32, u32)> = sections.iter() - .map(|s| (image_base + s.virtual_address, - image_base + s.virtual_address + s.virtual_size)) + let valid_ranges: Vec<(u32, u32)> = sections + .iter() + .map(|s| { + ( + image_base + s.virtual_address, + image_base + s.virtual_address + s.virtual_size, + ) + }) .collect(); let in_valid = |va: u32| valid_ranges.iter().any(|(lo, hi)| va >= *lo && va < *hi); let read_u32 = |abs: u32| -> Option { let off = abs.wrapping_sub(image_base) as usize; - if off + 4 > pe.len() { return None; } - Some(u32::from_be_bytes([pe[off], pe[off + 1], pe[off + 2], pe[off + 3]])) + if off + 4 > pe.len() { + return None; + } + Some(u32::from_be_bytes([ + pe[off], + pe[off + 1], + pe[off + 2], + pe[off + 3], + ])) }; let read_i32 = |abs: u32| -> Option { read_u32(abs).map(|u| u as i32) }; for section in sections { - if section.name != ".rdata" { continue; } + if section.name != ".rdata" { + continue; + } let raw_start = section.virtual_address as usize; let raw_end = (section.virtual_address + section.virtual_size) as usize; - if raw_end > pe.len() { continue; } + if raw_end > pe.len() { + continue; + } let bytes = &pe[raw_start..raw_end.min(pe.len())]; let va_base = image_base + section.virtual_address; // Walk on 4-byte alignment looking for the magic. let mut i = 0; while i + 4 <= bytes.len() { - if !i.is_multiple_of(4) { i += 1; continue; } + if !i.is_multiple_of(4) { + i += 1; + continue; + } let m = u32::from_be_bytes([bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3]]); if m == MAGIC_OLD || m == MAGIC_V21 || m == MAGIC_V22 { let addr = va_base + i as u32; @@ -152,23 +167,35 @@ fn parse_funcinfo( read_i32: &impl Fn(u32) -> Option, in_valid: &impl Fn(u32) -> bool, ) -> Option { - let max_state = read_i32(addr + 0x04)?; - let p_unwind_map = read_u32(addr + 0x08)?; - let n_try_blocks = read_u32(addr + 0x0C)?; - let p_try_block_map = read_u32(addr + 0x10)?; - let n_ip_map_entries = read_u32(addr + 0x14)?; + let max_state = read_i32(addr + 0x04)?; + let p_unwind_map = read_u32(addr + 0x08)?; + let n_try_blocks = read_u32(addr + 0x0C)?; + let p_try_block_map = read_u32(addr + 0x10)?; + let n_ip_map_entries = read_u32(addr + 0x14)?; let p_ip_to_state_map = read_u32(addr + 0x18)?; // Sanity caps: real FuncInfo records have max_state ≤ a few thousand, // n_try_blocks ≤ a few hundred. Reject obviously bogus values that // happened to alias the magic. - if !(0..=10_000).contains(&max_state) { return None; } - if n_try_blocks > 1_000 { return None; } - if n_ip_map_entries > 100_000 { return None; } + if !(0..=10_000).contains(&max_state) { + return None; + } + if n_try_blocks > 1_000 { + return None; + } + if n_ip_map_entries > 100_000 { + return None; + } // Pointers must either be NULL or land in a valid section. - if p_unwind_map != 0 && !in_valid(p_unwind_map) { return None; } - if p_try_block_map != 0 && !in_valid(p_try_block_map) { return None; } - if p_ip_to_state_map != 0 && !in_valid(p_ip_to_state_map) { return None; } + if p_unwind_map != 0 && !in_valid(p_unwind_map) { + return None; + } + if p_try_block_map != 0 && !in_valid(p_try_block_map) { + return None; + } + if p_ip_to_state_map != 0 && !in_valid(p_ip_to_state_map) { + return None; + } let (p_es_type_list, eh_flags) = if magic == MAGIC_V21 { (read_u32(addr + 0x1C), None) @@ -185,7 +212,10 @@ fn parse_funcinfo( let p = p_unwind_map.wrapping_add((i * 8) as u32); let to_state = read_i32(p)?; let action_pc = read_u32(p + 4)?; - unwind_map.push(UnwindMapEntry { to_state, action_pc }); + unwind_map.push(UnwindMapEntry { + to_state, + action_pc, + }); } } @@ -194,13 +224,17 @@ fn parse_funcinfo( if p_try_block_map != 0 && n_try_blocks > 0 { for i in 0..n_try_blocks { let p = p_try_block_map.wrapping_add(i * 20); - let try_low = read_i32(p)?; - let try_high = read_i32(p + 4)?; - let catch_high = read_i32(p + 8)?; - let n_catches = read_u32(p + 12)?; - let p_handler_a = read_u32(p + 16)?; + let try_low = read_i32(p)?; + let try_high = read_i32(p + 4)?; + let catch_high = read_i32(p + 8)?; + let n_catches = read_u32(p + 12)?; + let p_handler_a = read_u32(p + 16)?; try_blocks.push(TryBlockMapEntry { - try_low, try_high, catch_high, n_catches, p_handler_array: p_handler_a, + try_low, + try_high, + catch_high, + n_catches, + p_handler_array: p_handler_a, }); } } @@ -229,8 +263,10 @@ mod tests { fn mk_section(name: &str, va: u32, size: u32) -> PeSection { PeSection { name: name.into(), - virtual_address: va, virtual_size: size, - raw_offset: va, raw_size: size, + virtual_address: va, + virtual_size: size, + raw_offset: va, + raw_size: size, flags: 0x4000_0040, } } @@ -254,17 +290,17 @@ mod tests { let unwind_off = (rdata_va + 0x80) as usize; let unwind_va = image_base + rdata_va + 0x80; - write_be(&mut pe, fi_off, MAGIC_OLD); // magic - write_be_i32(&mut pe, fi_off + 4, 2); // maxState - write_be(&mut pe, fi_off + 8, unwind_va); // pUnwindMap - write_be(&mut pe, fi_off + 12, 0); // nTryBlocks - write_be(&mut pe, fi_off + 16, 0); // pTryBlockMap - write_be(&mut pe, fi_off + 20, 0); // nIPMapEntries - write_be(&mut pe, fi_off + 24, 0); // pIPtoStateMap + write_be(&mut pe, fi_off, MAGIC_OLD); // magic + write_be_i32(&mut pe, fi_off + 4, 2); // maxState + write_be(&mut pe, fi_off + 8, unwind_va); // pUnwindMap + write_be(&mut pe, fi_off + 12, 0); // nTryBlocks + write_be(&mut pe, fi_off + 16, 0); // pTryBlockMap + write_be(&mut pe, fi_off + 20, 0); // nIPMapEntries + write_be(&mut pe, fi_off + 24, 0); // pIPtoStateMap // Two unwind entries. - write_be_i32(&mut pe, unwind_off, -1); // to_state - write_be(&mut pe, unwind_off + 4, image_base + 0x500); // action_pc + write_be_i32(&mut pe, unwind_off, -1); // to_state + write_be(&mut pe, unwind_off + 4, image_base + 0x500); // action_pc write_be_i32(&mut pe, unwind_off + 8, 0); write_be(&mut pe, unwind_off + 12, image_base + 0x600); @@ -288,7 +324,7 @@ mod tests { let mut pe = vec![0u8; 0x4000]; let fi_off = (rdata_va + 0x10) as usize; write_be(&mut pe, fi_off, MAGIC_OLD); - write_be_i32(&mut pe, fi_off + 4, 0xFFFF); // bogus maxState + write_be_i32(&mut pe, fi_off + 4, 0xFFFF); // bogus maxState let sections = vec![mk_section(".rdata", rdata_va, 0x100)]; let recs = analyze(&pe, image_base, §ions); assert_eq!(recs.len(), 0); diff --git a/crates/sylpheed-xexdb/src/formatter.rs b/crates/sylpheed-xexdb/src/formatter.rs index 3c65a91c..5e573976 100644 --- a/crates/sylpheed-xexdb/src/formatter.rs +++ b/crates/sylpheed-xexdb/src/formatter.rs @@ -9,7 +9,7 @@ use sylpheed_xex::pe::PeSection; use crate::disasm::enrich_section; use crate::func::FuncAnalysis; use crate::sinks::text::write_instr_line; -use crate::xref::{XrefKind, Xref, XrefMap, resolve_source_label}; +use crate::xref::{Xref, XrefKind, XrefMap, resolve_source_label}; /// Metadata passed to the formatter (avoids exposing full Xex2Header internals). pub struct DisasmInfo<'a> { @@ -40,29 +40,53 @@ pub fn write_asm( data_words: &BTreeSet, ) -> anyhow::Result<()> { // Header - writeln!(out, "; ============================================================================")?; + writeln!( + out, + "; ============================================================================" + )?; writeln!(out, "; Xbox 360 Disassembly — generated by xenia-rs")?; if let Some(name) = info.original_pe_name { writeln!(out, "; Original PE: {name}")?; } if let (Some(title_id), Some(media_id)) = (info.title_id, info.media_id) { - writeln!(out, "; Title ID: 0x{title_id:08X} Media ID: 0x{media_id:08X}")?; + writeln!( + out, + "; Title ID: 0x{title_id:08X} Media ID: 0x{media_id:08X}" + )?; } - writeln!(out, "; Image base: 0x{:08X} Entry point: 0x{:08X}", info.image_base, info.entry_point)?; - writeln!(out, "; Functions detected: {}", func_analysis.functions.len())?; - writeln!(out, "; ============================================================================")?; + writeln!( + out, + "; Image base: 0x{:08X} Entry point: 0x{:08X}", + info.image_base, info.entry_point + )?; + writeln!( + out, + "; Functions detected: {}", + func_analysis.functions.len() + )?; + writeln!( + out, + "; ============================================================================" + )?; writeln!(out)?; // Import declarations if !info.import_libraries.is_empty() { - writeln!(out, "; ── Imports ─────────────────────────────────────────────────────────────────")?; + writeln!( + out, + "; ── Imports ─────────────────────────────────────────────────────────────────" + )?; for lib in info.import_libraries { writeln!(out, "; Library: {}", lib.name)?; for imp in &lib.imports { let resolved = crate::resolve_ordinal(&lib.name, imp.ordinal); let name = resolved.unwrap_or("???"); let kind = if imp.record_type == 1 { "thunk" } else { "var" }; - writeln!(out, "; [{kind}] 0x{:08X} ordinal 0x{:04X} = {}", imp.address, imp.ordinal, name)?; + writeln!( + out, + "; [{kind}] 0x{:08X} ordinal 0x{:04X} = {}", + imp.address, imp.ordinal, name + )?; } } writeln!(out)?; @@ -70,8 +94,11 @@ pub fn write_asm( // Disassemble each section for section in info.sections { - writeln!(out, "; ── Section: {:8} VA=0x{:08X} Size=0x{:08X} Flags=0x{:08X} ──", - section.name, section.virtual_address, section.virtual_size, section.flags)?; + writeln!( + out, + "; ── Section: {:8} VA=0x{:08X} Size=0x{:08X} Flags=0x{:08X} ──", + section.name, section.virtual_address, section.virtual_size, section.flags + )?; let va_start = section.virtual_address; let va_end = va_start + section.virtual_size; @@ -81,7 +108,8 @@ pub fn write_asm( let section_labels_sorted: Vec = if !section.is_code() { let sec_start = info.image_base + va_start; let sec_end = info.image_base + va_end; - let mut addrs: Vec = labels.keys() + let mut addrs: Vec = labels + .keys() .filter(|&&a| a >= sec_start && a < sec_end) .copied() .collect(); @@ -100,7 +128,13 @@ pub fn write_asm( let abs_end = info.image_base + va_end; let items = enrich_section( - pe, info.image_base, §ion.name, abs_start, abs_end, func_analysis, labels, + pe, + info.image_base, + §ion.name, + abs_start, + abs_end, + func_analysis, + labels, data_words, ); for ri in items { @@ -112,9 +146,14 @@ pub fn write_asm( writeln!(out, "; end function")?; } writeln!(out)?; - writeln!(out, "; ──────────────────────────────────────────────────────────────────────────")?; + writeln!( + out, + "; ──────────────────────────────────────────────────────────────────────────" + )?; - let lbl = labels.get(&abs_addr).cloned() + let lbl = labels + .get(&abs_addr) + .cloned() .unwrap_or_else(|| format!("sub_{abs_addr:08X}")); if fi.is_saverestore { @@ -144,7 +183,10 @@ pub fn write_asm( } } - writeln!(out, "; ──────────────────────────────────────────────────────────────────────────")?; + writeln!( + out, + "; ──────────────────────────────────────────────────────────────────────────" + )?; in_function = true; } @@ -152,7 +194,9 @@ pub fn write_asm( if let Some(lbl) = labels.get(&abs_addr) { if !func_analysis.is_function_start(abs_addr) { writeln!(out)?; - if let Some(xref_lines) = format_xrefs(abs_addr, xrefs, func_analysis, labels) { + if let Some(xref_lines) = + format_xrefs(abs_addr, xrefs, func_analysis, labels) + { for line in &xref_lines { writeln!(out, "{line}")?; } @@ -204,22 +248,32 @@ pub fn write_asm( line_end = lbl_va; break; } - if lbl_va >= line_end { break; } + if lbl_va >= line_end { + break; + } } let byte_count = (line_end - addr) as usize; - if off + byte_count > pe.len() { break; } + if off + byte_count > pe.len() { + break; + } write!(out, " {:08X}: ", abs_addr)?; for i in 0..byte_count { write!(out, "{:02X}", pe[off + i])?; - if i % 4 == 3 { write!(out, " ")?; } + if i % 4 == 3 { + write!(out, " ")?; + } } // ASCII representation let pad = (16 - byte_count) * 2 + (16 - byte_count) / 4; write!(out, "{:>width$} |", "", width = pad)?; for i in 0..byte_count { let b = pe[off + i]; - let ch = if b.is_ascii_graphic() || b == b' ' { b as char } else { '.' }; + let ch = if b.is_ascii_graphic() || b == b' ' { + b as char + } else { + '.' + }; write!(out, "{ch}")?; } writeln!(out, "|")?; @@ -242,7 +296,9 @@ fn format_xrefs( labels: &HashMap, ) -> Option> { let refs = xrefs.get(&target)?; - if refs.is_empty() { return None; } + if refs.is_empty() { + return None; + } let mut sorted: Vec = refs.clone(); sorted.sort(); @@ -254,17 +310,47 @@ fn format_xrefs( let calls = sorted.iter().filter(|x| x.kind == XrefKind::Call).count(); let jumps = sorted.iter().filter(|x| x.kind == XrefKind::Jump).count(); let branches = sorted.iter().filter(|x| x.kind == XrefKind::Branch).count(); - let reads = sorted.iter().filter(|x| x.kind == XrefKind::DataRead).count(); - let writes = sorted.iter().filter(|x| x.kind == XrefKind::DataWrite).count(); - let data_refs = sorted.iter().filter(|x| x.kind == XrefKind::DataRef).count(); + let reads = sorted + .iter() + .filter(|x| x.kind == XrefKind::DataRead) + .count(); + let writes = sorted + .iter() + .filter(|x| x.kind == XrefKind::DataWrite) + .count(); + let data_refs = sorted + .iter() + .filter(|x| x.kind == XrefKind::DataRef) + .count(); let mut summary_parts = Vec::new(); - if calls > 0 { summary_parts.push(format!("{calls} call{}", if calls != 1 { "s" } else { "" })); } - if jumps > 0 { summary_parts.push(format!("{jumps} jump{}", if jumps != 1 { "s" } else { "" })); } - if branches > 0 { summary_parts.push(format!("{branches} branch{}", if branches != 1 { "es" } else { "" })); } - if reads > 0 { summary_parts.push(format!("{reads} read{}", if reads != 1 { "s" } else { "" })); } - if writes > 0 { summary_parts.push(format!("{writes} write{}", if writes != 1 { "s" } else { "" })); } - if data_refs > 0 { summary_parts.push(format!("{data_refs} ref{}", if data_refs != 1 { "s" } else { "" })); } + if calls > 0 { + summary_parts.push(format!("{calls} call{}", if calls != 1 { "s" } else { "" })); + } + if jumps > 0 { + summary_parts.push(format!("{jumps} jump{}", if jumps != 1 { "s" } else { "" })); + } + if branches > 0 { + summary_parts.push(format!( + "{branches} branch{}", + if branches != 1 { "es" } else { "" } + )); + } + if reads > 0 { + summary_parts.push(format!("{reads} read{}", if reads != 1 { "s" } else { "" })); + } + if writes > 0 { + summary_parts.push(format!( + "{writes} write{}", + if writes != 1 { "s" } else { "" } + )); + } + if data_refs > 0 { + summary_parts.push(format!( + "{data_refs} ref{}", + if data_refs != 1 { "s" } else { "" } + )); + } lines.push(format!("; XREF: {} ({})", summary_parts.join(", "), total)); diff --git a/crates/sylpheed-xexdb/src/func.rs b/crates/sylpheed-xexdb/src/func.rs index f5275813..aef89f92 100644 --- a/crates/sylpheed-xexdb/src/func.rs +++ b/crates/sylpheed-xexdb/src/func.rs @@ -5,17 +5,17 @@ //! hence very likely function entry points. //! 2. Scan the save/restore GPR helper region and label it. //! 3. For each candidate entry, look for prologue patterns: -//! a) `mfspr rN, LR` (typically r0 or r12) -//! b) `bl __savegprlr_NN` (call into save stub) -//! c) `stwu r1, -N(r1)` (allocate stack frame) +//! a) `mfspr rN, LR` (typically r0 or r12) +//! b) `bl __savegprlr_NN` (call into save stub) +//! c) `stwu r1, -N(r1)` (allocate stack frame) //! If a prologue is confirmed, record the function and its stack frame size. //! 4. Walk forward from each function entry to find the epilogue: -//! a) `blr` (return) -//! b) `b __restgprlr_NN` (tail-branch into restore stub which returns) +//! a) `blr` (return) +//! b) `b __restgprlr_NN` (tail-branch into restore stub which returns) //! Mark the function's end address. //! 5. Detect leaf functions: `bl` targets that lack a prologue but eventually `blr`. -use std::collections::{HashMap, HashSet, BTreeMap}; +use std::collections::{BTreeMap, HashMap, HashSet}; /// Information about a detected function. #[derive(Debug, Clone)] @@ -63,37 +63,53 @@ pub struct FuncAnalysis { // ── Instruction field helpers ────────────────────────────────────────────── -fn op(instr: u32) -> u32 { (instr >> 26) & 0x3F } +fn op(instr: u32) -> u32 { + (instr >> 26) & 0x3F +} fn bits(instr: u32, hi: u32, lo: u32) -> u32 { (instr >> (31 - hi)) & ((1 << (hi - lo + 1)) - 1) } fn is_mfspr_lr(instr: u32) -> Option { // mfspr rD, LR → opcode 31, xo=339, spr=8 - if op(instr) != 31 { return None; } + if op(instr) != 31 { + return None; + } let xo = bits(instr, 30, 21); - if xo != 339 { return None; } + if xo != 339 { + return None; + } let spr = (bits(instr, 20, 16) << 5) | bits(instr, 15, 11); - if spr != 8 { return None; } + if spr != 8 { + return None; + } Some(bits(instr, 10, 6)) // return rD } #[allow(dead_code)] fn is_mtspr_lr(instr: u32) -> bool { // mtspr LR, rS → opcode 31, xo=467, spr=8 - if op(instr) != 31 { return false; } + if op(instr) != 31 { + return false; + } let xo = bits(instr, 30, 21); - if xo != 467 { return false; } + if xo != 467 { + return false; + } let spr = (bits(instr, 20, 16) << 5) | bits(instr, 15, 11); spr == 8 } fn is_stwu_r1(instr: u32) -> Option { // stwu r1, d(r1) → opcode 37, rS=1, rA=1 - if op(instr) != 37 { return None; } + if op(instr) != 37 { + return None; + } let rs = bits(instr, 10, 6); let ra = bits(instr, 15, 11); - if rs != 1 || ra != 1 { return None; } + if rs != 1 || ra != 1 { + return None; + } let d = ((instr & 0xFFFF) as i16) as i32; Some(d) // negative = frame allocation } @@ -108,9 +124,15 @@ fn is_bctr(instr: u32) -> bool { fn is_bl(instr: u32) -> Option { // bl target → opcode 18, LK=1, AA=0 - if op(instr) != 18 { return None; } - if instr & 1 == 0 { return None; } // must have LK bit - if instr & 2 != 0 { return None; } // not absolute + if op(instr) != 18 { + return None; + } + if instr & 1 == 0 { + return None; + } // must have LK bit + if instr & 2 != 0 { + return None; + } // not absolute // Return the signed offset let li = instr & 0x03FFFFFC; Some(li) @@ -118,9 +140,15 @@ fn is_bl(instr: u32) -> Option { fn is_b(instr: u32) -> Option { // b target → opcode 18, LK=0, AA=0 - if op(instr) != 18 { return None; } - if instr & 1 != 0 { return None; } // no LK bit - if instr & 2 != 0 { return None; } // not absolute + if op(instr) != 18 { + return None; + } + if instr & 1 != 0 { + return None; + } // no LK bit + if instr & 2 != 0 { + return None; + } // not absolute Some(instr & 0x03FFFFFC) } @@ -140,8 +168,15 @@ fn b_target(instr: u32, addr: u32) -> Option { fn read_instr(pe: &[u8], abs_addr: u32, image_base: u32) -> Option { let off = abs_addr.wrapping_sub(image_base) as usize; - if off + 4 > pe.len() { return None; } - Some(u32::from_be_bytes([pe[off], pe[off+1], pe[off+2], pe[off+3]])) + if off + 4 > pe.len() { + return None; + } + Some(u32::from_be_bytes([ + pe[off], + pe[off + 1], + pe[off + 2], + pe[off + 3], + ])) } // ── Detect the save/restore GPR helper stubs ─────────────────────────────── @@ -165,12 +200,28 @@ fn find_saverestore_stubs( let mut addr = start; while addr + 4 * 18 < end { // Check if this is `std r14, ...(r1)` — opcode 62 (std), rS=14, rA=1 - let instr = match read_instr(pe, addr, image_base) { Some(i) => i, None => { addr += 4; continue; } }; - if op(instr) == 62 && bits(instr, 10, 6) == 14 && bits(instr, 15, 11) == 1 && (instr & 3) == 0 { + let instr = match read_instr(pe, addr, image_base) { + Some(i) => i, + None => { + addr += 4; + continue; + } + }; + if op(instr) == 62 + && bits(instr, 10, 6) == 14 + && bits(instr, 15, 11) == 1 + && (instr & 3) == 0 + { // Verify it's a cascade: r14, r15, ..., r31 let mut ok = true; for i in 0u32..18 { - let check = match read_instr(pe, addr + i * 4, image_base) { Some(c) => c, None => { ok = false; break; } }; + let check = match read_instr(pe, addr + i * 4, image_base) { + Some(c) => c, + None => { + ok = false; + break; + } + }; if op(check) != 62 || bits(check, 10, 6) != 14 + i || bits(check, 15, 11) != 1 { ok = false; break; @@ -193,7 +244,9 @@ fn find_saverestore_stubs( } addr += 4; } - if save_base.is_some() { break; } + if save_base.is_some() { + break; + } } (save_base, restore_base) @@ -245,7 +298,8 @@ pub fn analyze_with_pdata( pdata: &[sylpheed_xex::pdata::PdataEntry], ) -> FuncAnalysis { let started = std::time::Instant::now(); - let code_ranges: Vec<(u32, u32)> = code_sections.iter() + let code_ranges: Vec<(u32, u32)> = code_sections + .iter() .map(|(va, sz, _)| (image_base + va, image_base + va + sz)) .collect(); @@ -262,11 +316,15 @@ pub fn analyze_with_pdata( let mut saverestore_addrs: HashSet = HashSet::new(); if let Some(sb) = save_base { // Save block: 18 std + stw + blr = 20 instructions - for i in 0..20 { saverestore_addrs.insert(sb + i * 4); } + for i in 0..20 { + saverestore_addrs.insert(sb + i * 4); + } } if let Some(rb) = restore_base { // Restore block: 18 ld + lwz + mtspr + blr = 21 instructions - for i in 0..21 { saverestore_addrs.insert(rb + i * 4); } + for i in 0..21 { + saverestore_addrs.insert(rb + i * 4); + } } // 2. Collect all bl targets as candidate function entries. @@ -278,12 +336,13 @@ pub fn analyze_with_pdata( let mut addr = start; while addr < end { if let Some(instr) = read_instr(pe, addr, image_base) - && let Some(target) = bl_target(instr, addr) { - // Don't count calls into save/restore stubs as function entries - if !saverestore_addrs.contains(&target) { - call_targets.insert(target); - } + && let Some(target) = bl_target(instr, addr) + { + // Don't count calls into save/restore stubs as function entries + if !saverestore_addrs.contains(&target) { + call_targets.insert(target); } + } addr += 4; } } @@ -311,7 +370,10 @@ pub fn analyze_with_pdata( // within this one. Intra-function jumps and switch arms both stay inside // the range and are therefore never nominated. let pdata_sorted: Vec<(u32, u32)> = { - let mut v: Vec<(u32, u32)> = pdata.iter().map(|e| (e.begin_address, e.end_address())).collect(); + let mut v: Vec<(u32, u32)> = pdata + .iter() + .map(|e| (e.begin_address, e.end_address())) + .collect(); v.sort_unstable(); v }; @@ -380,7 +442,12 @@ pub fn analyze_with_pdata( let pdata_entry = pdata_by_begin.get(&func_addr).copied(); if let Some(mut fi) = analyze_function( - pe, image_base, func_addr, &code_ranges, save_base, restore_base, + pe, + image_base, + func_addr, + &code_ranges, + save_base, + restore_base, ) { if let Some(p) = pdata_entry { fi.pdata_validated = true; @@ -428,33 +495,39 @@ pub fn analyze_with_pdata( // The save block is one cascade: entry at each rN, falls through to blr // Treat as a single function with the first entry point let pe_sb = pdata_by_begin.get(&sb).copied(); - functions.insert(sb, FuncInfo { - start: sb, - end: sb + 20 * 4, // 18 std + stw r12 + blr - frame_size: 0, - saved_gprs: 18, - is_leaf: true, - is_saverestore: true, - pdata_validated: pe_sb.is_some(), - pdata_length: pe_sb.map(|p| p.function_length), - pdata_prolog_length: pe_sb.map(|p| p.prolog_length), - has_eh: pe_sb.map(|p| (p.flags & 0x2) != 0).unwrap_or(false), - }); + functions.insert( + sb, + FuncInfo { + start: sb, + end: sb + 20 * 4, // 18 std + stw r12 + blr + frame_size: 0, + saved_gprs: 18, + is_leaf: true, + is_saverestore: true, + pdata_validated: pe_sb.is_some(), + pdata_length: pe_sb.map(|p| p.function_length), + pdata_prolog_length: pe_sb.map(|p| p.prolog_length), + has_eh: pe_sb.map(|p| (p.flags & 0x2) != 0).unwrap_or(false), + }, + ); } if let Some(rb) = restore_base { let pe_rb = pdata_by_begin.get(&rb).copied(); - functions.insert(rb, FuncInfo { - start: rb, - end: rb + 21 * 4, // 18 ld + lwz r12 + mtspr LR + blr - frame_size: 0, - saved_gprs: 18, - is_leaf: true, - is_saverestore: true, - pdata_validated: pe_rb.is_some(), - pdata_length: pe_rb.map(|p| p.function_length), - pdata_prolog_length: pe_rb.map(|p| p.prolog_length), - has_eh: pe_rb.map(|p| (p.flags & 0x2) != 0).unwrap_or(false), - }); + functions.insert( + rb, + FuncInfo { + start: rb, + end: rb + 21 * 4, // 18 ld + lwz r12 + mtspr LR + blr + frame_size: 0, + saved_gprs: 18, + is_leaf: true, + is_saverestore: true, + pdata_validated: pe_rb.is_some(), + pdata_length: pe_rb.map(|p| p.function_length), + pdata_prolog_length: pe_rb.map(|p| p.prolog_length), + has_eh: pe_rb.map(|p| (p.flags & 0x2) != 0).unwrap_or(false), + }, + ); } // 5. Reconcile candidate starts against the linker's ground truth. @@ -482,9 +555,13 @@ pub fn analyze_with_pdata( .filter(|&addr| { pdata_ranges .binary_search_by(|&(s, e)| { - if addr < s { std::cmp::Ordering::Greater } - else if addr >= e { std::cmp::Ordering::Less } - else { std::cmp::Ordering::Equal } + if addr < s { + std::cmp::Ordering::Greater + } else if addr >= e { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Equal + } }) .is_ok() }) @@ -534,7 +611,9 @@ pub fn analyze_with_pdata( fn range_has_call(pe: &[u8], image_base: u32, start: u32, end: u32) -> bool { let mut addr = start; while addr < end { - let Some(instr) = read_instr(pe, addr, image_base) else { return false }; + let Some(instr) = read_instr(pe, addr, image_base) else { + return false; + }; let opcode = op(instr); // I-form / B-form with LK, and XL-form bclrl / bcctrl. if (opcode == 18 || opcode == 16) && instr & 1 == 1 { @@ -558,8 +637,12 @@ fn analyze_function( restore_base: Option, ) -> Option { // Verify the address is within a code section - let in_code = code_ranges.iter().any(|&(s, e)| func_addr >= s && func_addr < e); - if !in_code { return None; } + let in_code = code_ranges + .iter() + .any(|&(s, e)| func_addr >= s && func_addr < e); + if !in_code { + return None; + } let instr0 = read_instr(pe, func_addr, image_base)?; @@ -576,11 +659,13 @@ fn analyze_function( // Check if next is bl to save stub if let Some(target) = bl_target(instr1, func_addr + 4) && let Some(sb) = save_base - && target >= sb && target < sb + 18 * 4 { - let idx = (target - sb) / 4; - saved_gprs = 18 - idx; - prologue_len = 8; - } + && target >= sb + && target < sb + 18 * 4 + { + let idx = (target - sb) / 4; + saved_gprs = 18 - idx; + prologue_len = 8; + } // Next should be stwu r1, -N(r1) let stwu_instr = read_instr(pe, func_addr + prologue_len, image_base).unwrap_or(0); @@ -601,7 +686,8 @@ fn analyze_function( } // Walk forward to find the end of the function - let max_range = code_ranges.iter() + let max_range = code_ranges + .iter() .find(|&&(s, e)| func_addr >= s && func_addr < e) .map(|&(_, e)| e) .unwrap_or(func_addr + 0x100000); @@ -628,10 +714,12 @@ fn analyze_function( // Epilogue: b __restgprlr_NN (tail branch into restore stub) if let Some(target) = b_target(instr, addr) && let Some(rb) = restore_base - && target >= rb && target < rb + 18 * 4 { - end_addr = addr + 4; - break; - } + && target >= rb + && target < rb + 18 * 4 + { + end_addr = addr + 4; + break; + } // Epilogue: bctr (indirect tail call — end of function) if is_bctr(instr) { @@ -680,21 +768,23 @@ impl FuncAnalysis { if fi.is_saverestore { // Label the block start, plus individual register entry points if let Some(sb) = self.save_gpr_base - && addr == sb { - for i in 0u32..18 { - let reg = 14 + i; - labels.insert(sb + i * 4, format!("__savegprlr_{reg}")); - } - continue; + && addr == sb + { + for i in 0u32..18 { + let reg = 14 + i; + labels.insert(sb + i * 4, format!("__savegprlr_{reg}")); } + continue; + } if let Some(rb) = self.restore_gpr_base - && addr == rb { - for i in 0u32..18 { - let reg = 14 + i; - labels.insert(rb + i * 4, format!("__restgprlr_{reg}")); - } - continue; + && addr == rb + { + for i in 0u32..18 { + let reg = 14 + i; + labels.insert(rb + i * 4, format!("__restgprlr_{reg}")); } + continue; + } } labels.insert(addr, format!("sub_{addr:08X}")); } diff --git a/crates/sylpheed-xexdb/src/funcptr_arrays.rs b/crates/sylpheed-xexdb/src/funcptr_arrays.rs index b6f9e7a3..ab3b54c7 100644 --- a/crates/sylpheed-xexdb/src/funcptr_arrays.rs +++ b/crates/sylpheed-xexdb/src/funcptr_arrays.rs @@ -75,16 +75,23 @@ pub fn analyze( // Scan only .rdata for dispatch tables — .data has too many false // positives from struct fields aliasing function VAs. for section in sections { - if section.name != ".rdata" { continue; } + if section.name != ".rdata" { + continue; + } let raw_start = section.virtual_address as usize; let raw_end = (section.virtual_address + section.virtual_size) as usize; - if raw_end > pe.len() { continue; } + if raw_end > pe.len() { + continue; + } let bytes = &pe[raw_start..raw_end.min(pe.len())]; let va_base = image_base + section.virtual_address; let mut i = 0usize; while i + 8 <= bytes.len() { - if !i.is_multiple_of(4) { i += 1; continue; } + if !i.is_multiple_of(4) { + i += 1; + continue; + } let mut entries: Vec = Vec::new(); let mut j = i; while j + 4 <= bytes.len() { @@ -120,7 +127,10 @@ pub fn analyze( let n_si = out.iter().filter(|a| a.kind == "static_init").count(); metrics::histogram!("analysis.phase_ms", "phase" => "funcptr_arrays").record(elapsed_ms); tracing::info!( - total = out.len(), vtable = n_vt, dispatch_table = n_dt, static_init = n_si, + total = out.len(), + vtable = n_vt, + dispatch_table = n_dt, + static_init = n_si, elapsed_ms, "function-pointer array scan complete", ); @@ -156,14 +166,18 @@ fn classify_run(image_base: u32, entries: &[u32], pe: &[u8]) -> &'static str { /// `mfspr r12, LR` immediately followed by `stwu r1, -N(r1)` with `N ≤ 0x80`. fn is_ctor_like(pe: &[u8], image_base: u32, fn_va: u32) -> bool { let off = fn_va.wrapping_sub(image_base) as usize; - if off + 8 > pe.len() { return false; } + if off + 8 > pe.len() { + return false; + } let i0 = u32::from_be_bytes([pe[off], pe[off + 1], pe[off + 2], pe[off + 3]]); let i1 = u32::from_be_bytes([pe[off + 4], pe[off + 5], pe[off + 6], pe[off + 7]]); // i0: mfspr rD, LR — opcode 31, xo 339, spr 8. let op0 = i0 >> 26; let xo0 = (i0 >> 1) & 0x3FF; let spr0 = (((i0 >> 11) & 0x1F) << 5) | ((i0 >> 16) & 0x1F); - if !(op0 == 31 && xo0 == 339 && spr0 == 8) { return false; } + if !(op0 == 31 && xo0 == 339 && spr0 == 8) { + return false; + } // i1 must be stwu r1, -N(r1) with N ≤ 0x80, OR a `bl __savegprlr_*` // followed eventually by stwu (full prologue). Allow either. let op1 = i1 >> 26; @@ -217,7 +231,9 @@ mod tests { let sections = vec![mk_section(".rdata", rdata_va, 0x100)]; let mut starts = BTreeSet::new(); - for &p in &pcs { starts.insert(p); } + for &p in &pcs { + starts.insert(p); + } let arrs = analyze(&pe, image_base, §ions, &starts, &[]); assert_eq!(arrs.len(), 1); @@ -231,13 +247,19 @@ mod tests { let rdata_va = 0x1000u32; let mut pe = vec![0u8; 0x4000]; - let pcs = [image_base + 0x2000, image_base + 0x2010, image_base + 0x2020]; + let pcs = [ + image_base + 0x2000, + image_base + 0x2010, + image_base + 0x2020, + ]; for (i, p) in pcs.iter().enumerate() { write_be_u32(&mut pe, rdata_va as usize + i * 4, *p); } let sections = vec![mk_section(".rdata", rdata_va, 0x100)]; let mut starts = BTreeSet::new(); - for &p in &pcs { starts.insert(p); } + for &p in &pcs { + starts.insert(p); + } let vt = Vtable { address: image_base + rdata_va, diff --git a/crates/sylpheed-xexdb/src/ind_dispatch_typed.rs b/crates/sylpheed-xexdb/src/ind_dispatch_typed.rs index d19912cf..d05943de 100644 --- a/crates/sylpheed-xexdb/src/ind_dispatch_typed.rs +++ b/crates/sylpheed-xexdb/src/ind_dispatch_typed.rs @@ -112,12 +112,12 @@ pub struct VptrWrite { pub writer_function: u32, } -const OP_ADDI: u32 = 14; +const OP_ADDI: u32 = 14; const OP_ADDIS: u32 = 15; const OP_BCCTR: u32 = 19; -const OP_LWZ: u32 = 32; -const OP_ORI: u32 = 24; -const OP_STW: u32 = 36; +const OP_LWZ: u32 = 32; +const OP_ORI: u32 = 24; +const OP_STW: u32 = 36; const OP_X_FORM: u32 = 31; /// Run the full M5.5 analysis. @@ -133,26 +133,36 @@ pub fn analyze( let started = std::time::Instant::now(); let vtable_addrs: BTreeSet = vtables.iter().map(|v| v.address).collect(); - let vtable_by_addr: BTreeMap = - vtables.iter().map(|v| (v.address, v)).collect(); + let vtable_by_addr: BTreeMap = vtables.iter().map(|v| (v.address, v)).collect(); let block_boundaries: HashSet = labels.keys().copied().collect(); // Phase 1: scan for vptr writes. let vptr_writes = scan_vptr_writes( - pe, image_base, func_analysis, &vtable_addrs, &block_boundaries, + pe, + image_base, + func_analysis, + &vtable_addrs, + &block_boundaries, ); // Phase 2: invert by offset. let mut vtables_by_offset: HashMap> = HashMap::new(); for w in &vptr_writes { - vtables_by_offset.entry(w.vptr_offset).or_default().insert(w.vtable_addr); + vtables_by_offset + .entry(w.vptr_offset) + .or_default() + .insert(w.vtable_addr); } // Phase 3 + 4: scan dispatches and emit edges. let mut dispatches = scan_dispatches_and_resolve( - pe, image_base, func_analysis, &block_boundaries, - &vtables_by_offset, &vtable_by_addr, + pe, + image_base, + func_analysis, + &block_boundaries, + &vtables_by_offset, + &vtable_by_addr, ); // Drop the per-candidate lists for sites the analysis could not narrow. @@ -180,7 +190,10 @@ pub fn analyze( } let elapsed_ms = started.elapsed().as_millis() as f64; - let single_candidate = dispatches.iter().filter(|d| d.total_candidates == 1).count(); + let single_candidate = dispatches + .iter() + .filter(|d| d.total_candidates == 1) + .count(); let multi_candidate = dispatches.len() - single_candidate; let total_edges: usize = dispatches.iter().map(|d| d.method_pcs.len()).sum(); metrics::histogram!("analysis.phase_ms", "phase" => "ind_dispatch_typed").record(elapsed_ms); @@ -197,13 +210,23 @@ pub fn analyze( "M5.5 typed indirect-dispatch scan complete", ); - TypedIndirectResult { dispatches, vptr_writes } + TypedIndirectResult { + dispatches, + vptr_writes, + } } fn read_instr(pe: &[u8], image_base: u32, addr: u32) -> Option { let off = addr.wrapping_sub(image_base) as usize; - if off + 4 > pe.len() { return None; } - Some(u32::from_be_bytes([pe[off], pe[off + 1], pe[off + 2], pe[off + 3]])) + if off + 4 > pe.len() { + return None; + } + Some(u32::from_be_bytes([ + pe[off], + pe[off + 1], + pe[off + 2], + pe[off + 3], + ])) } /// Phase 1 — find every `stw rA, off(rB)` where the lis+addi-tracked @@ -217,14 +240,18 @@ fn scan_vptr_writes( ) -> Vec { let mut writes: Vec = Vec::new(); for (&fn_start, fi) in &func_analysis.functions { - if fi.is_saverestore { continue; } + if fi.is_saverestore { + continue; + } let mut reg: [Option; 32] = [None; 32]; let mut pc = fn_start; while pc < fi.end { if pc != fn_start && block_boundaries.contains(&pc) { reg = [None; 32]; } - let Some(instr) = read_instr(pe, image_base, pc) else { break }; + let Some(instr) = read_instr(pe, image_base, pc) else { + break; + }; let op = instr >> 26; let rd = ((instr >> 21) & 0x1F) as usize; let ra = ((instr >> 16) & 0x1F) as usize; @@ -264,19 +291,23 @@ fn scan_vptr_writes( 32..=35 | 40..=43 | 48..=51 => reg[rd] = None, OP_X_FORM => { let xo = (instr >> 1) & 0x3FF; - if xo != 444 && xo != 467 { reg[rd] = None; } + if xo != 444 && xo != 467 { + reg[rd] = None; + } } 18 => { // `bl` (LK=1) clobbers volatile r0..r12 + ctr. Plain // `b` makes the next instruction unreachable; the // label-based reset handles join points. if (instr & 1) != 0 { - for r in 0..=12 { reg[r] = None; } + for r in 0..=12 { + reg[r] = None; + } } } - 16 => { - if (instr & 1) != 0 { - for r in 0..=12 { reg[r] = None; } + 16 if (instr & 1) != 0 => { + for r in 0..=12 { + reg[r] = None; } } _ => {} @@ -302,18 +333,29 @@ fn scan_dispatches_and_resolve( ) -> Vec { let mut out: Vec = Vec::new(); for (&fn_start, fi) in &func_analysis.functions { - if fi.is_saverestore { continue; } + if fi.is_saverestore { + continue; + } let mut pc = fn_start; while pc < fi.end { - let Some(instr) = read_instr(pe, image_base, pc) else { break }; + let Some(instr) = read_instr(pe, image_base, pc) else { + break; + }; let op = instr >> 26; if op == OP_BCCTR { let xo = (instr >> 1) & 0x3FF; let lk = (instr & 1) != 0; - if xo == 528 && lk + if xo == 528 + && lk && let Some(d) = try_resolve_dispatch_site( - pe, image_base, fn_start, fi.end, pc, - block_boundaries, vtables_by_offset, vtable_by_addr, + pe, + image_base, + fn_start, + fi.end, + pc, + block_boundaries, + vtables_by_offset, + vtable_by_addr, ) { out.push(d); @@ -346,9 +388,15 @@ fn try_resolve_dispatch_site( let mut mtctr_pc: Option = None; for i in 1..=LOOKBACK { let p = bcctrl_pc.wrapping_sub(i * 4); - if p < fn_start { break; } - if block_boundaries.contains(&p) { break; } - let Some(instr) = read_instr(pe, image_base, p) else { break }; + if p < fn_start { + break; + } + if block_boundaries.contains(&p) { + break; + } + let Some(instr) = read_instr(pe, image_base, p) else { + break; + }; let op = instr >> 26; if op == OP_X_FORM { let xo = (instr >> 1) & 0x3FF; @@ -371,17 +419,27 @@ fn try_resolve_dispatch_site( let mut fn_lwz_pc: Option = None; for i in 1..=LOOKBACK { let p = mtctr_pc.wrapping_sub(i * 4); - if p < fn_start { break; } - if block_boundaries.contains(&p) { break; } - let Some(instr) = read_instr(pe, image_base, p) else { break }; + if p < fn_start { + break; + } + if block_boundaries.contains(&p) { + break; + } + let Some(instr) = read_instr(pe, image_base, p) else { + break; + }; let op = instr >> 26; let rd = ((instr >> 21) & 0x1F) as usize; if op == OP_LWZ { if rd == mtctr_rs { let ra = ((instr >> 16) & 0x1F) as usize; - if ra == 0 { return None; } + if ra == 0 { + return None; + } let off = ((instr & 0xFFFF) as i16) as i32; - if off < 0 || (off % 4) != 0 { return None; } + if off < 0 || (off % 4) != 0 { + return None; + } slot = Some((off as u32) / 4); vt_reg = Some(ra); fn_lwz_pc = Some(p); @@ -402,14 +460,22 @@ fn try_resolve_dispatch_site( let mut vptr_off: Option = None; for i in 1..=LOOKBACK { let p = fn_lwz_pc.wrapping_sub(i * 4); - if p < fn_start { break; } - if block_boundaries.contains(&p) { break; } - let Some(instr) = read_instr(pe, image_base, p) else { break }; + if p < fn_start { + break; + } + if block_boundaries.contains(&p) { + break; + } + let Some(instr) = read_instr(pe, image_base, p) else { + break; + }; let op = instr >> 26; let rd = ((instr >> 21) & 0x1F) as usize; if op == OP_LWZ && rd == vt_reg { let ra = ((instr >> 16) & 0x1F) as usize; - if ra == 0 { return None; } + if ra == 0 { + return None; + } let off = ((instr & 0xFFFF) as i16) as i32; // Negative offsets are valid in C++ (multiple inheritance casts // can produce them in some ABIs); reinterpret as u32 wrap. @@ -435,7 +501,9 @@ fn try_resolve_dispatch_site( method_pcs.push(method_pc); } } - if method_pcs.is_empty() { return None; } + if method_pcs.is_empty() { + return None; + } let total_candidates = candidate_vtables.len(); Some(TypedDispatch { @@ -460,13 +528,16 @@ fn writes_reg(instr: u32, r: u32) -> bool { // Most arithmetic / load opcodes use bits 21..25 = rD/rT. 14 | 15 | 32..=43 | 46 | 48..=51 => rd == r, // ori/oris/xor/etc. opcodes 24..29 — rA in bits 16..20 is the dest. - 24 | 25 | 26 | 27 | 28 | 29 => ((instr >> 16) & 0x1F) == r, + 24..=29 => ((instr >> 16) & 0x1F) == r, // X-form: most write rD; some write rA. Check both, conservatively. OP_X_FORM => { let xo = (instr >> 1) & 0x3FF; // Logical X-form (and/or/xor/etc.): rA is the dest. // Logical X-form ops (and/or/xor/etc.) write rA, not rD. - if matches!(xo, 26 | 28 | 60 | 124 | 284 | 316 | 444 | 476 | 536 | 539 | 922 | 954) { + if matches!( + xo, + 26 | 28 | 60 | 124 | 284 | 316 | 444 | 476 | 536 | 539 | 922 | 954 + ) { ((instr >> 16) & 0x1F) == r } else { rd == r @@ -496,19 +567,27 @@ mod tests { fn mk_func_analysis(start: u32, len: u32) -> FuncAnalysis { let mut functions: BTreeMap = BTreeMap::new(); - functions.insert(start, FuncInfo { + functions.insert( start, - end: start + len, - frame_size: 0, - saved_gprs: 0, - is_leaf: false, - is_saverestore: false, - pdata_validated: false, - pdata_length: None, - pdata_prolog_length: None, - has_eh: false, - }); - FuncAnalysis { functions, save_gpr_base: None, restore_gpr_base: None, pdata_entries: Vec::new() } + FuncInfo { + start, + end: start + len, + frame_size: 0, + saved_gprs: 0, + is_leaf: false, + is_saverestore: false, + pdata_validated: false, + pdata_length: None, + pdata_prolog_length: None, + has_eh: false, + }, + ); + FuncAnalysis { + functions, + save_gpr_base: None, + restore_gpr_base: None, + pdata_entries: Vec::new(), + } } fn write_be(pe: &mut [u8], at: usize, v: u32) { @@ -519,7 +598,7 @@ mod tests { fn enc_vptr_write(pe: &mut [u8], at: usize, vt: u32, write_off: i16, dest_reg: u32) { let hi = (vt >> 16) as u16; let lo = (vt & 0xFFFF) as i16; - let lis = (15u32 << 26) | (3 << 21) | 0 << 16 | (hi as u32); + let lis = ((15u32 << 26) | (3 << 21)) | (hi as u32); let addi = (14u32 << 26) | (3 << 21) | (3 << 16) | ((lo as u16) as u32); let stw = (36u32 << 26) | (3 << 21) | (dest_reg << 16) | ((write_off as u16) as u32); write_be(pe, at, lis); @@ -560,11 +639,21 @@ mod tests { // Both functions in func_analysis (synthesise). let mut fa = mk_func_analysis(ctor_pc, 0x40); - fa.functions.insert(disp_pc, FuncInfo { - start: disp_pc, end: disp_pc + 0x40, frame_size: 0, saved_gprs: 0, - is_leaf: false, is_saverestore: false, - pdata_validated: false, pdata_length: None, pdata_prolog_length: None, has_eh: false, - }); + fa.functions.insert( + disp_pc, + FuncInfo { + start: disp_pc, + end: disp_pc + 0x40, + frame_size: 0, + saved_gprs: 0, + is_leaf: false, + is_saverestore: false, + pdata_validated: false, + pdata_length: None, + pdata_prolog_length: None, + has_eh: false, + }, + ); let vt = mk_vtable(0x82010000, vec![0xAA, 0xBB, 0xCC, 0xDD]); let labels: HashMap = HashMap::new(); @@ -585,9 +674,14 @@ mod tests { /// Two classes installing different vtables at offset 0, and one dispatch /// at slot 1 that therefore matches both. - fn multi_candidate_fixture(image_base: u32) - -> (Vec, FuncAnalysis, Vec, HashMap) - { + fn multi_candidate_fixture( + image_base: u32, + ) -> ( + Vec, + FuncAnalysis, + Vec, + HashMap, + ) { let mut pe = vec![0u8; 0x4000]; // Two ctors, each writing a different vtable at offset 0. @@ -601,16 +695,36 @@ mod tests { enc_dispatch(&mut pe, (disp - image_base) as usize, 0, 1); let mut fa = mk_func_analysis(ctor_a, 0x40); - fa.functions.insert(ctor_b, FuncInfo { - start: ctor_b, end: ctor_b + 0x40, frame_size: 0, saved_gprs: 0, - is_leaf: false, is_saverestore: false, - pdata_validated: false, pdata_length: None, pdata_prolog_length: None, has_eh: false, - }); - fa.functions.insert(disp, FuncInfo { - start: disp, end: disp + 0x40, frame_size: 0, saved_gprs: 0, - is_leaf: false, is_saverestore: false, - pdata_validated: false, pdata_length: None, pdata_prolog_length: None, has_eh: false, - }); + fa.functions.insert( + ctor_b, + FuncInfo { + start: ctor_b, + end: ctor_b + 0x40, + frame_size: 0, + saved_gprs: 0, + is_leaf: false, + is_saverestore: false, + pdata_validated: false, + pdata_length: None, + pdata_prolog_length: None, + has_eh: false, + }, + ); + fa.functions.insert( + disp, + FuncInfo { + start: disp, + end: disp + 0x40, + frame_size: 0, + saved_gprs: 0, + is_leaf: false, + is_saverestore: false, + pdata_validated: false, + pdata_length: None, + pdata_prolog_length: None, + has_eh: false, + }, + ); let vts = vec![ mk_vtable(0x82010000, vec![0x11, 0x22, 0x33, 0x44]), @@ -647,11 +761,21 @@ mod tests { enc_dispatch(&mut pe, (disp - image_base) as usize, 0, 10); let mut fa = mk_func_analysis(ctor, 0x40); - fa.functions.insert(disp, FuncInfo { - start: disp, end: disp + 0x40, frame_size: 0, saved_gprs: 0, - is_leaf: false, is_saverestore: false, - pdata_validated: false, pdata_length: None, pdata_prolog_length: None, has_eh: false, - }); + fa.functions.insert( + disp, + FuncInfo { + start: disp, + end: disp + 0x40, + frame_size: 0, + saved_gprs: 0, + is_leaf: false, + is_saverestore: false, + pdata_validated: false, + pdata_length: None, + pdata_prolog_length: None, + has_eh: false, + }, + ); let vt = mk_vtable(0x82010000, vec![0x11, 0x22, 0x33, 0x44]); let labels: HashMap = HashMap::new(); @@ -673,11 +797,21 @@ mod tests { enc_dispatch(&mut pe, (disp - image_base) as usize, 8, 1); let mut fa = mk_func_analysis(ctor, 0x40); - fa.functions.insert(disp, FuncInfo { - start: disp, end: disp + 0x40, frame_size: 0, saved_gprs: 0, - is_leaf: false, is_saverestore: false, - pdata_validated: false, pdata_length: None, pdata_prolog_length: None, has_eh: false, - }); + fa.functions.insert( + disp, + FuncInfo { + start: disp, + end: disp + 0x40, + frame_size: 0, + saved_gprs: 0, + is_leaf: false, + is_saverestore: false, + pdata_validated: false, + pdata_length: None, + pdata_prolog_length: None, + has_eh: false, + }, + ); let vt = mk_vtable(0x82010000, vec![0x11, 0x22, 0x33, 0x44]); let labels: HashMap = HashMap::new(); @@ -707,5 +841,4 @@ mod tests { assert!(d.method_pcs.is_empty(), "no speculative edges"); assert!(d.candidate_vtables.is_empty()); } - } diff --git a/crates/sylpheed-xexdb/src/indirect.rs b/crates/sylpheed-xexdb/src/indirect.rs index 970156a6..24904b9b 100644 --- a/crates/sylpheed-xexdb/src/indirect.rs +++ b/crates/sylpheed-xexdb/src/indirect.rs @@ -57,12 +57,12 @@ enum RegVal { }, } -const OP_ADDI: u32 = 14; +const OP_ADDI: u32 = 14; const OP_ADDIS: u32 = 15; -const OP_BCCTR: u32 = 19; // also covers blr — distinguish via XO -const OP_LWZ: u32 = 32; -const OP_ORI: u32 = 24; -const OP_X_FORM: u32 = 31; // mtspr / mr / etc. +const OP_BCCTR: u32 = 19; // also covers blr — distinguish via XO +const OP_LWZ: u32 = 32; +const OP_ORI: u32 = 24; +const OP_X_FORM: u32 = 31; // mtspr / mr / etc. /// Run the static indirect-dispatch scan. Returns one edge per resolvable /// `bcctrl` site. @@ -77,8 +77,7 @@ pub fn analyze( let started = std::time::Instant::now(); // Index vtables by their start VA so the lwz handler can decide // whether a given Const(addr) is "really" a vtable. - let vtable_by_addr: BTreeMap = - vtables.iter().map(|v| (v.address, v)).collect(); + let vtable_by_addr: BTreeMap = vtables.iter().map(|v| (v.address, v)).collect(); // Set of all "label"-bearing PCs in the analyzed binary. We treat each // label as a basic-block boundary (anything `loc_*` is a jump target, @@ -91,7 +90,9 @@ pub fn analyze( let mut edges: Vec = Vec::new(); for (&fn_start, fi) in &func_analysis.functions { - if fi.is_saverestore { continue; } + if fi.is_saverestore { + continue; + } let mut reg: [Option; 32] = [None; 32]; let mut ctr: Option = None; let mut pc = fn_start; @@ -162,7 +163,9 @@ pub fn analyze( let resolved = resolve_vtable_slot(target, &vtable_by_addr) .or_else(|| resolve_vtable_slot_via_off(base, simm, &vtable_by_addr)); reg[rd] = resolved.map(|(vt, slot, pc)| RegVal::MethodPtr { - vtable_addr: vt, slot, method_pc: pc, + vtable_addr: vt, + slot, + method_pc: pc, }); } else { reg[rd] = None; @@ -201,7 +204,11 @@ pub fn analyze( if xo == 528 { let lk = (instr & 1) != 0; if lk - && let Some(RegVal::MethodPtr { vtable_addr, slot, method_pc }) = ctr + && let Some(RegVal::MethodPtr { + vtable_addr, + slot, + method_pc, + }) = ctr { edges.push(IndirectEdge { source: pc, @@ -227,7 +234,9 @@ pub fn analyze( 18 => { let lk = (instr & 1) != 0; if lk { - for r in 0..=12 { reg[r] = None; } + for r in 0..=12 { + reg[r] = None; + } ctr = None; } // LK=0 (`b`) makes fall-through unreachable; nothing to do — @@ -239,7 +248,9 @@ pub fn analyze( 16 => { let lk = (instr & 1) != 0; if lk { - for r in 0..=12 { reg[r] = None; } + for r in 0..=12 { + reg[r] = None; + } ctr = None; } } @@ -274,8 +285,15 @@ pub fn analyze( fn read_instr(pe: &[u8], image_base: u32, addr: u32) -> Option { let off = addr.wrapping_sub(image_base) as usize; - if off + 4 > pe.len() { return None; } - Some(u32::from_be_bytes([pe[off], pe[off + 1], pe[off + 2], pe[off + 3]])) + if off + 4 > pe.len() { + return None; + } + Some(u32::from_be_bytes([ + pe[off], + pe[off + 1], + pe[off + 2], + pe[off + 3], + ])) } /// `target = base + simm` where `target` is an exact vtable head (rare, @@ -303,11 +321,17 @@ fn resolve_vtable_slot( ) -> Option<(u32, u32, u32)> { // BTreeMap range search for the largest key ≤ target. let (&vt_addr, vt) = vtable_by_addr.range(..=target).next_back()?; - if target < vt_addr { return None; } + if target < vt_addr { + return None; + } let off = target - vt_addr; - if !off.is_multiple_of(4) { return None; } + if !off.is_multiple_of(4) { + return None; + } let slot = off / 4; - if slot >= vt.length { return None; } + if slot >= vt.length { + return None; + } let method_pc = *vt.methods.get(slot as usize)?; Some((vt_addr, slot, method_pc)) } @@ -339,14 +363,14 @@ mod tests { fn encode_pattern(buf: &mut [u8], offset: usize, vtable_addr: u32, slot_off: i32) { let hi = (vtable_addr >> 16) as u16; let lo = (vtable_addr & 0xFFFF) as i16; - let lis = (15u32 << 26) | (3 << 21) | (0 << 16) | (hi as u32); + let lis = ((15u32 << 26) | (3 << 21)) | (hi as u32); // addi r3, r3, lo (signed) — note: addi is treated as signed let addi = (14u32 << 26) | (3 << 21) | (3 << 16) | ((lo as u16) as u32); let lwz = (32u32 << 26) | (4 << 21) | (3 << 16) | ((slot_off as u16) as u32); // mtctr r4 = mtspr CTR(=9), r4. SPR_low (=9) → Rust bits 16-20; // SPR_high (=0) → Rust bits 11-15. Rc bit 0. - let mtctr = (31u32 << 26) | (4 << 21) | (9 << 16) | (0 << 11) | (467 << 1); - let bcctrl = (19u32 << 26) | (20 << 21) | (528 << 1) | 1; // bcctrl 20, 0 + let mtctr = ((31u32 << 26) | (4 << 21) | (9 << 16)) | (467 << 1); + let bcctrl = (19u32 << 26) | (20 << 21) | (528 << 1) | 1; // bcctrl 20, 0 let words = [lis, addi, lwz, mtctr, bcctrl]; for (i, w) in words.iter().enumerate() { buf[offset + i * 4..offset + i * 4 + 4].copy_from_slice(&w.to_be_bytes()); @@ -365,18 +389,21 @@ mod tests { encode_pattern(&mut pe, text_va as usize, vtable_addr, 8); // slot 2 let mut functions: BTreeMap = BTreeMap::new(); - functions.insert(pc_start, FuncInfo { - start: pc_start, - end: pc_start + 5 * 4, - frame_size: 0, - saved_gprs: 0, - is_leaf: false, - is_saverestore: false, - pdata_validated: false, - pdata_length: None, - pdata_prolog_length: None, - has_eh: false, - }); + functions.insert( + pc_start, + FuncInfo { + start: pc_start, + end: pc_start + 5 * 4, + frame_size: 0, + saved_gprs: 0, + is_leaf: false, + is_saverestore: false, + pdata_validated: false, + pdata_length: None, + pdata_prolog_length: None, + has_eh: false, + }, + ); let func_analysis = FuncAnalysis { functions, save_gpr_base: None, @@ -407,18 +434,21 @@ mod tests { encode_pattern(&mut pe, text_va as usize, vtable_addr, 48); let mut functions: BTreeMap = BTreeMap::new(); - functions.insert(pc_start, FuncInfo { - start: pc_start, - end: pc_start + 5 * 4, - frame_size: 0, - saved_gprs: 0, - is_leaf: false, - is_saverestore: false, - pdata_validated: false, - pdata_length: None, - pdata_prolog_length: None, - has_eh: false, - }); + functions.insert( + pc_start, + FuncInfo { + start: pc_start, + end: pc_start + 5 * 4, + frame_size: 0, + saved_gprs: 0, + is_leaf: false, + is_saverestore: false, + pdata_validated: false, + pdata_length: None, + pdata_prolog_length: None, + has_eh: false, + }, + ); let func_analysis = FuncAnalysis { functions, save_gpr_base: None, @@ -443,18 +473,21 @@ mod tests { encode_pattern(&mut pe, text_va as usize, vtable_addr, 0); let mut functions: BTreeMap = BTreeMap::new(); - functions.insert(pc_start, FuncInfo { - start: pc_start, - end: pc_start + 5 * 4, - frame_size: 0, - saved_gprs: 0, - is_leaf: false, - is_saverestore: false, - pdata_validated: false, - pdata_length: None, - pdata_prolog_length: None, - has_eh: false, - }); + functions.insert( + pc_start, + FuncInfo { + start: pc_start, + end: pc_start + 5 * 4, + frame_size: 0, + saved_gprs: 0, + is_leaf: false, + is_saverestore: false, + pdata_validated: false, + pdata_length: None, + pdata_prolog_length: None, + has_eh: false, + }, + ); let func_analysis = FuncAnalysis { functions, save_gpr_base: None, @@ -469,6 +502,10 @@ mod tests { labels.insert(pc_start + 8, "loc_mid".to_string()); let edges = analyze(&pe, image_base, &func_analysis, &vtables, &labels); - assert_eq!(edges.len(), 0, "label in middle of pattern must kill register state"); + assert_eq!( + edges.len(), + 0, + "label in middle of pattern must kill register state" + ); } } diff --git a/crates/sylpheed-xexdb/src/jumptables.rs b/crates/sylpheed-xexdb/src/jumptables.rs index a9e3d381..93453db4 100644 --- a/crates/sylpheed-xexdb/src/jumptables.rs +++ b/crates/sylpheed-xexdb/src/jumptables.rs @@ -135,17 +135,33 @@ impl JumpTable { const BCTR: u32 = 0x4E80_0420; -fn op(i: u32) -> u32 { i >> 26 } -fn rt(i: u32) -> usize { ((i >> 21) & 0x1F) as usize } -fn ra(i: u32) -> usize { ((i >> 16) & 0x1F) as usize } -fn rb(i: u32) -> usize { ((i >> 11) & 0x1F) as usize } -fn xo(i: u32) -> u32 { (i >> 1) & 0x3FF } -fn simm(i: u32) -> i32 { ((i & 0xFFFF) as i16) as i32 } -fn uimm(i: u32) -> u32 { i & 0xFFFF } +fn op(i: u32) -> u32 { + i >> 26 +} +fn rt(i: u32) -> usize { + ((i >> 21) & 0x1F) as usize +} +fn ra(i: u32) -> usize { + ((i >> 16) & 0x1F) as usize +} +fn rb(i: u32) -> usize { + ((i >> 11) & 0x1F) as usize +} +fn xo(i: u32) -> u32 { + (i >> 1) & 0x3FF +} +fn simm(i: u32) -> i32 { + ((i & 0xFFFF) as i16) as i32 +} +fn uimm(i: u32) -> u32 { + i & 0xFFFF +} /// `mtctr rS` — `mtspr` (op 31, xo 467) with the split SPR field naming CTR (9). fn is_mtctr(i: u32) -> bool { - if op(i) != 31 || xo(i) != 467 { return false; } + if op(i) != 31 || xo(i) != 467 { + return false; + } let spr_field = (i >> 11) & 0x3FF; (((spr_field & 0x1F) << 5) | (spr_field >> 5)) == 9 } @@ -159,13 +175,13 @@ fn is_mtctr(i: u32) -> bool { fn op31_dest(i: u32) -> Option { // Logical / shift / sign-extend X-forms: destination is `rA` (bits 16..20). const WRITES_RA: &[u32] = &[ - 24, 26, 27, 28, 58, 60, 124, 284, 316, 412, 444, 476, - 536, 539, 792, 794, 824, 826, 827, 922, 954, 986, + 24, 26, 27, 28, 58, 60, 124, 284, 316, 412, 444, 476, 536, 539, 792, 794, 824, 826, 827, + 922, 954, 986, ]; // Stores, compares, traps, cache/sync ops and `mtspr`/`mtcrf`: no GPR write. const NO_GPR: &[u32] = &[ - 0, 4, 32, 68, // cmp, tw, cmpl, td - 150, 151, 215, 407, 662, 918, 660, 727, 231, // stwcx./stwx/stbx/sthx/stfsx/stfdx/… + 0, 4, 32, 68, // cmp, tw, cmpl, td + 150, 151, 215, 407, 662, 918, 660, 727, 231, // stwcx./stwx/stbx/sthx/stfsx/stfdx/… 144, 467, 512, 598, 854, 982, 1014, 86, 470, 54, // mtcrf/mtspr/mcrxr/sync/dcb*/icbi ]; // Store-*update* forms write back into `rA`. @@ -173,8 +189,12 @@ fn op31_dest(i: u32) -> Option { return Some(ra(i)); } let x = xo(i); - if NO_GPR.contains(&x) { return None; } - if WRITES_RA.contains(&x) { return Some(ra(i)); } + if NO_GPR.contains(&x) { + return None; + } + if WRITES_RA.contains(&x) { + return Some(ra(i)); + } Some(rt(i)) } @@ -204,13 +224,25 @@ pub fn analyze_with_stats( let code_ranges: Vec<(u32, u32)> = sections .iter() .filter(|s| s.is_code()) - .map(|s| (image_base + s.virtual_address, image_base + s.virtual_address + s.virtual_size)) + .map(|s| { + ( + image_base + s.virtual_address, + image_base + s.virtual_address + s.virtual_size, + ) + }) .collect(); let read = |va: u32| -> Option { let off = va.wrapping_sub(image_base) as usize; - if off.checked_add(4)? > pe.len() { return None; } - Some(u32::from_be_bytes([pe[off], pe[off + 1], pe[off + 2], pe[off + 3]])) + if off.checked_add(4)? > pe.len() { + return None; + } + Some(u32::from_be_bytes([ + pe[off], + pe[off + 1], + pe[off + 2], + pe[off + 3], + ])) }; let in_code = |va: u32| code_ranges.iter().any(|&(s, e)| va >= s && va < e); @@ -285,11 +317,11 @@ fn recover_at( // Straight-line constant propagation over [window_start, bctr_pc). let mut regs: [Option; 32] = [None; 32]; - let mut lwzx_dest: Option = None; // rT of the last lwzx + let mut lwzx_dest: Option = None; // rT of the last lwzx let mut lwzx_regs: Option<(Option, Option)> = None; - let mut lwzx_index_tainted = false; // did the index come from the lbzx? + let mut lwzx_index_tainted = false; // did the index come from the lbzx? let mut lbzx_regs: Option<(Option, Option)> = None; - let mut ctr_src: Option = None; // rS of the last mtctr + let mut ctr_src: Option = None; // rS of the last mtctr let mut bound: Option = None; // Taint: "this register holds a value derived from the byte the `lbzx` // read". Only a register carrying that taint may serve as the jump table's @@ -300,7 +332,7 @@ fn recover_at( let mut pc = window_start; while pc < bctr_pc { - let Some(i) = read(pc) else { return None }; + let i = read(pc)?; match op(i) { // addis rT, rA, SIMM (lis when rA == 0) 15 => { @@ -319,14 +351,16 @@ fn recover_at( 10 | 11 => bound = Some(uimm(i)), 31 => { match xo(i) { - 23 => { // lwzx rT, rA, rB — the table read + 23 => { + // lwzx rT, rA, rB — the table read lwzx_dest = Some(rt(i)); lwzx_regs = Some((regs[ra(i)], regs[rb(i)])); lwzx_index_tainted = from_lbzx[ra(i)] || from_lbzx[rb(i)]; regs[rt(i)] = None; from_lbzx[rt(i)] = false; } - 87 => { // lbzx rT, rA, rB — the sparse index-map read + 87 => { + // lbzx rT, rA, rB — the sparse index-map read lbzx_regs = Some((regs[ra(i)], regs[rb(i)])); regs[rt(i)] = None; from_lbzx = [false; 32]; @@ -363,14 +397,23 @@ fn recover_at( } // D/DS-form GPR loads write rT; the update forms also write rA. 32 | 34 | 40 | 42 => regs[rt(i)] = None, - 33 | 35 | 41 | 43 => { regs[rt(i)] = None; regs[ra(i)] = None; } + 33 | 35 | 41 | 43 => { + regs[rt(i)] = None; + regs[ra(i)] = None; + } // DS-form: bits 30..31 pick ld(0) / ldu(1) / lwa(2). 58 => { regs[rt(i)] = None; - if i & 3 == 1 { regs[ra(i)] = None; } + if i & 3 == 1 { + regs[ra(i)] = None; + } } // lmw loads rT..r31. - 46 => for r in rt(i)..32 { regs[r] = None; }, + 46 => { + for r in rt(i)..32 { + regs[r] = None; + } + } // FP loads touch no GPR — except the update forms, which write rA. // Plain stores write no register at all (their `rT` field is the // *source*), so a tracked base that merely gets spilled survives. @@ -440,7 +483,9 @@ fn recover_at( let word = read(byte_off & !3)?; let slot = (word >> (8 * (3 - (byte_off & 3)))) & 0xFF; let t = read(table_address.wrapping_add(slot * 4))?; - if !valid(t) { break; } + if !valid(t) { + break; + } max_slot = max_slot.max(slot); targets.push(t); } @@ -464,11 +509,18 @@ fn recover_at( } // Dense: read consecutive absolute targets until one leaves the function. - let cap = bound.map(|n| n.saturating_add(1)).unwrap_or(MAX_ENTRIES).min(MAX_ENTRIES); + let cap = bound + .map(|n| n.saturating_add(1)) + .unwrap_or(MAX_ENTRIES) + .min(MAX_ENTRIES); let mut targets = Vec::new(); for i in 0..cap { - let Some(t) = read(table_address.wrapping_add(i * 4)) else { break }; - if !valid(t) { break; } + let Some(t) = read(table_address.wrapping_add(i * 4)) else { + break; + }; + if !valid(t) { + break; + } targets.push(t); } if targets.len() < 2 { @@ -554,19 +606,27 @@ mod tests { fn one_function(start: u32, end: u32) -> FuncAnalysis { let mut functions = BTreeMap::new(); - functions.insert(start, FuncInfo { + functions.insert( start, - end, - frame_size: 0, - saved_gprs: 0, - is_leaf: false, - is_saverestore: false, - pdata_validated: true, - pdata_length: Some(end - start), - pdata_prolog_length: Some(0), - has_eh: false, - }); - FuncAnalysis { functions, save_gpr_base: None, restore_gpr_base: None, pdata_entries: Vec::new() } + FuncInfo { + start, + end, + frame_size: 0, + saved_gprs: 0, + is_leaf: false, + is_saverestore: false, + pdata_validated: true, + pdata_length: Some(end - start), + pdata_prolog_length: Some(0), + has_eh: false, + }, + ); + FuncAnalysis { + functions, + save_gpr_base: None, + restore_gpr_base: None, + pdata_entries: Vec::new(), + } } /// Encode the words of the canonical MSVC dense-switch dispatch, ending at @@ -575,13 +635,13 @@ mod tests { /// / mtctr r0 / bctr /
fn dense_switch(table_va: u32, n_cases: u32) -> Vec { vec![ - 0x2800_0000 | (10 << 16) | (n_cases - 1), // cmplwi r10, N - 0x4181_0000 | 0x20, // bc (bound check, target irrelevant) - 0x3D80_0000 | (table_va >> 16), // lis r12, hi - 0x398C_0000 | (table_va & 0xFFFF), // addi r12, r12, lo - 0x5540_103A, // slwi r0, r10, 2 - 0x7C0C_002E, // lwzx r0, r12, r0 - 0x7C09_03A6, // mtctr r0 + 0x2800_0000 | (10 << 16) | (n_cases - 1), // cmplwi r10, N + 0x4181_0000 | 0x20, // bc (bound check, target irrelevant) + 0x3D80_0000 | (table_va >> 16), // lis r12, hi + 0x398C_0000 | (table_va & 0xFFFF), // addi r12, r12, lo + 0x5540_103A, // slwi r0, r10, 2 + 0x7C0C_002E, // lwzx r0, r12, r0 + 0x7C09_03A6, // mtctr r0 BCTR, ] } @@ -600,7 +660,12 @@ mod tests { let table_va = TEXT_VA + 8 * 4; let mut words = dense_switch(table_va, 4); // Four case bodies, all inside the function. - let cases = [TEXT_VA + 0x40, TEXT_VA + 0x50, TEXT_VA + 0x60, TEXT_VA + 0x70]; + let cases = [ + TEXT_VA + 0x40, + TEXT_VA + 0x50, + TEXT_VA + 0x60, + TEXT_VA + 0x70, + ]; words.extend_from_slice(&cases); let pe = assemble(&words, 0x100); @@ -626,7 +691,9 @@ mod tests { let table_va = TEXT_VA + 8 * 4; let mut words = dense_switch(table_va, 8); words.extend_from_slice(&[ - TEXT_VA + 0x40, TEXT_VA + 0x50, TEXT_VA + 0x60, + TEXT_VA + 0x40, + TEXT_VA + 0x50, + TEXT_VA + 0x60, 0x8300_0000, // far outside TEXT_VA + 0x70, ]); @@ -659,26 +726,31 @@ mod tests { fn recovers_sparse_two_level_switch() { // cmplwi r10,5 / bc / lis+addi r11 = &map / lbzx r0,r11,r10 // / lis+addi r12 = &table / slwi r0,r0,2 / lwzx r0,r12,r0 / mtctr / bctr - let map_va = TEXT_VA + 13 * 4; // 6 bytes, then padding - let table_va = TEXT_VA + 17 * 4; // 3 distinct bodies + let map_va = TEXT_VA + 13 * 4; // 6 bytes, then padding + let table_va = TEXT_VA + 17 * 4; // 3 distinct bodies let words = vec![ - 0x2800_0000 | (10 << 16) | 5, // cmplwi r10, 5 - 0x4181_0000 | 0x20, // bc - 0x3D60_0000 | (map_va >> 16), // lis r11, map@h - 0x396B_0000 | (map_va & 0xFFFF), // addi r11, r11, map@l - 0x7C0B_50AE, // lbzx r0, r11, r10 - 0x3D80_0000 | (table_va >> 16), // lis r12, tab@h - 0x398C_0000 | (table_va & 0xFFFF), // addi r12, r12, tab@l - 0x5400_103A, // slwi r0, r0, 2 - 0x7C0C_002E, // lwzx r0, r12, r0 - 0x7C09_03A6, // mtctr r0 + 0x2800_0000 | (10 << 16) | 5, // cmplwi r10, 5 + 0x4181_0000 | 0x20, // bc + 0x3D60_0000 | (map_va >> 16), // lis r11, map@h + 0x396B_0000 | (map_va & 0xFFFF), // addi r11, r11, map@l + 0x7C0B_50AE, // lbzx r0, r11, r10 + 0x3D80_0000 | (table_va >> 16), // lis r12, tab@h + 0x398C_0000 | (table_va & 0xFFFF), // addi r12, r12, tab@l + 0x5400_103A, // slwi r0, r0, 2 + 0x7C0C_002E, // lwzx r0, r12, r0 + 0x7C09_03A6, // mtctr r0 BCTR, - 0, 0, + 0, + 0, // map[0..6] = 0,1,2,2,1,0 packed big-endian, then padding - 0x0001_0202, 0x0100_0000, - 0, 0, + 0x0001_0202, + 0x0100_0000, + 0, + 0, // table[0..3] - TEXT_VA + 0x80, TEXT_VA + 0x90, TEXT_VA + 0xA0, + TEXT_VA + 0x80, + TEXT_VA + 0x90, + TEXT_VA + 0xA0, ]; let pe = assemble(&words, 0x100); let sections = [text_section(0x100)]; @@ -691,10 +763,17 @@ mod tests { assert_eq!(jt.index_map_address, Some(map_va)); assert_eq!(jt.index_map_count, Some(6)); assert_eq!(jt.table_slots, 3); - assert_eq!(jt.targets, vec![ - TEXT_VA + 0x80, TEXT_VA + 0x90, TEXT_VA + 0xA0, - TEXT_VA + 0xA0, TEXT_VA + 0x90, TEXT_VA + 0x80, - ]); + assert_eq!( + jt.targets, + vec![ + TEXT_VA + 0x80, + TEXT_VA + 0x90, + TEXT_VA + 0xA0, + TEXT_VA + 0xA0, + TEXT_VA + 0x90, + TEXT_VA + 0x80, + ] + ); } /// An unrelated `lbzx` in the window must not be mistaken for a case index @@ -704,19 +783,24 @@ mod tests { fn unrelated_lbzx_does_not_become_an_index_map() { let table_va = TEXT_VA + 11 * 4; let mut words = vec![ - 0x2800_0000 | (10 << 16) | 3, // cmplwi r10, 3 - 0x4181_0000 | 0x20, // bc - 0x3D60_0000 | (TEXT_VA >> 16), // lis r11, text@h (a code constant) - 0x396B_0000 | (TEXT_VA & 0xFFFF), // addi r11, r11, text@l - 0x7CEB_44AE, // lbzx r7, r11, r8 — unrelated byte load - 0x3D80_0000 | (table_va >> 16), // lis r12, tab@h - 0x398C_0000 | (table_va & 0xFFFF), // addi r12, r12, tab@l - 0x5540_103A, // slwi r0, r10, 2 — index is r10, NOT r7 - 0x7C0C_002E, // lwzx r0, r12, r0 - 0x7C09_03A6, // mtctr r0 + 0x2800_0000 | (10 << 16) | 3, // cmplwi r10, 3 + 0x4181_0000 | 0x20, // bc + 0x3D60_0000 | (TEXT_VA >> 16), // lis r11, text@h (a code constant) + 0x396B_0000 | (TEXT_VA & 0xFFFF), // addi r11, r11, text@l + 0x7CEB_44AE, // lbzx r7, r11, r8 — unrelated byte load + 0x3D80_0000 | (table_va >> 16), // lis r12, tab@h + 0x398C_0000 | (table_va & 0xFFFF), // addi r12, r12, tab@l + 0x5540_103A, // slwi r0, r10, 2 — index is r10, NOT r7 + 0x7C0C_002E, // lwzx r0, r12, r0 + 0x7C09_03A6, // mtctr r0 ]; words.push(BCTR); - words.extend_from_slice(&[TEXT_VA + 0x80, TEXT_VA + 0x90, TEXT_VA + 0xA0, TEXT_VA + 0xB0]); + words.extend_from_slice(&[ + TEXT_VA + 0x80, + TEXT_VA + 0x90, + TEXT_VA + 0xA0, + TEXT_VA + 0xB0, + ]); let pe = assemble(&words, 0x100); let sections = [text_section(0x100)]; @@ -731,12 +815,22 @@ mod tests { #[test] fn data_regions_merge_adjacent_tables() { let a = JumpTable { - bctr_pc: 0x8200_1000, function: None, table_address: 0x8200_2000, - entry_count: 4, table_slots: 4, index_map_address: None, - index_map_count: None, bound: None, kind: "direct", + bctr_pc: 0x8200_1000, + function: None, + table_address: 0x8200_2000, + entry_count: 4, + table_slots: 4, + index_map_address: None, + index_map_count: None, + bound: None, + kind: "direct", targets: vec![0; 4], }; - let b = JumpTable { table_address: 0x8200_2010, bctr_pc: 0x8200_1004, ..a.clone() }; + let b = JumpTable { + table_address: 0x8200_2010, + bctr_pc: 0x8200_1004, + ..a.clone() + }; let merged = data_regions(&[a, b]); assert_eq!(merged, vec![(0x8200_2000, 32)]); } @@ -744,9 +838,15 @@ mod tests { #[test] fn data_word_addresses_covers_every_slot() { let jt = JumpTable { - bctr_pc: 0x8200_1000, function: None, table_address: 0x8200_2000, - entry_count: 3, table_slots: 3, index_map_address: Some(0x8200_3000), - index_map_count: Some(5), bound: Some(4), kind: "indexed", + bctr_pc: 0x8200_1000, + function: None, + table_address: 0x8200_2000, + entry_count: 3, + table_slots: 3, + index_map_address: Some(0x8200_3000), + index_map_count: Some(5), + bound: Some(4), + kind: "indexed", targets: vec![0; 5], }; let words = data_word_addresses(&[jt]); diff --git a/crates/sylpheed-xexdb/src/lib.rs b/crates/sylpheed-xexdb/src/lib.rs index 078aae07..a0a3025b 100644 --- a/crates/sylpheed-xexdb/src/lib.rs +++ b/crates/sylpheed-xexdb/src/lib.rs @@ -1,26 +1,46 @@ -pub mod ppc; -pub mod func; -pub mod xref; +// 🔴 THREE LINTS ARE OFF FOR THIS CRATE, WITH REASONS, RATHER THAN SILENTLY. +// +// * `needless_range_loop` — nine sites index `reg[r]` where **`r` is the +// PowerPC register number**. The index is the meaning; an iterator hides +// which GPR a pattern matched, which is the whole content of these passes. +// * `too_many_arguments` — five analysis passes take the image, its base, the +// section table, the function list and several output sinks. Bundling those +// into a struct moves the list rather than shortening it, and this code +// arrived whole from a retired repository: a refactor here would be an +// unreviewed edit dressed as a lint fix. +// * `type_complexity` — one return type in `vtables.rs`, same argument. +// +// Everything else clippy asked for was fixed, including every doc-indent site. +// See `docs/agents/CONSOLIDATION.md` Phase 3. +#![allow( + clippy::needless_range_loop, + clippy::too_many_arguments, + clippy::type_complexity +)] + pub mod db; +pub mod demangle; pub mod disasm; +pub mod eh_scope; pub mod formatter; +pub mod func; +pub mod funcptr_arrays; +pub mod ind_dispatch_typed; +pub mod indirect; +pub mod jumptables; +pub mod lookup; +pub mod ppc; +pub mod rtti; pub mod sinks; pub mod sql_views; -pub mod demangle; -pub mod vtables; -pub mod lookup; -pub mod indirect; -pub mod ind_dispatch_typed; -pub mod strings; -pub mod funcptr_arrays; -pub mod eh_scope; pub mod static_init; +pub mod strings; +pub mod vtables; pub mod xdbf; -pub mod jumptables; -pub mod rtti; +pub mod xref; mod ordinals; -pub use ordinals::resolve_ordinal; -pub use xref::{XrefKind, Xref, XrefMap, resolve_source_label}; -pub use db::{DbWriter, ExecTraceEntry, ImportCallEntry, BranchTraceEntry}; +pub use db::{BranchTraceEntry, DbWriter, ExecTraceEntry, ImportCallEntry}; pub use disasm::{RichDisasmItem, enrich_section}; +pub use ordinals::resolve_ordinal; +pub use xref::{Xref, XrefKind, XrefMap, resolve_source_label}; diff --git a/crates/sylpheed-xexdb/src/lookup.rs b/crates/sylpheed-xexdb/src/lookup.rs index df21e4e6..ec0ed3a4 100644 --- a/crates/sylpheed-xexdb/src/lookup.rs +++ b/crates/sylpheed-xexdb/src/lookup.rs @@ -13,7 +13,7 @@ use std::path::Path; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use duckdb::params; /// Parse one probe token into one or more PCs. @@ -72,7 +72,10 @@ pub fn resolve_probe_token(db_path: Option<&Path>, token: &str) -> Result Option { - if let Some(hex) = token.strip_prefix("0x").or_else(|| token.strip_prefix("0X")) { + if let Some(hex) = token + .strip_prefix("0x") + .or_else(|| token.strip_prefix("0X")) + { return u32::from_str_radix(hex, 16).ok(); } token.parse::().ok() @@ -89,7 +92,9 @@ fn resolve_class_method(conn: &duckdb::Connection, class: &str, method: &str) -> WHERE c.name = ? AND dn.method_name = ?", )?; let pcs: Vec = stmt - .query_map(params![class, method], |r| r.get::<_, i64>(0).map(|x| x as u32))? + .query_map(params![class, method], |r| { + r.get::<_, i64>(0).map(|x| x as u32) + })? .filter_map(|r| r.ok()) .collect(); Ok(pcs) diff --git a/crates/sylpheed-xexdb/src/ppc.rs b/crates/sylpheed-xexdb/src/ppc.rs index 34ff9d80..7d7fb83a 100644 --- a/crates/sylpheed-xexdb/src/ppc.rs +++ b/crates/sylpheed-xexdb/src/ppc.rs @@ -24,5 +24,8 @@ impl Decoded { pub fn disasm(instr: u32, addr: u32) -> Decoded { let d = decode(instr, addr); let t = format(&d); - Decoded { base: t.disasm, ext: t.ext_disasm } + Decoded { + base: t.disasm, + ext: t.ext_disasm, + } } diff --git a/crates/sylpheed-xexdb/src/rtti.rs b/crates/sylpheed-xexdb/src/rtti.rs index 36bfe43d..1aa8f16c 100644 --- a/crates/sylpheed-xexdb/src/rtti.rs +++ b/crates/sylpheed-xexdb/src/rtti.rs @@ -117,8 +117,11 @@ impl RttiResult { /// `vftable[0]` VA → `(demangled class name, subobject offset)`. pub fn vtable_class_names(&self) -> BTreeMap { - let td: BTreeMap = - self.type_descriptors.iter().map(|t| (t.address, t)).collect(); + let td: BTreeMap = self + .type_descriptors + .iter() + .map(|t| (t.address, t)) + .collect(); let mut out = BTreeMap::new(); for col in &self.locators { if let (Some(vt), Some(t)) = (col.vtable_address, td.get(&col.type_descriptor)) { @@ -138,8 +141,15 @@ pub fn analyze(pe: &[u8], image_base: u32, sections: &[PeSection]) -> RttiResult let read = |va: u32| -> Option { let off = va.wrapping_sub(image_base) as usize; - if off.checked_add(4)? > pe.len() { return None; } - Some(u32::from_be_bytes([pe[off], pe[off + 1], pe[off + 2], pe[off + 3]])) + if off.checked_add(4)? > pe.len() { + return None; + } + Some(u32::from_be_bytes([ + pe[off], + pe[off + 1], + pe[off + 2], + pe[off + 3], + ])) }; // Byte ranges actually backed by file data (a section's tail beyond @@ -151,10 +161,16 @@ pub fn analyze(pe: &[u8], image_base: u32, sections: &[PeSection]) -> RttiResult }; let ranges: Vec<(String, u32, u32)> = sections .iter() - .map(|s| { let (a, b) = backed(s); (s.name.clone(), a, b) }) + .map(|s| { + let (a, b) = backed(s); + (s.name.clone(), a, b) + }) .collect(); let range_of = |name: &str| -> Option<(u32, u32)> { - ranges.iter().find(|(n, _, _)| n == name).map(|&(_, a, b)| (a, b)) + ranges + .iter() + .find(|(n, _, _)| n == name) + .map(|&(_, a, b)| (a, b)) }; // 1. TypeDescriptors. The decorated name lives at descriptor+8 and always @@ -162,19 +178,35 @@ pub fn analyze(pe: &[u8], image_base: u32, sections: &[PeSection]) -> RttiResult let mut type_descriptors: Vec = Vec::new(); let mut td_addrs: BTreeSet = BTreeSet::new(); for (name, start, end) in &ranges { - if !matches!(name.as_str(), ".data" | ".rdata") { continue; } + if !matches!(name.as_str(), ".data" | ".rdata") { + continue; + } let s = (*start).wrapping_sub(image_base) as usize; let e = (*end).wrapping_sub(image_base) as usize; - if e > pe.len() || s >= e { continue; } + if e > pe.len() || s >= e { + continue; + } let bytes = &pe[s..e]; let mut i = 0usize; while i + 3 < bytes.len() { - if &bytes[i..i + 3] != b".?A" { i += 1; continue; } + if &bytes[i..i + 3] != b".?A" { + i += 1; + continue; + } let name_va = start.wrapping_add(i as u32); // The descriptor head sits 8 bytes before the name. - let Some(td_va) = name_va.checked_sub(8) else { i += 1; continue }; - if td_va < *start { i += 1; continue; } - let Some(decorated) = read_cstr(bytes, i, 512) else { i += 1; continue }; + let Some(td_va) = name_va.checked_sub(8) else { + i += 1; + continue; + }; + if td_va < *start { + i += 1; + continue; + } + let Some(decorated) = read_cstr(bytes, i, 512) else { + i += 1; + continue; + }; i += decorated.len() + 1; if td_addrs.insert(td_va) { type_descriptors.push(TypeDescriptor { @@ -197,8 +229,14 @@ pub fn analyze(pe: &[u8], image_base: u32, sections: &[PeSection]) -> RttiResult let mut va = rd_start; while va + 20 <= rd_end { let (Some(sig), Some(off), Some(cd), Some(ptd), Some(pchd)) = ( - read(va), read(va + 4), read(va + 8), read(va + 12), read(va + 16), - ) else { break }; + read(va), + read(va + 4), + read(va + 8), + read(va + 12), + read(va + 16), + ) else { + break; + }; if sig == 0 && td_addrs.contains(&ptd) && pchd >= rd_start && pchd < rd_end { col_addrs.insert(va); locators.push(CompleteObjectLocator { @@ -217,7 +255,9 @@ pub fn analyze(pe: &[u8], image_base: u32, sections: &[PeSection]) -> RttiResult // 3. `vftable[-1]` sites: any word in initialised data whose value is a COL. let mut vtable_to_locator: BTreeMap = BTreeMap::new(); for (name, start, end) in &ranges { - if !matches!(name.as_str(), ".rdata" | ".data") { continue; } + if !matches!(name.as_str(), ".rdata" | ".data") { + continue; + } let mut va = *start; while va + 4 <= *end { if let Some(w) = read(va) @@ -228,8 +268,10 @@ pub fn analyze(pe: &[u8], image_base: u32, sections: &[PeSection]) -> RttiResult va += 4; } } - let locator_to_vtable: BTreeMap = - vtable_to_locator.iter().map(|(&vt, &col)| (col, vt)).collect(); + let locator_to_vtable: BTreeMap = vtable_to_locator + .iter() + .map(|(&vt, &col)| (col, vt)) + .collect(); for col in &mut locators { col.vtable_address = locator_to_vtable.get(&col.address).copied(); } @@ -241,19 +283,37 @@ pub fn analyze(pe: &[u8], image_base: u32, sections: &[PeSection]) -> RttiResult let chds: BTreeSet = locators.iter().map(|c| c.class_hierarchy).collect(); if let Some((rd_start, rd_end)) = rdata { for chd in chds { - let (Some(n_bases), Some(p_array)) = (read(chd + 8), read(chd + 12)) else { continue }; + let (Some(n_bases), Some(p_array)) = (read(chd + 8), read(chd + 12)) else { + continue; + }; // A malformed or misidentified descriptor would blow the scan up; // real hierarchies are small. - if n_bases == 0 || n_bases > 64 { continue; } - if p_array < rd_start || p_array >= rd_end { continue; } + if n_bases == 0 || n_bases > 64 { + continue; + } + if p_array < rd_start || p_array >= rd_end { + continue; + } for i in 0..n_bases { - let Some(bcd) = read(p_array + i * 4) else { break }; - if bcd < rd_start || bcd >= rd_end { break; } + let Some(bcd) = read(p_array + i * 4) else { + break; + }; + if bcd < rd_start || bcd >= rd_end { + break; + } let (Some(ptd), Some(ncb), Some(md), Some(pd), Some(vd), Some(attr)) = ( - read(bcd), read(bcd + 4), read(bcd + 8), - read(bcd + 12), read(bcd + 16), read(bcd + 20), - ) else { break }; - let Some(td) = td_by_addr.get(&ptd) else { break }; + read(bcd), + read(bcd + 4), + read(bcd + 8), + read(bcd + 12), + read(bcd + 16), + read(bcd + 20), + ) else { + break; + }; + let Some(td) = td_by_addr.get(&ptd) else { + break; + }; base_classes.push(BaseClass { class_hierarchy: chd, index: i, @@ -280,7 +340,12 @@ pub fn analyze(pe: &[u8], image_base: u32, sections: &[PeSection]) -> RttiResult "RTTI walk complete", ); - RttiResult { type_descriptors, locators, base_classes, vtable_to_locator } + RttiResult { + type_descriptors, + locators, + base_classes, + vtable_to_locator, + } } /// Read a NUL-terminated ASCII string starting at `off` in `bytes`. @@ -308,14 +373,18 @@ mod tests { vec![ PeSection { name: ".rdata".into(), - virtual_address: RDATA_RVA, virtual_size: SEC_SIZE, - raw_offset: RDATA_RVA, raw_size: SEC_SIZE, + virtual_address: RDATA_RVA, + virtual_size: SEC_SIZE, + raw_offset: RDATA_RVA, + raw_size: SEC_SIZE, flags: 0x4000_0040, }, PeSection { name: ".data".into(), - virtual_address: DATA_RVA, virtual_size: SEC_SIZE, - raw_offset: DATA_RVA, raw_size: SEC_SIZE, + virtual_address: DATA_RVA, + virtual_size: SEC_SIZE, + raw_offset: DATA_RVA, + raw_size: SEC_SIZE, flags: 0xC000_0040, }, ] @@ -323,7 +392,9 @@ mod tests { struct Image(Vec); impl Image { - fn new() -> Self { Image(vec![0u8; (DATA_RVA + SEC_SIZE) as usize]) } + fn new() -> Self { + Image(vec![0u8; (DATA_RVA + SEC_SIZE) as usize]) + } fn put_u32(&mut self, va: u32, v: u32) { let o = (va - BASE) as usize; self.0[o..o + 4].copy_from_slice(&v.to_be_bytes()); @@ -339,16 +410,23 @@ mod tests { /// and the `vftable[-1]` word that points at the COL. #[allow(clippy::too_many_arguments)] fn emit_class( - img: &mut Image, td: u32, name: &str, - col: u32, offset: u32, chd: u32, bcd_array: u32, bcd: u32, base_name_td: Option, + img: &mut Image, + td: u32, + name: &str, + col: u32, + offset: u32, + chd: u32, + bcd_array: u32, + bcd: u32, + base_name_td: Option, vtable_minus_one: u32, ) { img.put_u32(td, 0xDEAD_BEEF); // type_info vftable — value is irrelevant img.put_str(td + 8, name); - img.put_u32(col, 0); // signature + img.put_u32(col, 0); // signature img.put_u32(col + 4, offset); - img.put_u32(col + 8, 0); // cdOffset + img.put_u32(col + 8, 0); // cdOffset img.put_u32(col + 12, td); img.put_u32(col + 16, chd); @@ -362,16 +440,16 @@ mod tests { img.put_u32(bcd_array, bcd); img.put_u32(bcd, td); img.put_u32(bcd + 4, n_bases - 1); - img.put_u32(bcd + 8, 0); // mdisp - img.put_u32(bcd + 12, u32::MAX); // pdisp = -1 - img.put_u32(bcd + 16, 0); // vdisp - img.put_u32(bcd + 20, 0x40); // attributes + img.put_u32(bcd + 8, 0); // mdisp + img.put_u32(bcd + 12, u32::MAX); // pdisp = -1 + img.put_u32(bcd + 16, 0); // vdisp + img.put_u32(bcd + 20, 0x40); // attributes if let Some(base_td) = base_name_td { let bcd2 = bcd + 24; img.put_u32(bcd_array + 4, bcd2); img.put_u32(bcd2, base_td); img.put_u32(bcd2 + 4, 0); - img.put_u32(bcd2 + 8, 4); // mdisp = 4 + img.put_u32(bcd2 + 8, 4); // mdisp = 4 img.put_u32(bcd2 + 12, u32::MAX); img.put_u32(bcd2 + 16, 0); img.put_u32(bcd2 + 20, 0); @@ -387,18 +465,39 @@ mod tests { let da = BASE + DATA_RVA; // Base class Foo, then Derived : Foo. - emit_class(&mut img, da + 0x100, ".?AVFoo@ns@@", - rd + 0x100, 0, rd + 0x200, rd + 0x280, rd + 0x300, None, - rd + 0x000); - emit_class(&mut img, da + 0x200, ".?AVDerived@ns@@", - rd + 0x400, 0, rd + 0x500, rd + 0x580, rd + 0x600, Some(da + 0x100), - rd + 0x040); + emit_class( + &mut img, + da + 0x100, + ".?AVFoo@ns@@", + rd + 0x100, + 0, + rd + 0x200, + rd + 0x280, + rd + 0x300, + None, + rd, + ); + emit_class( + &mut img, + da + 0x200, + ".?AVDerived@ns@@", + rd + 0x400, + 0, + rd + 0x500, + rd + 0x580, + rd + 0x600, + Some(da + 0x100), + rd + 0x040, + ); let r = analyze(&img.0, BASE, §ions()); assert_eq!(r.type_descriptors.len(), 2); - let derived = r.type_descriptors.iter() - .find(|t| t.mangled_name.contains("Derived")).unwrap(); + let derived = r + .type_descriptors + .iter() + .find(|t| t.mangled_name.contains("Derived")) + .unwrap(); assert_eq!(derived.demangled_name, "ns::Derived"); assert_eq!(r.locators.len(), 2); @@ -407,10 +506,15 @@ mod tests { assert!(r.vtable_anchors().contains(&(rd + 0x004))); let names = r.vtable_class_names(); - assert_eq!(names.get(&(rd + 0x044)), Some(&("ns::Derived".to_string(), 0))); + assert_eq!( + names.get(&(rd + 0x044)), + Some(&("ns::Derived".to_string(), 0)) + ); // Derived's hierarchy lists itself at index 0 and Foo at index 1. - let mut bases: Vec<_> = r.base_classes.iter() + let mut bases: Vec<_> = r + .base_classes + .iter() .filter(|b| b.class_hierarchy == rd + 0x500) .collect(); bases.sort_by_key(|b| b.index); @@ -425,9 +529,18 @@ mod tests { let mut img = Image::new(); let rd = BASE + RDATA_RVA; let da = BASE + DATA_RVA; - emit_class(&mut img, da + 0x100, ".?AVMulti@@", - rd + 0x100, 0x8, rd + 0x200, rd + 0x280, rd + 0x300, None, - rd + 0x000); + emit_class( + &mut img, + da + 0x100, + ".?AVMulti@@", + rd + 0x100, + 0x8, + rd + 0x200, + rd + 0x280, + rd + 0x300, + None, + rd, + ); let r = analyze(&img.0, BASE, §ions()); let names = r.vtable_class_names(); diff --git a/crates/sylpheed-xexdb/src/sinks/text.rs b/crates/sylpheed-xexdb/src/sinks/text.rs index b073d0e3..f91a996e 100644 --- a/crates/sylpheed-xexdb/src/sinks/text.rs +++ b/crates/sylpheed-xexdb/src/sinks/text.rs @@ -28,7 +28,8 @@ pub fn write_instr_line( // A word the analysis proved is data (a recovered jump table or its index // map) must not be printed as if it decoded to something meaningful. if item.is_data { - let lbl = labels.get(&item.item.raw) + let lbl = labels + .get(&item.item.raw) .map(|s| format!(" ; -> {s}")) .unwrap_or_default(); return writeln!( @@ -52,12 +53,13 @@ pub fn write_instr_line( if let Some((data_addr, kind)) = data_annotation { let tag = match kind { - XrefKind::DataRead => "[R]", + XrefKind::DataRead => "[R]", XrefKind::DataWrite => "[W]", - _ => "[&]", + _ => "[&]", }; let sec = section_for_addr(data_addr, sections, image_base).unwrap_or("?"); - let data_lbl = labels.get(&data_addr) + let data_lbl = labels + .get(&data_addr) .map(|s| format!(" = {s}")) .unwrap_or_default(); if !annotated.contains("; ->") { @@ -67,5 +69,9 @@ pub fn write_instr_line( } } - writeln!(out, " {:08X}: {:08X} {}", item.item.addr, item.item.raw, annotated) + writeln!( + out, + " {:08X}: {:08X} {}", + item.item.addr, item.item.raw, annotated + ) } diff --git a/crates/sylpheed-xexdb/src/sql_views.rs b/crates/sylpheed-xexdb/src/sql_views.rs index ddcef4e7..ec3ede27 100644 --- a/crates/sylpheed-xexdb/src/sql_views.rs +++ b/crates/sylpheed-xexdb/src/sql_views.rs @@ -23,7 +23,6 @@ //! kind-classification CASE drifted out of agreement with `xref.rs`, and //! is worth a one-line warning at log time. - /// Every XDBF string side-by-side across the languages the title ships, so a /// piece of UI text can be looked up once and read in all locales. const V_XDBF_TEXT: &str = " @@ -66,7 +65,10 @@ pub const ALL_VIEWS: &[(&str, &str)] = &[ ("v_branch_xrefs", V_BRANCH_XREFS), ("v_call_graph", V_CALL_GRAPH), ("v_reachability_from_entry", V_REACHABILITY_FROM_ENTRY), - ("v_indirect_reachability_from_entry", V_INDIRECT_REACHABILITY_FROM_ENTRY), + ( + "v_indirect_reachability_from_entry", + V_INDIRECT_REACHABILITY_FROM_ENTRY, + ), ("v_function_first_instruction", V_FUNCTION_FIRST_INSTRUCTION), ("v_imports_called", V_IMPORTS_CALLED), ("v_xdbf_text", V_XDBF_TEXT), diff --git a/crates/sylpheed-xexdb/src/static_init.rs b/crates/sylpheed-xexdb/src/static_init.rs index 3d3876a4..3af9926b 100644 --- a/crates/sylpheed-xexdb/src/static_init.rs +++ b/crates/sylpheed-xexdb/src/static_init.rs @@ -69,10 +69,10 @@ pub struct StaticInitResult { pub arrays: Vec, } -const OP_ADDI: u32 = 14; +const OP_ADDI: u32 = 14; const OP_ADDIS: u32 = 15; const OP_BCCTR: u32 = 19; -const OP_LWZ: u32 = 32; +const OP_LWZ: u32 = 32; const OP_X_FORM: u32 = 31; #[derive(Debug, Clone, Copy)] @@ -95,10 +95,12 @@ pub fn analyze( let mut drivers: Vec = Vec::new(); for (&fn_start, fi) in &func_analysis.functions { - if fi.is_saverestore { continue; } - if let Some(d) = scan_function_for_driver( - pe, image_base, fn_start, fi.end, &block_boundaries, - ) { + if fi.is_saverestore { + continue; + } + if let Some(d) = + scan_function_for_driver(pe, image_base, fn_start, fi.end, &block_boundaries) + { drivers.push(d); } } @@ -106,7 +108,14 @@ pub fn analyze( // Build arrays from the discovered drivers + section data. let mut arrays: Vec = Vec::new(); for d in &drivers { - if let Some(entries) = read_array(pe, image_base, sections, d.array_start, d.array_end, function_starts) { + if let Some(entries) = read_array( + pe, + image_base, + sections, + d.array_start, + d.array_end, + function_starts, + ) { arrays.push(FuncPtrArray { address: d.array_start, length: entries.len() as u32, @@ -140,7 +149,9 @@ fn read_array( end: u32, function_starts: &BTreeSet, ) -> Option> { - if end <= start || (end - start) > 4096 { return None; } + if end <= start || (end - start) > 4096 { + return None; + } let _section = sections.iter().find(|s| { let lo = image_base + s.virtual_address; let hi = lo + s.virtual_size; @@ -150,15 +161,21 @@ fn read_array( let mut p = start; while p < end { let off = p.wrapping_sub(image_base) as usize; - if off + 4 > pe.len() { return None; } + if off + 4 > pe.len() { + return None; + } let v = u32::from_be_bytes([pe[off], pe[off + 1], pe[off + 2], pe[off + 3]]); if v != 0 { - if !function_starts.contains(&v) { return None; } + if !function_starts.contains(&v) { + return None; + } entries.push(v); } p = p.wrapping_add(4); } - if entries.is_empty() { return None; } + if entries.is_empty() { + return None; + } Some(entries) } @@ -194,7 +211,9 @@ fn scan_function_for_driver( reg = [None; 32]; } let off = pc.wrapping_sub(image_base) as usize; - if off + 4 > pe.len() { break; } + if off + 4 > pe.len() { + break; + } let instr = u32::from_be_bytes([pe[off], pe[off + 1], pe[off + 2], pe[off + 3]]); let op = instr >> 26; let rd = ((instr >> 21) & 0x1F) as usize; @@ -207,7 +226,9 @@ fn scan_function_for_driver( OP_ADDIS => { if let Some(RegVal::Const(b)) = reg[ra] { reg[rd] = Some(RegVal::Const(b.wrapping_add(uimm << 16))); - } else { reg[rd] = None; } + } else { + reg[rd] = None; + } } OP_ADDI if ra != 0 => { let prev = reg[ra]; @@ -229,7 +250,9 @@ fn scan_function_for_driver( end_init = Some(v); end_reg = Some(rd); } - } else { reg[rd] = None; } + } else { + reg[rd] = None; + } } OP_LWZ => { if ra != 0 && Some(ra) == cursor_reg { @@ -241,9 +264,13 @@ fn scan_function_for_driver( let xo = (instr >> 1) & 0x3FF; if xo == 467 { let spr = (((instr >> 11) & 0x1F) << 5) | ((instr >> 16) & 0x1F); - if spr == 9 && saw_lwz_through_cursor { saw_mtctr = true; } + if spr == 9 && saw_lwz_through_cursor { + saw_mtctr = true; + } + } + if xo != 444 && xo != 467 { + reg[rd] = None; } - if xo != 444 && xo != 467 { reg[rd] = None; } } OP_BCCTR => { let xo = (instr >> 1) & 0x3FF; @@ -254,12 +281,14 @@ fn scan_function_for_driver( } 18 => { if (instr & 1) != 0 { - for r in 0..=12 { reg[r] = None; } + for r in 0..=12 { + reg[r] = None; + } } } - 16 => { - if (instr & 1) != 0 { - for r in 0..=12 { reg[r] = None; } + 16 if (instr & 1) != 0 => { + for r in 0..=12 { + reg[r] = None; } } _ => {} @@ -273,8 +302,12 @@ fn scan_function_for_driver( } let cursor_init = cursor_init?; let end_init = end_init?; - if end_init <= cursor_init { return None; } - if end_init - cursor_init > 4096 { return None; } + if end_init <= cursor_init { + return None; + } + if end_init - cursor_init > 4096 { + return None; + } Some(StaticInitDriver { driver_function: fn_start, @@ -294,8 +327,10 @@ mod tests { fn mk_section(name: &str, va: u32, size: u32) -> PeSection { PeSection { name: name.into(), - virtual_address: va, virtual_size: size, - raw_offset: va, raw_size: size, + virtual_address: va, + virtual_size: size, + raw_offset: va, + raw_size: size, flags: 0x4000_0040, } } @@ -311,7 +346,11 @@ mod tests { // Array at .rdata + 0x800: 3 function pointers. let arr_va_lo = 0x800u32; - let fns = [image_base + 0x2000, image_base + 0x2010, image_base + 0x2020]; + let fns = [ + image_base + 0x2000, + image_base + 0x2010, + image_base + 0x2020, + ]; for (i, p) in fns.iter().enumerate() { write_be(&mut pe, arr_va_lo as usize + i * 4, *p); } @@ -330,32 +369,52 @@ mod tests { // blr let driver = 0x82001000u32; let off = (driver - image_base) as usize; - let lis_r3 = (15u32 << 26) | (3 << 21) | ((array_start >> 16) as u32); + let lis_r3 = (15u32 << 26) | (3 << 21) | (array_start >> 16); let addi_r3 = (14u32 << 26) | (3 << 21) | (3 << 16) | ((array_start as u16) as u32); - let lis_r4 = (15u32 << 26) | (4 << 21) | ((array_end >> 16) as u32); + let lis_r4 = (15u32 << 26) | (4 << 21) | (array_end >> 16); let addi_r4 = (14u32 << 26) | (4 << 21) | (4 << 16) | ((array_end as u16) as u32); let lwz = (32u32 << 26) | (5 << 21) | (3 << 16); let mtctr = (31u32 << 26) | (5 << 21) | (9 << 16) | (467 << 1); let bcctrl = (19u32 << 26) | (20 << 21) | (528 << 1) | 1; let addi_inc = (14u32 << 26) | (3 << 21) | (3 << 16) | 4; let blr = (19u32 << 26) | (20 << 21) | (16 << 1); - for (i, w) in [lis_r3, addi_r3, lis_r4, addi_r4, lwz, mtctr, bcctrl, addi_inc, blr].iter().enumerate() { + for (i, w) in [ + lis_r3, addi_r3, lis_r4, addi_r4, lwz, mtctr, bcctrl, addi_inc, blr, + ] + .iter() + .enumerate() + { write_be(&mut pe, off + i * 4, *w); } let mut functions: BTreeMap = BTreeMap::new(); - functions.insert(driver, FuncInfo { - start: driver, end: driver + 0x40, frame_size: 0, saved_gprs: 0, - is_leaf: false, is_saverestore: false, - pdata_validated: false, pdata_length: None, pdata_prolog_length: None, has_eh: false, - }); + functions.insert( + driver, + FuncInfo { + start: driver, + end: driver + 0x40, + frame_size: 0, + saved_gprs: 0, + is_leaf: false, + is_saverestore: false, + pdata_validated: false, + pdata_length: None, + pdata_prolog_length: None, + has_eh: false, + }, + ); let fa = FuncAnalysis { - functions, save_gpr_base: None, restore_gpr_base: None, pdata_entries: Vec::new(), + functions, + save_gpr_base: None, + restore_gpr_base: None, + pdata_entries: Vec::new(), }; let sections = vec![mk_section(".rdata", 0x800, 0x100)]; let mut starts = BTreeSet::new(); - for &p in &fns { starts.insert(p); } + for &p in &fns { + starts.insert(p); + } let labels: HashMap = HashMap::new(); let r = analyze(&pe, image_base, §ions, &fa, &starts, &labels); @@ -382,13 +441,26 @@ mod tests { write_be(&mut pe, (driver - image_base) as usize, blr); let mut functions: BTreeMap = BTreeMap::new(); - functions.insert(driver, FuncInfo { - start: driver, end: driver + 0x40, frame_size: 0, saved_gprs: 0, - is_leaf: true, is_saverestore: false, - pdata_validated: false, pdata_length: None, pdata_prolog_length: None, has_eh: false, - }); + functions.insert( + driver, + FuncInfo { + start: driver, + end: driver + 0x40, + frame_size: 0, + saved_gprs: 0, + is_leaf: true, + is_saverestore: false, + pdata_validated: false, + pdata_length: None, + pdata_prolog_length: None, + has_eh: false, + }, + ); let fa = FuncAnalysis { - functions, save_gpr_base: None, restore_gpr_base: None, pdata_entries: Vec::new(), + functions, + save_gpr_base: None, + restore_gpr_base: None, + pdata_entries: Vec::new(), }; let sections = vec![mk_section(".rdata", 0x800, 0x100)]; let starts: BTreeSet = BTreeSet::new(); diff --git a/crates/sylpheed-xexdb/src/strings.rs b/crates/sylpheed-xexdb/src/strings.rs index 1d7266ef..8d3ddb55 100644 --- a/crates/sylpheed-xexdb/src/strings.rs +++ b/crates/sylpheed-xexdb/src/strings.rs @@ -52,12 +52,16 @@ pub fn analyze(pe: &[u8], image_base: u32, sections: &[PeSection]) -> Vec = Vec::new(); for section in sections { - if !matches!(section.name.as_str(), ".rdata" | ".data") { continue; } + if !matches!(section.name.as_str(), ".rdata" | ".data") { + continue; + } let raw_start = section.virtual_address as usize; // Clamp to the file-backed extent — everything past `raw_size` is BSS. let backed = section.virtual_size.min(section.raw_size) as usize; let raw_end = (raw_start + backed).min(pe.len()); - if raw_start >= raw_end { continue; } + if raw_start >= raw_end { + continue; + } let bytes = &pe[raw_start..raw_end]; let va_base = image_base + section.virtual_address; @@ -74,8 +78,8 @@ pub fn analyze(pe: &[u8], image_base: u32, sections: &[PeSection]) -> Vec "strings").record(elapsed_ms); tracing::info!( ascii = n_ascii, @@ -104,7 +108,9 @@ fn scan_ascii(bytes: &[u8], va_base: u32, out: &mut Vec) { continue; } let start = i; - while i < bytes.len() && is_printable_ascii(bytes[i]) { i += 1; } + while i < bytes.len() && is_printable_ascii(bytes[i]) { + i += 1; + } let run_len = i - start; // Require NUL termination and minimum length. if run_len >= MIN_LEN && i < bytes.len() && bytes[i] == 0 { @@ -118,7 +124,9 @@ fn scan_ascii(bytes: &[u8], va_base: u32, out: &mut Vec) { }); } // Skip the NUL (if any) before continuing. - if i < bytes.len() && bytes[i] == 0 { i += 1; } + if i < bytes.len() && bytes[i] == 0 { + i += 1; + } } } @@ -127,7 +135,10 @@ fn scan_utf16le(bytes: &[u8], va_base: u32, out: &mut Vec) { // offsets to avoid misaligned hits. let mut i = 0; while i + 2 <= bytes.len() { - if !i.is_multiple_of(2) { i += 1; continue; } + if !i.is_multiple_of(2) { + i += 1; + continue; + } let lo = bytes[i]; let hi = bytes[i + 1]; // Restrict scan-start to printable ASCII range with a zero high byte — @@ -141,7 +152,9 @@ fn scan_utf16le(bytes: &[u8], va_base: u32, out: &mut Vec) { while i + 2 <= bytes.len() { let l = bytes[i]; let h = bytes[i + 1]; - if h != 0 || !is_printable_ascii(l) { break; } + if h != 0 || !is_printable_ascii(l) { + break; + } codeunits.push((h as u16) << 8 | l as u16); i += 2; } @@ -158,7 +171,9 @@ fn scan_utf16le(bytes: &[u8], va_base: u32, out: &mut Vec) { }); } // Skip past the terminator. - if nul_terminated { i += 2; } + if nul_terminated { + i += 2; + } } } @@ -183,16 +198,16 @@ fn is_sjis_trail(b: u8) -> bool { /// ASCII. fn is_text_like(ch: char) -> bool { let o = ch as u32; - matches!(o, 0x20..=0x7E) - || matches!(ch, '\t' | '\n' | '\r') - || is_wide(ch) + matches!(o, 0x20..=0x7E) || matches!(ch, '\t' | '\n' | '\r') || is_wide(ch) } /// A full-width character — kana, CJK punctuation, ideograph, or full-width /// ASCII. Used to tell "real text" from a lucky byte pair. fn is_wide(ch: char) -> bool { let o = ch as u32; - (0x3000..=0x30FF).contains(&o) || (0x4E00..=0x9FFF).contains(&o) || (0xFF01..=0xFF5E).contains(&o) + (0x3000..=0x30FF).contains(&o) + || (0x4E00..=0x9FFF).contains(&o) + || (0xFF01..=0xFF5E).contains(&o) } /// True when `t` contains a lone ASCII character with a full-width character @@ -207,9 +222,8 @@ fn is_wide(ch: char) -> bool { /// character wedged between two wide ones. fn has_isolated_ascii(t: &str) -> bool { let chars: Vec = t.chars().collect(); - (1..chars.len().saturating_sub(1)).any(|k| { - !is_wide(chars[k]) && is_wide(chars[k - 1]) && is_wide(chars[k + 1]) - }) + (1..chars.len().saturating_sub(1)) + .any(|k| !is_wide(chars[k]) && is_wide(chars[k - 1]) && is_wide(chars[k + 1])) } /// Decode `raw` as Shift_JIS, rejecting anything that is not convincingly @@ -224,7 +238,8 @@ fn decode_sjis(raw: &[u8]) -> Option { // obscure kanji, but hiragana/katakana (U+3040..U+30FF) essentially never // appear by accident and are ubiquitous in genuine Japanese. let has_kana = t.chars().any(|c| ('\u{3040}'..='\u{30FF}').contains(&c)); - if t.chars().count() >= 4 && has_kana && t.chars().all(is_text_like) && !has_isolated_ascii(&t) { + if t.chars().count() >= 4 && has_kana && t.chars().all(is_text_like) && !has_isolated_ascii(&t) + { Some(t) } else { None @@ -274,7 +289,9 @@ fn scan_shift_jis(bytes: &[u8], va_base: u32, out: &mut Vec) { i = end + 1; // skip NUL } else { i = start + 1; - if i < bytes.len() && bytes[i] == 0 { i += 1; } + if i < bytes.len() && bytes[i] == 0 { + i += 1; + } } } } @@ -291,12 +308,16 @@ fn scan_utf8(bytes: &[u8], va_base: u32, out: &mut Vec) { while i < bytes.len() { let b = bytes[i]; if b < 0x80 { - if !is_printable_ascii(b) { break; } + if !is_printable_ascii(b) { + break; + } nbytes += 1; i += 1; } else if (b & 0xE0) == 0xC0 { // 2-byte: 110xxxxx 10xxxxxx - if i + 1 >= bytes.len() || (bytes[i + 1] & 0xC0) != 0x80 { break; } + if i + 1 >= bytes.len() || (bytes[i + 1] & 0xC0) != 0x80 { + break; + } has_multibyte = true; nbytes += 2; i += 2; @@ -304,7 +325,10 @@ fn scan_utf8(bytes: &[u8], va_base: u32, out: &mut Vec) { // 3-byte: 1110xxxx 10xxxxxx 10xxxxxx if i + 2 >= bytes.len() || (bytes[i + 1] & 0xC0) != 0x80 - || (bytes[i + 2] & 0xC0) != 0x80 { break; } + || (bytes[i + 2] & 0xC0) != 0x80 + { + break; + } has_multibyte = true; nbytes += 3; i += 3; @@ -314,7 +338,8 @@ fn scan_utf8(bytes: &[u8], va_base: u32, out: &mut Vec) { } if has_multibyte && nbytes >= MIN_LEN - && i < bytes.len() && bytes[i] == 0 + && i < bytes.len() + && bytes[i] == 0 && let Ok(s) = std::str::from_utf8(&bytes[start..i]) { out.push(DetectedString { @@ -327,7 +352,9 @@ fn scan_utf8(bytes: &[u8], va_base: u32, out: &mut Vec) { i += 1; // skip NUL } else { i = start + 1; - if i < bytes.len() && bytes[i] == 0 { i += 1; } + if i < bytes.len() && bytes[i] == 0 { + i += 1; + } } } } @@ -402,7 +429,10 @@ mod tests { pe[off..off + s.len()].copy_from_slice(s); let sections = vec![mk_section(".rdata", 0x1000, 0x100)]; let strings = analyze(&pe, image_base, §ions); - let sjis: Vec<_> = strings.iter().filter(|s| s.encoding == "shift_jis").collect(); + let sjis: Vec<_> = strings + .iter() + .filter(|s| s.encoding == "shift_jis") + .collect(); assert_eq!(sjis.len(), 1); // Decoded to real UTF-8, not rendered as escaped bytes. assert_eq!(sjis[0].content, "ABCあい"); @@ -420,8 +450,10 @@ mod tests { pe[off..off + s.len()].copy_from_slice(s); let sections = vec![mk_section(".rdata", 0x1000, 0x100)]; let strings = analyze(&pe, image_base, §ions); - assert!(strings.iter().all(|s| s.encoding != "shift_jis"), - "float table must not be reported as Japanese text"); + assert!( + strings.iter().all(|s| s.encoding != "shift_jis"), + "float table must not be reported as Japanese text" + ); } #[test] @@ -440,7 +472,10 @@ mod tests { pe[off..off + s.len()].copy_from_slice(s); let sections = vec![mk_section(".rdata", 0x1000, 0x100)]; let strings = analyze(&pe, image_base, §ions); - let sjis: Vec<_> = strings.iter().filter(|s| s.encoding == "shift_jis").collect(); + let sjis: Vec<_> = strings + .iter() + .filter(|s| s.encoding == "shift_jis") + .collect(); assert_eq!(sjis.len(), 1); assert_eq!(sjis[0].content, "システム"); // Reported at the true start, one byte past the run's beginning. @@ -471,7 +506,9 @@ mod tests { let s = b"abcdefghij"; pe[off..off + s.len()].copy_from_slice(s); // Fill rest of section with 0xFF so the run terminates cleanly without NUL. - for j in off + s.len()..off + 0x100 { pe[j] = 0xFF; } + for j in off + s.len()..off + 0x100 { + pe[j] = 0xFF; + } let sections = vec![mk_section(".rdata", 0x1000, 0x100)]; let strings = analyze(&pe, image_base, §ions); assert_eq!(strings.len(), 0); diff --git a/crates/sylpheed-xexdb/src/vtables.rs b/crates/sylpheed-xexdb/src/vtables.rs index 3d4d5d02..c95bd417 100644 --- a/crates/sylpheed-xexdb/src/vtables.rs +++ b/crates/sylpheed-xexdb/src/vtables.rs @@ -65,7 +65,13 @@ pub fn analyze( sections: &[PeSection], function_starts: &std::collections::BTreeSet, ) -> Vec { - analyze_with_anchors(pe, image_base, sections, function_starts, &std::collections::BTreeSet::new()) + analyze_with_anchors( + pe, + image_base, + sections, + function_starts, + &std::collections::BTreeSet::new(), + ) } /// Like [`analyze`], but additionally recovers vtables whose base address is @@ -106,7 +112,12 @@ pub fn analyze_with_anchors( let rdata_ranges: Vec<(u32, u32)> = sections .iter() .filter(|s| s.name == ".rdata") - .map(|s| (image_base + s.virtual_address, image_base + s.virtual_address + s.virtual_size)) + .map(|s| { + ( + image_base + s.virtual_address, + image_base + s.virtual_address + s.virtual_size, + ) + }) .collect(); // TypeDescriptors are *written at startup* (their first word is // `type_info`'s vftable), so MSVC emits them into writable `.data`, not @@ -115,7 +126,12 @@ pub fn analyze_with_anchors( let typedesc_ranges: Vec<(u32, u32)> = sections .iter() .filter(|s| matches!(s.name.as_str(), ".rdata" | ".data")) - .map(|s| (image_base + s.virtual_address, image_base + s.virtual_address + s.virtual_size)) + .map(|s| { + ( + image_base + s.virtual_address, + image_base + s.virtual_address + s.virtual_size, + ) + }) .collect(); let mut candidates: Vec = Vec::new(); @@ -125,13 +141,18 @@ pub fn analyze_with_anchors( let va_end = va_start + section.virtual_size; let raw_start = section.virtual_address as usize; let raw_end = (section.virtual_address + section.virtual_size) as usize; - if raw_end > pe.len() { continue; } + if raw_end > pe.len() { + continue; + } let bytes = &pe[raw_start..raw_end.min(pe.len())]; let mut i = 0usize; while i + 12 <= bytes.len() { // Try to start a run at this 4-aligned offset. - if !i.is_multiple_of(4) { i += 1; continue; } + if !i.is_multiple_of(4) { + i += 1; + continue; + } let mut run_len = 0usize; let mut methods: Vec = Vec::new(); let mut j = i; @@ -203,12 +224,19 @@ pub fn analyze_with_anchors( let mut recovered = 0usize; let mut newly: Vec = Vec::new(); for &anchor in anchors { - if is_covered(anchor, &covered) { continue; } + if is_covered(anchor, &covered) { + continue; + } // Locate the containing .rdata/.data section. - let Some(&(va_lo, va_hi, raw_lo, raw_hi)) = - scan_targets_va.iter().find(|&&(lo, hi, _, _)| anchor >= lo && anchor < hi) - else { continue }; - if anchor % 4 != 0 { continue; } + let Some(&(va_lo, va_hi, raw_lo, raw_hi)) = scan_targets_va + .iter() + .find(|&&(lo, hi, _, _)| anchor >= lo && anchor < hi) + else { + continue; + }; + if anchor % 4 != 0 { + continue; + } let raw_hi = raw_hi.min(pe.len()); // Read the fnptr-array run starting at the anchor. Tolerate small // gaps of non-function slots (null / pure-virtual / unrecognised), @@ -221,7 +249,11 @@ pub fn analyze_with_anchors( let mut off = (anchor - va_lo) as usize + raw_lo; let mut va = anchor; while off + 4 <= raw_hi && va < va_hi { - if let Some(nb) = next_base && va >= nb { break; } + if let Some(nb) = next_base + && va >= nb + { + break; + } let val = u32::from_be_bytes([pe[off], pe[off + 1], pe[off + 2], pe[off + 3]]); if function_starts.contains(&val) { methods.push(val); @@ -244,10 +276,15 @@ pub fn analyze_with_anchors( } // Trim any trailing non-function slots (the table ends at its last // real method). - while methods.last().is_some_and(|&m| !function_starts.contains(&m)) { + while methods + .last() + .is_some_and(|&m| !function_starts.contains(&m)) + { methods.pop(); } - if real_fns == 0 || methods.is_empty() { continue; } + if real_fns == 0 || methods.is_empty() { + continue; + } let length = methods.len() as u32; newly.push(Vtable { address: anchor, @@ -266,8 +303,10 @@ pub fn analyze_with_anchors( // contiguity-scan artifact of the same table. Keep fragments that // only partially overlap (defensive; shouldn't happen for true // sub-runs) so we never lose method coverage. - let recovered_spans: Vec<(u32, u32)> = - newly.iter().map(|v| (v.address, v.address + v.length * 4)).collect(); + let recovered_spans: Vec<(u32, u32)> = newly + .iter() + .map(|v| (v.address, v.address + v.length * 4)) + .collect(); candidates.retain(|v| { !recovered_spans .iter() @@ -281,22 +320,37 @@ pub fn analyze_with_anchors( // RTTI walk: for each candidate, look at vtable[-1]. let pe_image_base = image_base; for v in &mut candidates { - if v.address < 4 { continue; } + if v.address < 4 { + continue; + } let col_off = (v.address - pe_image_base - 4) as usize; - if col_off + 4 > pe.len() { continue; } - let col_ptr = u32::from_be_bytes([pe[col_off], pe[col_off + 1], pe[col_off + 2], pe[col_off + 3]]); - if col_ptr == 0 { continue; } - if !is_in_ranges(col_ptr, &rdata_ranges) { continue; } + if col_off + 4 > pe.len() { + continue; + } + let col_ptr = u32::from_be_bytes([ + pe[col_off], + pe[col_off + 1], + pe[col_off + 2], + pe[col_off + 3], + ]); + if col_ptr == 0 { + continue; + } + if !is_in_ranges(col_ptr, &rdata_ranges) { + continue; + } // Try to extract the TypeDescriptor mangled-name string. if let Some((td_ptr, hierarchy_ptr)) = read_col(pe, image_base, col_ptr) - && let Some(mangled) = read_typedescriptor_name(pe, image_base, td_ptr, &typedesc_ranges) + && let Some(mangled) = + read_typedescriptor_name(pe, image_base, td_ptr, &typedesc_ranges) && let Some(class) = demangle_rtti_typename(&mangled) { v.col_address = Some(col_ptr); v.class_name = class; v.rtti_present = true; - v.base_classes_json = read_class_hierarchy(pe, image_base, hierarchy_ptr, &rdata_ranges); + v.base_classes_json = + read_class_hierarchy(pe, image_base, hierarchy_ptr, &rdata_ranges); } } @@ -320,8 +374,15 @@ fn is_in_ranges(addr: u32, ranges: &[(u32, u32)]) -> bool { /// Read 4 big-endian bytes at absolute VA `addr` from the PE image. fn read_be_u32(pe: &[u8], image_base: u32, addr: u32) -> Option { let off = addr.wrapping_sub(image_base) as usize; - if off + 4 > pe.len() { return None; } - Some(u32::from_be_bytes([pe[off], pe[off + 1], pe[off + 2], pe[off + 3]])) + if off + 4 > pe.len() { + return None; + } + Some(u32::from_be_bytes([ + pe[off], + pe[off + 1], + pe[off + 2], + pe[off + 3], + ])) } /// Parse a `CompleteObjectLocator` at VA `col`. Returns @@ -338,7 +399,9 @@ fn read_be_u32(pe: &[u8], image_base: u32, addr: u32) -> Option { fn read_col(pe: &[u8], image_base: u32, col: u32) -> Option<(u32, u32)> { let td = read_be_u32(pe, image_base, col + 0x0C)?; let chd = read_be_u32(pe, image_base, col + 0x10)?; - if td == 0 { return None; } + if td == 0 { + return None; + } Some((td, chd)) } @@ -352,17 +415,27 @@ fn read_typedescriptor_name( td: u32, rdata_ranges: &[(u32, u32)], ) -> Option { - if !is_in_ranges(td, rdata_ranges) { return None; } + if !is_in_ranges(td, rdata_ranges) { + return None; + } let name_va = td + 0x08; let off = name_va.wrapping_sub(image_base) as usize; - if off + 1 > pe.len() { return None; } + if off + 1 > pe.len() { + return None; + } // Read up to 256 bytes or until NUL. let mut end = off; - while end < pe.len().min(off + 256) && pe[end] != 0 { end += 1; } - if end == off { return None; } + while end < pe.len().min(off + 256) && pe[end] != 0 { + end += 1; + } + if end == off { + return None; + } let s = std::str::from_utf8(&pe[off..end]).ok()?; // Sanity: MSVC RTTI names always start with `.?A`. - if !s.starts_with(".?A") { return None; } + if !s.starts_with(".?A") { + return None; + } Some(s.to_string()) } @@ -403,11 +476,17 @@ fn read_class_hierarchy( chd: u32, rdata_ranges: &[(u32, u32)], ) -> Option { - if !is_in_ranges(chd, rdata_ranges) { return None; } + if !is_in_ranges(chd, rdata_ranges) { + return None; + } let num_bases = read_be_u32(pe, image_base, chd + 0x08)?; - if num_bases == 0 || num_bases > 256 { return None; } // sanity cap + if num_bases == 0 || num_bases > 256 { + return None; + } // sanity cap let bca_ptr = read_be_u32(pe, image_base, chd + 0x0C)?; - if !is_in_ranges(bca_ptr, rdata_ranges) { return None; } + if !is_in_ranges(bca_ptr, rdata_ranges) { + return None; + } let mut names: Vec = Vec::new(); for i in 0..num_bases { @@ -419,10 +498,7 @@ fn read_class_hierarchy( Some(p) if is_in_ranges(p, rdata_ranges) => p, _ => return None, }; - let mangled = match read_typedescriptor_name(pe, image_base, td_ptr, rdata_ranges) { - Some(s) => s, - None => return None, - }; + let mangled = read_typedescriptor_name(pe, image_base, td_ptr, rdata_ranges)?; let cls = demangle_rtti_typename(&mangled).unwrap_or(mangled); names.push(cls); } @@ -453,7 +529,12 @@ pub fn scan_vptr_write_constants( let data_ranges: Vec<(u32, u32)> = sections .iter() .filter(|s| matches!(s.name.as_str(), ".rdata" | ".data")) - .map(|s| (image_base + s.virtual_address, image_base + s.virtual_address + s.virtual_size)) + .map(|s| { + ( + image_base + s.virtual_address, + image_base + s.virtual_address + s.virtual_size, + ) + }) .collect(); let in_data = |a: u32| data_ranges.iter().any(|&(s, e)| a >= s && a < e); @@ -465,13 +546,22 @@ pub fn scan_vptr_write_constants( let read = |addr: u32| -> Option { let off = addr.wrapping_sub(image_base) as usize; - if off + 4 > pe.len() { return None; } - Some(u32::from_be_bytes([pe[off], pe[off + 1], pe[off + 2], pe[off + 3]])) + if off + 4 > pe.len() { + return None; + } + Some(u32::from_be_bytes([ + pe[off], + pe[off + 1], + pe[off + 2], + pe[off + 3], + ])) }; let mut anchors: std::collections::BTreeSet = std::collections::BTreeSet::new(); for (&fn_start, &(fn_end, is_saverestore)) in functions { - if is_saverestore { continue; } + if is_saverestore { + continue; + } let mut reg: [Option; 32] = [None; 32]; let mut pc = fn_start; while pc < fn_end { @@ -506,11 +596,13 @@ pub fn scan_vptr_write_constants( 32..=35 | 40..=43 | 48..=51 => reg[rd] = None, OP_X_FORM => { let xo = (instr >> 1) & 0x3FF; - if xo != 444 && xo != 467 { reg[rd] = None; } // keep `or`(444=mr)/`mtspr`-ish + if xo != 444 && xo != 467 { + reg[rd] = None; + } // keep `or`(444=mr)/`mtspr`-ish } - 18 | 16 => { - if (instr & 1) != 0 { - for r in 0..=12 { reg[r] = None; } + 18 | 16 if (instr & 1) != 0 => { + for r in 0..=12 { + reg[r] = None; } } _ => {} @@ -550,7 +642,8 @@ pub fn methods_table( for v in vtables { for (slot, &fn_va) in v.methods.iter().enumerate() { let label = labels.get(&fn_va).cloned(); - let demangled = label.as_ref() + let demangled = label + .as_ref() .and_then(|l| demangle::demangle(l).map(|d| d.raw_demangled)); out.push((v.address, slot as u32, fn_va, label, demangled)); } @@ -572,6 +665,71 @@ pub fn classes_table(vtables: &[Vtable]) -> Vec<(String, u32, bool, Option`. [`crate::rtti`] +/// resolves the same question top-down from the structures the linker emitted, +/// which is exact — so wherever the two disagree, RTTI wins. Rows RTTI knows +/// nothing about keep their heuristic name. +/// +/// `base_classes_json` is rebuilt here as the class's full linearised base list +/// (excluding index 0, which is the class itself), which is strictly more than +/// the first-level list the inline walk produced. +/// +/// Returns the number of vtables that gained a real class name. +pub fn apply_rtti_names(vtables: &mut [Vtable], rtti: &crate::rtti::RttiResult) -> usize { + use std::collections::BTreeMap; + + let names = rtti.vtable_class_names(); + let locator_by_vtable: BTreeMap = rtti + .locators + .iter() + .filter_map(|c| c.vtable_address.map(|v| (v, c))) + .collect(); + + // class-hierarchy VA → base class names, in the linker's order. + let mut bases_by_chd: BTreeMap> = BTreeMap::new(); + for b in &rtti.base_classes { + if b.index == 0 { + continue; + } // index 0 is the class itself + bases_by_chd + .entry(b.class_hierarchy) + .or_default() + .push(b.name.as_str()); + } + + let mut named = 0usize; + for vt in vtables.iter_mut() { + let Some((class_name, offset)) = names.get(&vt.address) else { + continue; + }; + // A secondary-base vftable belongs to the same class but is a distinct + // table; keep them apart by suffixing the subobject offset. + vt.class_name = if *offset == 0 { + class_name.clone() + } else { + format!("{class_name}#base+0x{offset:X}") + }; + vt.rtti_present = true; + if let Some(col) = locator_by_vtable.get(&vt.address) { + vt.col_address = Some(col.address); + vt.base_classes_json = bases_by_chd.get(&col.class_hierarchy).map(|names| { + let items: Vec = names + .iter() + .map(|n| format!("\"{}\"", n.replace('\\', "\\\\").replace('"', "\\\""))) + .collect(); + format!("[{}]", items.join(",")) + }); + } + named += 1; + } + named +} + #[cfg(test)] mod tests { use super::*; @@ -603,7 +761,11 @@ mod tests { let mut pe = vec![0u8; total]; // Vtable: 3 method PCs at .rdata start, all valid function entries. - let m: [u32; 3] = [image_base + text_va, image_base + text_va + 0x10, image_base + text_va + 0x20]; + let m: [u32; 3] = [ + image_base + text_va, + image_base + text_va + 0x10, + image_base + text_va + 0x20, + ]; for (i, val) in m.iter().enumerate() { pe[rdata_va as usize + i * 4..rdata_va as usize + (i + 1) * 4] .copy_from_slice(&val.to_be_bytes()); @@ -628,7 +790,9 @@ mod tests { }, ]; let mut function_starts = std::collections::BTreeSet::new(); - for &pc in &m { function_starts.insert(pc); } + for &pc in &m { + function_starts.insert(pc); + } let vtables = analyze(&pe, image_base, §ions, &function_starts); assert_eq!(vtables.len(), 1); @@ -680,7 +844,9 @@ mod tests { }, ]; let mut function_starts = std::collections::BTreeSet::new(); - for &pc in &[f0, f1, f2] { function_starts.insert(pc); } + for &pc in &[f0, f1, f2] { + function_starts.insert(pc); + } // Without an anchor: the head gap (null + nonfn = 2 slots) means the // contiguous run is only [f0,f1,f2]=3 starting at +0x08, so pass-1 @@ -715,13 +881,13 @@ mod tests { let mut pe = vec![0u8; 0x4000]; // Lay out a tiny .rdata at 0x...A900 so the constant lands in-range. let vt_base = 0x8200A908u32; // 0x82010000 - 22264 - let addis = (15u32 << 26) | (11 << 21) | (0 << 16) | 0x8201; + let addis = ((15u32 << 26) | (11 << 21)) | 0x8201; let lo = (vt_base & 0xFFFF) as i16; // -22264 - let addi = (14u32 << 26) | (11 << 21) | (0 << 16) | ((lo as u16) as u32); + let addi = ((14u32 << 26) | (11 << 21)) | ((lo as u16) as u32); // addi r11,r0,lo would set r11=lo (sign-extended); we need addis+addi // chained. Re-encode addis into r11 from r0, then addi r11,r11,lo. let addi2 = (14u32 << 26) | (11 << 21) | (11 << 16) | ((lo as u16) as u32); - let stw = (36u32 << 26) | (11 << 21) | (31 << 16) | 0; // stw r11,0(r31) + let stw = (36u32 << 26) | (11 << 21) | (31 << 16); // stw r11,0(r31) let at = (ctor - image_base) as usize; pe[at..at + 4].copy_from_slice(&addis.to_be_bytes()); pe[at + 4..at + 8].copy_from_slice(&addi2.to_be_bytes()); @@ -736,12 +902,20 @@ mod tests { raw_size: 0x200, flags: 0x4000_0040, }]; - let mut funcs: std::collections::BTreeMap = std::collections::BTreeMap::new(); + let mut funcs: std::collections::BTreeMap = + std::collections::BTreeMap::new(); funcs.insert(ctor, (ctor + 0x40, false)); let anchors = scan_vptr_write_constants( - &pe, image_base, &funcs, §ions, &std::collections::HashSet::new(), + &pe, + image_base, + &funcs, + §ions, + &std::collections::HashSet::new(), + ); + assert!( + anchors.contains(&vt_base), + "ctor vptr store must yield anchor {vt_base:#x}, got {anchors:?}" ); - assert!(anchors.contains(&vt_base), "ctor vptr store must yield anchor {vt_base:#x}, got {anchors:?}"); } #[test] @@ -776,66 +950,14 @@ mod tests { }, ]; let mut function_starts = std::collections::BTreeSet::new(); - for &pc in &m { function_starts.insert(pc); } - let vtables = analyze(&pe, image_base, §ions, &function_starts); - assert_eq!(vtables.len(), 0, "runs of 2 must be rejected to keep false-positive rate down"); - } -} - -// ── RTTI relabelling ─────────────────────────────────────────────────────── - -/// Overwrite heuristic vtable identity with the authoritative RTTI walk. -/// -/// [`analyze_with_anchors`] names a table either from its own inline COL walk -/// or, failing that, with a synthetic `ANON_Class_`. [`crate::rtti`] -/// resolves the same question top-down from the structures the linker emitted, -/// which is exact — so wherever the two disagree, RTTI wins. Rows RTTI knows -/// nothing about keep their heuristic name. -/// -/// `base_classes_json` is rebuilt here as the class's full linearised base list -/// (excluding index 0, which is the class itself), which is strictly more than -/// the first-level list the inline walk produced. -/// -/// Returns the number of vtables that gained a real class name. -pub fn apply_rtti_names(vtables: &mut [Vtable], rtti: &crate::rtti::RttiResult) -> usize { - use std::collections::BTreeMap; - - let names = rtti.vtable_class_names(); - let locator_by_vtable: BTreeMap = rtti - .locators - .iter() - .filter_map(|c| c.vtable_address.map(|v| (v, c))) - .collect(); - - // class-hierarchy VA → base class names, in the linker's order. - let mut bases_by_chd: BTreeMap> = BTreeMap::new(); - for b in &rtti.base_classes { - if b.index == 0 { continue; } // index 0 is the class itself - bases_by_chd.entry(b.class_hierarchy).or_default().push(b.name.as_str()); - } - - let mut named = 0usize; - for vt in vtables.iter_mut() { - let Some((class_name, offset)) = names.get(&vt.address) else { continue }; - // A secondary-base vftable belongs to the same class but is a distinct - // table; keep them apart by suffixing the subobject offset. - vt.class_name = if *offset == 0 { - class_name.clone() - } else { - format!("{class_name}#base+0x{offset:X}") - }; - vt.rtti_present = true; - if let Some(col) = locator_by_vtable.get(&vt.address) { - vt.col_address = Some(col.address); - vt.base_classes_json = bases_by_chd.get(&col.class_hierarchy).map(|names| { - let items: Vec = names - .iter() - .map(|n| format!("\"{}\"", n.replace('\\', "\\\\").replace('"', "\\\""))) - .collect(); - format!("[{}]", items.join(",")) - }); + for &pc in &m { + function_starts.insert(pc); } - named += 1; + let vtables = analyze(&pe, image_base, §ions, &function_starts); + assert_eq!( + vtables.len(), + 0, + "runs of 2 must be rejected to keep false-positive rate down" + ); } - named } diff --git a/crates/sylpheed-xexdb/src/xdbf.rs b/crates/sylpheed-xexdb/src/xdbf.rs index 9b2be0b0..6f2ed96a 100644 --- a/crates/sylpheed-xexdb/src/xdbf.rs +++ b/crates/sylpheed-xexdb/src/xdbf.rs @@ -119,7 +119,10 @@ fn be16(b: &[u8], o: usize) -> Option { } fn be32(b: &[u8], o: usize) -> Option { Some(u32::from_be_bytes([ - *b.get(o)?, *b.get(o + 1)?, *b.get(o + 2)?, *b.get(o + 3)?, + *b.get(o)?, + *b.get(o + 1)?, + *b.get(o + 2)?, + *b.get(o + 3)?, ])) } fn be64(b: &[u8], o: usize) -> Option { @@ -188,9 +191,12 @@ pub fn analyze(image: &[u8], base: usize) -> Option { for i in 0..entry_used { let p = entry_table + i * 18; - let (Some(namespace), Some(id), Some(off), Some(size)) = - (be16(image, p), be64(image, p + 2), be32(image, p + 10), be32(image, p + 14)) - else { + let (Some(namespace), Some(id), Some(off), Some(size)) = ( + be16(image, p), + be64(image, p + 2), + be32(image, p + 10), + be32(image, p + 14), + ) else { continue; }; let body = data_start + off as usize; @@ -212,7 +218,11 @@ pub fn analyze(image: &[u8], base: usize) -> Option { id, offset: body, size, - format: if image[body..].starts_with(b"\x89PNG") { "png" } else { "unknown" }, + format: if image[body..].starts_with(b"\x89PNG") { + "png" + } else { + "unknown" + }, }), NS_STRING_TABLE => { if let Some(t) = parse_string_table(image, body, size, id as u32) { @@ -220,7 +230,9 @@ pub fn analyze(image: &[u8], base: usize) -> Option { } } NS_METADATA => match magic.as_deref() { - Some("XACH") => out.achievements.extend(parse_achievements(image, body, size)), + Some("XACH") => out + .achievements + .extend(parse_achievements(image, body, size)), Some("XTHD") => out.title = parse_title_header(image, body), Some("XSTC") => out.default_language = be32(image, body + 12), _ => {} @@ -244,16 +256,21 @@ pub fn analyze(image: &[u8], base: usize) -> Option { /// `XACH`: `magic, version, size, count u16`, then 36-byte records. fn parse_achievements(image: &[u8], body: usize, size: usize) -> Vec { - let Some(count) = be16(image, body + 12) else { return Vec::new() }; + let Some(count) = be16(image, body + 12) else { + return Vec::new(); + }; let mut out = Vec::with_capacity(count as usize); for i in 0..count as usize { let p = body + 14 + i * 36; if p + 36 > body + size { break; } - let (Some(id), Some(label_id), Some(description_id), Some(unachieved_id)) = - (be16(image, p), be16(image, p + 2), be16(image, p + 4), be16(image, p + 6)) - else { + let (Some(id), Some(label_id), Some(description_id), Some(unachieved_id)) = ( + be16(image, p), + be16(image, p + 2), + be16(image, p + 4), + be16(image, p + 6), + ) else { break; }; out.push(Achievement { @@ -273,7 +290,12 @@ fn parse_achievements(image: &[u8], body: usize, size: usize) -> Vec Option { +fn parse_string_table( + image: &[u8], + body: usize, + size: usize, + language: u32, +) -> Option { if fourcc(be32(image, body)?)? != "XSTR" { return None; } @@ -282,7 +304,9 @@ fn parse_string_table(image: &[u8], body: usize, size: usize, language: u32) -> let mut p = body + 14; let mut strings = Vec::with_capacity(count as usize); for _ in 0..count { - let (Some(id), Some(len)) = (be16(image, p), be16(image, p + 2)) else { break }; + let (Some(id), Some(len)) = (be16(image, p), be16(image, p + 2)) else { + break; + }; let s = p + 4; let e = s + len as usize; if e > end || e > image.len() { @@ -328,12 +352,12 @@ mod tests { xach.extend(0u32.to_be_bytes()); xach.extend(1u16.to_be_bytes()); // count let mut rec = Vec::new(); - rec.extend(7u16.to_be_bytes()); // id + rec.extend(7u16.to_be_bytes()); // id rec.extend(100u16.to_be_bytes()); // label rec.extend(101u16.to_be_bytes()); // description rec.extend(102u16.to_be_bytes()); // unachieved - rec.extend(9u32.to_be_bytes()); // image id - rec.extend(20u16.to_be_bytes()); // gamerscore + rec.extend(9u32.to_be_bytes()); // image id + rec.extend(20u16.to_be_bytes()); // gamerscore rec.extend(0u16.to_be_bytes()); rec.extend(0x0Cu32.to_be_bytes()); // flags rec.extend([0u8; 16]); @@ -358,7 +382,7 @@ mod tests { xthd.extend(1u32.to_be_bytes()); xthd.extend(0u32.to_be_bytes()); xthd.extend(0x5351_07D4u32.to_be_bytes()); // title id - xthd.extend(1u32.to_be_bytes()); // type = full + xthd.extend(1u32.to_be_bytes()); // type = full xthd.extend(1u16.to_be_bytes()); xthd.extend(2u16.to_be_bytes()); xthd.extend(3u16.to_be_bytes()); @@ -418,7 +442,11 @@ mod tests { assert_eq!(t.language, 1); assert_eq!(t.strings[0], (100, "Space Combat Award".to_string())); // The achievement's label resolves through the table. - let name = t.strings.iter().find(|(i, _)| *i == a.label_id).map(|(_, s)| s.as_str()); + let name = t + .strings + .iter() + .find(|(i, _)| *i == a.label_id) + .map(|(_, s)| s.as_str()); assert_eq!(name, Some("Space Combat Award")); } diff --git a/crates/sylpheed-xexdb/src/xref.rs b/crates/sylpheed-xexdb/src/xref.rs index 9bd4bfe5..818d8456 100644 --- a/crates/sylpheed-xexdb/src/xref.rs +++ b/crates/sylpheed-xexdb/src/xref.rs @@ -1,8 +1,8 @@ //! Cross-reference analysis for Xbox 360 PE images. +use crate::func::FuncAnalysis; use std::collections::HashMap; use sylpheed_xex::pe::PeSection; -use crate::func::FuncAnalysis; // ── Cross-reference types ──────────────────────────────────────────────── @@ -21,19 +21,22 @@ pub enum XrefKind { impl XrefKind { pub fn tag(self) -> &'static str { match self { - XrefKind::Call => "call", + XrefKind::Call => "call", XrefKind::IndirectCall => "ind_call", - XrefKind::JumpTable => "jt", - XrefKind::Jump => "j", - XrefKind::Branch => "br", - XrefKind::DataRead => "read", - XrefKind::DataWrite => "write", - XrefKind::DataRef => "ref", + XrefKind::JumpTable => "jt", + XrefKind::Jump => "j", + XrefKind::Branch => "br", + XrefKind::DataRead => "read", + XrefKind::DataWrite => "write", + XrefKind::DataRef => "ref", } } pub fn is_data(self) -> bool { - matches!(self, XrefKind::DataRead | XrefKind::DataWrite | XrefKind::DataRef) + matches!( + self, + XrefKind::DataRead | XrefKind::DataWrite | XrefKind::DataRef + ) } pub fn db_tag(self) -> &'static str { @@ -73,14 +76,14 @@ pub enum AddrMode { impl AddrMode { pub fn tag(self) -> &'static str { match self { - AddrMode::DForm => "d_form", - AddrMode::LisAddi => "lis_addi", - AddrMode::LisOri => "lis_ori", - AddrMode::Multiword => "multiword", + AddrMode::DForm => "d_form", + AddrMode::LisAddi => "lis_addi", + AddrMode::LisOri => "lis_ori", + AddrMode::Multiword => "multiword", AddrMode::XFormIndexed => "x_form_indexed", AddrMode::XFormByteRev => "x_form_byterev", - AddrMode::Atomic => "atomic", - AddrMode::DCBZ => "dcbz", + AddrMode::Atomic => "atomic", + AddrMode::DCBZ => "dcbz", } } } @@ -113,7 +116,12 @@ pub fn analyze_xrefs( import_map: &HashMap, ) -> XrefResult { analyze_xrefs_skipping( - pe, image_base, entry_point, sections, func_analysis, import_map, + pe, + image_base, + entry_point, + sections, + func_analysis, + import_map, &std::collections::BTreeSet::new(), ) } @@ -151,7 +159,9 @@ pub fn analyze_xrefs_skipping( let mut xrefs: XrefMap = HashMap::new(); for section in sections { - if !section.is_code() { continue; } + if !section.is_code() { + continue; + } let va_start = section.virtual_address; let va_end = va_start + section.virtual_size; let file_start = section.virtual_address as usize; @@ -160,10 +170,10 @@ pub fn analyze_xrefs_skipping( while addr < va_end { let abs_addr = image_base + addr; let off = (addr - va_start) as usize + file_start; - if off + 4 > pe.len() { break; } - let instr = u32::from_be_bytes([ - pe[off], pe[off+1], pe[off+2], pe[off+3] - ]); + if off + 4 > pe.len() { + break; + } + let instr = u32::from_be_bytes([pe[off], pe[off + 1], pe[off + 2], pe[off + 3]]); if !data_words.contains(&abs_addr) { collect_branch_target(instr, abs_addr, &mut labels, &mut xrefs); @@ -176,13 +186,20 @@ pub fn analyze_xrefs_skipping( let mut data_annotations: HashMap = HashMap::new(); // Build set of valid data address ranges for filtering false positives - let data_ranges: Vec<(u32, u32)> = sections.iter() - .map(|s| (image_base + s.virtual_address, - image_base + s.virtual_address + s.virtual_size)) + let data_ranges: Vec<(u32, u32)> = sections + .iter() + .map(|s| { + ( + image_base + s.virtual_address, + image_base + s.virtual_address + s.virtual_size, + ) + }) .collect(); for section in sections { - if !section.is_code() { continue; } + if !section.is_code() { + continue; + } let va_start = section.virtual_address; let va_end = va_start + section.virtual_size; let file_start = section.virtual_address as usize; @@ -194,10 +211,10 @@ pub fn analyze_xrefs_skipping( while addr < va_end { let abs_addr = image_base + addr; let off = (addr - va_start) as usize + file_start; - if off + 4 > pe.len() { break; } - let instr = u32::from_be_bytes([ - pe[off], pe[off+1], pe[off+2], pe[off+3] - ]); + if off + 4 > pe.len() { + break; + } + let instr = u32::from_be_bytes([pe[off], pe[off + 1], pe[off + 2], pe[off + 3]]); // A jump-table word is not an instruction. Skip it, and drop the // tracked constants with it: the words around it belong to @@ -217,9 +234,11 @@ pub fn analyze_xrefs_skipping( // Reset tracking on function boundaries (prologue = mfspr rN, LR) if opcode == 31 { let xo = (instr >> 1) & 0x3FF; - if xo == 339 { // mfspr + if xo == 339 { + // mfspr let spr = (((instr >> 16) & 0x1F) << 5) | ((instr >> 11) & 0x1F); - if spr == 8 { // LR + if spr == 8 { + // LR reg_hi = [None; 32]; } } @@ -245,10 +264,13 @@ pub fn analyze_xrefs_skipping( if is_in_ranges(data_addr, &data_ranges) { data_annotations.insert(abs_addr, (data_addr, XrefKind::DataRef)); xrefs.entry(data_addr).or_default().push(Xref { - source: abs_addr, kind: XrefKind::DataRef, + source: abs_addr, + kind: XrefKind::DataRef, addr_mode: Some(AddrMode::LisAddi), }); - labels.entry(data_addr).or_insert_with(|| format!("dat_{data_addr:08X}")); + labels + .entry(data_addr) + .or_insert_with(|| format!("dat_{data_addr:08X}")); } reg_hi[rd] = Some(data_addr); // propagate for chained access } else { @@ -263,10 +285,13 @@ pub fn analyze_xrefs_skipping( if is_in_ranges(data_addr, &data_ranges) { data_annotations.insert(abs_addr, (data_addr, XrefKind::DataRef)); xrefs.entry(data_addr).or_default().push(Xref { - source: abs_addr, kind: XrefKind::DataRef, + source: abs_addr, + kind: XrefKind::DataRef, addr_mode: Some(AddrMode::LisOri), }); - labels.entry(data_addr).or_insert_with(|| format!("dat_{data_addr:08X}")); + labels + .entry(data_addr) + .or_insert_with(|| format!("dat_{data_addr:08X}")); } reg_hi[ra] = Some(data_addr); } else { @@ -276,17 +301,21 @@ pub fn analyze_xrefs_skipping( // Load instructions: lwz, lbz, lhz, lha, lfs, lfd, lwzu, etc. 32 | 33 | 34 | 35 | 40 | 41 | 42 | 43 | 48 | 49 | 50 | 51 => { if ra != 0 - && let Some(base) = reg_hi[ra] { - let data_addr = base.wrapping_add(simm as u32); - if is_in_ranges(data_addr, &data_ranges) { - data_annotations.insert(abs_addr, (data_addr, XrefKind::DataRead)); - xrefs.entry(data_addr).or_default().push(Xref { - source: abs_addr, kind: XrefKind::DataRead, - addr_mode: Some(AddrMode::DForm), - }); - labels.entry(data_addr).or_insert_with(|| format!("dat_{data_addr:08X}")); - } + && let Some(base) = reg_hi[ra] + { + let data_addr = base.wrapping_add(simm as u32); + if is_in_ranges(data_addr, &data_ranges) { + data_annotations.insert(abs_addr, (data_addr, XrefKind::DataRead)); + xrefs.entry(data_addr).or_default().push(Xref { + source: abs_addr, + kind: XrefKind::DataRead, + addr_mode: Some(AddrMode::DForm), + }); + labels + .entry(data_addr) + .or_insert_with(|| format!("dat_{data_addr:08X}")); } + } // Load into rD may clobber the tracked value reg_hi[rd] = None; } @@ -302,10 +331,13 @@ pub fn analyze_xrefs_skipping( if is_in_ranges(addr_w, &data_ranges) { data_annotations.insert(abs_addr, (addr_w, XrefKind::DataRead)); xrefs.entry(addr_w).or_default().push(Xref { - source: abs_addr, kind: XrefKind::DataRead, + source: abs_addr, + kind: XrefKind::DataRead, addr_mode: Some(AddrMode::Multiword), }); - labels.entry(addr_w).or_insert_with(|| format!("dat_{addr_w:08X}")); + labels + .entry(addr_w) + .or_insert_with(|| format!("dat_{addr_w:08X}")); } addr_w = addr_w.wrapping_add(4); } @@ -315,17 +347,21 @@ pub fn analyze_xrefs_skipping( // Store instructions: stw, stb, sth, stfs, stfd, stwu, etc. 36 | 37 | 38 | 39 | 44 | 45 | 52 | 53 | 54 | 55 => { if ra != 0 - && let Some(base) = reg_hi[ra] { - let data_addr = base.wrapping_add(simm as u32); - if is_in_ranges(data_addr, &data_ranges) { - data_annotations.insert(abs_addr, (data_addr, XrefKind::DataWrite)); - xrefs.entry(data_addr).or_default().push(Xref { - source: abs_addr, kind: XrefKind::DataWrite, - addr_mode: Some(AddrMode::DForm), - }); - labels.entry(data_addr).or_insert_with(|| format!("dat_{data_addr:08X}")); - } + && let Some(base) = reg_hi[ra] + { + let data_addr = base.wrapping_add(simm as u32); + if is_in_ranges(data_addr, &data_ranges) { + data_annotations.insert(abs_addr, (data_addr, XrefKind::DataWrite)); + xrefs.entry(data_addr).or_default().push(Xref { + source: abs_addr, + kind: XrefKind::DataWrite, + addr_mode: Some(AddrMode::DForm), + }); + labels + .entry(data_addr) + .or_insert_with(|| format!("dat_{data_addr:08X}")); } + } } // stmw rS, simm(rA) — D-form multi-word store. Writes // (32-rS) consecutive 4-byte words from rS..r31 to @@ -339,10 +375,13 @@ pub fn analyze_xrefs_skipping( if is_in_ranges(addr_w, &data_ranges) { data_annotations.insert(abs_addr, (addr_w, XrefKind::DataWrite)); xrefs.entry(addr_w).or_default().push(Xref { - source: abs_addr, kind: XrefKind::DataWrite, + source: abs_addr, + kind: XrefKind::DataWrite, addr_mode: Some(AddrMode::Multiword), }); - labels.entry(addr_w).or_insert_with(|| format!("dat_{addr_w:08X}")); + labels + .entry(addr_w) + .or_insert_with(|| format!("dat_{addr_w:08X}")); } addr_w = addr_w.wrapping_add(4); } @@ -374,8 +413,8 @@ pub fn analyze_xrefs_skipping( 662 => Some((AddrMode::XFormByteRev, XrefKind::DataWrite)), // stwbrx 918 => Some((AddrMode::XFormByteRev, XrefKind::DataWrite)), // sthbrx // Byte-reverse loads - 534 => Some((AddrMode::XFormByteRev, XrefKind::DataRead)), // lwbrx - 790 => Some((AddrMode::XFormByteRev, XrefKind::DataRead)), // lhbrx + 534 => Some((AddrMode::XFormByteRev, XrefKind::DataRead)), // lwbrx + 790 => Some((AddrMode::XFormByteRev, XrefKind::DataRead)), // lhbrx // dcbz — cache-line zero (32-byte clear). Treat as a write. 1014 => Some((AddrMode::DCBZ, XrefKind::DataWrite)), // Plain X-form indexed stores (the common ones) @@ -388,32 +427,32 @@ pub fn analyze_xrefs_skipping( 149 => Some((AddrMode::XFormIndexed, XrefKind::DataWrite)), // stdx 181 => Some((AddrMode::XFormIndexed, XrefKind::DataWrite)), // stdux // Plain X-form indexed loads - 23 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lwzx - 87 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lbzx - 279 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lhzx - 343 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lhax - 55 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lwzux - 119 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lbzux - 311 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lhzux - 375 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lhaux - 21 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // ldx - 53 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // ldux + 23 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lwzx + 87 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lbzx + 279 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lhzx + 343 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lhax + 55 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lwzux + 119 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lbzux + 311 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lhzux + 375 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lhaux + 21 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // ldx + 53 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // ldux // AltiVec/VMX (opcode 31) loads & stores. Element // variants store one byte/halfword/word; full // `stvx` stores 16 bytes. Address resolution still // requires both rA and rB constant — common only // in static-table setup loops. - 231 => Some((AddrMode::XFormIndexed, XrefKind::DataWrite)), // stvx - 487 => Some((AddrMode::XFormIndexed, XrefKind::DataWrite)), // stvxl - 135 => Some((AddrMode::XFormIndexed, XrefKind::DataWrite)), // stvebx - 167 => Some((AddrMode::XFormIndexed, XrefKind::DataWrite)), // stvehx - 199 => Some((AddrMode::XFormIndexed, XrefKind::DataWrite)), // stvewx + 231 => Some((AddrMode::XFormIndexed, XrefKind::DataWrite)), // stvx + 487 => Some((AddrMode::XFormIndexed, XrefKind::DataWrite)), // stvxl + 135 => Some((AddrMode::XFormIndexed, XrefKind::DataWrite)), // stvebx + 167 => Some((AddrMode::XFormIndexed, XrefKind::DataWrite)), // stvehx + 199 => Some((AddrMode::XFormIndexed, XrefKind::DataWrite)), // stvewx // AltiVec/VMX loads — same XO range, kind=read. - 103 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lvx - 359 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lvxl - 7 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lvebx - 39 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lvehx - 71 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lvewx + 103 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lvx + 359 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lvxl + 7 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lvebx + 39 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lvehx + 71 => Some((AddrMode::XFormIndexed, XrefKind::DataRead)), // lvewx _ => None, } }; @@ -423,10 +462,13 @@ pub fn analyze_xrefs_skipping( { data_annotations.insert(abs_addr, (data_addr, kind)); xrefs.entry(data_addr).or_default().push(Xref { - source: abs_addr, kind, + source: abs_addr, + kind, addr_mode: Some(addr_mode), }); - labels.entry(data_addr).or_insert_with(|| format!("dat_{data_addr:08X}")); + labels + .entry(data_addr) + .or_insert_with(|| format!("dat_{data_addr:08X}")); } // Fall through: any X-form op may write rD; invalidate. reg_hi[rd] = None; @@ -435,7 +477,8 @@ pub fn analyze_xrefs_skipping( _ => { // Conservatively invalidate for instructions that modify rD // (most ALU ops, loads, etc.) - if opcode != 18 && opcode != 16 && opcode != 17 { // skip branch/sc + if opcode != 18 && opcode != 16 && opcode != 17 { + // skip branch/sc reg_hi[rd] = None; } } @@ -456,10 +499,19 @@ pub fn analyze_xrefs_skipping( "xref analysis complete" ); - XrefResult { labels, xrefs, data_annotations } + XrefResult { + labels, + xrefs, + data_annotations, + } } -fn collect_branch_target(instr: u32, addr: u32, labels: &mut HashMap, xrefs: &mut XrefMap) { +fn collect_branch_target( + instr: u32, + addr: u32, + labels: &mut HashMap, + xrefs: &mut XrefMap, +) { let op = (instr >> 26) & 0x3F; match op { 18 => { @@ -467,18 +519,38 @@ fn collect_branch_target(instr: u32, addr: u32, labels: &mut HashMap { // B-form: bc/bcl let bd = sign_ext16(instr & 0xFFFC); let aa = instr & 2 != 0; - let target = if aa { bd as u32 } else { addr.wrapping_add(bd as u32) }; - labels.entry(target).or_insert_with(|| format!("loc_{target:08X}")); - xrefs.entry(target).or_default().push(Xref { source: addr, kind: XrefKind::Branch, addr_mode: None }); + let target = if aa { + bd as u32 + } else { + addr.wrapping_add(bd as u32) + }; + labels + .entry(target) + .or_insert_with(|| format!("loc_{target:08X}")); + xrefs.entry(target).or_default().push(Xref { + source: addr, + kind: XrefKind::Branch, + addr_mode: None, + }); } _ => {} } @@ -493,7 +565,9 @@ fn sign_ext26(val: u32) -> i32 { } fn is_in_ranges(addr: u32, ranges: &[(u32, u32)]) -> bool { - ranges.iter().any(|&(start, end)| addr >= start && addr < end) + ranges + .iter() + .any(|&(start, end)| addr >= start && addr < end) } /// Find which section a data address falls in. @@ -521,10 +595,11 @@ pub fn resolve_source_label( // Find the containing function (largest start <= addr) if let Some((&func_start, _fi)) = func_analysis.functions.range(..=addr).next_back() - && let Some(func_label) = labels.get(&func_start) { - let offset = addr - func_start; - return format!("{func_label}+0x{offset:X}"); - } + && let Some(func_label) = labels.get(&func_start) + { + let offset = addr - func_start; + return format!("{func_label}+0x{offset:X}"); + } format!("0x{addr:08X}") } @@ -546,18 +621,30 @@ mod tests { AddrMode::DCBZ, ]; let tags: std::collections::HashSet<&str> = modes.iter().map(|m| m.tag()).collect(); - assert_eq!(tags.len(), modes.len(), "every AddrMode variant must have a unique tag"); + assert_eq!( + tags.len(), + modes.len(), + "every AddrMode variant must have a unique tag" + ); } #[test] fn xref_struct_carries_addr_mode_for_data_edges() { - let x = Xref { source: 0x1234, kind: XrefKind::DataWrite, addr_mode: Some(AddrMode::DForm) }; + let x = Xref { + source: 0x1234, + kind: XrefKind::DataWrite, + addr_mode: Some(AddrMode::DForm), + }; assert_eq!(x.addr_mode.unwrap().tag(), "d_form"); } #[test] fn xref_struct_addr_mode_is_none_for_call_edges() { - let x = Xref { source: 0x1234, kind: XrefKind::Call, addr_mode: None }; + let x = Xref { + source: 0x1234, + kind: XrefKind::Call, + addr_mode: None, + }; assert!(x.addr_mode.is_none()); } } diff --git a/crates/sylpheed-xexdb/tests/db_schema_golden.rs b/crates/sylpheed-xexdb/tests/db_schema_golden.rs index ecad1782..303cf12f 100644 --- a/crates/sylpheed-xexdb/tests/db_schema_golden.rs +++ b/crates/sylpheed-xexdb/tests/db_schema_golden.rs @@ -13,15 +13,19 @@ use std::io::Write; use duckdb::Connection; +use sylpheed_xex::pe::PeSection; use sylpheed_xexdb::DbWriter; use sylpheed_xexdb::formatter::DisasmInfo; use sylpheed_xexdb::func::{FuncAnalysis, FuncInfo}; use sylpheed_xexdb::rtti::RttiResult; use sylpheed_xexdb::xref::XrefMap; -use sylpheed_xex::pe::PeSection; /// Build a 16-byte `.text` section: 4 instructions (mflr / nop / blr / nop). -fn synthetic_pe() -> (Vec, Vec, Vec) { +fn synthetic_pe() -> ( + Vec, + Vec, + Vec, +) { // VA layout: image_base + 0x1000 = .text start (so RVA = 0x1000). // The DB writer expects pe[rva] to hold the byte at that RVA, so the // buffer must be at least 0x1000 + section_size bytes long. @@ -30,9 +34,9 @@ fn synthetic_pe() -> (Vec, Vec, Vec = Vec::new(); @@ -397,7 +502,8 @@ fn db_schema_matches_expected_columns() { "{table}: column count mismatch (got {}, expected {})", rows.len(), cols.len() - ).ok(); + ) + .ok(); errs.push(format!("{table}: count {} vs {}", rows.len(), cols.len())); } for (i, (got, expected_col)) in rows.iter().zip(cols.iter()).enumerate() { @@ -410,19 +516,31 @@ fn db_schema_matches_expected_columns() { } } - assert!(errs.is_empty(), "schema drift detected:\n {}", errs.join("\n ")); + assert!( + errs.is_empty(), + "schema drift detected:\n {}", + errs.join("\n ") + ); // Verify row counts in the populated tables. let n_instr: i64 = conn .query_row("SELECT COUNT(*) FROM instructions", [], |r| r.get(0)) .unwrap(); - assert_eq!(n_instr, 4, "expected 4 instruction rows from the synthetic PE"); + assert_eq!( + n_instr, 4, + "expected 4 instruction rows from the synthetic PE" + ); // The synthetic mflr should produce target_hex = NULL, blr likewise (indirect). let n_with_target: i64 = conn - .query_row("SELECT COUNT(target_hex) FROM instructions", [], |r| r.get(0)) + .query_row("SELECT COUNT(target_hex) FROM instructions", [], |r| { + r.get(0) + }) .unwrap(); - assert_eq!(n_with_target, 0, "indirect-only fixture should have no direct branch targets"); + assert_eq!( + n_with_target, 0, + "indirect-only fixture should have no direct branch targets" + ); // SQL views must be queryable. The `_` in SQL LIKE is a single-char // wildcard, so we list the names explicitly rather than `LIKE 'v_%'` diff --git a/crates/sylpheed-xexdb/tests/disasm_goldens.rs b/crates/sylpheed-xexdb/tests/disasm_goldens.rs index 60df0082..022a673a 100644 --- a/crates/sylpheed-xexdb/tests/disasm_goldens.rs +++ b/crates/sylpheed-xexdb/tests/disasm_goldens.rs @@ -41,7 +41,10 @@ fn cpu_fixture(name: &str) -> PathBuf { } fn parse_hex(s: &str) -> u32 { - let trimmed = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")).unwrap_or(s); + let trimmed = s + .strip_prefix("0x") + .or_else(|| s.strip_prefix("0X")) + .unwrap_or(s); u32::from_str_radix(trimmed, 16).expect("hex u32") } @@ -61,8 +64,7 @@ fn check_fixture(fixture_name: &str) { let raw = parse_hex(&row.raw); let addr = parse_hex(&row.addr); - let canonical = - sylpheed_ppc::disasm::format(&sylpheed_ppc::decode(raw, addr)); + let canonical = sylpheed_ppc::disasm::format(&sylpheed_ppc::decode(raw, addr)); let shim = sylpheed_xexdb::ppc::disasm(raw, addr); assert_eq!( @@ -78,13 +80,33 @@ fn check_fixture(fixture_name: &str) { // Also pin against the fixture's structured fields — guards against // someone changing the cpu canon without regenerating the fixture. - assert_eq!(canonical.mnemonic, row.mnemonic, "mnemonic drift: {}", row.label); - assert_eq!(canonical.operands, row.operands, "operands drift: {}", row.label); - assert_eq!(canonical.ext_mnemonic, row.ext_mnemonic, "ext_mnemonic drift: {}", row.label); - assert_eq!(canonical.ext_operands, row.ext_operands, "ext_operands drift: {}", row.label); + assert_eq!( + canonical.mnemonic, row.mnemonic, + "mnemonic drift: {}", + row.label + ); + assert_eq!( + canonical.operands, row.operands, + "operands drift: {}", + row.label + ); + assert_eq!( + canonical.ext_mnemonic, row.ext_mnemonic, + "ext_mnemonic drift: {}", + row.label + ); + assert_eq!( + canonical.ext_operands, row.ext_operands, + "ext_operands drift: {}", + row.label + ); let target_str = canonical.branch_target.map(|t| format!("0x{t:08X}")); - assert_eq!(target_str, row.branch_target, "branch_target drift: {}", row.label); + assert_eq!( + target_str, row.branch_target, + "branch_target drift: {}", + row.label + ); } }