#![allow(clippy::unwrap_used)]
mod compare;
mod inputs;
use compare::{difference, flag_words, microsofts};
use dxbc_reader::{Options, disassemble_with, parts};
use inputs::{Game, OracleSet};
use primase_testdata::Tree;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Mutex, PoisonError};
static FOLDER_READS: Mutex<()> = Mutex::new(());
fn read(tree: &Tree, name: &str) -> std::io::Result<Vec<u8>> {
let _turn = tree
.as_folder()
.map(|_| FOLDER_READS.lock().unwrap_or_else(PoisonError::into_inner));
tree.read(name)
}
#[derive(Default)]
struct Tally {
compared: usize,
debug: usize,
differing: Vec<String>,
}
fn one(tree: &Tree, name: &str, flags: &[u32], listing: &impl Fn(&str, u32) -> String) -> Tally {
let mut tally = Tally::default();
let bytes = match read(tree, name) {
Ok(bytes) => bytes,
Err(e) => {
tally.differing.push(format!("{name}: {e}"));
return tally;
}
};
let debug = parts(&bytes).unwrap().iter().any(|c| &c.kind == b"SPDB");
for &word in flags {
let what = format!("{name} flags 0x{word:02x}");
let theirs = match read(tree, &listing(name, word)) {
Ok(theirs) => microsofts(&theirs),
Err(e) => {
tally.differing.push(format!("{what}: {e}"));
continue;
}
};
if debug && word & 0x10 == 0 {
tally.debug += 1;
}
tally.compared += 1;
match disassemble_with(&bytes, &Options::from_flags(word).unwrap()) {
Ok(ours) => tally.differing.extend(difference(&what, &ours, &theirs)),
Err(e) => tally.differing.push(format!("{what}: refused: {e}")),
}
}
tally
}
fn compare(
tree: &Tree,
programs: &[&str],
flags: &[u32],
listing: impl Fn(&str, u32) -> String + Sync,
) -> Tally {
let names: Vec<&str> = tree
.names()
.into_iter()
.filter(|n| n.rsplit_once('.').is_some_and(|(_, e)| programs.contains(&e)))
.collect();
let next = AtomicUsize::new(0);
let mut tallies: Vec<(usize, Tally)> = std::thread::scope(|scope| {
let workers: Vec<_> = (0..primase_testdata::threads().get())
.map(|_| {
scope.spawn(|| {
let mut done = Vec::new();
loop {
let at = next.fetch_add(1, Ordering::Relaxed);
let Some(name) = names.get(at) else {
break;
};
done.push((at, one(tree, name, flags, &listing)));
}
done
})
})
.collect();
workers.into_iter().flat_map(|w| w.join().unwrap()).collect()
});
tallies.sort_by_key(|(at, _)| *at);
let mut tally = Tally::default();
for (_, t) in tallies {
tally.compared += t.compared;
tally.debug += t.debug;
tally.differing.extend(t.differing);
}
assert!(
tally.differing.is_empty(),
"{} of {} listings differ:\n{}",
tally.differing.len(),
tally.compared,
tally.differing.join("\n")
);
tally
}
fn beside(program: &str, flags: u32) -> String {
if flags == 0 {
format!("{program}.asm")
} else {
format!("{program}.flag{flags:02x}.asm")
}
}
#[test]
fn riders_republic_shader_pack() {
let Some(tree) = inputs::oracle_tree(OracleSet::D3dDisassembler, Game::Riders, "shader-pack") else {
return;
};
let tally = compare(&tree, &["dxbc"], &[0], |program, _| {
format!("{}.txt", program.strip_suffix(".dxbc").unwrap())
});
assert_eq!((tally.compared, tally.debug), (2443, 0));
}
#[test]
fn sekiro_programs_under_every_flags_word() {
let Some(tree) = inputs::oracle_tree(OracleSet::D3dDisassembler, Game::Sekiro, "programs") else {
return;
};
let tally = compare(&tree, &["ppo", "vpo"], &flag_words(), beside);
assert_eq!((tally.compared, tally.debug), (9936, 0));
}
#[test]
fn sledding_game_unity_player() {
let Some(tree) = inputs::oracle_tree(OracleSet::D3dDisassembler, Game::Sledding, "corpus-unity") else {
return;
};
let tally = compare(&tree, &["dxbc"], &[0], beside);
assert_eq!((tally.compared, tally.debug), (4854, 16));
}
#[test]
fn fxc_sweep_zi_under_every_flags_word() {
let Some(tree) = inputs::oracle_tree(OracleSet::D3dDisassembler, Game::General, "fxc-sweep-zi") else {
return;
};
let tally = compare(&tree, &["dxbc"], &flag_words(), beside);
assert_eq!((tally.compared, tally.debug), (106_099, 73_808));
}
#[test]
fn fxc_sweep_zod_under_every_flags_word() {
let Some(tree) = inputs::oracle_tree(OracleSet::D3dDisassembler, Game::General, "fxc-sweep-zod") else {
return;
};
let tally = compare(&tree, &["dxbc"], &flag_words(), beside);
assert_eq!((tally.compared, tally.debug), (106_122, 73_824));
}