char create: the CharBaseInfo guard requires every shipped race/class pair and lets a patch add more, as the class-list builder lists every row (#631)
`CharCreateCatalog::load`'s misparse guard demanded each race's `CharBaseInfo` classes equal the shipped set exactly. A patch that adds a race/class row therefore failed the load, and without the catalog the create screen is disabled and the glue preview stands no body. 1.12.1's class-list builder (`0x4706b0`) lists every `CharBaseInfo` row for the selected race, skipping only Dwarf+Mage (`0x4706e3`-`0x4706ec`), so the patched class appears there. The guard, now `shipped_combos_present`, requires every shipped pair to be present and lets extra pairs through, so a misparse that loses a row still refuses. `a_patched_combo_passes_the_guard_and_a_lost_one_does_not` fails under the old exact-match rule. Built and gated on macOS. Fixes #617 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6e9e5e995d
commit
06f9cc93df
@@ -43,8 +43,9 @@ const KNOWN_FILES: [(u8, &str); 8] = [
|
||||
(8, "Troll"),
|
||||
];
|
||||
|
||||
/// The shipped `CharBaseInfo` rows by race, the load-time guard on the 2-byte parse; not the
|
||||
/// playable set, as it holds the dead Dwarf-Mage row ([`UNUSED_COMBOS`]).
|
||||
/// The shipped `CharBaseInfo` rows by race, the load-time guard on the 2-byte parse: each must be
|
||||
/// present, and a patch may add more, as the class-list builder `0x4706b0` lists every row for the
|
||||
/// race. Not the playable set, as it holds the dead Dwarf-Mage row ([`UNUSED_COMBOS`]).
|
||||
const KNOWN_COMBOS: [(u8, &[u8]); 8] = [
|
||||
(1, &[1, 2, 4, 5, 8, 9]), // Human
|
||||
(2, &[1, 3, 4, 7, 9]), // Orc
|
||||
@@ -60,6 +61,24 @@ const KNOWN_COMBOS: [(u8, &[u8]); 8] = [
|
||||
/// literal `race == 3 && class == 8` (Dwarf-Mage), though the row and its outfit are populated.
|
||||
const UNUSED_COMBOS: [(u8, u8); 1] = [(3, 8)];
|
||||
|
||||
/// The `CharBaseInfo` misparse guard: every shipped pair is present; a patch's extra pairs pass.
|
||||
fn shipped_combos_present(combos: &HashSet<(u8, u8)>) -> Result<()> {
|
||||
for (race, classes) in KNOWN_COMBOS {
|
||||
let missing: Vec<u8> = classes
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&c| !combos.contains(&(race, c)))
|
||||
.collect();
|
||||
if !missing.is_empty() {
|
||||
bail!(
|
||||
"CharBaseInfo misparse: race {race} lacks shipped classes {missing:?} \
|
||||
(check the 2-byte race/class layout)"
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// One worn item of a CharStartOutfit row: its ItemDisplayInfo id and its InventoryType.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct StartOutfitItem {
|
||||
@@ -183,15 +202,7 @@ impl CharCreateCatalog {
|
||||
}
|
||||
|
||||
fn self_check(&self) -> Result<()> {
|
||||
for (race, classes) in KNOWN_COMBOS {
|
||||
let got = self.classes_for_race(race);
|
||||
if got != classes {
|
||||
bail!(
|
||||
"CharBaseInfo misparse: race {race} classes {got:?} != known {classes:?} \
|
||||
(check the 2-byte race/class layout)"
|
||||
);
|
||||
}
|
||||
}
|
||||
shipped_combos_present(&self.combos)?;
|
||||
for (race, file) in KNOWN_FILES {
|
||||
if self.race_file(race) != Some(file) {
|
||||
bail!(
|
||||
@@ -518,6 +529,21 @@ mod tests {
|
||||
.is_some_and(|vs| vs.contains(&facial_hair))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_patched_combo_passes_the_guard_and_a_lost_one_does_not() {
|
||||
let mut combos: HashSet<(u8, u8)> = KNOWN_COMBOS
|
||||
.iter()
|
||||
.flat_map(|&(r, cs)| cs.iter().map(move |&c| (r, c)))
|
||||
.collect();
|
||||
assert!(shipped_combos_present(&combos).is_ok());
|
||||
// A patch that adds Human Hunter: 1.12.1's builder lists it, so the load must go on.
|
||||
combos.insert((1, 3));
|
||||
assert!(shipped_combos_present(&combos).is_ok());
|
||||
// A misparse that drops a shipped pair still refuses.
|
||||
combos.remove(&(2, 7));
|
||||
assert!(shipped_combos_present(&combos).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn char_create_catalog_matches_the_5875_dbcs() {
|
||||
let data = crate::wow_data_or_skip!();
|
||||
|
||||
Reference in New Issue
Block a user