Files
Mike Grimesanditsgrimetime 40885ed138 dat: replace symbol wildcards with explicit archive entries (#3657)
* dat: replace symbol wildcards with explicit archive entries

* dat: match symbol list formatting and ordering

---------

Co-authored-by: itsgrimetime <[email protected]>
2026-10-12 14:54:58 -07:00

158 lines
5.1 KiB
Rust

use melee_dat::{
hsd::{Archive, archive_name},
symbols::SymbolFile,
};
use std::{
collections::{BTreeMap, BTreeSet},
ops::Bound,
path::PathBuf,
};
#[test]
fn symbol_entries_sort_by_case_sensitive_filename_and_numeric_location() {
let repo = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
let symbols = SymbolFile::parse(
&std::fs::read_to_string(repo.join("config/GALE01/dat_symbols.txt"))
.unwrap(),
)
.unwrap();
let entries: Vec<_> = symbols.entries().collect();
let location = |entry: &melee_dat::symbols::Entry| {
let (file, at) = entry.archive.rsplit_once('@').map_or(
(entry.archive.as_str(), 0),
|(file, at)| {
(
file,
u64::from_str_radix(at.strip_prefix("0x").unwrap(), 16)
.unwrap(),
)
},
);
(file.to_owned(), at + 32 + u64::from(entry.address))
};
for pair in entries.windows(2) {
assert!(
location(pair[0]) <= location(pair[1]),
"{} precedes {}",
pair[0],
pair[1]
);
}
}
/// `MELEE_DAT_DIR`, or the project's extracted disc files. Read at run time
/// so that a checkout without the disc still compiles.
fn dat_paths() -> Vec<PathBuf> {
let dir = std::env::var_os("MELEE_DAT_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../orig/GALE01/files")
});
let Ok(entries) = std::fs::read_dir(&dir) else {
return Vec::new();
};
let mut paths: Vec<_> = entries
.filter_map(|e| Some(e.ok()?.path()))
.filter(|p| {
let name = p.file_name().unwrap_or_default().to_string_lossy();
name.ends_with(".dat")
})
.collect();
paths.sort();
paths
}
#[test]
fn size_matches_header() {
let paths = dat_paths();
if paths.is_empty() {
eprintln!("skipping: no DAT files found");
return;
}
for path in paths {
let bytes = std::fs::read(&path).unwrap();
let archives = Archive::parse_packed(&bytes)
.unwrap_or_else(|e| panic!("{}: {e}", path.display()));
let (at, last) = archives.last().expect("no archives");
// The last archive may be padded to 32 bytes like the others
let end = at + last.header.file_size as usize;
assert!(
(end..=end.next_multiple_of(32)).contains(&bytes.len()),
"{}: archives end at {end:#X}, file is {:#X}",
path.display(),
bytes.len()
);
}
}
#[test]
fn explicit_symbols_match_real_archive_locations() {
let paths = dat_paths();
if paths.is_empty() {
assert!(
std::env::var_os("MELEE_DAT_DIR").is_none(),
"no DAT files in MELEE_DAT_DIR"
);
eprintln!("skipping: no DAT files found");
return;
}
let repo = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
let symbols = SymbolFile::parse(
&std::fs::read_to_string(repo.join("config/GALE01/dat_symbols.txt"))
.unwrap(),
)
.unwrap();
let mut entries = BTreeMap::<_, Vec<_>>::new();
for entry in symbols.entries() {
entries
.entry(entry.archive.as_str())
.or_default()
.push(entry);
}
let (mut publics, mut aliases) = (0, 0);
for path in paths {
let bytes = std::fs::read(&path).unwrap();
let file = path.file_name().unwrap().to_str().unwrap();
for (at, archive) in Archive::parse_packed(&bytes).unwrap() {
let name = archive_name(file, at);
let group = entries.remove(name.as_str()).unwrap_or_default();
let boundaries: BTreeSet<_> = archive
.publics
.iter()
.map(|p| p.offset)
.chain(group.iter().map(|e| e.address))
.chain([archive.header.data_size])
.collect();
for entry in group {
let end = boundaries
.range((Bound::Excluded(entry.address), Bound::Unbounded))
.next()
.copied()
.unwrap_or(archive.header.data_size);
assert_eq!(entry.size, Some(end - entry.address), "{entry}");
if let Some((_, public)) = archive
.named_publics()
.find(|(name, _)| *name == entry.name.as_bytes())
{
assert_eq!(entry.address, public.offset, "{entry}");
publics += 1;
} else {
assert!(entry.ty.is_some(), "alias needs a type: {entry}");
assert!(
entry.address < archive.header.data_size,
"{entry}"
);
aliases += 1;
}
}
}
}
assert!(
entries.is_empty(),
"entries reference absent archives: {:?}",
entries.keys()
);
eprintln!("checked {publics} public entries and {aliases} aliases");
}