mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 10:54:55 +08:00
feat(assetbundle): 完善官方资源解析与双目录发布
This commit is contained in:
@@ -87,6 +87,16 @@ pub struct UnityFsImportSummary {
|
||||
pub directory_count: usize,
|
||||
/// UnityFS directory 路径。
|
||||
pub directories: Vec<String>,
|
||||
/// UnityFS directory 解出的文件数量。
|
||||
pub file_count: usize,
|
||||
/// 成功解析出的 Unity serialized file 数量。
|
||||
pub serialized_file_count: usize,
|
||||
/// 成功解析出的 TextAsset 数量。
|
||||
pub text_asset_count: usize,
|
||||
/// TextAsset 名称列表。
|
||||
pub text_assets: Vec<String>,
|
||||
/// 非致命 serialized-file 解析诊断数量。
|
||||
pub serialized_parse_error_count: usize,
|
||||
}
|
||||
|
||||
/// Manifest 导入报告。
|
||||
@@ -266,6 +276,15 @@ impl<'a> ResourceImportService<'a> {
|
||||
.into_iter()
|
||||
.map(|directory| directory.path)
|
||||
.collect(),
|
||||
file_count: parsed.files.len(),
|
||||
serialized_file_count: parsed.serialized_files.len(),
|
||||
text_asset_count: parsed.text_assets.len(),
|
||||
text_assets: parsed
|
||||
.text_assets
|
||||
.into_iter()
|
||||
.map(|asset| asset.name)
|
||||
.collect(),
|
||||
serialized_parse_error_count: parsed.serialized_parse_errors.len(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -375,6 +394,26 @@ mod tests {
|
||||
data.extend_from_slice(&value.to_be_bytes());
|
||||
}
|
||||
|
||||
fn push_i16_le(data: &mut Vec<u8>, value: i16) {
|
||||
data.extend_from_slice(&value.to_le_bytes());
|
||||
}
|
||||
|
||||
fn push_u32_le(data: &mut Vec<u8>, value: u32) {
|
||||
data.extend_from_slice(&value.to_le_bytes());
|
||||
}
|
||||
|
||||
fn push_i32_le(data: &mut Vec<u8>, value: i32) {
|
||||
data.extend_from_slice(&value.to_le_bytes());
|
||||
}
|
||||
|
||||
fn push_i64_le(data: &mut Vec<u8>, value: i64) {
|
||||
data.extend_from_slice(&value.to_le_bytes());
|
||||
}
|
||||
|
||||
fn push_u64_le(data: &mut Vec<u8>, value: u64) {
|
||||
data.extend_from_slice(&value.to_le_bytes());
|
||||
}
|
||||
|
||||
fn align(data: &mut Vec<u8>, alignment: usize) {
|
||||
let remainder = data.len() % alignment;
|
||||
if remainder != 0 {
|
||||
@@ -414,6 +453,83 @@ mod tests {
|
||||
data
|
||||
}
|
||||
|
||||
fn synthetic_text_asset_unityfs_bundle() -> Vec<u8> {
|
||||
let serialized_file = synthetic_serialized_text_asset();
|
||||
let mut blocks_info = Vec::new();
|
||||
blocks_info.extend_from_slice(&[1; 16]);
|
||||
push_i32(&mut blocks_info, 1);
|
||||
push_u32(&mut blocks_info, serialized_file.len() as u32);
|
||||
push_u32(&mut blocks_info, serialized_file.len() as u32);
|
||||
push_u16(&mut blocks_info, 0);
|
||||
push_i32(&mut blocks_info, 1);
|
||||
push_u64(&mut blocks_info, 0);
|
||||
push_u64(&mut blocks_info, serialized_file.len() as u64);
|
||||
push_u32(&mut blocks_info, 0);
|
||||
push_c_string(&mut blocks_info, "CAB-scenario");
|
||||
|
||||
let mut data = Vec::new();
|
||||
push_c_string(&mut data, "UnityFS");
|
||||
push_u32(&mut data, 8);
|
||||
push_c_string(&mut data, "5.x.x");
|
||||
push_c_string(&mut data, "2021.3.56f2");
|
||||
push_u64(&mut data, 0);
|
||||
push_u32(&mut data, blocks_info.len() as u32);
|
||||
push_u32(&mut data, blocks_info.len() as u32);
|
||||
push_u32(&mut data, 0);
|
||||
align(&mut data, 16);
|
||||
data.extend_from_slice(&blocks_info);
|
||||
data.extend_from_slice(&serialized_file);
|
||||
|
||||
let total_size = data.len() as u64;
|
||||
let total_size_offset = b"UnityFS\0".len() + 4 + b"5.x.x\0".len() + b"2021.3.56f2\0".len();
|
||||
data[total_size_offset..total_size_offset + 8].copy_from_slice(&total_size.to_be_bytes());
|
||||
data
|
||||
}
|
||||
|
||||
fn synthetic_serialized_text_asset() -> Vec<u8> {
|
||||
let mut object_data = Vec::new();
|
||||
push_u32_le(&mut object_data, 8);
|
||||
object_data.extend_from_slice(b"Scenario");
|
||||
align(&mut object_data, 4);
|
||||
push_u32_le(&mut object_data, 15);
|
||||
object_data.extend_from_slice("こんにちは".as_bytes());
|
||||
|
||||
let mut metadata = Vec::new();
|
||||
metadata.extend_from_slice(b"2021.3.56f2\0");
|
||||
push_i32_le(&mut metadata, 19);
|
||||
metadata.push(0);
|
||||
push_i32_le(&mut metadata, 1);
|
||||
push_i32_le(&mut metadata, 49);
|
||||
metadata.push(0);
|
||||
push_i16_le(&mut metadata, 0);
|
||||
metadata.extend_from_slice(&[0; 16]);
|
||||
push_i32_le(&mut metadata, 1);
|
||||
align(&mut metadata, 4);
|
||||
push_i64_le(&mut metadata, 1);
|
||||
push_u64_le(&mut metadata, 0);
|
||||
push_u32_le(&mut metadata, object_data.len() as u32);
|
||||
push_i32_le(&mut metadata, 0);
|
||||
|
||||
let header_len = 48usize;
|
||||
let data_offset = header_len + metadata.len();
|
||||
let file_size = data_offset + object_data.len();
|
||||
|
||||
let mut file = Vec::new();
|
||||
push_u32(&mut file, metadata.len() as u32);
|
||||
push_u32(&mut file, file_size as u32);
|
||||
push_u32(&mut file, 22);
|
||||
push_u32(&mut file, 0);
|
||||
file.push(0);
|
||||
file.extend_from_slice(&[0, 0, 0]);
|
||||
push_u32(&mut file, metadata.len() as u32);
|
||||
push_u64(&mut file, file_size as u64);
|
||||
push_u64(&mut file, data_offset as u64);
|
||||
push_u64(&mut file, 0);
|
||||
file.extend_from_slice(&metadata);
|
||||
file.extend_from_slice(&object_data);
|
||||
file
|
||||
}
|
||||
|
||||
fn synthetic_manifest() -> GenericManifest {
|
||||
GenericManifest {
|
||||
format: ManifestFormat::AddressablesCatalog,
|
||||
@@ -594,6 +710,11 @@ mod tests {
|
||||
assert_eq!(unityfs.block_count, 1);
|
||||
assert_eq!(unityfs.directory_count, 1);
|
||||
assert_eq!(unityfs.directories, vec!["SYNTHETIC-CAB".to_string()]);
|
||||
assert_eq!(unityfs.file_count, 1);
|
||||
assert_eq!(unityfs.serialized_file_count, 0);
|
||||
assert_eq!(unityfs.text_asset_count, 0);
|
||||
assert!(unityfs.text_assets.is_empty());
|
||||
assert_eq!(unityfs.serialized_parse_error_count, 0);
|
||||
assert_eq!(
|
||||
report.imported[1].category,
|
||||
ResourceImportCategory::TextAsset
|
||||
@@ -645,6 +766,41 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn import_summary_reports_text_assets_inside_assetbundle() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let cas = FileSystemCasRepository::new(temp_dir.path().join("cas"));
|
||||
let resources = InMemoryResourceRepository::new();
|
||||
let service = ResourceImportService::new(&cas, &resources);
|
||||
let manifest = manifest_with(vec![ResourceEntry {
|
||||
path: "synthetic/scenario.bundle".to_string(),
|
||||
hash: "synthetic-scenario-hash".to_string(),
|
||||
size: 1,
|
||||
resource_type: ResourceType::AssetBundle,
|
||||
address: None,
|
||||
dependencies: Vec::new(),
|
||||
crc: None,
|
||||
}]);
|
||||
|
||||
let report = service
|
||||
.import_manifest_bundles(
|
||||
&manifest,
|
||||
&[BundleSource::new(
|
||||
"scenario.bundle",
|
||||
synthetic_text_asset_unityfs_bundle(),
|
||||
)],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let unityfs = report.imported[0].unityfs.as_ref().unwrap();
|
||||
assert_eq!(unityfs.file_count, 1);
|
||||
assert_eq!(unityfs.serialized_file_count, 1);
|
||||
assert_eq!(unityfs.text_asset_count, 1);
|
||||
assert_eq!(unityfs.text_assets, vec!["Scenario".to_string()]);
|
||||
assert_eq!(unityfs.serialized_parse_error_count, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn returns_error_when_bundle_data_is_missing() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user