diff --git a/src/mod_lint.rs b/src/mod_lint.rs index b31d6817..9e7c900e 100644 --- a/src/mod_lint.rs +++ b/src/mod_lint.rs @@ -9,6 +9,12 @@ use tracing::{info, span, trace, Level}; use crate::providers::ModSpecification; use crate::{lint_get_all_files_from_data, open_file, GetAllFilesFromDataError, PakOrNotPak}; +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum SplitUasset { + MissingUasset, + MissingUexp, +} + #[derive(Debug, Clone)] pub struct ModLintReport { pub conflicting_mods: BTreeMap)>>, @@ -19,6 +25,7 @@ pub struct ModLintReport { pub archive_with_only_non_pak_files_mods: BTreeSet, pub archive_with_multiple_paks_mods: BTreeSet, pub non_asset_file_mods: BTreeMap>, + pub split_uasset_uexp_mods: BTreeMap>, } pub fn lint(mods: &[(ModSpecification, PathBuf)]) -> Result { @@ -36,6 +43,8 @@ pub fn lint(mods: &[(ModSpecification, PathBuf)]) -> Result { let mut empty_archive_mods = BTreeSet::new(); let mut archive_with_multiple_paks_mods = BTreeSet::new(); let mut non_asset_file_mods = BTreeMap::new(); + let mut path_extensions_map: BTreeMap> = BTreeMap::new(); + let mut split_uasset_uexp_mods = BTreeMap::new(); for (mod_spec, mod_pak_path) in mods { trace!(?mod_spec, ?mod_pak_path); @@ -95,10 +104,7 @@ pub fn lint(mods: &[(ModSpecification, PathBuf)]) -> Result { || lowercase.ends_with("assetregistry.bin") || lowercase.ends_with(".ushaderbytecode")) { - trace!( - "file is not known unreal asset: `{}`", - lowercase - ); + trace!("file is not known unreal asset: `{}`", lowercase); non_asset_file_mods .entry(mod_spec.clone()) .and_modify(|files: &mut BTreeSet| { @@ -107,6 +113,21 @@ pub fn lint(mods: &[(ModSpecification, PathBuf)]) -> Result { .or_insert_with(|| [lowercase.clone()].into()); } + path_extensions_map + .entry(p.clone()) + .and_modify(|extensions| { + if let Some(ext) = p.rsplit('.').next() { + extensions.insert(ext.to_string()); + } + }) + .or_insert_with(|| { + if let Some(ext) = p.rsplit('.').next() { + [ext.to_string()].into() + } else { + BTreeSet::default() + } + }); + let mut buf = vec![]; let mut writer = Cursor::new(&mut buf); pak.read_file(&p, &mut pak_bufs[0].1, &mut writer)?; @@ -146,6 +167,42 @@ pub fn lint(mods: &[(ModSpecification, PathBuf)]) -> Result { } } } + + path_extensions_map + .iter() + .for_each(|(path_without_ext, exts)| { + match (exts.contains("uasset"), exts.contains("uexp")) { + (true, false) => { + split_uasset_uexp_mods + .entry(mod_spec.clone()) + .and_modify( + |mismatched_pairs_map: &mut BTreeMap| { + mismatched_pairs_map + .insert(path_without_ext.clone(), SplitUasset::MissingUexp); + }, + ) + .or_insert_with(|| { + [(path_without_ext.clone(), SplitUasset::MissingUexp)].into() + }); + } + (false, true) => { + split_uasset_uexp_mods + .entry(mod_spec.clone()) + .and_modify( + |mismatched_pairs_map: &mut BTreeMap| { + mismatched_pairs_map.insert( + path_without_ext.clone(), + SplitUasset::MissingUasset, + ); + }, + ) + .or_insert_with(|| { + [(path_without_ext.clone(), SplitUasset::MissingUasset)].into() + }); + } + _ => {} + } + }); } const CONFLICTING_MODS_LINT_WHITELIST: [&str; 1] = ["fsd/content/_interop"]; @@ -189,5 +246,6 @@ pub fn lint(mods: &[(ModSpecification, PathBuf)]) -> Result { archive_with_only_non_pak_files_mods, archive_with_multiple_paks_mods, non_asset_file_mods, + split_uasset_uexp_mods, }) } diff --git a/test_assets/lints/split_uasset_uexp.pak b/test_assets/lints/split_uasset_uexp.pak new file mode 100644 index 00000000..d2ae7cba Binary files /dev/null and b/test_assets/lints/split_uasset_uexp.pak differ diff --git a/test_assets/lints/split_uasset_uexp/missing_uasset/a.uexp b/test_assets/lints/split_uasset_uexp/missing_uasset/a.uexp new file mode 100644 index 00000000..e69de29b diff --git a/test_assets/lints/split_uasset_uexp/missing_uexp/b.uasset b/test_assets/lints/split_uasset_uexp/missing_uexp/b.uasset new file mode 100644 index 00000000..e69de29b diff --git a/tests/lint/mod.rs b/tests/lint/mod.rs index de5ce8ad..6586290e 100644 --- a/tests/lint/mod.rs +++ b/tests/lint/mod.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use std::str::FromStr; -use drg_mod_integration::mod_lint::ModLintReport; +use drg_mod_integration::mod_lint::{ModLintReport, SplitUasset}; use drg_mod_integration::providers::ModSpecification; #[test] @@ -215,3 +215,35 @@ pub fn test_lint_non_asset_files() { Some(&["never_gonna_let_you_down.txt".to_string()].into()) ); } + +#[test] +pub fn test_lint_split_uasset_uexp_pairs() { + let base_path = PathBuf::from_str("test_assets/lints/").unwrap(); + assert!(base_path.exists()); + let split_uasset_uexp_pak_path = base_path.clone().join("split_uasset_uexp.pak"); + assert!(split_uasset_uexp_pak_path.exists()); + + let split_uasset_uexp_spec = ModSpecification { + url: "split_uasset_uexp".to_string(), + }; + + let mods = vec![(split_uasset_uexp_spec.clone(), split_uasset_uexp_pak_path)]; + + let ModLintReport { + split_uasset_uexp_mods, + .. + } = drg_mod_integration::mod_lint::lint(&mods).unwrap(); + + println!("{:#?}", split_uasset_uexp_mods); + + assert_eq!( + split_uasset_uexp_mods.get(&split_uasset_uexp_spec), + Some( + &[ + ("missing_uasset/a.uexp".to_string(), SplitUasset::MissingUasset), + ("missing_uexp/b.uasset".to_string(), SplitUasset::MissingUexp) + ] + .into() + ) + ); +}