mirror of
https://github.com/obhq/obliteration.git
synced 2024-06-12 09:37:15 -04:00
Uses total files for progress when installing a PKG (#758)
This commit is contained in:
parent
ea3380c49f
commit
9b3c26c80d
44
src/core.hpp
44
src/core.hpp
|
@ -3,8 +3,13 @@
|
|||
#include <QString>
|
||||
#include <QUtf8StringView>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
struct error;
|
||||
struct param;
|
||||
struct pkg;
|
||||
|
||||
typedef void (*pkg_extract_status_t) (const char *status, std::size_t current, std::size_t total, void *ud);
|
||||
|
||||
extern "C" {
|
||||
void error_free(error *err);
|
||||
|
@ -21,6 +26,12 @@ extern "C" {
|
|||
void param_title_id_get(const param *param, QString &buf);
|
||||
void param_version_get(const param *param, QString &buf);
|
||||
|
||||
pkg *pkg_open(const char *file, error **error);
|
||||
void pkg_close(pkg *pkg);
|
||||
|
||||
param *pkg_get_param(const pkg *pkg, error **error);
|
||||
error *pkg_extract(const pkg *pkg, const char *dir, pkg_extract_status_t status, void *ud);
|
||||
|
||||
error *system_download(
|
||||
const char *from,
|
||||
const char *to,
|
||||
|
@ -155,3 +166,36 @@ public:
|
|||
private:
|
||||
param *m_obj;
|
||||
};
|
||||
|
||||
class Pkg final {
|
||||
public:
|
||||
Pkg() : m_obj(nullptr) {}
|
||||
Pkg(const Pkg &) = delete;
|
||||
~Pkg() { close(); }
|
||||
|
||||
public:
|
||||
Pkg &operator=(const Pkg &) = delete;
|
||||
Pkg &operator=(pkg *obj)
|
||||
{
|
||||
if (m_obj) {
|
||||
pkg_close(m_obj);
|
||||
}
|
||||
|
||||
m_obj = obj;
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator pkg *() const { return m_obj; }
|
||||
|
||||
public:
|
||||
void close()
|
||||
{
|
||||
if (m_obj) {
|
||||
pkg_close(m_obj);
|
||||
m_obj = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
pkg *m_obj;
|
||||
};
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
#![allow(clippy::enum_variant_names)]
|
||||
|
||||
// The purpose of this crate is to generate a static library to link with the GUI. So it is required
|
||||
// to add other crates that expose API to the GUI as a dependency of this crate then re-export all
|
||||
// of those APIs here.
|
||||
pub use error::*;
|
||||
pub use pkg::*;
|
||||
|
||||
mod ffi;
|
||||
mod fwdl;
|
||||
mod param;
|
||||
mod pkg;
|
||||
|
|
52
src/core/src/pkg.rs
Normal file
52
src/core/src/pkg.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
use error::Error;
|
||||
use param::Param;
|
||||
use pkg::Pkg;
|
||||
use std::ffi::{c_char, c_void, CStr};
|
||||
use std::ptr::null_mut;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pkg_open(file: *const c_char, error: *mut *mut Error) -> *mut Pkg {
|
||||
let path = CStr::from_ptr(file);
|
||||
let pkg = match Pkg::open(path.to_str().unwrap()) {
|
||||
Ok(v) => Box::new(v),
|
||||
Err(e) => {
|
||||
*error = Error::new(e);
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
Box::into_raw(pkg)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pkg_close(pkg: *mut Pkg) {
|
||||
drop(Box::from_raw(pkg));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pkg_get_param(pkg: &Pkg, error: *mut *mut Error) -> *mut Param {
|
||||
let param = match pkg.get_param() {
|
||||
Ok(v) => Box::new(v),
|
||||
Err(e) => {
|
||||
*error = Error::new(e);
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
Box::into_raw(param)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pkg_extract(
|
||||
pkg: &Pkg,
|
||||
dir: *const c_char,
|
||||
status: extern "C" fn(*const c_char, usize, usize, *mut c_void),
|
||||
ud: *mut c_void,
|
||||
) -> *mut Error {
|
||||
let dir = CStr::from_ptr(dir);
|
||||
|
||||
match pkg.extract(dir.to_str().unwrap(), status, ud) {
|
||||
Ok(_) => null_mut(),
|
||||
Err(e) => Error::new(e),
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
#include "game_models.hpp"
|
||||
#include "path.hpp"
|
||||
#include "pkg.hpp"
|
||||
|
||||
Game::Game(const QString &id, const QString &name, const QString &directory) :
|
||||
m_id(id),
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
#include "main_window.hpp"
|
||||
#include "app_data.hpp"
|
||||
#include "core.hpp"
|
||||
#include "game_models.hpp"
|
||||
#include "game_settings.hpp"
|
||||
#include "game_settings_dialog.hpp"
|
||||
#include "log_formatter.hpp"
|
||||
#include "path.hpp"
|
||||
#include "pkg.hpp"
|
||||
#include "progress_dialog.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "string.hpp"
|
||||
|
@ -317,34 +317,16 @@ void MainWindow::installPkg()
|
|||
// Extract items.
|
||||
progress.setWindowTitle(title);
|
||||
|
||||
error = pkg_extract(pkg, directory.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;
|
||||
}
|
||||
};
|
||||
|
||||
error = pkg_extract(pkg, directory.c_str(), [](const char *status, std::size_t current, std::size_t total, void *ud) {
|
||||
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);
|
||||
progress->setStatusText(status);
|
||||
|
||||
if (current) {
|
||||
progress->setValue(static_cast<int>(current));
|
||||
} else {
|
||||
progress->setValue(value == max && written != total ? value - 1 : value);
|
||||
progress->setValue(0);
|
||||
progress->setMaximum(static_cast<int>(total));
|
||||
}
|
||||
}, &progress);
|
||||
|
||||
|
|
|
@ -167,6 +167,10 @@ pub struct Items<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Items<'a> {
|
||||
pub fn len(&self) -> usize {
|
||||
self.items.len()
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &[u8]) -> Option<&Item<'a>> {
|
||||
self.items.get(name)
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ pub mod image;
|
|||
pub mod inode;
|
||||
pub mod pfsc;
|
||||
|
||||
pub fn open<'a, I>(mut image: I, ekpfs: Option<&[u8]>) -> Result<Directory<'a>, OpenError>
|
||||
pub fn open<'a, I>(mut image: I, ekpfs: Option<&[u8]>) -> Result<Rc<Pfs<'a>>, OpenError>
|
||||
where
|
||||
I: Read + Seek + 'a,
|
||||
{
|
||||
|
@ -126,19 +126,28 @@ where
|
|||
return Err(OpenError::InvalidSuperRoot);
|
||||
}
|
||||
|
||||
// Construct super-root.
|
||||
let pfs = Pfs {
|
||||
Ok(Rc::new(Pfs {
|
||||
image: Mutex::new(image),
|
||||
inodes,
|
||||
};
|
||||
|
||||
Ok(Directory::new(Rc::new(pfs), super_root))
|
||||
root: super_root,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Represents a loaded PFS.
|
||||
pub(crate) struct Pfs<'a> {
|
||||
pub struct Pfs<'a> {
|
||||
image: Mutex<Box<dyn Image + 'a>>,
|
||||
inodes: Vec<Inode>,
|
||||
root: usize,
|
||||
}
|
||||
|
||||
impl<'a> Pfs<'a> {
|
||||
pub fn inodes(&self) -> usize {
|
||||
self.inodes.len()
|
||||
}
|
||||
|
||||
pub fn root(self: &Rc<Self>) -> Directory<'a> {
|
||||
Directory::new(self.clone(), self.root)
|
||||
}
|
||||
}
|
||||
|
||||
/// Encapsulate a PFS image.
|
||||
|
|
51
src/pkg.hpp
51
src/pkg.hpp
|
@ -1,51 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "core.hpp"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
|
||||
struct pkg;
|
||||
|
||||
typedef void (*pkg_extract_status_t) (const char *name, std::uint64_t total, std::uint64_t written, void *ud);
|
||||
|
||||
extern "C" {
|
||||
pkg *pkg_open(const char *file, error **error);
|
||||
void pkg_close(pkg *pkg);
|
||||
|
||||
param *pkg_get_param(const pkg *pkg, error **error);
|
||||
error *pkg_extract(const pkg *pkg, const char *dir, pkg_extract_status_t status, void *ud);
|
||||
}
|
||||
|
||||
class Pkg final {
|
||||
public:
|
||||
Pkg() : m_obj(nullptr) {}
|
||||
Pkg(const Pkg &) = delete;
|
||||
~Pkg() { close(); }
|
||||
|
||||
public:
|
||||
Pkg &operator=(const Pkg &) = delete;
|
||||
Pkg &operator=(pkg *obj)
|
||||
{
|
||||
if (m_obj) {
|
||||
pkg_close(m_obj);
|
||||
}
|
||||
|
||||
m_obj = obj;
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator pkg *() const { return m_obj; }
|
||||
|
||||
public:
|
||||
void close()
|
||||
{
|
||||
if (m_obj) {
|
||||
pkg_close(m_obj);
|
||||
m_obj = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
pkg *m_obj;
|
||||
};
|
|
@ -7,66 +7,18 @@ use param::Param;
|
|||
use sha2::Digest;
|
||||
use std::convert::Infallible;
|
||||
use std::error::Error;
|
||||
use std::ffi::{c_void, CStr, CString};
|
||||
use std::ffi::{c_void, CString};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fs::{create_dir_all, File, OpenOptions};
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use std::os::raw::c_char;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::ptr::null_mut;
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod entry;
|
||||
pub mod header;
|
||||
pub mod keys;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pkg_open(file: *const c_char, error: *mut *mut error::Error) -> *mut Pkg {
|
||||
let path = CStr::from_ptr(file);
|
||||
let pkg = match Pkg::open(path.to_str().unwrap()) {
|
||||
Ok(v) => Box::new(v),
|
||||
Err(e) => {
|
||||
*error = error::Error::new(e);
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
Box::into_raw(pkg)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pkg_close(pkg: *mut Pkg) {
|
||||
drop(Box::from_raw(pkg));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pkg_get_param(pkg: &Pkg, error: *mut *mut error::Error) -> *mut Param {
|
||||
let param = match pkg.get_param() {
|
||||
Ok(v) => Box::new(v),
|
||||
Err(e) => {
|
||||
*error = error::Error::new(e);
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
Box::into_raw(param)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pkg_extract(
|
||||
pkg: &Pkg,
|
||||
dir: *const c_char,
|
||||
status: extern "C" fn(*const c_char, u64, u64, ud: *mut c_void),
|
||||
ud: *mut c_void,
|
||||
) -> *mut error::Error {
|
||||
let dir = CStr::from_ptr(dir);
|
||||
|
||||
match pkg.extract(dir.to_str().unwrap(), status, ud) {
|
||||
Ok(_) => null_mut(),
|
||||
Err(e) => error::Error::new(e),
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.psdevwiki.com/ps4/Package_Files
|
||||
pub struct Pkg {
|
||||
raw: memmap2::Mmap,
|
||||
|
@ -127,7 +79,7 @@ impl Pkg {
|
|||
pub fn extract(
|
||||
&self,
|
||||
dir: impl AsRef<Path>,
|
||||
status: extern "C" fn(*const c_char, u64, u64, ud: *mut c_void),
|
||||
status: extern "C" fn(*const c_char, usize, usize, *mut c_void),
|
||||
ud: *mut c_void,
|
||||
) -> Result<(), ExtractError> {
|
||||
let dir = dir.as_ref();
|
||||
|
@ -141,7 +93,7 @@ impl Pkg {
|
|||
fn extract_entries(
|
||||
&self,
|
||||
dir: impl AsRef<Path>,
|
||||
status: extern "C" fn(*const c_char, u64, u64, ud: *mut c_void),
|
||||
status: extern "C" fn(*const c_char, usize, usize, *mut c_void),
|
||||
ud: *mut c_void,
|
||||
) -> Result<(), ExtractError> {
|
||||
for num in 0..self.header.entry_count() {
|
||||
|
@ -182,7 +134,7 @@ impl Pkg {
|
|||
// Report status.
|
||||
let name = CString::new(path.to_string_lossy().as_ref()).unwrap();
|
||||
|
||||
status(name.as_ptr(), size as u64, 0, ud);
|
||||
status(name.as_ptr(), num, self.header.entry_count(), ud);
|
||||
|
||||
// Create a directory for destination file.
|
||||
let dir = path.parent().unwrap();
|
||||
|
@ -205,9 +157,13 @@ impl Pkg {
|
|||
} else if let Err(e) = file.write_all(data) {
|
||||
return Err(ExtractError::WriteEntryFailed(path, e));
|
||||
}
|
||||
}
|
||||
|
||||
// Report status.
|
||||
status(name.as_ptr(), size as u64, size as u64, ud);
|
||||
// Report completion.
|
||||
if self.header.entry_count() != 0 {
|
||||
let total = self.header.entry_count();
|
||||
|
||||
status(c"Entries extraction completed".as_ptr(), total, total, ud);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -216,7 +172,7 @@ impl Pkg {
|
|||
fn extract_pfs(
|
||||
&self,
|
||||
dir: impl AsRef<Path>,
|
||||
status: extern "C" fn(*const c_char, u64, u64, ud: *mut c_void),
|
||||
status: extern "C" fn(*const c_char, usize, usize, *mut c_void),
|
||||
ud: *mut c_void,
|
||||
) -> Result<(), ExtractError> {
|
||||
use pfs::directory::Item;
|
||||
|
@ -231,7 +187,7 @@ impl Pkg {
|
|||
|
||||
// Open outer PFS.
|
||||
let mut outer = match pfs::open(Cursor::new(outer), Some(&self.ekpfs)) {
|
||||
Ok(v) => match v.open() {
|
||||
Ok(v) => match v.root().open() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(ExtractError::OpenOuterSuperRootFailed(e)),
|
||||
},
|
||||
|
@ -256,38 +212,56 @@ impl Pkg {
|
|||
};
|
||||
|
||||
// Open inner PFS.
|
||||
let mut inner = if inner.is_compressed() {
|
||||
let inner = if inner.is_compressed() {
|
||||
let pfsc = match pfs::pfsc::Reader::open(inner) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(ExtractError::CreateInnerDecompressorFailed(e)),
|
||||
};
|
||||
|
||||
match pfs::open(pfsc, None) {
|
||||
Ok(v) => match v.open() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(ExtractError::OpenInnerSuperRootFailed(e)),
|
||||
},
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(ExtractError::OpenInnerFailed(e)),
|
||||
}
|
||||
} else {
|
||||
match pfs::open(inner, None) {
|
||||
Ok(v) => match v.open() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(ExtractError::OpenInnerSuperRootFailed(e)),
|
||||
},
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(ExtractError::OpenInnerFailed(e)),
|
||||
}
|
||||
};
|
||||
|
||||
// Open inner root.
|
||||
let mut inodes = inner.inodes();
|
||||
let mut root = match inner.root().open() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(ExtractError::OpenInnerSuperRootFailed(e)),
|
||||
};
|
||||
|
||||
inodes -= 1; // Exclude root.
|
||||
inodes -= root.len(); // Exclude top-level items.
|
||||
|
||||
// Open inner uroot directory.
|
||||
let uroot = match inner.take(b"uroot") {
|
||||
let uroot = match root.take(b"uroot") {
|
||||
Some(Item::Directory(v)) => v,
|
||||
Some(Item::File(_)) => return Err(ExtractError::NoInnerUroot),
|
||||
None => return Err(ExtractError::NoInnerUroot),
|
||||
};
|
||||
|
||||
// Extract inner uroot.
|
||||
self.extract_directory(Vec::new(), uroot, dir, status, ud)
|
||||
let mut progress = 0;
|
||||
|
||||
self.extract_directory(Vec::new(), uroot, dir, &mut |path| {
|
||||
let path = CString::new(path.to_string_lossy().into_owned()).unwrap();
|
||||
|
||||
status(path.as_ptr(), progress, inodes, ud);
|
||||
progress += 1;
|
||||
})?;
|
||||
|
||||
// Report completion.
|
||||
if progress != 0 {
|
||||
status(c"PFS extraction completed".as_ptr(), inodes, inodes, ud);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_directory(
|
||||
|
@ -295,8 +269,7 @@ impl Pkg {
|
|||
path: Vec<&[u8]>,
|
||||
dir: pfs::directory::Directory,
|
||||
output: impl AsRef<Path>,
|
||||
status: extern "C" fn(*const c_char, u64, u64, ud: *mut c_void),
|
||||
ud: *mut c_void,
|
||||
status: &mut impl FnMut(&Path),
|
||||
) -> Result<(), ExtractError> {
|
||||
// Open PFS directory.
|
||||
let items = match dir.open() {
|
||||
|
@ -352,12 +325,14 @@ impl Pkg {
|
|||
};
|
||||
|
||||
// Create output directory.
|
||||
status(&output);
|
||||
|
||||
if let Err(e) = create_dir_all(&output) {
|
||||
return Err(ExtractError::CreateDirectoryFailed(output, e));
|
||||
}
|
||||
|
||||
// Extract files.
|
||||
self.extract_directory(path, i, &output, status, ud)?;
|
||||
self.extract_directory(path, i, &output, status)?;
|
||||
|
||||
meta
|
||||
}
|
||||
|
@ -377,11 +352,7 @@ impl Pkg {
|
|||
gid: file.gid(),
|
||||
};
|
||||
|
||||
// Report initial status.
|
||||
let size = file.len();
|
||||
let status_name = CString::new(name).unwrap();
|
||||
|
||||
status(status_name.as_ptr(), size, 0, ud);
|
||||
status(&output);
|
||||
|
||||
// Open destination file.
|
||||
let mut dest = match OpenOptions::new()
|
||||
|
@ -394,8 +365,6 @@ impl Pkg {
|
|||
};
|
||||
|
||||
// Copy.
|
||||
let mut written = 0u64;
|
||||
|
||||
loop {
|
||||
// Read source.
|
||||
let read = match file.read(&mut buffer) {
|
||||
|
@ -420,11 +389,6 @@ impl Pkg {
|
|||
if let Err(e) = dest.write_all(&buffer[..read]) {
|
||||
return Err(ExtractError::WriteFileFailed(output, e));
|
||||
}
|
||||
|
||||
written += read as u64; // Buffer size just 32768.
|
||||
|
||||
// Update status.
|
||||
status(status_name.as_ptr(), size, written, ud);
|
||||
}
|
||||
|
||||
meta
|
||||
|
|
Loading…
Reference in a new issue