Files
benilla-octo/crates/benilla-formats/src/loading_screen.rs
T
SamandClaude Fable 5 6b980b9efb the player build: one binary that carries its content
- a player build ships beside the dev one — dev chords and probe surfaces go dark, and a gate enforces what a player can still reach, by medium not by module
- the binary carries its own shaders and its shipped UI, finds the WoW folder by itself, and keeps its local state in one benilla-config folder
- the glue screens get a capture shutter, and the engine stops depending on the protocol crate

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 18:18:02 +07:00

108 lines
4.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! LoadingScreens.dbc loader: resolves a `LoadingScreenID` (the FK in `Map.dbc` field 38, see
//! [`crate::MapCatalog::loading_screen_id`]) to the full-screen load-art **BLP path**.
//!
//! Layout — VERIFIED against build 5875 (raw-byte decode of extracted `DBFilesClient\
//! LoadingScreens.dbc`, 2026-06-02): the WDBC header reports **40 records · 3 fields · 12 B/record**
//! (3 × 4 = 12, and `20 + 40·12 + 2926 == 3426` file bytes). Fields = `(ID, Name string, FileName
//! string)`; field 0 = id, field 2 = BLP path (e.g. `Interface\Glues\LoadingScreens\
//! LoadScreenEasternKingdom.blp`). Open-world art is continent-wide — id 3 = Kalimdor, id 4 =
//! Azeroth/EasternKingdom — the other 38 rows are per-instance. The art is a 512×512 square scene
//! with the WoW logo composited in; the client stretch/crop-fills it to the screen.
use std::collections::HashMap;
use crate::Chain;
use anyhow::{Context, Result};
use benilla_dbc::{FieldType, Schema, SchemaField};
use crate::dbc::{parse, str_at, u32_at};
const LOADING_SCREENS: &str = "DBFilesClient\\LoadingScreens.dbc";
/// Resolved `LoadingScreenID → BLP path` map, built once at startup off the client's
/// LoadingScreens.dbc.
pub struct LoadingScreenCatalog {
paths: HashMap<u32, String>,
}
impl LoadingScreenCatalog {
/// The load-art BLP path for `id` (a `Map.dbc` `LoadingScreenID`), or `None` if absent.
pub fn path(&self, id: u32) -> Option<&str> {
self.paths.get(&id).map(String::as_str)
}
pub fn len(&self) -> usize {
self.paths.len()
}
pub fn is_empty(&self) -> bool {
self.paths.is_empty()
}
}
/// 3 fields: ID (0), Name string (1, unused), FileName string (2 = BLP path).
fn schema() -> Schema {
let mut s = Schema::new("LoadingScreens");
s.add_field(SchemaField::new("ID", FieldType::UInt32));
s.add_field(SchemaField::new("Name", FieldType::String));
s.add_field(SchemaField::new("FileName", FieldType::String));
s
}
/// Read LoadingScreens.dbc off the patch chain into a [`LoadingScreenCatalog`].
pub fn load_loading_screens(chain: &mut Chain) -> Result<LoadingScreenCatalog> {
let bytes = chain
.read_file(LOADING_SCREENS)
.with_context(|| format!("reading {LOADING_SCREENS}"))?;
let rs = parse(&bytes, schema(), "LoadingScreens")?;
let mut paths = HashMap::with_capacity(rs.records().len());
for r in rs.records() {
if let (Some(id), Some(path)) = (u32_at(r, 0), str_at(&rs, r, 2)) {
paths.insert(id, path);
}
}
Ok(LoadingScreenCatalog { paths })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::load_map_catalog;
/// Golden test of the full Map.dbc→LoadingScreens.dbc→BLP FK chain against the real client.
/// Verified pairs (decoded 2026-06-02): mapId 0 (Azeroth) → 4 → EasternKingdom art; mapId 1
/// (Kalimdor) → 3 → Kalimdor art. Skips when the client isn't present.
#[test]
fn resolves_open_world_loading_art() {
let data = crate::wow_data_or_skip!();
let mut chain = crate::open_chain(&data).expect("open chain");
let maps = load_map_catalog(&mut chain).expect("Map.dbc");
let screens = load_loading_screens(&mut chain).expect("LoadingScreens.dbc");
// FK values (Map.dbc field 38).
assert_eq!(maps.loading_screen_id(0), Some(4), "Azeroth → 4");
assert_eq!(maps.loading_screen_id(1), Some(3), "Kalimdor → 3");
// FK → BLP path (LoadingScreens.dbc field 2).
assert_eq!(
screens.path(4),
Some("Interface\\Glues\\LoadingScreens\\LoadScreenEasternKingdom.blp")
);
assert_eq!(
screens.path(3),
Some("Interface\\Glues\\LoadingScreens\\LoadScreenKalimdor.blp")
);
// End-to-end resolution for both continents.
let resolve = |map_id| {
maps.loading_screen_id(map_id)
.and_then(|id| screens.path(id))
};
assert!(resolve(0)
.unwrap()
.ends_with("LoadScreenEasternKingdom.blp"));
assert!(resolve(1).unwrap().ends_with("LoadScreenKalimdor.blp"));
}
}