mirror of
https://github.com/obhq/obliteration.git
synced 2024-12-02 16:17:01 +00:00
Extracts PUP (#134)
This commit is contained in:
parent
7abe3b4187
commit
e17652814b
@ -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.
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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(¶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)),
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user