chore: establish development baseline

This commit is contained in:
2026-06-28 01:27:09 +08:00
commit dd53e3054e
131 changed files with 19327 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
[package]
name = "bat-adapters"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
[dependencies]
bat-core = { path = "../core" }
anyhow.workspace = true
thiserror.workspace = true
serde.workspace = true
serde_json.workspace = true
async-trait.workspace = true
tokio.workspace = true
[dev-dependencies]
tokio = { workspace = true, features = ["test-util", "macros"] }
+11
View File
@@ -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;
+90
View File
@@ -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);
}
}
+53
View File
@@ -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 {
// 测试将在实现时添加
}
+135
View File
@@ -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);
}
}
+110
View File
@@ -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(_)));
}
}
+21
View File
@@ -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");
+11
View File
@@ -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;
+185
View File
@@ -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);
}
}
+93
View File
@@ -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 IDAddressables
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);
}
}
+338
View File
@@ -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);
}
}
+11
View File
@@ -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;
+122
View File
@@ -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 中标记为 TODOPhase 2 实现
async fn parse(&self, bundle: &RawAssetBundle) -> Result<ParsedAssetBundle, String>;
/// 序列化 AssetBundle
///
/// # 参数
/// - `parsed`: 解析后的 AssetBundle
///
/// # 返回
/// - 成功:返回序列化后的数据
/// - 失败:返回错误
///
/// # 注意
/// Phase 1 中标记为 TODOPhase 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()));
}
}
+114
View File
@@ -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());
}
}
+158
View File
@@ -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"));
}
}