mirror of
https://github.com/Bluemangoo/sekai-unpacker.git
synced 2026-09-19 15:59:01 +08:00
Initial Commit
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "common"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
tokio = { workspace = true, features = ["io-util", "fs"] }
|
||||
anyhow = { workspace = true }
|
||||
h2 = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
@@ -0,0 +1,19 @@
|
||||
use crate::updater::DownloadTask;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SyncResponse {
|
||||
pub id: String,
|
||||
pub tasks: Vec<DownloadTask>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DownloadRequest {
|
||||
pub id: String,
|
||||
pub task: DownloadTask,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CloseRequest {
|
||||
pub id: String,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod stream;
|
||||
pub mod updater;
|
||||
pub mod http;
|
||||
@@ -0,0 +1,85 @@
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use h2::SendStream;
|
||||
use std::path::Path;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt};
|
||||
|
||||
pub async fn server_send_files<P: AsRef<Path>>(
|
||||
mut send_stream: SendStream<Bytes>,
|
||||
base: P,
|
||||
files: &[P],
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut header = BytesMut::with_capacity(4);
|
||||
header.put_u32(files.len() as u32);
|
||||
send_stream.send_data(header.freeze(), false)?;
|
||||
|
||||
for (i, path_ref) in files.iter().enumerate() {
|
||||
let path = path_ref.as_ref();
|
||||
let mut file = File::open(path).await?;
|
||||
let metadata = file.metadata().await?;
|
||||
|
||||
let file_name = path
|
||||
.strip_prefix(base.as_ref())
|
||||
.map(|n| n.to_string_lossy().to_string())?;
|
||||
let name_bytes = file_name.as_bytes();
|
||||
let file_size = metadata.len();
|
||||
|
||||
let mut meta_buf = BytesMut::with_capacity(2 + name_bytes.len() + 8);
|
||||
meta_buf.put_u16(name_bytes.len() as u16);
|
||||
meta_buf.put_slice(name_bytes);
|
||||
meta_buf.put_u64(file_size);
|
||||
|
||||
send_stream.send_data(meta_buf.freeze(), false)?;
|
||||
|
||||
let mut buffer = vec![0u8; 8192];
|
||||
let mut sent_size = 0u64;
|
||||
|
||||
while sent_size < file_size {
|
||||
let n = file.read(&mut buffer).await?;
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let chunk = Bytes::copy_from_slice(&buffer[..n]);
|
||||
|
||||
let is_last_chunk = (i == files.len() - 1) && (sent_size + n as u64 == file_size);
|
||||
|
||||
send_stream.send_data(chunk, is_last_chunk)?;
|
||||
|
||||
sent_size += n as u64;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn client_receive(
|
||||
mut response_body_reader: impl AsyncRead + Unpin,
|
||||
file_root: &Path,
|
||||
) -> anyhow::Result<()> {
|
||||
let file_count = response_body_reader.read_u32().await?;
|
||||
|
||||
for _ in 0..file_count {
|
||||
let name_len = response_body_reader.read_u16().await?;
|
||||
|
||||
let mut name_buf = vec![0u8; name_len as usize];
|
||||
response_body_reader.read_exact(&mut name_buf).await?;
|
||||
let file_name = String::from_utf8(name_buf)?;
|
||||
|
||||
let data_len = response_body_reader.read_u64().await?;
|
||||
|
||||
let path = file_root.join(&file_name);
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
}
|
||||
|
||||
let mut file = File::create(&path).await?;
|
||||
|
||||
let mut take = response_body_reader.take(data_len);
|
||||
tokio::io::copy(&mut take, &mut file).await?;
|
||||
|
||||
response_body_reader = take.into_inner();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SyncContext {
|
||||
pub region: String,
|
||||
#[serde(default)]
|
||||
pub filters: RegionFiltersConfig,
|
||||
#[serde(default)]
|
||||
pub export: RegionExportConfig,
|
||||
#[serde(default)]
|
||||
pub asset_version: Option<String>,
|
||||
#[serde(default)]
|
||||
pub asset_hash: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct RegionFiltersConfig {
|
||||
#[serde(default)]
|
||||
pub start_app: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub on_demand: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub skip: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub file_ext: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct RegionExportConfig {
|
||||
#[serde(default)]
|
||||
pub by_category: bool,
|
||||
#[serde(default)]
|
||||
pub usm: UsmExportConfig,
|
||||
#[serde(default)]
|
||||
pub acb: AcbExportConfig,
|
||||
#[serde(default)]
|
||||
pub hca: HcaExportConfig,
|
||||
#[serde(default)]
|
||||
pub images: ImageExportConfig,
|
||||
#[serde(default)]
|
||||
pub video: VideoExportConfig,
|
||||
#[serde(default)]
|
||||
pub audio: AudioExportConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct UsmExportConfig {
|
||||
pub export: bool,
|
||||
pub decode: bool,
|
||||
}
|
||||
|
||||
impl Default for UsmExportConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
export: true,
|
||||
decode: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct AcbExportConfig {
|
||||
pub export: bool,
|
||||
pub decode: bool,
|
||||
}
|
||||
|
||||
impl Default for AcbExportConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
export: true,
|
||||
decode: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct HcaExportConfig {
|
||||
pub decode: bool,
|
||||
}
|
||||
|
||||
impl Default for HcaExportConfig {
|
||||
fn default() -> Self {
|
||||
Self { decode: true }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct ImageExportConfig {
|
||||
pub convert_to_webp: bool,
|
||||
pub remove_png: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct VideoExportConfig {
|
||||
pub convert_to_mp4: bool,
|
||||
pub direct_usm_to_mp4_with_ffmpeg: bool,
|
||||
pub remove_m2v: bool,
|
||||
}
|
||||
|
||||
impl Default for VideoExportConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
convert_to_mp4: true,
|
||||
direct_usm_to_mp4_with_ffmpeg: false,
|
||||
remove_m2v: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct AudioExportConfig {
|
||||
pub convert_to_mp3: bool,
|
||||
pub convert_to_flac: bool,
|
||||
pub remove_wav: bool,
|
||||
}
|
||||
|
||||
impl Default for AudioExportConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
convert_to_mp3: true,
|
||||
convert_to_flac: false,
|
||||
remove_wav: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DownloadTask {
|
||||
pub download_path: String,
|
||||
pub bundle_path: String,
|
||||
pub bundle_hash: String,
|
||||
pub category: AssetCategory,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
||||
pub enum AssetCategory {
|
||||
StartApp,
|
||||
OnDemand,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for AssetCategory {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
// Treat nil/null as Other("") — matches Go's zero-value coercion.
|
||||
let raw = Option::<String>::deserialize(deserializer)?.unwrap_or_default();
|
||||
Ok(match raw.as_str() {
|
||||
"StartApp" | "startApp" => Self::StartApp,
|
||||
"OnDemand" | "onDemand" => Self::OnDemand,
|
||||
other => Self::Other(other.to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user