mirror of
https://github.com/StarCitizenToolBox/app.git
synced 2026-02-06 15:10:20 +00:00
feat: update unp4k
This commit is contained in:
2
rust/Cargo.lock
generated
2
rust/Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user