mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-07-22 06:35:16 +08:00
chore: establish development baseline
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
//! 客户端集成模块
|
||||
//!
|
||||
//! 提供客户端发现、备份和集成功能
|
||||
|
||||
pub mod integration;
|
||||
pub mod backup;
|
||||
pub mod discovery;
|
||||
|
||||
pub use integration::ClientIntegration;
|
||||
pub use backup::BackupManager;
|
||||
pub use discovery::ClientDiscovery;
|
||||
@@ -0,0 +1,90 @@
|
||||
//! 备份管理接口
|
||||
|
||||
use async_trait::async_trait;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// 备份信息
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BackupInfo {
|
||||
/// 备份ID
|
||||
pub id: String,
|
||||
/// 备份时间
|
||||
pub timestamp: i64,
|
||||
/// 备份路径
|
||||
pub path: PathBuf,
|
||||
/// 备份大小(字节)
|
||||
pub size: u64,
|
||||
/// 备份描述
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
/// 备份管理器接口
|
||||
///
|
||||
/// 管理游戏客户端的备份和恢复
|
||||
#[async_trait]
|
||||
pub trait BackupManager: Send + Sync {
|
||||
/// 创建备份
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// - `source`: 源目录
|
||||
/// - `description`: 备份描述
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// - 成功:返回备份信息
|
||||
/// - 失败:返回错误
|
||||
async fn create_backup(&self, source: &Path, description: &str) -> Result<BackupInfo, String>;
|
||||
|
||||
/// 列出所有备份
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// - 成功:返回备份列表
|
||||
/// - 失败:返回错误
|
||||
async fn list_backups(&self) -> Result<Vec<BackupInfo>, String>;
|
||||
|
||||
/// 恢复备份
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// - `backup_id`: 备份ID
|
||||
/// - `target`: 目标目录
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// - 成功:返回 Ok(())
|
||||
/// - 失败:返回错误
|
||||
async fn restore_backup(&self, backup_id: &str, target: &Path) -> Result<(), String>;
|
||||
|
||||
/// 删除备份
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// - `backup_id`: 备份ID
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// - 成功:返回 Ok(())
|
||||
/// - 失败:返回错误
|
||||
async fn delete_backup(&self, backup_id: &str) -> Result<(), String>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_backup_info() {
|
||||
let info = BackupInfo {
|
||||
id: "backup_001".to_string(),
|
||||
timestamp: 1234567890,
|
||||
path: PathBuf::from("/backups/001"),
|
||||
size: 1024,
|
||||
description: "Test backup".to_string(),
|
||||
};
|
||||
|
||||
assert_eq!(info.id, "backup_001");
|
||||
assert_eq!(info.size, 1024);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//! 客户端发现接口
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bat_core::domain::{GameClient, GameRegion};
|
||||
|
||||
/// 客户端发现接口
|
||||
///
|
||||
/// 在系统中自动发现已安装的游戏客户端
|
||||
#[async_trait]
|
||||
pub trait ClientDiscovery: Send + Sync {
|
||||
/// 发现所有客户端
|
||||
///
|
||||
/// 在系统的常见位置搜索游戏客户端
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// - 成功:返回找到的客户端列表
|
||||
/// - 失败:返回错误
|
||||
async fn discover_all(&self) -> Result<Vec<GameClient>, String>;
|
||||
|
||||
/// 验证客户端
|
||||
///
|
||||
/// 验证给定路径是否是有效的游戏客户端
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// - `path`: 客户端路径
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// - true: 是有效的客户端
|
||||
/// - false: 不是有效的客户端
|
||||
async fn verify_client(&self, path: &str) -> bool;
|
||||
|
||||
/// 检测客户端区域
|
||||
///
|
||||
/// 根据客户端文件特征检测区域
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// - `path`: 客户端路径
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// - 成功:返回区域
|
||||
/// - 失败:返回错误
|
||||
async fn detect_region(&self, path: &str) -> Result<GameRegion, String>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// 测试将在实现时添加
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
//! 客户端集成接口
|
||||
//!
|
||||
//! 定义如何与游戏客户端集成的统一接口
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bat_core::domain::GameClient;
|
||||
use std::path::Path;
|
||||
|
||||
/// 集成操作结果
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IntegrationResult {
|
||||
/// 操作是否成功
|
||||
pub success: bool,
|
||||
/// 操作消息
|
||||
pub message: String,
|
||||
/// 受影响的文件数量
|
||||
pub files_affected: usize,
|
||||
}
|
||||
|
||||
/// 客户端集成接口
|
||||
///
|
||||
/// 提供与游戏客户端交互的统一接口,包括:
|
||||
/// - 资源备份和恢复
|
||||
/// - 翻译应用
|
||||
/// - 完整性验证
|
||||
/// - 回滚操作
|
||||
///
|
||||
/// # 设计原则
|
||||
///
|
||||
/// - **安全第一**:任何修改前先备份
|
||||
/// - **原子操作**:要么全部成功,要么全部回滚
|
||||
/// - **可逆性**:所有操作都可以撤销
|
||||
/// - **完整性**:自动验证文件完整性
|
||||
#[async_trait]
|
||||
pub trait ClientIntegration: Send + Sync {
|
||||
/// 发现游戏客户端
|
||||
///
|
||||
/// 在系统中搜索已安装的游戏客户端。
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// - 成功:返回找到的客户端列表
|
||||
/// - 失败:返回错误
|
||||
///
|
||||
/// # 实现注意
|
||||
///
|
||||
/// - 搜索常见安装路径
|
||||
/// - 验证客户端有效性
|
||||
/// - 识别客户端区域
|
||||
async fn discover_clients(&self) -> Result<Vec<GameClient>, String>;
|
||||
|
||||
/// 备份客户端资源
|
||||
///
|
||||
/// 在修改前备份客户端资源,确保可以回滚。
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// - `client`: 游戏客户端
|
||||
/// - `backup_path`: 备份存储路径
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// - 成功:返回备份ID
|
||||
/// - 失败:返回错误
|
||||
async fn backup_resources(
|
||||
&self,
|
||||
client: &GameClient,
|
||||
backup_path: &Path,
|
||||
) -> Result<String, String>;
|
||||
|
||||
/// 应用翻译
|
||||
///
|
||||
/// 将翻译后的资源应用到游戏客户端。
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// - `client`: 游戏客户端
|
||||
/// - `translation_path`: 翻译资源路径
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// - 成功:返回集成结果
|
||||
/// - 失败:返回错误
|
||||
async fn apply_translation(
|
||||
&self,
|
||||
client: &GameClient,
|
||||
translation_path: &Path,
|
||||
) -> Result<IntegrationResult, String>;
|
||||
|
||||
/// 验证客户端完整性
|
||||
///
|
||||
/// 验证客户端文件的完整性。
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// - `client`: 游戏客户端
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// - true: 完整性正常
|
||||
/// - false: 发现问题
|
||||
async fn verify_integrity(&self, client: &GameClient) -> Result<bool, String>;
|
||||
|
||||
/// 回滚到备份
|
||||
///
|
||||
/// 恢复之前的备份。
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// - `client`: 游戏客户端
|
||||
/// - `backup_id`: 备份ID
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// - 成功:返回集成结果
|
||||
/// - 失败:返回错误
|
||||
async fn rollback(&self, client: &GameClient, backup_id: &str) -> Result<IntegrationResult, String>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_integration_result() {
|
||||
let result = IntegrationResult {
|
||||
success: true,
|
||||
message: "Success".to_string(),
|
||||
files_affected: 5,
|
||||
};
|
||||
|
||||
assert!(result.success);
|
||||
assert_eq!(result.files_affected, 5);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
//! 适配器错误类型
|
||||
//!
|
||||
//! 定义适配器层的错误类型,提供更详细的错误信息
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// 适配器错误类型
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AdapterError {
|
||||
/// Unity 版本不支持
|
||||
#[error("Unsupported Unity version: {0}")]
|
||||
UnsupportedUnityVersion(String),
|
||||
|
||||
/// AssetBundle 解析错误
|
||||
#[error("Failed to parse AssetBundle: {0}")]
|
||||
AssetBundleParseError(String),
|
||||
|
||||
/// Manifest 解析错误
|
||||
#[error("Failed to parse Manifest: {0}")]
|
||||
ManifestParseError(String),
|
||||
|
||||
/// 找不到合适的适配器
|
||||
#[error("No suitable adapter found for {0}")]
|
||||
NoSuitableAdapter(String),
|
||||
|
||||
/// 客户端未找到
|
||||
#[error("Game client not found at: {0}")]
|
||||
ClientNotFound(String),
|
||||
|
||||
/// 客户端版本不匹配
|
||||
#[error("Client version mismatch: expected {expected}, found {found}")]
|
||||
VersionMismatch {
|
||||
/// 期望的版本
|
||||
expected: String,
|
||||
/// 实际的版本
|
||||
found: String
|
||||
},
|
||||
|
||||
/// 备份操作失败
|
||||
#[error("Backup operation failed: {0}")]
|
||||
BackupFailed(String),
|
||||
|
||||
/// 恢复操作失败
|
||||
#[error("Restore operation failed: {0}")]
|
||||
RestoreFailed(String),
|
||||
|
||||
/// 完整性验证失败
|
||||
#[error("Integrity check failed: {0}")]
|
||||
IntegrityCheckFailed(String),
|
||||
|
||||
/// I/O 错误
|
||||
#[error("I/O error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
/// 核心错误
|
||||
#[error("Core error: {0}")]
|
||||
Core(#[from] bat_core::Error),
|
||||
|
||||
/// 其他错误
|
||||
#[error("{0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
/// 适配器 Result 类型
|
||||
pub type Result<T> = std::result::Result<T, AdapterError>;
|
||||
|
||||
impl From<String> for AdapterError {
|
||||
fn from(s: String) -> Self {
|
||||
AdapterError::Other(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for AdapterError {
|
||||
fn from(s: &str) -> Self {
|
||||
AdapterError::Other(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_unsupported_unity_version() {
|
||||
let err = AdapterError::UnsupportedUnityVersion("2022.1.0".to_string());
|
||||
assert!(err.to_string().contains("2022.1.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_version_mismatch() {
|
||||
let err = AdapterError::VersionMismatch {
|
||||
expected: "1.0.0".to_string(),
|
||||
found: "2.0.0".to_string(),
|
||||
};
|
||||
assert!(err.to_string().contains("expected 1.0.0"));
|
||||
assert!(err.to_string().contains("found 2.0.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_string() {
|
||||
let err: AdapterError = "test error".to_string().into();
|
||||
assert!(matches!(err, AdapterError::Other(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_str() {
|
||||
let err: AdapterError = "test error".into();
|
||||
assert!(matches!(err, AdapterError::Other(_)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//! # BAT Adapters - 适配器层
|
||||
//!
|
||||
//! 提供各种版本和格式的适配器
|
||||
//!
|
||||
//! 包含:
|
||||
//! - Unity 版本适配器
|
||||
//! - Manifest 格式适配器
|
||||
//! - 客户端平台适配器
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![warn(clippy::all)]
|
||||
|
||||
pub mod unity;
|
||||
pub mod manifest;
|
||||
pub mod client;
|
||||
pub mod error;
|
||||
|
||||
pub use error::{AdapterError, Result};
|
||||
|
||||
/// Adapters 版本号
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
@@ -0,0 +1,11 @@
|
||||
//! Manifest 适配器模块
|
||||
//!
|
||||
//! 提供多种 Manifest 格式的解析支持
|
||||
|
||||
pub mod driver;
|
||||
pub mod addressables;
|
||||
pub mod registry;
|
||||
|
||||
pub use driver::{ManifestDriver, GenericManifest, ManifestFormat, ManifestMetadata};
|
||||
pub use addressables::AddressablesCatalogDriver;
|
||||
pub use registry::ManifestDriverRegistry;
|
||||
@@ -0,0 +1,185 @@
|
||||
//! Unity Addressables Catalog Driver
|
||||
//!
|
||||
//! 解析 Unity Addressables 的 catalog_Remote.json 文件
|
||||
|
||||
use super::driver::{GenericManifest, ManifestDriver, ManifestFormat, ManifestMetadata};
|
||||
use async_trait::async_trait;
|
||||
use bat_core::domain::{ResourceEntry, ResourceType};
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Addressables Catalog Driver
|
||||
pub struct AddressablesCatalogDriver;
|
||||
|
||||
impl AddressablesCatalogDriver {
|
||||
/// 创建新的 Driver 实例
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AddressablesCatalogDriver {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ManifestDriver for AddressablesCatalogDriver {
|
||||
fn name(&self) -> &str {
|
||||
"Unity Addressables Catalog"
|
||||
}
|
||||
|
||||
fn format_version(&self) -> &str {
|
||||
"1.0"
|
||||
}
|
||||
|
||||
fn can_parse(&self, raw_data: &[u8]) -> bool {
|
||||
// 检测是否是 Addressables Catalog
|
||||
// 1. 必须是有效的 JSON
|
||||
// 2. 必须包含 m_LocatorId 字段
|
||||
// 3. m_LocatorId 值应该是 "AddressablesMainContentCatalog"
|
||||
|
||||
let text = match std::str::from_utf8(raw_data) {
|
||||
Ok(t) => t,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
let json: Value = match serde_json::from_str(text) {
|
||||
Ok(j) => j,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
let locator_id = match json.get("m_LocatorId") {
|
||||
Some(id) => id,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
let id_str = match locator_id.as_str() {
|
||||
Some(s) => s,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
id_str.contains("AddressablesMainContentCatalog") || id_str.contains("Addressables")
|
||||
}
|
||||
|
||||
async fn parse(&self, raw_data: &[u8]) -> Result<GenericManifest, String> {
|
||||
// 解析 JSON
|
||||
let text = std::str::from_utf8(raw_data)
|
||||
.map_err(|e| format!("Invalid UTF-8: {}", e))?;
|
||||
|
||||
let json: Value = serde_json::from_str(text)
|
||||
.map_err(|e| format!("Invalid JSON: {}", e))?;
|
||||
|
||||
// 提取元数据
|
||||
let locator_id = json.get("m_LocatorId")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
// 提取 CDN 前缀
|
||||
let mut cdn_prefixes = Vec::new();
|
||||
if let Some(prefixes) = json.get("m_InternalIdPrefixes").and_then(|v| v.as_array()) {
|
||||
for prefix in prefixes {
|
||||
if let Some(s) = prefix.as_str() {
|
||||
cdn_prefixes.push(s.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提取资源列表(基础版本 - 仅从 m_InternalIds 提取)
|
||||
let mut resources = Vec::new();
|
||||
if let Some(internal_ids) = json.get("m_InternalIds").and_then(|v| v.as_array()) {
|
||||
for (index, id) in internal_ids.iter().enumerate() {
|
||||
if let Some(path) = id.as_str() {
|
||||
// 跳过空路径
|
||||
if path.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 判断资源类型
|
||||
let resource_type = if path.ends_with(".bundle") {
|
||||
ResourceType::AssetBundle
|
||||
} else if path.contains("catalog") {
|
||||
ResourceType::Manifest
|
||||
} else {
|
||||
ResourceType::Other
|
||||
};
|
||||
|
||||
resources.push(ResourceEntry {
|
||||
path: path.to_string(),
|
||||
hash: format!("addressable_{}", index), // 临时 Hash
|
||||
size: 0, // 大小未知
|
||||
resource_type,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 解析 m_KeyDataString、m_EntryDataString 等压缩字段
|
||||
// 这些字段使用了自定义压缩格式,需要实现解压缩算法
|
||||
// 参考:Unity Addressables 源代码
|
||||
|
||||
let metadata = ManifestMetadata {
|
||||
locator_id,
|
||||
cdn_prefixes,
|
||||
extra: HashMap::new(),
|
||||
};
|
||||
|
||||
Ok(GenericManifest {
|
||||
format: ManifestFormat::AddressablesCatalog,
|
||||
resources,
|
||||
metadata,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_can_parse_valid_catalog() {
|
||||
let driver = AddressablesCatalogDriver::new();
|
||||
|
||||
let valid_json = r#"{
|
||||
"m_LocatorId": "AddressablesMainContentCatalog",
|
||||
"m_InternalIds": []
|
||||
}"#;
|
||||
|
||||
assert!(driver.can_parse(valid_json.as_bytes()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_parse_invalid() {
|
||||
let driver = AddressablesCatalogDriver::new();
|
||||
|
||||
let invalid_json = r#"{"some": "other"}"#;
|
||||
assert!(!driver.can_parse(invalid_json.as_bytes()));
|
||||
|
||||
let not_json = b"not json at all";
|
||||
assert!(!driver.can_parse(not_json));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_parse_simple_catalog() {
|
||||
let driver = AddressablesCatalogDriver::new();
|
||||
|
||||
let catalog_json = r#"{
|
||||
"m_LocatorId": "AddressablesMainContentCatalog",
|
||||
"m_InternalIdPrefixes": [],
|
||||
"m_InternalIds": [
|
||||
"academy.bundle",
|
||||
"character.bundle",
|
||||
"catalog.json"
|
||||
]
|
||||
}"#;
|
||||
|
||||
let result = driver.parse(catalog_json.as_bytes()).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let manifest = result.unwrap();
|
||||
assert_eq!(manifest.format, ManifestFormat::AddressablesCatalog);
|
||||
assert_eq!(manifest.resources.len(), 3);
|
||||
assert_eq!(manifest.resources[0].resource_type, ResourceType::AssetBundle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//! Manifest Driver 接口定义
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bat_core::domain::ResourceEntry;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Manifest 格式
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ManifestFormat {
|
||||
/// 简单的资源列表
|
||||
Simple,
|
||||
/// Unity Addressables Catalog
|
||||
AddressablesCatalog,
|
||||
}
|
||||
|
||||
/// Manifest 元数据
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ManifestMetadata {
|
||||
/// Locator ID(Addressables)
|
||||
pub locator_id: Option<String>,
|
||||
/// CDN 前缀列表
|
||||
pub cdn_prefixes: Vec<String>,
|
||||
/// 额外元数据
|
||||
pub extra: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// 通用 Manifest 结构
|
||||
///
|
||||
/// 所有 Manifest 格式都解析到这个通用结构
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GenericManifest {
|
||||
/// Manifest 格式
|
||||
pub format: ManifestFormat,
|
||||
/// 资源列表
|
||||
pub resources: Vec<ResourceEntry>,
|
||||
/// 元数据
|
||||
pub metadata: ManifestMetadata,
|
||||
}
|
||||
|
||||
/// Manifest Driver 接口
|
||||
///
|
||||
/// 用于解析不同格式的 Manifest 文件
|
||||
#[async_trait]
|
||||
pub trait ManifestDriver: Send + Sync {
|
||||
/// Driver 名称
|
||||
fn name(&self) -> &str;
|
||||
|
||||
/// 格式版本标识
|
||||
fn format_version(&self) -> &str;
|
||||
|
||||
/// 检测是否可以解析这个 Manifest
|
||||
///
|
||||
/// # 参数
|
||||
/// - `raw_data`: 原始数据
|
||||
///
|
||||
/// # 返回
|
||||
/// - true: 可以解析
|
||||
/// - false: 不能解析
|
||||
fn can_parse(&self, raw_data: &[u8]) -> bool;
|
||||
|
||||
/// 解析 Manifest
|
||||
///
|
||||
/// # 参数
|
||||
/// - `raw_data`: 原始数据
|
||||
///
|
||||
/// # 返回
|
||||
/// - 成功:返回通用 Manifest 结构
|
||||
/// - 失败:返回错误
|
||||
async fn parse(&self, raw_data: &[u8]) -> Result<GenericManifest, String>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_manifest_format() {
|
||||
let format = ManifestFormat::AddressablesCatalog;
|
||||
assert_eq!(format, ManifestFormat::AddressablesCatalog);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_manifest_metadata() {
|
||||
let metadata = ManifestMetadata {
|
||||
locator_id: Some("test".to_string()),
|
||||
cdn_prefixes: vec!["https://cdn.example.com".to_string()],
|
||||
extra: HashMap::new(),
|
||||
};
|
||||
|
||||
assert_eq!(metadata.locator_id, Some("test".to_string()));
|
||||
assert_eq!(metadata.cdn_prefixes.len(), 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
//! Manifest Driver 注册表
|
||||
//!
|
||||
//! 管理所有 Manifest Driver 并自动选择合适的 Driver
|
||||
|
||||
use super::driver::{GenericManifest, ManifestDriver};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Manifest Driver 注册表
|
||||
///
|
||||
/// 管理多个 Manifest Driver 实现,并根据输入数据自动选择合适的 Driver。
|
||||
///
|
||||
/// # 设计模式
|
||||
///
|
||||
/// 使用责任链模式(Chain of Responsibility):
|
||||
/// - 依次尝试每个 Driver 的 `can_parse()` 方法
|
||||
/// - 返回第一个能够解析的 Driver
|
||||
///
|
||||
/// # 使用场景
|
||||
///
|
||||
/// - 支持多种 Manifest 格式
|
||||
/// - 自动识别格式
|
||||
/// - 易于扩展新格式
|
||||
///
|
||||
/// # 示例
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use bat_adapters::manifest::{ManifestDriverRegistry, AddressablesCatalogDriver};
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// let mut registry = ManifestDriverRegistry::new();
|
||||
/// registry.register(Arc::new(AddressablesCatalogDriver::new()));
|
||||
///
|
||||
/// // 自动选择合适的 Driver
|
||||
/// let driver = registry.select_driver(&raw_data)?;
|
||||
/// let manifest = driver.parse(&raw_data).await?;
|
||||
/// ```
|
||||
pub struct ManifestDriverRegistry {
|
||||
/// 已注册的 Driver 列表
|
||||
drivers: Vec<Arc<dyn ManifestDriver>>,
|
||||
}
|
||||
|
||||
impl ManifestDriverRegistry {
|
||||
/// 创建新的注册表
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// 返回一个空的注册表,需要手动注册 Driver。
|
||||
///
|
||||
/// # 示例
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let registry = ManifestDriverRegistry::new();
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
drivers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 创建带有默认 Driver 的注册表
|
||||
///
|
||||
/// 自动注册所有内置的 Driver。
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// 返回已注册默认 Driver 的注册表。
|
||||
///
|
||||
/// # 默认 Driver
|
||||
///
|
||||
/// - AddressablesCatalogDriver
|
||||
///
|
||||
/// # 示例
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let registry = ManifestDriverRegistry::with_defaults();
|
||||
/// // 已经包含了 Addressables Catalog Driver
|
||||
/// ```
|
||||
pub fn with_defaults() -> Self {
|
||||
let mut registry = Self::new();
|
||||
|
||||
// 注册 Addressables Catalog Driver
|
||||
registry.register(Arc::new(crate::manifest::AddressablesCatalogDriver::new()));
|
||||
|
||||
registry
|
||||
}
|
||||
|
||||
/// 注册一个 Driver
|
||||
///
|
||||
/// 将 Driver 添加到注册表中。Driver 的顺序很重要:
|
||||
/// 先注册的 Driver 优先级更高。
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// - `driver`: Driver 实现
|
||||
///
|
||||
/// # 示例
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let mut registry = ManifestDriverRegistry::new();
|
||||
/// registry.register(Arc::new(MyCustomDriver::new()));
|
||||
/// ```
|
||||
///
|
||||
/// # 注意
|
||||
///
|
||||
/// - 相同的 Driver 可以注册多次
|
||||
/// - 建议按优先级从高到低注册
|
||||
pub fn register(&mut self, driver: Arc<dyn ManifestDriver>) {
|
||||
self.drivers.push(driver);
|
||||
}
|
||||
|
||||
/// 自动选择合适的 Driver
|
||||
///
|
||||
/// 根据输入数据自动选择能够解析的 Driver。
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// - `raw_data`: 原始 Manifest 数据
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// - 成功:返回合适的 Driver
|
||||
/// - 失败:如果没有合适的 Driver,返回错误
|
||||
///
|
||||
/// # 选择算法
|
||||
///
|
||||
/// 1. 依次调用每个 Driver 的 `can_parse()` 方法
|
||||
/// 2. 返回第一个返回 `true` 的 Driver
|
||||
/// 3. 如果所有 Driver 都返回 `false`,返回错误
|
||||
///
|
||||
/// # 示例
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let driver = registry.select_driver(&raw_data)?;
|
||||
/// println!("Selected driver: {}", driver.name());
|
||||
/// ```
|
||||
///
|
||||
/// # 性能
|
||||
///
|
||||
/// - 时间复杂度:O(n),n 为注册的 Driver 数量
|
||||
/// - 通常很快,因为 `can_parse()` 只检查格式签名
|
||||
pub fn select_driver(&self, raw_data: &[u8]) -> Result<Arc<dyn ManifestDriver>, String> {
|
||||
for driver in &self.drivers {
|
||||
if driver.can_parse(raw_data) {
|
||||
return Ok(Arc::clone(driver));
|
||||
}
|
||||
}
|
||||
|
||||
Err(format!(
|
||||
"No suitable Manifest Driver found. Registered drivers: {}",
|
||||
self.drivers.len()
|
||||
))
|
||||
}
|
||||
|
||||
/// 尝试解析 Manifest
|
||||
///
|
||||
/// 自动选择 Driver 并解析 Manifest,一步完成。
|
||||
///
|
||||
/// # 参数
|
||||
///
|
||||
/// - `raw_data`: 原始 Manifest 数据
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// - 成功:返回解析后的通用 Manifest
|
||||
/// - 失败:返回错误
|
||||
///
|
||||
/// # 示例
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let manifest = registry.parse(&raw_data).await?;
|
||||
/// println!("Parsed {} resources", manifest.resources.len());
|
||||
/// ```
|
||||
///
|
||||
/// # 等价于
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let driver = registry.select_driver(&raw_data)?;
|
||||
/// let manifest = driver.parse(&raw_data).await?;
|
||||
/// ```
|
||||
pub async fn parse(&self, raw_data: &[u8]) -> Result<GenericManifest, String> {
|
||||
let driver = self.select_driver(raw_data)?;
|
||||
driver.parse(raw_data).await
|
||||
}
|
||||
|
||||
/// 获取所有已注册的 Driver
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// 返回 Driver 列表的引用。
|
||||
///
|
||||
/// # 示例
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// for driver in registry.all_drivers() {
|
||||
/// println!("Driver: {} (version {})", driver.name(), driver.format_version());
|
||||
/// }
|
||||
/// ```
|
||||
pub fn all_drivers(&self) -> &[Arc<dyn ManifestDriver>] {
|
||||
&self.drivers
|
||||
}
|
||||
|
||||
/// 获取已注册的 Driver 数量
|
||||
///
|
||||
/// # 返回
|
||||
///
|
||||
/// 返回注册的 Driver 数量。
|
||||
///
|
||||
/// # 示例
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// println!("Registered {} drivers", registry.count());
|
||||
/// ```
|
||||
pub fn count(&self) -> usize {
|
||||
self.drivers.len()
|
||||
}
|
||||
|
||||
/// 清空所有 Driver
|
||||
///
|
||||
/// 移除所有已注册的 Driver。
|
||||
///
|
||||
/// # 示例
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// registry.clear();
|
||||
/// assert_eq!(registry.count(), 0);
|
||||
/// ```
|
||||
pub fn clear(&mut self) {
|
||||
self.drivers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ManifestDriverRegistry {
|
||||
/// 创建带有默认 Driver 的注册表
|
||||
///
|
||||
/// 等同于 `ManifestDriverRegistry::with_defaults()`
|
||||
fn default() -> Self {
|
||||
Self::with_defaults()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::manifest::AddressablesCatalogDriver;
|
||||
|
||||
#[test]
|
||||
fn test_registry_new() {
|
||||
let registry = ManifestDriverRegistry::new();
|
||||
assert_eq!(registry.count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registry_with_defaults() {
|
||||
let registry = ManifestDriverRegistry::with_defaults();
|
||||
assert_eq!(registry.count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_driver() {
|
||||
let mut registry = ManifestDriverRegistry::new();
|
||||
let driver = Arc::new(AddressablesCatalogDriver::new());
|
||||
|
||||
registry.register(driver);
|
||||
assert_eq!(registry.count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_driver_success() {
|
||||
let registry = ManifestDriverRegistry::with_defaults();
|
||||
|
||||
// 有效的 Addressables Catalog JSON
|
||||
let valid_json = r#"{
|
||||
"m_LocatorId": "AddressablesMainContentCatalog",
|
||||
"m_InternalIds": []
|
||||
}"#;
|
||||
|
||||
let result = registry.select_driver(valid_json.as_bytes());
|
||||
assert!(result.is_ok());
|
||||
|
||||
let driver = result.unwrap();
|
||||
assert_eq!(driver.name(), "Unity Addressables Catalog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_driver_not_found() {
|
||||
let registry = ManifestDriverRegistry::new();
|
||||
|
||||
let invalid_data = b"not a valid manifest";
|
||||
let result = registry.select_driver(invalid_data);
|
||||
|
||||
assert!(result.is_err());
|
||||
match result {
|
||||
Err(msg) => assert!(msg.contains("No suitable")),
|
||||
Ok(_) => panic!("Expected error"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_parse_success() {
|
||||
let registry = ManifestDriverRegistry::with_defaults();
|
||||
|
||||
let catalog_json = r#"{
|
||||
"m_LocatorId": "AddressablesMainContentCatalog",
|
||||
"m_InternalIdPrefixes": [],
|
||||
"m_InternalIds": ["test.bundle"]
|
||||
}"#;
|
||||
|
||||
let result = registry.parse(catalog_json.as_bytes()).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let manifest = result.unwrap();
|
||||
assert_eq!(manifest.resources.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_drivers() {
|
||||
let registry = ManifestDriverRegistry::with_defaults();
|
||||
let drivers = registry.all_drivers();
|
||||
|
||||
assert_eq!(drivers.len(), 1);
|
||||
assert_eq!(drivers[0].name(), "Unity Addressables Catalog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear() {
|
||||
let mut registry = ManifestDriverRegistry::with_defaults();
|
||||
assert_eq!(registry.count(), 1);
|
||||
|
||||
registry.clear();
|
||||
assert_eq!(registry.count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default() {
|
||||
let registry = ManifestDriverRegistry::default();
|
||||
assert_eq!(registry.count(), 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
//! Unity 版本适配器模块
|
||||
//!
|
||||
//! 提供多个 Unity 版本的 AssetBundle 解析支持
|
||||
|
||||
pub mod adapter;
|
||||
pub mod unity_2021_3;
|
||||
pub mod registry;
|
||||
|
||||
pub use adapter::{UnityAdapter, VersionRange, RawAssetBundle, ParsedAssetBundle};
|
||||
pub use unity_2021_3::Unity2021_3Adapter;
|
||||
pub use registry::UnityAdapterRegistry;
|
||||
@@ -0,0 +1,122 @@
|
||||
//! Unity Adapter 接口定义
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
/// Unity 版本范围
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VersionRange {
|
||||
/// 最小版本(包含)
|
||||
pub min: String,
|
||||
/// 最大版本(包含)
|
||||
pub max: String,
|
||||
}
|
||||
|
||||
impl VersionRange {
|
||||
/// 创建新的版本范围
|
||||
pub fn new(min: impl Into<String>, max: impl Into<String>) -> Self {
|
||||
Self {
|
||||
min: min.into(),
|
||||
max: max.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查版本是否在范围内
|
||||
pub fn contains(&self, version: &str) -> bool {
|
||||
version >= self.min.as_str() && version <= self.max.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
/// 原始 AssetBundle 数据
|
||||
#[derive(Debug)]
|
||||
pub struct RawAssetBundle {
|
||||
/// 文件数据
|
||||
pub data: Vec<u8>,
|
||||
/// 文件路径(可选)
|
||||
pub path: Option<String>,
|
||||
}
|
||||
|
||||
/// 解析后的 AssetBundle
|
||||
#[derive(Debug)]
|
||||
pub struct ParsedAssetBundle {
|
||||
/// Unity 版本
|
||||
pub unity_version: String,
|
||||
/// 资源列表(简化表示)
|
||||
pub assets: Vec<String>,
|
||||
/// 原始数据(保留用于序列化)
|
||||
pub raw_data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Unity Adapter 接口
|
||||
///
|
||||
/// 用于解析不同 Unity 版本的 AssetBundle
|
||||
#[async_trait]
|
||||
pub trait UnityAdapter: Send + Sync {
|
||||
/// 适配器名称(例如 "Unity-2021.3")
|
||||
fn name(&self) -> &str;
|
||||
|
||||
/// 支持的 Unity 版本范围
|
||||
fn supported_versions(&self) -> VersionRange;
|
||||
|
||||
/// 检测是否可以处理这个 AssetBundle
|
||||
///
|
||||
/// # 参数
|
||||
/// - `bundle`: 原始 AssetBundle
|
||||
///
|
||||
/// # 返回
|
||||
/// - true: 可以处理
|
||||
/// - false: 不能处理
|
||||
fn can_handle(&self, bundle: &RawAssetBundle) -> bool;
|
||||
|
||||
/// 解析 AssetBundle
|
||||
///
|
||||
/// # 参数
|
||||
/// - `bundle`: 原始 AssetBundle
|
||||
///
|
||||
/// # 返回
|
||||
/// - 成功:返回解析后的 AssetBundle
|
||||
/// - 失败:返回错误
|
||||
///
|
||||
/// # 注意
|
||||
/// Phase 1 中标记为 TODO,Phase 2 实现
|
||||
async fn parse(&self, bundle: &RawAssetBundle) -> Result<ParsedAssetBundle, String>;
|
||||
|
||||
/// 序列化 AssetBundle
|
||||
///
|
||||
/// # 参数
|
||||
/// - `parsed`: 解析后的 AssetBundle
|
||||
///
|
||||
/// # 返回
|
||||
/// - 成功:返回序列化后的数据
|
||||
/// - 失败:返回错误
|
||||
///
|
||||
/// # 注意
|
||||
/// Phase 1 中标记为 TODO,Phase 2 实现
|
||||
async fn serialize(&self, parsed: &ParsedAssetBundle) -> Result<Vec<u8>, String>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_version_range() {
|
||||
let range = VersionRange::new("2021.3.0", "2021.3.99");
|
||||
|
||||
assert!(range.contains("2021.3.0"));
|
||||
assert!(range.contains("2021.3.56"));
|
||||
assert!(range.contains("2021.3.99"));
|
||||
assert!(!range.contains("2021.2.0"));
|
||||
assert!(!range.contains("2021.4.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_raw_assetbundle() {
|
||||
let bundle = RawAssetBundle {
|
||||
data: vec![0x55, 0x6e, 0x69, 0x74, 0x79, 0x46, 0x53], // "UnityFS"
|
||||
path: Some("test.bundle".to_string()),
|
||||
};
|
||||
|
||||
assert_eq!(bundle.data.len(), 7);
|
||||
assert_eq!(bundle.path, Some("test.bundle".to_string()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
//! Unity Adapter 注册表
|
||||
//!
|
||||
//! 管理所有 Unity Adapter 并自动选择合适的适配器
|
||||
|
||||
use super::adapter::{RawAssetBundle, UnityAdapter};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Unity Adapter 注册表
|
||||
pub struct UnityAdapterRegistry {
|
||||
adapters: Vec<Arc<dyn UnityAdapter>>,
|
||||
}
|
||||
|
||||
impl UnityAdapterRegistry {
|
||||
/// 创建新的注册表
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
adapters: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 注册一个适配器
|
||||
pub fn register(&mut self, adapter: Arc<dyn UnityAdapter>) {
|
||||
self.adapters.push(adapter);
|
||||
}
|
||||
|
||||
/// 自动选择合适的适配器
|
||||
///
|
||||
/// # 参数
|
||||
/// - `bundle`: 原始 AssetBundle
|
||||
///
|
||||
/// # 返回
|
||||
/// - 成功:返回合适的适配器
|
||||
/// - 失败:如果没有合适的适配器,返回错误
|
||||
pub fn select_adapter(&self, bundle: &RawAssetBundle) -> Result<Arc<dyn UnityAdapter>, String> {
|
||||
for adapter in &self.adapters {
|
||||
if adapter.can_handle(bundle) {
|
||||
return Ok(Arc::clone(adapter));
|
||||
}
|
||||
}
|
||||
|
||||
Err("No suitable adapter found for this AssetBundle".to_string())
|
||||
}
|
||||
|
||||
/// 获取所有已注册的适配器
|
||||
pub fn all_adapters(&self) -> &[Arc<dyn UnityAdapter>] {
|
||||
&self.adapters
|
||||
}
|
||||
|
||||
/// 获取适配器数量
|
||||
pub fn count(&self) -> usize {
|
||||
self.adapters.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UnityAdapterRegistry {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::unity::Unity2021_3Adapter;
|
||||
|
||||
#[test]
|
||||
fn test_registry_new() {
|
||||
let registry = UnityAdapterRegistry::new();
|
||||
assert_eq!(registry.count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_adapter() {
|
||||
let mut registry = UnityAdapterRegistry::new();
|
||||
let adapter = Arc::new(Unity2021_3Adapter::new());
|
||||
|
||||
registry.register(adapter);
|
||||
assert_eq!(registry.count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_adapter() {
|
||||
let mut registry = UnityAdapterRegistry::new();
|
||||
registry.register(Arc::new(Unity2021_3Adapter::new()));
|
||||
|
||||
// 创建一个 Unity 2021.3 的 bundle
|
||||
let mut data = Vec::new();
|
||||
data.extend_from_slice(b"UnityFS\0");
|
||||
data.extend_from_slice(&[0, 0, 0, 8]);
|
||||
data.extend_from_slice(b"5.x.x\x002021.3.56f2\0");
|
||||
|
||||
let bundle = RawAssetBundle {
|
||||
data,
|
||||
path: Some("test.bundle".to_string()),
|
||||
};
|
||||
|
||||
let result = registry.select_adapter(&bundle);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap().name(), "Unity-2021.3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_adapter_not_found() {
|
||||
let registry = UnityAdapterRegistry::new();
|
||||
|
||||
let bundle = RawAssetBundle {
|
||||
data: vec![0, 1, 2, 3],
|
||||
path: None,
|
||||
};
|
||||
|
||||
let result = registry.select_adapter(&bundle);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
//! Unity 2021.3 Adapter
|
||||
//!
|
||||
//! 支持 Unity 2021.3.x 版本的 AssetBundle
|
||||
|
||||
use super::adapter::{ParsedAssetBundle, RawAssetBundle, UnityAdapter, VersionRange};
|
||||
use async_trait::async_trait;
|
||||
|
||||
/// Unity 2021.3 Adapter
|
||||
pub struct Unity2021_3Adapter;
|
||||
|
||||
impl Unity2021_3Adapter {
|
||||
/// 创建新的适配器实例
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// 检测 Unity 版本(从文件头)
|
||||
fn detect_unity_version(data: &[u8]) -> Option<String> {
|
||||
// UnityFS 文件头结构:
|
||||
// 0x00-0x06: "UnityFS\0"
|
||||
// 0x08-0x0B: format version
|
||||
// 0x0C-...: Unity version string
|
||||
|
||||
if data.len() < 20 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// 检查签名
|
||||
if &data[0..7] != b"UnityFS" {
|
||||
return None;
|
||||
}
|
||||
|
||||
// 读取 Unity 版本字符串
|
||||
// 版本字符串从偏移 0x14 开始(可能因格式版本不同而变化)
|
||||
if let Some(version_start) = data.windows(7).position(|w| w.starts_with(b"2021.3.")) {
|
||||
// 读取版本字符串直到 null 或非 ASCII
|
||||
let version_bytes = &data[version_start..];
|
||||
if let Some(end) = version_bytes.iter().position(|&b| b == 0 || !b.is_ascii()) {
|
||||
if let Ok(version) = std::str::from_utf8(&version_bytes[..end]) {
|
||||
return Some(version.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Unity2021_3Adapter {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UnityAdapter for Unity2021_3Adapter {
|
||||
fn name(&self) -> &str {
|
||||
"Unity-2021.3"
|
||||
}
|
||||
|
||||
fn supported_versions(&self) -> VersionRange {
|
||||
VersionRange::new("2021.3.0", "2021.3.99")
|
||||
}
|
||||
|
||||
fn can_handle(&self, bundle: &RawAssetBundle) -> bool {
|
||||
// 检测 Unity 版本
|
||||
if let Some(version) = Self::detect_unity_version(&bundle.data) {
|
||||
self.supported_versions().contains(&version)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
async fn parse(&self, _bundle: &RawAssetBundle) -> Result<ParsedAssetBundle, String> {
|
||||
// TODO: Phase 2 实现
|
||||
// 需要:
|
||||
// 1. 解析 UnityFS 文件头
|
||||
// 2. 解压缩数据块
|
||||
// 3. 解析 TypeTree
|
||||
// 4. 提取 Asset 对象
|
||||
Err("parse() 将在 Phase 2 实现".to_string())
|
||||
}
|
||||
|
||||
async fn serialize(&self, _parsed: &ParsedAssetBundle) -> Result<Vec<u8>, String> {
|
||||
// TODO: Phase 2 实现
|
||||
// 需要:
|
||||
// 1. 序列化 Asset 对象
|
||||
// 2. 重新构建 TypeTree
|
||||
// 3. 压缩数据块
|
||||
// 4. 写入 UnityFS 文件头
|
||||
Err("serialize() 将在 Phase 2 实现".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_adapter_name() {
|
||||
let adapter = Unity2021_3Adapter::new();
|
||||
assert_eq!(adapter.name(), "Unity-2021.3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_supported_versions() {
|
||||
let adapter = Unity2021_3Adapter::new();
|
||||
let range = adapter.supported_versions();
|
||||
|
||||
assert!(range.contains("2021.3.0"));
|
||||
assert!(range.contains("2021.3.56"));
|
||||
assert!(!range.contains("2021.2.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_handle_valid_bundle() {
|
||||
let adapter = Unity2021_3Adapter::new();
|
||||
|
||||
// 模拟 UnityFS 文件头
|
||||
let mut data = Vec::new();
|
||||
data.extend_from_slice(b"UnityFS\0");
|
||||
data.extend_from_slice(&[0, 0, 0, 8]); // format version
|
||||
data.extend_from_slice(b"5.x.x\x002021.3.56f2\0");
|
||||
|
||||
let bundle = RawAssetBundle {
|
||||
data,
|
||||
path: Some("test.bundle".to_string()),
|
||||
};
|
||||
|
||||
assert!(adapter.can_handle(&bundle));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_handle_invalid_bundle() {
|
||||
let adapter = Unity2021_3Adapter::new();
|
||||
|
||||
let bundle = RawAssetBundle {
|
||||
data: vec![0, 1, 2, 3],
|
||||
path: None,
|
||||
};
|
||||
|
||||
assert!(!adapter.can_handle(&bundle));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_parse_not_implemented() {
|
||||
let adapter = Unity2021_3Adapter::new();
|
||||
|
||||
let bundle = RawAssetBundle {
|
||||
data: vec![],
|
||||
path: None,
|
||||
};
|
||||
|
||||
let result = adapter.parse(&bundle).await;
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("Phase 2"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user