//! EE RAM: a flat 32 MiB window, plus the content search the locators use. //! //! The harness never hardcodes an address. Every structure the comparison //! needs is found by content, because the addresses differ per level and per //! run, and a pointer read from the wrong base is indistinguishable from a //! right read from the right one. `EeRam` is deliberately dumb: byte-range //! reads and a substring search. /// The PlayStation 2's main RAM: 32 MiB at physical 0 (no extended RAM). pub const EE_SIZE: usize = 32 * 1024 * 1024; /// A borrowed EE RAM image. #[derive(Debug, Clone, Copy)] pub struct EeRam<'a>(&'a [u8]); impl<'a> EeRam<'a> { /// Wrap a RAM image. The length must be exactly 32 MiB, so a short dump is /// an error rather than a search that silently stops early. pub fn new(bytes: &'a [u8]) -> anyhow::Result { if bytes.len() != EE_SIZE { anyhow::bail!( "EE RAM image is {} bytes, expected exactly {EE_SIZE}", bytes.len() ); } Ok(Self(bytes)) } pub fn bytes(&self) -> &'a [u8] { self.0 } /// Every offset at which `needle` occurs, scanning 32 MiB. /// /// A naive first-byte scan keeps this correct for any needle, including /// one inside a buffer that also contains zeros — which is most of RAM. pub fn find_all(&self, needle: &[u8]) -> Vec { if needle.is_empty() || needle.len() > self.0.len() { return Vec::new(); } let first = needle[0]; let mut out = Vec::new(); let mut i = 0usize; while i + needle.len() <= self.0.len() { if self.0[i] == first && &self.0[i..i + needle.len()] == needle { out.push(i); } i += 1; } out } /// The first offset at which `needle` occurs. pub fn find(&self, needle: &[u8]) -> Option { self.find_all(needle).into_iter().next() } pub fn slice(&self, at: usize, len: usize) -> Option<&'a [u8]> { self.0.get(at..at.checked_add(len)?) } /// A little-endian `i32` at `at`. pub fn i32(&self, at: usize) -> Option { self.slice(at, 4) .map(|b| i32::from_le_bytes(b.try_into().unwrap())) } /// A little-endian `u32` at `at`. pub fn u32(&self, at: usize) -> Option { self.slice(at, 4) .map(|b| u32::from_le_bytes(b.try_into().unwrap())) } } #[cfg(test)] mod tests { use super::*; /// Fill with a non-zero byte: a zero-filled image makes any needle ending /// in `0x00` match by accident, which is not what this is testing. fn ram_with(needle: &[u8], at: usize) -> Vec { let mut v = vec![0xaau8; EE_SIZE]; v[at..at + needle.len()].copy_from_slice(needle); v } #[test] fn only_a_32mib_image_is_accepted() { EeRam::new(&[0u8; EE_SIZE]).expect("32 MiB is exactly right"); assert!(EeRam::new(&[0u8; EE_SIZE - 1]).is_err()); assert!(EeRam::new(&[]).is_err()); } #[test] fn find_reports_every_occurrence() { let v = ram_with(&[0xde, 0xad, 0xbe, 0xef], 0x1000); let ram = EeRam::new(&v).unwrap(); assert_eq!(ram.find(&[0xde, 0xad, 0xbe, 0xef]), Some(0x1000)); assert!(ram.find(&[0xde, 0xad, 0xbe, 0xef, 0x00]).is_none()); } #[test] fn an_empty_or_oversized_needle_finds_nothing() { let v = vec![7u8; EE_SIZE]; let ram = EeRam::new(&v).unwrap(); assert!(ram.find(&[]).is_none()); // Longer than the image cannot occur inside it, however uniform the // image is. let too_long = vec![7u8; EE_SIZE + 1]; assert!(ram.find(&too_long).is_none()); } #[test] fn a_needle_at_the_very_end_is_found() { let mut v = vec![0u8; EE_SIZE]; let n = [1u8, 2, 3, 4]; v[EE_SIZE - 4..].copy_from_slice(&n); let ram = EeRam::new(&v).unwrap(); assert_eq!(ram.find(&n), Some(EE_SIZE - 4)); } }