feat: update unp4k

This commit is contained in:
xkeyC
2025-12-04 16:06:49 +08:00
parent e3c3986379
commit e1ed30b6e6
14 changed files with 362 additions and 411 deletions

2
rust/Cargo.lock generated
View File

@@ -3868,7 +3868,7 @@ checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3"
[[package]]
name = "unp4k_rs"
version = "0.1.0"
source = "git+https://github.com/StarCitizenToolBox/unp4k_rs?tag=V0.0.1#67f6ae242c480de9ee50ed03d31c948b68bf9522"
source = "git+https://github.com/StarCitizenToolBox/unp4k_rs?tag=V0.0.2#02867472dda1c18e81b0f635b8653fa86bd145cb"
dependencies = [
"aes",
"anyhow",

View File

@@ -30,7 +30,7 @@ ort = { version = "2.0.0-rc.10", features = ["xnnpack", "download-binaries", "nd
tokenizers = { version = "0.22", default-features = false, features = ["onig"] }
ndarray = "0.17"
serde_json = "1.0"
unp4k_rs = { git = "https://github.com/StarCitizenToolBox/unp4k_rs", tag = "V0.0.1" }
unp4k_rs = { git = "https://github.com/StarCitizenToolBox/unp4k_rs", tag = "V0.0.2" }
[target.'cfg(windows)'.dependencies]
windows = { version = "0.62.2", features = [

View File

@@ -16,6 +16,36 @@ pub struct P4kFileItem {
pub size: u64,
/// 压缩后大小(字节)
pub compressed_size: u64,
/// 文件修改时间(毫秒时间戳)
pub date_modified: i64,
}
/// 将 DOS 日期时间转换为毫秒时间戳
fn dos_datetime_to_millis(date: u16, time: u16) -> i64 {
let year = ((date >> 9) & 0x7F) as i32 + 1980;
let month = ((date >> 5) & 0x0F) as u32;
let day = (date & 0x1F) as u32;
let hour = ((time >> 11) & 0x1F) as u32;
let minute = ((time >> 5) & 0x3F) as u32;
let second = ((time & 0x1F) * 2) as u32;
let days_since_epoch = {
let mut days = 0i64;
for y in 1970..year {
days += if (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0) { 366 } else { 365 };
}
let days_in_months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
if month >= 1 && month <= 12 {
days += days_in_months[(month - 1) as usize] as i64;
if month > 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
days += 1;
}
}
days += (day as i64) - 1;
days
};
(days_since_epoch * 86400 + (hour as i64) * 3600 + (minute as i64) * 60 + (second as i64)) * 1000
}
// 全局 P4K 读取器实例(用于保持状态)
@@ -25,118 +55,84 @@ static GLOBAL_P4K_READER: once_cell::sync::Lazy<Arc<Mutex<Option<P4kFile>>>> =
static GLOBAL_P4K_FILES: once_cell::sync::Lazy<Arc<Mutex<HashMap<String, P4kEntry>>>> =
once_cell::sync::Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
/// 打开 P4K 文件
pub async fn p4k_open(p4k_path: String) -> Result<usize> {
/// 打开 P4K 文件(仅打开,不读取文件列表)
pub async fn p4k_open(p4k_path: String) -> Result<()> {
let path = PathBuf::from(&p4k_path);
if !path.exists() {
return Err(anyhow!("P4K file not found: {}", p4k_path));
}
// 在后台线程执行阻塞操作
let (reader, file_count, files_map) = tokio::task::spawn_blocking(move || {
let reader = tokio::task::spawn_blocking(move || {
let reader = P4kFile::open(&path)?;
let entries = reader.entries();
let file_count = entries.len();
let mut files_map = HashMap::new();
for entry in entries {
// 将路径转换为 Windows 风格,以 \ 开头
let name = if entry.name.starts_with("\\") {
entry.name.clone()
} else {
format!("\\{}", entry.name.replace("/", "\\"))
};
files_map.insert(name, entry.clone());
}
Ok::<_, anyhow::Error>((reader, file_count, files_map))
Ok::<_, anyhow::Error>(reader)
})
.await??;
*GLOBAL_P4K_READER.lock().unwrap() = Some(reader);
*GLOBAL_P4K_FILES.lock().unwrap() = files_map;
// 清空之前的文件列表缓存
GLOBAL_P4K_FILES.lock().unwrap().clear();
Ok(file_count)
Ok(())
}
/// 确保文件列表已加载(内部使用)
fn ensure_files_loaded() -> Result<usize> {
let mut files = GLOBAL_P4K_FILES.lock().unwrap();
if !files.is_empty() {
return Ok(files.len());
}
let reader = GLOBAL_P4K_READER.lock().unwrap();
if reader.is_none() {
return Err(anyhow!("P4K reader not initialized"));
}
let entries = reader.as_ref().unwrap().entries();
for entry in entries {
let name = if entry.name.starts_with("\\") {
entry.name.clone()
} else {
format!("\\{}", entry.name.replace("/", "\\"))
};
files.insert(name, entry.clone());
}
Ok(files.len())
}
/// 获取文件数量(会触发文件列表加载)
pub async fn p4k_get_file_count() -> Result<usize> {
tokio::task::spawn_blocking(|| ensure_files_loaded()).await?
}
/// 获取所有文件列表
pub async fn p4k_get_all_files() -> Result<Vec<P4kFileItem>> {
let files = GLOBAL_P4K_FILES.lock().unwrap();
let mut result = Vec::with_capacity(files.len());
tokio::task::spawn_blocking(|| {
ensure_files_loaded()?;
let files = GLOBAL_P4K_FILES.lock().unwrap();
let mut result = Vec::with_capacity(files.len());
for (name, entry) in files.iter() {
result.push(P4kFileItem {
name: name.clone(),
is_directory: false,
size: entry.uncompressed_size,
compressed_size: entry.compressed_size,
});
}
Ok(result)
}
/// 获取指定目录下的文件列表
pub async fn p4k_get_files_in_directory(directory: String) -> Result<Vec<P4kFileItem>> {
let files = GLOBAL_P4K_FILES.lock().unwrap();
let mut result = Vec::new();
let mut dirs = std::collections::HashSet::new();
// 确保目录路径以 \ 开头和结尾
let dir_path = if !directory.starts_with("\\") {
format!("\\{}", directory)
} else {
directory.clone()
};
let dir_path = if !dir_path.ends_with("\\") {
format!("{}\\", dir_path)
} else {
dir_path
};
for (name, entry) in files.iter() {
if name.starts_with(&dir_path) {
let relative = &name[dir_path.len()..];
if let Some(slash_pos) = relative.find("\\") {
// 这是一个子目录
let subdir = &relative[..slash_pos];
if !dirs.contains(subdir) {
dirs.insert(subdir.to_string());
result.push(P4kFileItem {
name: format!("{}{}\\", dir_path, subdir),
is_directory: true,
size: 0,
compressed_size: 0,
});
}
} else {
// 这是一个文件
result.push(P4kFileItem {
name: name.clone(),
is_directory: false,
size: entry.uncompressed_size,
compressed_size: entry.compressed_size,
});
}
for (name, entry) in files.iter() {
result.push(P4kFileItem {
name: name.clone(),
is_directory: false,
size: entry.uncompressed_size,
compressed_size: entry.compressed_size,
date_modified: dos_datetime_to_millis(entry.mod_date, entry.mod_time),
});
}
}
// 按目录优先,然后按名称排序
result.sort_by(|a, b| {
if a.is_directory && !b.is_directory {
std::cmp::Ordering::Less
} else if !a.is_directory && b.is_directory {
std::cmp::Ordering::Greater
} else {
a.name.cmp(&b.name)
}
});
Ok(result)
Ok(result)
})
.await?
}
/// 提取文件到内存
pub async fn p4k_extract_to_memory(file_path: String) -> Result<Vec<u8>> {
// 确保文件列表已加载
tokio::task::spawn_blocking(|| ensure_files_loaded()).await??;
// 规范化路径
let normalized_path = if file_path.starts_with("\\") {
file_path.clone()

View File

@@ -37,7 +37,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
default_rust_auto_opaque = RustAutoOpaqueNom,
);
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1";
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -737964996;
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1801517256;
// Section: executor
@@ -390,24 +390,20 @@ fn wire__crate__api__unp4k_api__p4k_get_all_files_impl(
},
)
}
fn wire__crate__api__unp4k_api__p4k_get_files_in_directory_impl(
fn wire__crate__api__unp4k_api__p4k_get_file_count_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
directory: impl CstDecode<String>,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::DcoCodec, _, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "p4k_get_files_in_directory",
debug_name: "p4k_get_file_count",
port: Some(port_),
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
},
move || {
let api_directory = directory.cst_decode();
move |context| async move {
transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>(
(move || async move {
let output_ok =
crate::api::unp4k_api::p4k_get_files_in_directory(api_directory)
.await?;
let output_ok = crate::api::unp4k_api::p4k_get_file_count().await?;
Ok(output_ok)
})()
.await,
@@ -713,6 +709,12 @@ impl CstDecode<i32> for i32 {
self
}
}
impl CstDecode<i64> for i64 {
// Codec=Cst (C-struct based), see doc to use other codecs
fn cst_decode(self) -> i64 {
self
}
}
impl CstDecode<crate::http_package::MyHttpVersion> for i32 {
// Codec=Cst (C-struct based), see doc to use other codecs
fn cst_decode(self) -> crate::http_package::MyHttpVersion {
@@ -836,6 +838,13 @@ impl SseDecode for i32 {
}
}
impl SseDecode for i64 {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
deserializer.cursor.read_i64::<NativeEndian>().unwrap()
}
}
impl SseDecode for Vec<String> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@@ -999,11 +1008,13 @@ impl SseDecode for crate::api::unp4k_api::P4kFileItem {
let mut var_isDirectory = <bool>::sse_decode(deserializer);
let mut var_size = <u64>::sse_decode(deserializer);
let mut var_compressedSize = <u64>::sse_decode(deserializer);
let mut var_dateModified = <i64>::sse_decode(deserializer);
return crate::api::unp4k_api::P4kFileItem {
name: var_name,
is_directory: var_isDirectory,
size: var_size,
compressed_size: var_compressedSize,
date_modified: var_dateModified,
};
}
}
@@ -1223,6 +1234,7 @@ impl flutter_rust_bridge::IntoDart for crate::api::unp4k_api::P4kFileItem {
self.is_directory.into_into_dart().into_dart(),
self.size.into_into_dart().into_dart(),
self.compressed_size.into_into_dart().into_dart(),
self.date_modified.into_into_dart().into_dart(),
]
.into_dart()
}
@@ -1400,6 +1412,13 @@ impl SseEncode for i32 {
}
}
impl SseEncode for i64 {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
serializer.cursor.write_i64::<NativeEndian>(self).unwrap();
}
}
impl SseEncode for Vec<String> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@@ -1550,6 +1569,7 @@ impl SseEncode for crate::api::unp4k_api::P4kFileItem {
<bool>::sse_encode(self.is_directory, serializer);
<u64>::sse_encode(self.size, serializer);
<u64>::sse_encode(self.compressed_size, serializer);
<i64>::sse_encode(self.date_modified, serializer);
}
}
@@ -1809,6 +1829,7 @@ mod io {
is_directory: self.is_directory.cst_decode(),
size: self.size.cst_decode(),
compressed_size: self.compressed_size.cst_decode(),
date_modified: self.date_modified.cst_decode(),
}
}
}
@@ -1869,6 +1890,7 @@ mod io {
is_directory: Default::default(),
size: Default::default(),
compressed_size: Default::default(),
date_modified: Default::default(),
}
}
}
@@ -2075,11 +2097,10 @@ mod io {
}
#[unsafe(no_mangle)]
pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__unp4k_api__p4k_get_files_in_directory(
pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__unp4k_api__p4k_get_file_count(
port_: i64,
directory: *mut wire_cst_list_prim_u_8_strict,
) {
wire__crate__api__unp4k_api__p4k_get_files_in_directory_impl(port_, directory)
wire__crate__api__unp4k_api__p4k_get_file_count_impl(port_)
}
#[unsafe(no_mangle)]
@@ -2317,6 +2338,7 @@ mod io {
is_directory: bool,
size: u64,
compressed_size: u64,
date_modified: i64,
}
#[repr(C)]
#[derive(Clone, Copy)]