diff --git a/README.md b/README.md index 3dddd378..9a992a95 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ We have a Matrix Room Space `#obliteration:matrix.org` on [Matrix.to](https://ma ## Features -- [ ] Built-in PUP file supports for decrypted PUP from [pup_decrypt](https://github.com/idc/ps4-pup_decrypt). +- [x] Built-in PUP file supports for decrypted PUP from [pup_decrypt](https://github.com/idc/ps4-pup_decrypt). - [x] Built-in PKG file supports for Fake PKG. - [x] Game library. - [ ] Emulate system calls instead of user-space libraries. diff --git a/src/exfat/src/cluster.rs b/src/exfat/src/cluster.rs index 0edf4740..58c62f45 100644 --- a/src/exfat/src/cluster.rs +++ b/src/exfat/src/cluster.rs @@ -1,8 +1,7 @@ -use crate::entries::ClusterAllocation; use crate::fat::Fat; use crate::param::Params; use std::cmp::min; -use std::io::{ErrorKind, Read, Seek, SeekFrom}; +use std::io::{Read, Seek, SeekFrom}; use thiserror::Error; /// A cluster reader to read all data in a cluster chain. @@ -10,138 +9,107 @@ pub(crate) struct ClustersReader<'a, I: Read + Seek> { params: &'a Params, image: &'a mut I, chain: Vec, - cluster_size: u64, // In bytes. - tail_size: u64, // In bytes. - cluster: usize, // Index into chain. - offset: u64, // Offset into the current cluster. + data_length: u64, + offset: u64, } impl<'a, I: Read + Seek> ClustersReader<'a, I> { - /// Construct a [`ClustersReader`] from the specified [`ClusterAllocation`]. - pub fn from_alloc( - params: &'a Params, - fat: &Fat, - image: &'a mut I, - alloc: &ClusterAllocation, - no_fat_chain: Option, - ) -> Result { - // Get cluster chain. - let first_cluster = alloc.first_cluster(); - let data_length = alloc.data_length(); - let cluster_size = params.cluster_size(); - let chain: Vec = if no_fat_chain.unwrap_or(false) { - fat.get_cluster_chain(first_cluster).collect() - } else if data_length == 0 { - return Err(FromAllocError::InvalidDataLength); - } else { - // FIXME: Use div_ceil once https://github.com/rust-lang/rust/issues/88581 stabilized. - let count = (data_length + cluster_size - 1) / cluster_size; - - (first_cluster..(first_cluster + count as usize)).collect() - }; - - // Seek to first cluster. - let tail_size = data_length % cluster_size; - let mut reader = Self { - params, - image, - chain, - cluster_size, - tail_size: if tail_size == 0 { - cluster_size - } else { - tail_size - }, - cluster: 0, - offset: 0, - }; - - if let Err(e) = reader.seek() { - return Err(FromAllocError::IoFailed(e)); - } - - Ok(reader) - } - pub fn new( params: &'a Params, fat: &Fat, image: &'a mut I, first_cluster: usize, data_length: Option, + no_fat_chain: Option, ) -> Result { if first_cluster < 2 { return Err(NewError::InvalidFirstCluster); } // Get cluster chain. - let chain: Vec = fat.get_cluster_chain(first_cluster).collect(); - - if chain.is_empty() { - return Err(NewError::InvalidFirstCluster); - } - - // Get data length. let cluster_size = params.cluster_size(); - let data_length = match data_length { - Some(v) => { - if v > cluster_size * chain.len() as u64 { - return Err(NewError::InvalidDataLength); - } else { - v - } - } - None => params.bytes_per_sector * (params.sectors_per_cluster * chain.len() as u64), - }; + let (chain, data_length) = if no_fat_chain.unwrap_or(false) { + // If the NoFatChain bit is 1 then DataLength must not be zero. + let data_length = match data_length { + Some(v) if v > 0 => v, + _ => return Err(NewError::InvalidDataLength), + }; - let tail_size = data_length % cluster_size; + // FIXME: Use div_ceil once https://github.com/rust-lang/rust/issues/88581 stabilized. + let count = (data_length + cluster_size - 1) / cluster_size; + let chain: Vec = (first_cluster..(first_cluster + count as usize)).collect(); + + (chain, data_length) + } else { + let chain: Vec = fat.get_cluster_chain(first_cluster).collect(); + + if chain.is_empty() { + return Err(NewError::InvalidFirstCluster); + } + + let data_length = match data_length { + Some(v) => { + if v > cluster_size * chain.len() as u64 { + return Err(NewError::InvalidDataLength); + } else { + v + } + } + None => params.bytes_per_sector * (params.sectors_per_cluster * chain.len() as u64), + }; + + (chain, data_length) + }; // Seek to first cluster. let mut reader = Self { params, image, chain, - cluster_size, - tail_size: if tail_size == 0 { - cluster_size - } else { - tail_size - }, - cluster: 0, + data_length, offset: 0, }; - if let Err(e) = reader.seek() { - return Err(NewError::IoFailed(e)); + if let Err(e) = reader.seek_current_cluster() { + return Err(NewError::SeekToFirstClusterFailed(e)); } Ok(reader) } pub fn cluster(&self) -> usize { - self.chain[self.cluster] + self.chain[(self.offset / self.params.cluster_size()) as usize] } - fn seek(&mut self) -> Result<(), std::io::Error> { - // Get offset into image. - let cluster = self.cluster(); + fn seek_current_cluster(&mut self) -> Result<(), std::io::Error> { + use std::io::{Error, ErrorKind}; + + // Check if the offset is exactly at the cluster beginning. + let cluster_size = self.params.cluster_size(); + + if self.offset % cluster_size != 0 { + panic!("The current offset must be at the beginning of the cluster."); + } + + // Calculate an offset for the cluster. + let cluster = self.chain[(self.offset / cluster_size) as usize]; let offset = match self.params.cluster_offset(cluster) { - Some(v) => v + self.offset, + Some(v) => v, None => { - return Err(std::io::Error::new( + return Err(Error::new( ErrorKind::Other, - format!("cluster #{} is not available", cluster), + format!("cluster #{cluster} does not exists in the image"), )); } }; - // Seek image reader. + // Seek image to the cluster. match self.image.seek(SeekFrom::Start(offset)) { Ok(v) => { if v != offset { - return Err(std::io::Error::new( + return Err(Error::new( ErrorKind::Other, - format!("cluster #{} is not available", cluster), + format!("cluster #{cluster} does not exists in the image"), )); } } @@ -152,55 +120,130 @@ impl<'a, I: Read + Seek> ClustersReader<'a, I> { } } -impl<'a, I: Read + Seek> Read for ClustersReader<'a, I> { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - // Check if the actual read is required. - if buf.is_empty() || self.cluster == self.chain.len() { - return Ok(0); - } +impl<'a, I: Read + Seek> Seek for ClustersReader<'a, I> { + fn seek(&mut self, pos: SeekFrom) -> std::io::Result { + use std::io::{Error, ErrorKind}; - // Get cluster size. - let cluster_size = if self.cluster == self.chain.len() - 1 { - self.tail_size - } else { - self.cluster_size + // Calculate target offset. + let offset = match pos { + SeekFrom::Start(v) => min(v, self.data_length), + SeekFrom::End(v) => { + if v >= 0 { + self.data_length + } else if let Some(v) = self.data_length.checked_sub(v.unsigned_abs()) { + v + } else { + return Err(Error::from(ErrorKind::InvalidInput)); + } + } + SeekFrom::Current(v) => { + if v >= 0 { + min(self.offset + (v as u64), self.data_length) + } else if let Some(v) = self.offset.checked_sub(v.unsigned_abs()) { + v + } else { + return Err(Error::from(ErrorKind::InvalidInput)); + } + } }; - // Read image. - let remaining = cluster_size - self.offset; - let target = min(buf.len(), remaining as usize); - let read = self.image.read(&mut buf[..target])?; + // Check if we need to do the actual seek. + if offset != self.offset { + // Calculate the offset for the cluster where the target offset is belong. + let cluster_size = self.params.cluster_size(); + let cluster = self.chain[(offset / cluster_size) as usize]; + let cluster_offset = match self.params.cluster_offset(cluster) { + Some(v) => v, + None => { + return Err(Error::new( + ErrorKind::Other, + format!("cluster #{cluster} is not available"), + )); + } + }; - if read == 0 { - return Err(ErrorKind::UnexpectedEof.into()); - } + // Seek image to the target offset inside the cluster. + let image_offset = cluster_offset + offset % cluster_size; - self.offset += read as u64; - - // Check if all data in the current cluster is read. - if self.offset == cluster_size { - self.cluster += 1; - self.offset = 0; - - if self.cluster < self.chain.len() { - self.seek()?; + match self.image.seek(SeekFrom::Start(image_offset)) { + Ok(v) => { + if v != image_offset { + return Err(Error::new( + ErrorKind::Other, + format!("offset {v} does not exists in the image"), + )); + } + } + Err(e) => return Err(e), } + + self.offset = offset; } - Ok(read) + Ok(offset) + } + + fn rewind(&mut self) -> std::io::Result<()> { + if self.offset != 0 { + // Seek image to the first cluster. + // We don't need to check if the first cluster is valid because we already checked it + // inside new. + let first_cluster = self.params.cluster_offset(self.chain[0]).unwrap(); + + self.image.seek(SeekFrom::Start(first_cluster))?; + + // Set the offset. + self.offset = 0; + } + + Ok(()) + } + + fn stream_position(&mut self) -> std::io::Result { + Ok(self.offset) } } -/// Represents an error for [`from_alloc()`][ClustersReader::from_alloc]. -#[derive(Debug, Error)] -pub enum FromAllocError { - #[error("data length is not valid")] - InvalidDataLength, +impl<'a, I: Read + Seek> Read for ClustersReader<'a, I> { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + // Check if the actual read is required. + if buf.is_empty() || self.offset == self.data_length { + return Ok(0); + } - #[error("I/O failed")] - IoFailed(#[source] std::io::Error), + // Get remaining data in the current cluster. + let cluster_size = self.params.cluster_size(); + let cluster_remaining = cluster_size - self.offset % cluster_size; + let remaining = min(cluster_remaining, self.data_length - self.offset); + + // Read image. + let amount = min(buf.len(), remaining as usize); + + self.image.read_exact(&mut buf[..amount])?; + self.offset += amount as u64; + + // Check if we need to move to next cluster. + if self.offset != self.data_length && amount == (cluster_remaining as usize) { + if let Err(e) = self.seek_current_cluster() { + // Reset offset back to the previous position. + let previous_offset = self.offset - (amount as u64); + + if let Err(e) = self.seek(SeekFrom::Start(previous_offset)) { + panic!("Cannot seek back to the previous offset: {e}."); + } + + // Don't do this before we invoke seek on the above. + self.offset -= amount as u64; + + return Err(e); + } + } + + Ok(amount) + } } +/// Represents an error for [`new()`][ClustersReader::new()]. #[derive(Debug, Error)] pub enum NewError { #[error("first cluster is not valid")] @@ -209,6 +252,6 @@ pub enum NewError { #[error("data length is not valid")] InvalidDataLength, - #[error("I/O failed")] - IoFailed(#[source] std::io::Error), + #[error("cannot seek to the first cluster")] + SeekToFirstClusterFailed(#[source] std::io::Error), } diff --git a/src/exfat/src/directory.rs b/src/exfat/src/directory.rs index 932d68ef..9ea3d279 100644 --- a/src/exfat/src/directory.rs +++ b/src/exfat/src/directory.rs @@ -33,12 +33,17 @@ impl Directory { let fat = self.image.fat(); let mut image = self.image.reader(); let alloc = self.stream.allocation(); - let no_fat_chain = Some(self.stream.no_fat_chain()); - let mut reader = - match ClustersReader::from_alloc(params, fat, image.deref_mut(), alloc, no_fat_chain) { - Ok(v) => EntriesReader::new(v), - Err(e) => return Err(OpenError::CreateClustersReaderFailed(alloc.clone(), e)), - }; + let mut reader = match ClustersReader::new( + params, + fat, + image.deref_mut(), + alloc.first_cluster(), + Some(alloc.data_length()), + Some(self.stream.no_fat_chain()), + ) { + Ok(v) => EntriesReader::new(v), + Err(e) => return Err(OpenError::CreateClustersReaderFailed(alloc.clone(), e)), + }; // Read file entries. let mut items: Vec> = Vec::new(); @@ -95,7 +100,7 @@ pub enum Item { #[derive(Debug, Error)] pub enum OpenError { #[error("cannot create a clusters reader for allocation {0}")] - CreateClustersReaderFailed(ClusterAllocation, #[source] crate::cluster::FromAllocError), + CreateClustersReaderFailed(ClusterAllocation, #[source] crate::cluster::NewError), #[error("cannot read an entry")] ReadEntryFailed(#[source] crate::entries::ReaderError), diff --git a/src/exfat/src/entries.rs b/src/exfat/src/entries.rs index 586ff95a..70de1827 100644 --- a/src/exfat/src/entries.rs +++ b/src/exfat/src/entries.rs @@ -259,7 +259,6 @@ impl EntryType { pub const PRIMARY: u8 = 0; pub const SECONDARY: u8 = 1; pub const CRITICAL: u8 = 0; - pub const BENIGN: u8 = 1; pub fn is_regular(self) -> bool { self.0 >= 0x81 diff --git a/src/exfat/src/file.rs b/src/exfat/src/file.rs index 10663382..d9eb33fe 100644 --- a/src/exfat/src/file.rs +++ b/src/exfat/src/file.rs @@ -1,7 +1,13 @@ +use crate::cluster::ClustersReader; use crate::entries::StreamEntry; +use crate::fat::Fat; use crate::image::Image; -use std::io::{Read, Seek}; -use std::sync::Arc; +use crate::param::Params; +use std::io::{IoSliceMut, Read, Seek, SeekFrom}; +use std::mem::transmute; +use std::ops::DerefMut; +use std::sync::{Arc, MutexGuard}; +use thiserror::Error; /// Represents a file in the exFAT. pub struct File { @@ -22,4 +28,94 @@ impl File { pub fn name(&self) -> &str { self.name.as_ref() } + + pub fn len(&self) -> u64 { + self.stream.valid_data_length() + } + + pub fn open(&mut self) -> Result>, OpenError> { + // Check if file is empty. + let alloc = self.stream.allocation(); + let first_cluster = alloc.first_cluster(); + + if first_cluster == 0 { + return Ok(None); + } + + // Create a clusters reader. + let params = self.image.params() as *const Params; + let fat = self.image.fat() as *const Fat; + let mut image = Box::new(self.image.reader()); + let reader = match ClustersReader::new( + unsafe { transmute(params) }, + unsafe { transmute(fat) }, + unsafe { transmute(image.as_mut().deref_mut()) }, + first_cluster, + Some(self.stream.valid_data_length()), + Some(self.stream.no_fat_chain()), + ) { + Ok(v) => v, + Err(e) => { + return Err(OpenError::CreateClustersReaderFailed( + first_cluster, + self.stream.valid_data_length(), + e, + )); + } + }; + + Ok(Some(FileReader { image, reader })) + } +} + +/// A struct to read file data on exFAT. +pub struct FileReader<'a, I: Read + Seek> { + reader: ClustersReader<'a, I>, + + // We need to keep this and drop it last because the reader is referencing this via a pointer. + #[allow(unused)] + image: Box>, +} + +impl<'a, I: Read + Seek> Seek for FileReader<'a, I> { + fn seek(&mut self, pos: SeekFrom) -> std::io::Result { + self.reader.seek(pos) + } + + fn rewind(&mut self) -> std::io::Result<()> { + self.reader.rewind() + } + + fn stream_position(&mut self) -> std::io::Result { + self.reader.stream_position() + } +} + +impl<'a, I: Read + Seek> Read for FileReader<'a, I> { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.reader.read(buf) + } + + fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> std::io::Result { + self.reader.read_vectored(bufs) + } + + fn read_to_end(&mut self, buf: &mut Vec) -> std::io::Result { + self.reader.read_to_end(buf) + } + + fn read_to_string(&mut self, buf: &mut String) -> std::io::Result { + self.reader.read_to_string(buf) + } + + fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> { + self.reader.read_exact(buf) + } +} + +/// Represents an error for [`open()`][File::open()]. +#[derive(Debug, Error)] +pub enum OpenError { + #[error("cannot create a clusters reader for allocation {0}:{1}")] + CreateClustersReaderFailed(usize, u64, #[source] crate::cluster::NewError), } diff --git a/src/exfat/src/lib.rs b/src/exfat/src/lib.rs index bc337b42..1c2a7c72 100644 --- a/src/exfat/src/lib.rs +++ b/src/exfat/src/lib.rs @@ -92,8 +92,8 @@ impl ExFat { }; // Create a entries reader for the root directory. - let root_cluster = params.first_cluster_of_root_directory; - let mut reader = match ClustersReader::new(¶ms, &fat, &mut image, root_cluster, None) { + let root = params.first_cluster_of_root_directory; + let mut reader = match ClustersReader::new(¶ms, &fat, &mut image, root, None, None) { Ok(v) => EntriesReader::new(v), Err(e) => return Err(OpenError::CreateClustersReaderFailed(e)), }; diff --git a/src/exfat/src/param.rs b/src/exfat/src/param.rs index 6a0229e4..5fed13f6 100644 --- a/src/exfat/src/param.rs +++ b/src/exfat/src/param.rs @@ -11,6 +11,7 @@ pub(crate) struct Params { } impl Params { + /// Calculates offset in the image of a specified cluster. pub fn cluster_offset(&self, index: usize) -> Option { if index < 2 { return None; diff --git a/src/pup.hpp b/src/pup.hpp index 580b6d27..84e02921 100644 --- a/src/pup.hpp +++ b/src/pup.hpp @@ -4,9 +4,11 @@ struct pup; +typedef void (*pup_dump_status_t) (const char *name, std::uint64_t total, std::uint64_t written, void *ud); + extern "C" { pup *pup_open(const char *file, error **err); - error *pup_dump_system(const pup *pup, const char *path); + error *pup_dump_system(const pup *pup, const char *path, pup_dump_status_t status, void *ud); void pup_free(pup *pup); } diff --git a/src/pup/src/lib.rs b/src/pup/src/lib.rs index f3b1833e..58cf3252 100644 --- a/src/pup/src/lib.rs +++ b/src/pup/src/lib.rs @@ -2,14 +2,15 @@ use self::entry::Entry; use self::reader::{BlockedReader, EntryReader, NonBlockedReader}; use exfat::ExFat; use std::error::Error; +use std::ffi::{c_void, CString}; use std::fmt::{Display, Formatter}; use std::fs::{create_dir, File}; -use std::io::{Read, Seek}; +use std::io::{Read, Seek, Write}; use std::os::raw::c_char; use std::path::{Path, PathBuf}; use std::ptr::null_mut; use thiserror::Error; -use util::mem::{read_array, read_u16_le}; +use util::mem::{new_buffer, read_array, read_u16_le}; pub mod entry; pub mod reader; @@ -29,10 +30,15 @@ pub unsafe extern "C" fn pup_open(file: *const c_char, err: *mut *mut error::Err } #[no_mangle] -pub unsafe extern "C" fn pup_dump_system(pup: &Pup, path: *const c_char) -> *mut error::Error { +pub unsafe extern "C" fn pup_dump_system( + pup: &Pup, + path: *const c_char, + status: extern "C" fn(*const c_char, u64, u64, *mut c_void), + ud: *mut c_void, +) -> *mut error::Error { let path = unsafe { util::str::from_c_unchecked(path) }; - if let Err(e) = pup.dump_system_image(path) { + if let Err(e) = pup.dump_system_image(path, status, ud) { return error::Error::new(&e); } @@ -91,7 +97,12 @@ impl Pup { Ok(Self { file, entries }) } - pub fn dump_system_image>(&self, output: O) -> Result<(), DumpSystemImageError> { + pub fn dump_system_image>( + &self, + output: O, + status: extern "C" fn(*const c_char, u64, u64, *mut c_void), + ud: *mut c_void, + ) -> Result<(), DumpSystemImageError> { // Get entry. let (entry, index) = match self.get_data_entry(6) { Some(v) => v, @@ -117,8 +128,8 @@ impl Pup { use exfat::directory::Item; match item { - Item::Directory(i) => Self::dump_system_dir(output, i)?, - Item::File(i) => Self::dump_system_file(output, i)?, + Item::Directory(i) => Self::dump_system_dir(output, i, status, ud)?, + Item::File(i) => Self::dump_system_file(output, i, status, ud)?, } } @@ -128,6 +139,8 @@ impl Pup { fn dump_system_dir( parent: P, dir: exfat::directory::Directory, + status: extern "C" fn(*const c_char, u64, u64, *mut c_void), + ud: *mut c_void, ) -> Result<(), DumpSystemImageError> where P: AsRef, @@ -145,12 +158,7 @@ impl Pup { // Open the exFAT directory. let items = match dir.open() { Ok(v) => v, - Err(e) => { - return Err(DumpSystemImageError::OpenDirectoryFailed( - dir.name().into(), - e, - )); - } + Err(e) => return Err(DumpSystemImageError::OpenDirectoryFailed(path, e)), }; // Dump files. @@ -158,20 +166,82 @@ impl Pup { use exfat::directory::Item; match item { - Item::Directory(i) => Self::dump_system_dir(&path, i)?, - Item::File(i) => Self::dump_system_file(&path, i)?, + Item::Directory(i) => Self::dump_system_dir(&path, i, status, ud)?, + Item::File(i) => Self::dump_system_file(&path, i, status, ud)?, } } Ok(()) } - fn dump_system_file(_: P, _: exfat::file::File) -> Result<(), DumpSystemImageError> + fn dump_system_file( + parent: P, + mut file: exfat::file::File, + status: extern "C" fn(*const c_char, u64, u64, *mut c_void), + ud: *mut c_void, + ) -> Result<(), DumpSystemImageError> where P: AsRef, I: Read + Seek, { - // TODO: Write file. + let path = parent.as_ref().join(file.name()); + let len = file.len(); + + // Open the exFAT file. + let reader = match file.open() { + Ok(v) => v, + Err(e) => return Err(DumpSystemImageError::OpenFileFailed(path, e)), + }; + + // Create a destination file. + let mut writer = match File::create(&path) { + Ok(v) => v, + Err(e) => return Err(DumpSystemImageError::CreateFileFailed(path, e)), + }; + + // Check if an empty file. + let mut reader = match reader { + Some(v) => v, + None => return Ok(()), + }; + + // Report initial status. + let display = CString::new(path.to_string_lossy().as_ref()).unwrap(); + + status(display.as_ptr(), len, 0, ud); + + // Copy content. + let mut buf = unsafe { new_buffer(32768) }; + let mut written = 0; + + loop { + // Read the source. + let read = match reader.read(&mut buf) { + Ok(v) => v, + Err(e) => { + if e.kind() == std::io::ErrorKind::Interrupted { + continue; + } else { + return Err(DumpSystemImageError::ReadFileFailed(path, e)); + } + } + }; + + if read == 0 { + break; + } + + // Write destination. + if let Err(e) = writer.write_all(&mut buf[..read]) { + return Err(DumpSystemImageError::WriteFileFailed(path, e)); + } + + written += read; + + // Report status. + status(display.as_ptr(), len, written as u64, ud); + } + Ok(()) } @@ -256,6 +326,18 @@ pub enum DumpSystemImageError { #[error("cannot create directory {0}")] CreateDirectoryFailed(PathBuf, #[source] std::io::Error), - #[error("cannot open directory {0} on the image")] - OpenDirectoryFailed(String, #[source] exfat::directory::OpenError), + #[error("cannot open a corresponding directory {0} on the image")] + OpenDirectoryFailed(PathBuf, #[source] exfat::directory::OpenError), + + #[error("cannot open a corresponding file {0} on the image")] + OpenFileFailed(PathBuf, #[source] exfat::file::OpenError), + + #[error("cannot create file {0}")] + CreateFileFailed(PathBuf, #[source] std::io::Error), + + #[error("cannot read a corresponding file {0} on the image")] + ReadFileFailed(PathBuf, #[source] std::io::Error), + + #[error("cannot write {0}")] + WriteFileFailed(PathBuf, #[source] std::io::Error), } diff --git a/src/system.cpp b/src/system.cpp index 4bb621b1..b170de57 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -47,12 +47,45 @@ bool updateSystemFiles(QWidget *parent) // Dump system image. auto output = readSystemDirectorySetting().toStdString(); - error = pup_dump_system(pup, output.c_str()); + error = pup_dump_system(pup, output.c_str(), [](const char *name, std::uint64_t total, std::uint64_t written, void *ud) { + auto toProgress = [total](std::uint64_t v) -> int { + if (total >= 1024UL*1024UL*1024UL*1024UL) { // >= 1TB + return v / (1024UL*1024UL*1024UL*10UL); // 10GB step. + } else if (total >= 1024UL*1024UL*1024UL*100UL) { // >= 100GB + return v / (1024UL*1024UL*1024UL); // 1GB step. + } else if (total >= 1024UL*1024UL*1024UL*10UL) { // >= 10GB + return v / (1024UL*1024UL*100UL); // 100MB step. + } else if (total >= 1024UL*1024UL*1024UL) { // >= 1GB + return v / (1024UL*1024UL*10UL); // 10MB step. + } else if (total >= 1024UL*1024UL*100UL) { // >= 100MB + return v / (1024UL*1024UL);// 1MB step. + } else { + return v; + } + }; + + auto progress = reinterpret_cast(ud); + auto max = toProgress(total); + auto value = toProgress(written); + auto label = QString("Installing %1...").arg(name); + + if (progress->statusText() != label) { + progress->setStatusText(label); + progress->setValue(0); + progress->setMaximum(max); + } else { + progress->setValue(value == max && written != total ? value - 1 : value); + } + }, &progress); + + progress.complete(); if (error) { - QMessageBox::critical(&progress, "Error", QString("Failed to install %1 to %2: %3").arg(pupPath.c_str()).arg(output.c_str()).arg(error.message())); + QMessageBox::critical(parent, "Error", QString("Failed to install %1 to %2: %3").arg(pupPath.c_str()).arg(output.c_str()).arg(error.message())); return false; } + QMessageBox::information(parent, "Success", "Installation completed successfully."); + return true; }