Skip to content

Commit

Permalink
Implement "decrypt suite" command.
Browse files Browse the repository at this point in the history
  • Loading branch information
Duosion committed Nov 14, 2024
1 parent 5161d9c commit a5eafad
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 78 deletions.
27 changes: 25 additions & 2 deletions src/api/sekai_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,31 @@ impl<T: UrlProvider> SekaiClient<T> {
///
/// These files contain information about what character cards and gacha banners exist among many other things.
///
/// This function will, if successful, return a ``serde_json::Value`` representing the suitemasterfile.
pub async fn get_suitemasterfile(&self, file_path: &str) -> Result<Value, ApiError> {
/// This function will, if successful, return bytes representing an encrypted suitemasterfile.
pub async fn get_suitemasterfile(&self, file_path: &str) -> Result<Vec<u8>, ApiError> {
let request = self
.client
.get(self.url_provider.suitemasterfile(file_path))
.headers(self.headers.get_map());

match request.send().await?.error_for_status() {
Ok(response) => {
// parse body
let bytes = response.bytes().await?;
Ok(bytes.to_vec())
}
Err(err) => Err(ApiError::InvalidRequest(err.to_string())),
}
}

/// Performs a request to download a suitemasterfile.
///
/// The suitemasterfile endpoint is used for download split suite master files.
///
/// These files contain information about what character cards and gacha banners exist among many other things.
///
/// This function will, if successful, return a ``serde_json::Value`` representing a decrypted suitemasterfile.
pub async fn get_suitemasterfile_as_value(&self, file_path: &str) -> Result<Value, ApiError> {
let request = self
.client
.get(self.url_provider.suitemasterfile(file_path))
Expand Down
1 change: 1 addition & 0 deletions src/constants/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub mod command {
// encrypt suite
pub const SUITE_PROCESSING: &str = "Processing suitemaster files...";
pub const SUITE_SAVING: &str = "Saving encrypted suitemaster files...";
pub const SUITE_DECRYPTING: &str = "Decrypting suitemaster files...";
pub const SUITE_ENCRYPTED_FILE_NAME: &str = "00_suitemasterfile";

// extract hash
Expand Down
15 changes: 3 additions & 12 deletions src/crypto/assetbundle.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::{io::SeekFrom, path::PathBuf};

use tokio::{
fs::{create_dir_all, File},
fs::File,
io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, AsyncWrite, AsyncWriteExt, BufReader},
};

use crate::error::AssetbundleError;
use crate::{error::AssetbundleError, utils::fs::write_file};

const UNITY_ASSETBUNDLE_MAGIC: &[u8] = b"\x55\x6e\x69\x74\x79\x46";
const SEKAI_ASSETBUNDLE_MAGIC: &[u8] = b"\x10\x00\x00\x00";
Expand Down Expand Up @@ -136,16 +136,7 @@ pub async fn crypt_file(
};

// create parent folders if they do not exist
if let Some(parent) = out_path.parent() {
create_dir_all(parent).await?;
}
let mut out_file = File::options()
.write(true)
.create(true)
.truncate(true)
.open(out_path)
.await?;
out_file.write_all(&crypted).await?;
write_file(out_path, &crypted).await?;

Ok(())
}
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ pub enum CommandError {
#[error("rmp_serde encode error: {0}")]
RmpSerdeEncode(#[from] rmp_serde::encode::Error),

#[error("rmp_serde encode error: {0}")]
RmpSerdeDecode(#[from] rmp_serde::decode::Error),

#[error("not enough space: {0}")]
NotEnoughSpace(String),

Expand Down
5 changes: 5 additions & 0 deletions src/subcommands/crypt/decrypt/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
pub mod ab;
pub mod suite;

use ab::DecryptAbArgs;
use clap::{Args, Subcommand};
use suite::DecryptSuiteArgs;

use crate::error::CommandError;

#[derive(Debug, Subcommand)]
enum Commands {
/// Decrypt assetbundles.
Ab(DecryptAbArgs),
/// Decrypt suitemaster files.
Suite(DecryptSuiteArgs),
}

#[derive(Debug, Args)]
Expand All @@ -21,5 +25,6 @@ pub struct DecryptArgs {
pub async fn decrypt(args: DecryptArgs) -> Result<(), CommandError> {
match args.command {
Commands::Ab(args) => ab::decrypt_ab(&args).await,
Commands::Suite(args) => suite::decrypt_suite(args).await,
}
}
118 changes: 118 additions & 0 deletions src/subcommands/crypt/decrypt/suite.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use crate::{
constants::{color, strings},
crypto::aes_msgpack,
error::CommandError,
models::enums::Server,
utils::{
fs::{extract_suitemaster_file, scan_path},
progress::{ProgressBar, WithProgress},
},
};
use clap::Args;
use futures::{stream, StreamExt};
use serde_json::Value;
use std::path::{Path, PathBuf};
use tokio::{fs::File, io::AsyncReadExt, time::Instant};

#[derive(Debug, Args)]
pub struct DecryptSuiteArgs {
/// If the input is a directory, whether to recursively decrypt valid files in that directory
#[arg(long, short, default_value_t = false)]
pub recursive: bool,

/// The maximum number of files to decrypt simultaneously
#[arg(long, short, default_value_t = crate::utils::available_parallelism())]
pub concurrent: usize,

/// The server to decrypt the suitemasterfiles for
#[arg(short, long, value_enum, default_value_t = Server::Japan)]
pub server: Server,

/// Whether to output debug messages, such as errors
#[arg(long, short, default_value_t = false)]
pub debug: bool,

/// Path to the file or directory to decrypt
pub in_path: String,

/// Path to a directory or file to output to.
pub out_path: String,
}

/// Reads the file at the input path as a [`serde_json::Value`]
/// and extracts its inner fields to out_path as .json files.
async fn decrypt_suitemaster_file(
in_path: PathBuf,
out_path: &Path,
server: &Server,
) -> Result<(), CommandError> {
// read in file
let mut file = File::open(in_path).await?;
let mut file_buf = Vec::new();
file.read_to_end(&mut file_buf).await?;

// deserialize as a value
let deserialized: Value = aes_msgpack::from_slice(&file_buf, server)?;

// write to out_path
extract_suitemaster_file(deserialized, out_path).await?;

Ok(())
}

/// Decrypts encrypted suitemaster files into individual .json files.
pub async fn decrypt_suite(args: DecryptSuiteArgs) -> Result<(), CommandError> {
// get paths that we need to decrypt
let decrypt_start = Instant::now();
let to_decrypt_paths = scan_path(&Path::new(&args.in_path), args.recursive).await?;
let out_path = Path::new(&args.out_path);

// create decrypt progress bar
println!(
"{}[1/1] {}{}",
color::TEXT_VARIANT.render_fg(),
color::TEXT.render_fg(),
strings::command::SUITE_DECRYPTING,
);
let decrypt_progress = ProgressBar::progress(to_decrypt_paths.len() as u64);

// begin decrypting
let server = args.server;
let decrypt_results: Vec<Result<(), CommandError>> = stream::iter(to_decrypt_paths)
.map(|in_path| {
decrypt_suitemaster_file(in_path, &out_path, &server).with_progress(&decrypt_progress)
})
.buffer_unordered(args.concurrent)
.collect()
.await;

decrypt_progress.finish_and_clear();

// count the number of successes
let do_debug = args.debug;
let success_count = decrypt_results
.iter()
.filter(|&result| {
if let Err(err) = result {
if do_debug {
println!("suite decrypt error: {:?}", err);
}
false
} else {
true
}
})
.count();

// print the result
println!(
"{}Successfully {} {} files in {:?}.{}",
color::SUCCESS.render_fg(),
strings::crypto::decrypt::PROCESSED,
success_count,
Instant::now().duration_since(decrypt_start),
color::TEXT.render_fg(),
);

Ok(())
}
16 changes: 3 additions & 13 deletions src/subcommands/crypt/encrypt/suite.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
use crate::constants::{color, strings};
use crate::crypto::aes_msgpack;
use crate::models::enums::Server;
use crate::utils::fs::write_file;
use crate::utils::progress::{ProgressBar, WithProgress};
use crate::{error::CommandError, utils::fs::scan_path};
use clap::Args;
use futures::{stream, StreamExt};
use serde_json::{Map, Value};
use std::path::{Path, PathBuf};
use tokio::fs::create_dir_all;
use tokio::io::AsyncWriteExt;
use tokio::time::Instant;

#[derive(Debug, Args)]
pub struct EncryptSuiteArgs {
/// If the input is a directory, whether to recursively decrypt valid files in that directory
/// If the input is a directory, whether to recursively encrypt valid files in that directory
#[arg(long, short, default_value_t = false)]
pub recursive: bool,

Expand Down Expand Up @@ -105,16 +104,7 @@ pub async fn encrypt_suite(args: EncryptSuiteArgs) -> Result<(), CommandError> {

// write to out directory
let out_path = Path::new(&args.out_path).join(strings::command::SUITE_ENCRYPTED_FILE_NAME);
if let Some(parent) = out_path.parent() {
create_dir_all(parent).await?;
}
let mut out_file = tokio::fs::File::options()
.write(true)
.create(true)
.truncate(true)
.open(out_path)
.await?;
out_file.write_all(&serialized).await?;
write_file(&out_path, &serialized).await?;

// print the result
println!(
Expand Down
19 changes: 2 additions & 17 deletions src/subcommands/fetch/abinfo.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
use std::path::Path;

use clap::Args;
use tokio::{
fs::{create_dir_all, File},
io::AsyncWriteExt,
};

use crate::{
api::sekai_client::SekaiClient,
constants::{color, strings},
error::CommandError,
models::enums::{Platform, Server},
subcommands::fetch::get_assetbundle_info,
utils::progress::ProgressBar,
utils::{fs::write_file, progress::ProgressBar},
};

#[derive(Debug, Args)]
Expand Down Expand Up @@ -73,18 +69,7 @@ pub async fn abinfo(args: AbInfoArgs) -> Result<(), CommandError> {
let out_path = out_dir_path.join(Path::new(&format!("{}.json", assetbundle_info.version)));

// create parent folders if they do not exist
if let Some(parent) = out_path.parent() {
create_dir_all(parent).await?;
}

// save file
let mut out_file = File::options()
.write(true)
.create(true)
.truncate(true)
.open(&out_path)
.await?;
out_file.write_all(&assetbundle_info_serialized).await?;
write_file(&out_path, &assetbundle_info_serialized).await?;

println!(
"{}{}{}{}",
Expand Down
Loading

0 comments on commit a5eafad

Please sign in to comment.