use crate::error::Error;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Filter {
V4108,
V5200,
V5_3_9,
}
impl Filter {
pub(crate) fn for_version(version: &crate::version::Version) -> Self {
if version.at_least(5, 3, 9) {
Self::V5_3_9
} else if version.at_least(5, 2, 0) {
Self::V5200
} else {
Self::V4108
}
}
pub(crate) fn apply(self, buf: &mut [u8]) -> Result<(), Error> {
match self {
Self::V4108 => apply_4108(buf),
Self::V5200 => apply_5200(buf, false),
Self::V5_3_9 => apply_5200(buf, true),
}
}
}
const CALL: u8 = 0xE8;
const JMP: u8 = 0xE9;
fn apply_4108(buf: &mut [u8]) -> Result<(), Error> {
let mut addr: u32 = 0;
let mut addr_bytes_left: u32 = 0;
let mut addr_offset: u32 = 5;
for slot in buf.iter_mut() {
let byte = *slot;
let mut emit = byte;
if addr_bytes_left == 0 {
if byte == CALL || byte == JMP {
addr = (!addr_offset).wrapping_add(1);
addr_bytes_left = 4;
}
} else {
addr = addr.wrapping_add(u32::from(byte));
#[allow(clippy::cast_possible_truncation)]
{
emit = addr as u8;
}
addr >>= 8;
addr_bytes_left = addr_bytes_left.saturating_sub(1);
}
*slot = emit;
addr_offset = addr_offset.wrapping_add(1);
}
Ok(())
}
fn apply_5200(buf: &mut [u8], flip_high_byte: bool) -> Result<(), Error> {
const BLOCK_SIZE: usize = 0x10000;
let len = buf.len();
let mut i: usize = 0;
while i < len {
let head = match buf.get(i) {
Some(b) => *b,
None => break,
};
if head != CALL && head != JMP {
i = i.saturating_add(1);
continue;
}
let block_offset = i.checked_rem(BLOCK_SIZE).unwrap_or(0);
let block_left = BLOCK_SIZE.saturating_sub(block_offset);
if block_left < 5 {
i = i.saturating_add(1);
continue;
}
let end = match i.checked_add(5) {
Some(end) if end <= len => end,
_ => break,
};
let Some(window) = buf.get_mut(i..end) else {
break;
};
let [_call, b1, b2, b3, b4] = window else {
break;
};
if *b4 == 0x00 || *b4 == 0xff {
let rel_low24 = u32::from(*b1) | (u32::from(*b2) << 8) | (u32::from(*b3) << 16);
let position_after = u32::try_from(end).unwrap_or(u32::MAX);
let addr = position_after & 0x00FF_FFFF;
let rel = rel_low24.wrapping_sub(addr);
#[allow(clippy::cast_possible_truncation)]
{
*b1 = rel as u8;
*b2 = (rel >> 8) as u8;
*b3 = (rel >> 16) as u8;
}
if flip_high_byte && (rel & 0x0080_0000) != 0 {
*b4 = !*b4;
}
}
i = i.saturating_add(5);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn v4108_round_trip_leaves_non_call_bytes_unchanged() {
let mut buf = vec![0u8, 1, 2, 3, 4, 5];
Filter::V4108.apply(&mut buf).unwrap();
assert_eq!(buf, vec![0, 1, 2, 3, 4, 5]);
}
#[test]
fn v5_3_9_skips_high_byte_not_sign() {
let mut buf = vec![CALL, 0x10, 0x20, 0x30, 0x42, 0x99];
Filter::V5_3_9.apply(&mut buf).unwrap();
assert_eq!(buf, vec![CALL, 0x10, 0x20, 0x30, 0x42, 0x99]);
}
#[test]
fn v5_3_9_rewrites_when_high_byte_is_sign() {
let mut buf = vec![CALL, 0x10, 0x20, 0x30, 0x00, 0x99];
Filter::V5_3_9.apply(&mut buf).unwrap();
assert_ne!(buf[..5], [CALL, 0x10, 0x20, 0x30, 0x00]);
assert_eq!(buf[5], 0x99);
}
#[test]
fn v5_3_9_block_spanning_call_skipped() {
let mut buf = vec![0u8; 65540];
if let Some(slot) = buf.get_mut(65535) {
*slot = CALL;
}
let before = buf.clone();
Filter::V5_3_9.apply(&mut buf).unwrap();
assert_eq!(buf, before);
}
#[test]
fn v5200_no_flip_keeps_high_byte_as_is() {
let mut buf = vec![CALL, 0xFF, 0xFF, 0x7F, 0x00];
Filter::V5200.apply(&mut buf).unwrap();
assert_eq!(buf[4], 0x00);
}
}