use indicatif::{ProgressBar, ProgressStyle};
use memmap2::MmapMut;
use samloader_fus::FusClient;
use samloader_fus::aes::cipher::BlockModeDecrypt;
use samloader_fus::aes::cipher::inout::InOutBuf;
use std::fs::OpenOptions;
use std::io::Read;
use std::thread;
use std::time::Duration;
const PROGRESS_TEMPLATE: &str =
"[{elapsed_precise}] [{bar:40}] {bytes}/{total_bytes} ({bytes_per_sec}) [{eta_precise}]";
pub(crate) struct DownloadArgs {
pub(crate) model: String,
pub(crate) region: String,
pub(crate) threads: u64,
pub(crate) out_dir: Option<String>,
pub(crate) out_file: Option<String>,
}
pub(crate) fn download_latest_firmware(args: DownloadArgs) {
let mut client = FusClient::new().expect("Unable to establish FusClient");
client.fetch_binary_info(&args.model, &args.region);
println!("Firmware Version: {}", client.info.version);
let default_name = client
.info
.filename
.strip_suffix(".enc4")
.unwrap_or(client.info.filename.as_str());
let final_out = match (args.out_file, args.out_dir) {
(Some(name), _) => name,
(None, Some(dir)) => format!("{}/{}", dir, default_name),
_ => default_name.to_string(),
};
println!("Downloading {} to {}", client.info.filename, final_out);
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(&final_out)
.unwrap();
file.set_len(client.info.size)
.expect("Cannot pre-allocate file");
let mut map = unsafe { MmapMut::map_mut(&file).expect("Cannot map file") };
client.init_download();
let chunk_size = (client.info.size / args.threads / 16 + 1) * 16;
let progress = ProgressBar::new(client.info.size)
.with_style(ProgressStyle::with_template(PROGRESS_TEMPLATE).unwrap());
progress.enable_steady_tick(Duration::from_secs(1));
thread::scope(|s| {
let mut chunks = map.chunks_mut(chunk_size as usize).enumerate().peekable();
while let Some((i, chunk)) = chunks.next() {
let is_last = chunks.peek().is_none();
let start = i as u64 * chunk_size;
let end = if is_last {
None
} else {
Some(start + chunk_size - 1)
};
let mut resp = client
.download_file(Some(start), end)
.expect("Download request failed");
let mut dec = client.get_decryptor();
let progress_ref = &progress;
s.spawn(move || {
let mut dl_pos = 0_usize;
let mut dec_pos = 0_usize;
loop {
match resp.read(&mut chunk[dl_pos..]) {
Ok(0) => break, Ok(n) => {
dl_pos += n;
progress_ref.inc(n as u64);
}
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {
continue;
}
Err(e) => panic!("Download failed: {e:?}"),
}
let encrypted = InOutBuf::from(&mut chunk[dec_pos..dl_pos]);
let (blocks, tail) = encrypted.into_chunks();
dec.decrypt_blocks_inout(blocks);
dec_pos = dl_pos - tail.len();
}
});
thread::sleep(Duration::from_millis(100));
}
});
let last_byte = *map.last().unwrap();
map.flush().ok();
drop(map);
if last_byte > 0 && last_byte <= 16 {
let file_len = file
.metadata()
.ok()
.map(|m| m.len())
.unwrap_or(client.info.size);
file.set_len(file_len - last_byte as u64)
.expect("Failed to truncate file");
}
progress.finish();
}