Extracts PUP (#134)

This commit is contained in:
Putta Khunchalee 2023-02-08 04:37:15 +07:00 committed by GitHub
parent 7abe3b4187
commit e17652814b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 422 additions and 161 deletions

View File

@ -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.

View File

@ -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<usize>,
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<bool>,
) -> Result<Self, FromAllocError> {
// Get cluster chain.
let first_cluster = alloc.first_cluster();
let data_length = alloc.data_length();
let cluster_size = params.cluster_size();
let chain: Vec<usize> = 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<u64>,
no_fat_chain: Option<bool>,
) -> Result<Self, NewError> {
if first_cluster < 2 {
return Err(NewError::InvalidFirstCluster);
}
// Get cluster chain.
let chain: Vec<usize> = 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<usize> = (first_cluster..(first_cluster + count as usize)).collect();
(chain, data_length)
} else {
let chain: Vec<usize> = 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<usize> {
// 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<u64> {
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<u64> {
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<usize> {
// 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),
}

View File

@ -33,12 +33,17 @@ impl<I: Read + Seek> Directory<I> {
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<Item<I>> = Vec::new();
@ -95,7 +100,7 @@ pub enum Item<I: Read + Seek> {
#[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),

View File

@ -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

View File

@ -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<I: Read + Seek> {
@ -22,4 +28,94 @@ impl<I: Read + Seek> File<I> {
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<Option<FileReader<'_, I>>, 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<MutexGuard<'a, I>>,
}
impl<'a, I: Read + Seek> Seek for FileReader<'a, I> {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.reader.seek(pos)
}
fn rewind(&mut self) -> std::io::Result<()> {
self.reader.rewind()
}
fn stream_position(&mut self) -> std::io::Result<u64> {
self.reader.stream_position()
}
}
impl<'a, I: Read + Seek> Read for FileReader<'a, I> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.reader.read(buf)
}
fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> std::io::Result<usize> {
self.reader.read_vectored(bufs)
}
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> std::io::Result<usize> {
self.reader.read_to_end(buf)
}
fn read_to_string(&mut self, buf: &mut String) -> std::io::Result<usize> {
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),
}

View File

@ -92,8 +92,8 @@ impl<I: Read + Seek> ExFat<I> {
};
// Create a entries reader for the root directory.
let root_cluster = params.first_cluster_of_root_directory;
let mut reader = match ClustersReader::new(&params, &fat, &mut image, root_cluster, None) {
let root = params.first_cluster_of_root_directory;
let mut reader = match ClustersReader::new(&params, &fat, &mut image, root, None, None) {
Ok(v) => EntriesReader::new(v),
Err(e) => return Err(OpenError::CreateClustersReaderFailed(e)),
};

View File

@ -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<u64> {
if index < 2 {
return None;

View File

@ -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);
}

View File

@ -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<O: AsRef<Path>>(&self, output: O) -> Result<(), DumpSystemImageError> {
pub fn dump_system_image<O: AsRef<Path>>(
&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<P, I>(
parent: P,
dir: exfat::directory::Directory<I>,
status: extern "C" fn(*const c_char, u64, u64, *mut c_void),
ud: *mut c_void,
) -> Result<(), DumpSystemImageError>
where
P: AsRef<Path>,
@ -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, I>(_: P, _: exfat::file::File<I>) -> Result<(), DumpSystemImageError>
fn dump_system_file<P, I>(
parent: P,
mut file: exfat::file::File<I>,
status: extern "C" fn(*const c_char, u64, u64, *mut c_void),
ud: *mut c_void,
) -> Result<(), DumpSystemImageError>
where
P: AsRef<Path>,
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),
}

View File

@ -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<ProgressDialog *>(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;
}