From be41fa839fe99f152d26048675b290599492f16b Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Mon, 22 Jun 2026 16:02:16 +0400 Subject: fix: harden resource and world state correctness --- crates/fparkan-material/src/lib.rs | 49 +++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 9 deletions(-) (limited to 'crates/fparkan-material/src/lib.rs') diff --git a/crates/fparkan-material/src/lib.rs b/crates/fparkan-material/src/lib.rs index a7ec5d7..2a05f87 100644 --- a/crates/fparkan-material/src/lib.rs +++ b/crates/fparkan-material/src/lib.rs @@ -417,15 +417,8 @@ pub fn resolve_material( { return Ok(resolved); } - if let Some(first) = table.entries.first() { - if let Some(resolved) = load_material_entry( - repository, - archive, - &first.material, - MaterialFallback::FirstEntry, - )? { - return Ok(resolved); - } + if let Some(resolved) = load_first_material_entry(repository, archive)? { + return Ok(resolved); } Err(MaterialError::MissingMaterial( String::from_utf8_lossy(&entry.material.0).into_owned(), @@ -610,6 +603,26 @@ fn load_material_entry( })) } +fn load_first_material_entry( + repository: &dyn ResourceRepository, + archive: fparkan_resource::ArchiveId, +) -> Result, MaterialError> { + let Some(handle) = repository.first_entry(archive)? else { + return Ok(None); + }; + let info = repository.entry_info(handle)?; + if info.key.type_id != Some(MAT0_KIND) { + return Ok(None); + } + let bytes = repository.read(handle)?.into_owned(); + let document = decode_mat0(&bytes, info.attr2)?; + Ok(Some(ResolvedMaterial { + name: info.key.name, + fallback: MaterialFallback::FirstEntry, + document, + })) +} + fn parse_lightmaps(lines: &[&str]) -> Result, MaterialError> { if lines.is_empty() || lines.iter().all(|line| line.trim().is_empty()) { return Ok(Vec::new()); @@ -926,6 +939,24 @@ mod tests { assert_eq!(resolved.fallback, MaterialFallback::FirstEntry); } + #[test] + fn resolve_material_first_entry_uses_material_archive_not_wear_row_zero() { + let repo = material_repo(&[ + material_entry(b"MAT_ARCHIVE_FIRST", &mat0_with_texture(b"TEX_ARCHIVE")), + material_entry(b"MAT_WEAR_FIRST", &mat0_with_texture(b"TEX_WEAR")), + ]); + let table = decode_wear(b"2\n0 MAT_WEAR_FIRST\n1 MISSING\n").expect("wear"); + + let resolved = resolve_material(&repo, &table, 1).expect("resolved"); + + assert_eq!(resolved.name.0, b"MAT_ARCHIVE_FIRST"); + assert_eq!(resolved.fallback, MaterialFallback::FirstEntry); + assert_eq!( + resolved.document.primary_texture().expect("texture").0, + b"TEX_ARCHIVE" + ); + } + #[test] fn resolve_material_empty_texture_means_untextured() { let repo = material_repo(&[material_entry(b"MAT_EMPTY", &mat0_with_texture(b""))]); -- cgit v1.2.3