Uses total files for progress when installing a PKG (#758)

This commit is contained in:
Putta Khunchalee 2024-03-23 23:17:57 +07:00 committed by GitHub
parent ea3380c49f
commit 9b3c26c80d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 171 additions and 173 deletions

View file

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

View file

@ -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
View 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),
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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