pub fn encode(data: &[u8]) -> Vec<u8> {
let mut out = data.to_vec();
let len = out.len();
let mut i = 0;
while i + 4 < len {
let opcode = out[i];
if opcode == 0xE8 || opcode == 0xE9 {
let rel = i32::from_le_bytes([
out[i + 1], out[i + 2], out[i + 3], out[i + 4],
]);
let abs = rel.wrapping_add((i as i32).wrapping_add(5));
let abs_bytes = abs.to_le_bytes();
out[i + 1] = abs_bytes[0];
out[i + 2] = abs_bytes[1];
out[i + 3] = abs_bytes[2];
out[i + 4] = abs_bytes[3];
i += 5; } else {
i += 1;
}
}
out
}
pub fn decode(data: &[u8]) -> Vec<u8> {
let mut out = data.to_vec();
let len = out.len();
let mut i = 0;
while i + 4 < len {
let opcode = out[i];
if opcode == 0xE8 || opcode == 0xE9 {
let abs = i32::from_le_bytes([
out[i + 1], out[i + 2], out[i + 3], out[i + 4],
]);
let rel = abs.wrapping_sub((i as i32).wrapping_add(5));
let rel_bytes = rel.to_le_bytes();
out[i + 1] = rel_bytes[0];
out[i + 2] = rel_bytes[1];
out[i + 3] = rel_bytes[2];
out[i + 4] = rel_bytes[3];
i += 5;
} else {
i += 1;
}
}
out
}
pub fn suitability_score(data: &[u8]) -> f64 {
if data.len() < 16 {
return 0.0;
}
let sample = &data[..data.len().min(65536)];
let mut i = 0;
let mut call_jmp_count = 0usize;
while i + 4 < sample.len() {
let b = sample[i];
if b == 0xE8 || b == 0xE9 {
let high = sample[i + 4];
if high == 0x00 || high == 0xFF {
call_jmp_count += 1;
i += 5;
continue;
}
}
i += 1;
}
let density = call_jmp_count as f64 / (sample.len() as f64 / 100.0);
(density / 10.0).min(1.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_roundtrip_empty() {
assert_eq!(encode(&[]), vec![]);
assert_eq!(decode(&[]), vec![]);
}
#[test]
fn test_roundtrip_no_calls() {
let data = vec![0x90u8; 100]; let encoded = encode(&data);
let decoded = decode(&encoded);
assert_eq!(data, decoded);
assert_eq!(data, encoded); }
#[test]
fn test_single_call() {
let mut data = vec![0x90u8; 20];
data[5] = 0xE8;
data[6] = 0x0A; data[7] = 0x00;
data[8] = 0x00;
data[9] = 0x00;
let encoded = encode(&data);
let decoded = decode(&encoded);
assert_eq!(data, decoded, "BCJ roundtrip başarısız");
let abs = i32::from_le_bytes([encoded[6], encoded[7], encoded[8], encoded[9]]);
assert_eq!(abs, 20, "Absolute adres yanlış");
}
#[test]
fn test_same_target_different_positions() {
let mut data = vec![0x90u8; 120];
data[10] = 0xE8;
let rel1 = (100i32 - (10 + 5)).to_le_bytes();
data[11..15].copy_from_slice(&rel1);
data[50] = 0xE8;
let rel2 = (100i32 - (50 + 5)).to_le_bytes();
data[51..55].copy_from_slice(&rel2);
let encoded = encode(&data);
let abs1 = i32::from_le_bytes([encoded[11], encoded[12], encoded[13], encoded[14]]);
let abs2 = i32::from_le_bytes([encoded[51], encoded[52], encoded[53], encoded[54]]);
assert_eq!(abs1, 100, "Call 1 absolute yanlış");
assert_eq!(abs2, 100, "Call 2 absolute yanlış");
let decoded = decode(&encoded);
assert_eq!(data, decoded);
}
#[test]
fn test_notepad_roundtrip() {
let data = std::fs::read("C:/Windows/System32/notepad.exe")
.unwrap_or_else(|_| {
let mut d = vec![0u8; 1000];
d[0] = 0x4D; d[1] = 0x5A; d[100] = 0xE8;
d[101..105].copy_from_slice(&50i32.to_le_bytes());
d
});
let encoded = encode(&data);
let decoded = decode(&encoded);
assert_eq!(data, decoded, "notepad.exe BCJ roundtrip başarısız");
}
#[test]
fn test_suitability_score_exe() {
let data = std::fs::read("C:/Windows/System32/notepad.exe")
.unwrap_or_else(|_| {
let mut d = vec![0x90u8; 5000];
for i in (0..4990).step_by(50) {
d[i] = 0xE8;
d[i+4] = 0x00;
}
d
});
let score = suitability_score(&data);
eprintln!("notepad.exe BCJ suitability: {:.3}", score);
assert!(score > 0.0);
}
}