mirror of
https://github.com/obhq/obliteration.git
synced 2024-06-12 09:37:15 -04:00
Changes to PKG installer (#514)
Co-authored-by: SuchAFuriousDeath <48620541+SuchAFuriousDeath@users.noreply.github.com>
This commit is contained in:
parent
b5722ffe50
commit
dff58d46be
40
src/core.hpp
40
src/core.hpp
|
@ -13,8 +13,13 @@ extern "C" {
|
|||
param *param_open(const char *file, error **error);
|
||||
void param_close(param *param);
|
||||
|
||||
void param_app_ver_get(const param *param, QString &buf);
|
||||
void param_category_get(const param *param, QString &buf);
|
||||
void param_content_id_get(const param *param, QString &buf);
|
||||
void param_short_content_id_get(const param *param, QString &buf);
|
||||
void param_title_get(const param *param, QString &buf);
|
||||
void param_title_id_get(const param *param, QString &buf);
|
||||
void param_version_get(const param *param, QString &buf);
|
||||
|
||||
error *system_download(
|
||||
const char *from,
|
||||
|
@ -90,6 +95,34 @@ public:
|
|||
operator param *() const { return m_obj; }
|
||||
|
||||
public:
|
||||
QString appver() const
|
||||
{
|
||||
QString s;
|
||||
param_app_ver_get(m_obj, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
QString category() const
|
||||
{
|
||||
QString s;
|
||||
param_category_get(m_obj, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
QString contentId() const
|
||||
{
|
||||
QString s;
|
||||
param_content_id_get(m_obj, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
QString shortContentId() const
|
||||
{
|
||||
QString s;
|
||||
param_short_content_id_get(m_obj, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
QString title() const
|
||||
{
|
||||
QString s;
|
||||
|
@ -104,6 +137,13 @@ public:
|
|||
return s;
|
||||
}
|
||||
|
||||
QString version() const
|
||||
{
|
||||
QString s;
|
||||
param_version_get(m_obj, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
if (m_obj) {
|
||||
|
|
|
@ -33,12 +33,43 @@ pub unsafe extern "C" fn param_close(param: *mut Param) {
|
|||
drop(Box::from_raw(param));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_app_ver_get(param: &Param, buf: &mut QString) {
|
||||
match ¶m.app_ver() {
|
||||
Some(app_ver) => buf.set(app_ver),
|
||||
None => buf.set(""),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_category_get(param: &Param, buf: &mut QString) {
|
||||
buf.set(param.category());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_content_id_get(param: &Param, buf: &mut QString) {
|
||||
buf.set(param.content_id());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_short_content_id_get(param: &Param, buf: &mut QString) {
|
||||
buf.set(param.shortcontent_id());
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_title_get(param: &Param, buf: &mut QString) {
|
||||
buf.set(param.title());
|
||||
match ¶m.title() {
|
||||
Some(title) => buf.set(title),
|
||||
None => buf.set(""),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_title_id_get(param: &Param, buf: &mut QString) {
|
||||
buf.set(param.title_id());
|
||||
buf.set(param.title_id())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn param_version_get(param: &Param, buf: &mut QString) {
|
||||
buf.set(param.version());
|
||||
}
|
||||
|
|
|
@ -145,8 +145,20 @@ fn main() -> ExitCode {
|
|||
}
|
||||
|
||||
// Param information
|
||||
writeln!(log, "Application Title : {}", param.title()).unwrap();
|
||||
writeln!(
|
||||
log,
|
||||
"Application Title : {}",
|
||||
param.title().as_ref().unwrap()
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(log, "Application ID : {}", param.title_id()).unwrap();
|
||||
writeln!(log, "Application Category: {}", param.category()).unwrap();
|
||||
writeln!(
|
||||
log,
|
||||
"Application Version : {}",
|
||||
param.app_ver().as_ref().unwrap()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Hardware information
|
||||
writeln!(
|
||||
|
@ -401,7 +413,11 @@ fn discord_presence(param: &Param) {
|
|||
}
|
||||
|
||||
// Create details about game.
|
||||
let details = format!("Playing {} - {}", param.title(), param.title_id());
|
||||
let details = format!(
|
||||
"Playing {} - {}",
|
||||
param.title().as_ref().unwrap(),
|
||||
param.title_id()
|
||||
);
|
||||
let start = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
|
|
|
@ -11,7 +11,7 @@ use elf::{
|
|||
};
|
||||
use gmtx::{Gutex, GutexGroup, GutexReadGuard, GutexWriteGuard};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fs::File;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
@ -322,7 +322,11 @@ impl<E: ExecutionEngine> Module<E> {
|
|||
|
||||
pub fn dump<P: AsRef<Path>>(&mut self, path: P) -> Result<(), std::io::Error> {
|
||||
let path = path.as_ref();
|
||||
let mut file = File::create(path)?;
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(path)?;
|
||||
|
||||
file.write_all(unsafe { self.memory.as_bytes() })
|
||||
}
|
||||
|
|
|
@ -264,19 +264,52 @@ void MainWindow::installPkg()
|
|||
// Create game directory.
|
||||
auto gamesDirectory = readGamesDirectorySetting();
|
||||
|
||||
if (!QDir(gamesDirectory).mkdir(param.titleId())) {
|
||||
QString msg(
|
||||
"Cannot create directory %1 inside %2. "
|
||||
"If you have a failed installation from a previous attempt, you will need to remove this directory before trying again.");
|
||||
// Get Param information
|
||||
auto appver = param.appver();
|
||||
auto category = param.category();
|
||||
auto shortContentId = param.shortContentId();
|
||||
auto title = param.title();
|
||||
auto titleId = param.titleId();
|
||||
|
||||
QMessageBox::critical(&progress, "Error", msg.arg(param.titleId()).arg(gamesDirectory));
|
||||
// Check if file is Patch/DLC.
|
||||
bool patchOrDlc = false;
|
||||
if (category.startsWith("gp") || category.contains("ac")) {
|
||||
patchOrDlc = true;
|
||||
}
|
||||
|
||||
// If PKG isn't a game, DLC, or Patch, don't allow.
|
||||
if (!patchOrDlc && !category.startsWith("gd")) {
|
||||
QString msg("PKG file is not a Patch, DLC, or a Game. Possibly a corrupted PKG?");
|
||||
|
||||
QMessageBox::critical(&progress, "Error", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
auto directory = joinPath(gamesDirectory, titleId);
|
||||
|
||||
// Setup folders for DLC and Patch PKGs
|
||||
if (patchOrDlc == true) {
|
||||
if (category.contains("ac")) {
|
||||
// TODO: Add DLC support, short_content_id is most likely to be used.
|
||||
QString msg("DLC PKG support is not yet implemented.");
|
||||
|
||||
QMessageBox::critical(&progress, "Error", msg);
|
||||
return;
|
||||
} else {
|
||||
// If our PKG is for Patching, add -PATCH- to the end of the foldername along with the patch APPVER. (-PATCH-01.01)
|
||||
directory += "-PATCH-" + appver.toStdString();
|
||||
}
|
||||
}
|
||||
|
||||
if (!QDir().mkdir(QString::fromStdString(directory))) {
|
||||
QString msg("Install directory could not be created at\n%1");
|
||||
|
||||
QMessageBox::critical(&progress, "Error", msg.arg(QString::fromStdString(directory)));
|
||||
return;
|
||||
}
|
||||
|
||||
auto directory = joinPath(gamesDirectory, param.titleId());
|
||||
|
||||
// Extract items.
|
||||
progress.setWindowTitle(param.title());
|
||||
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 {
|
||||
|
@ -317,8 +350,13 @@ void MainWindow::installPkg()
|
|||
return;
|
||||
}
|
||||
|
||||
// Add to game list.
|
||||
auto success = loadGame(param.titleId());
|
||||
// Add to game list if new game.
|
||||
bool success = false;
|
||||
if (!patchOrDlc) {
|
||||
success = loadGame(titleId);
|
||||
} else {
|
||||
success = true;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
QMessageBox::information(this, "Success", "Package installed successfully.");
|
||||
|
@ -390,7 +428,7 @@ void MainWindow::requestGamesContextMenu(const QPoint &pos)
|
|||
|
||||
void MainWindow::startGame(const QModelIndex &index)
|
||||
{
|
||||
if (!requireEmulatorStopped()) {
|
||||
if (requireEmulatorStopped()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -525,22 +563,30 @@ bool MainWindow::loadGame(const QString &gameId)
|
|||
{
|
||||
auto gamesDirectory = readGamesDirectorySetting();
|
||||
auto gamePath = joinPath(gamesDirectory, gameId);
|
||||
auto gameList = reinterpret_cast<GameListModel *>(m_games->model());
|
||||
|
||||
// Read game title from param.sfo.
|
||||
auto paramDir = joinPath(gamePath.c_str(), "sce_sys");
|
||||
auto paramPath = joinPath(paramDir.c_str(), "param.sfo");
|
||||
Error error;
|
||||
Param param(param_open(paramPath.c_str(), &error));
|
||||
// Ignore entry if it is DLC or Patch.
|
||||
auto lastSlashPos = gamePath.find_last_of("/\\");
|
||||
auto lastFolder = (lastSlashPos != std::string::npos) ? gamePath.substr(lastSlashPos + 1) : gamePath;
|
||||
bool isPatch = lastFolder.find("-PATCH-") != std::string::npos;
|
||||
bool isAddCont = lastFolder.size() >= 8 && lastFolder.substr(lastFolder.size() - 8) == "-ADDCONT";
|
||||
|
||||
if (!param) {
|
||||
QMessageBox::critical(this, "Error", QString("Cannot open %1: %2").arg(paramPath.c_str()).arg(error.message()));
|
||||
return false;
|
||||
if (!isPatch && !isAddCont) {
|
||||
|
||||
// Read game title from param.sfo.
|
||||
auto paramDir = joinPath(gamePath.c_str(), "sce_sys");
|
||||
auto paramPath = joinPath(paramDir.c_str(), "param.sfo");
|
||||
Error error;
|
||||
Param param(param_open(paramPath.c_str(), &error));
|
||||
|
||||
if (!param) {
|
||||
QMessageBox::critical(this, "Error", QString("Cannot open %1: %2").arg(paramPath.c_str()).arg(error.message()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add to list if not a DLC/Patch refresh.
|
||||
auto gameList = reinterpret_cast<GameListModel *>(m_games->model());
|
||||
gameList->add(new Game(param.title(), gamePath.c_str()));
|
||||
}
|
||||
|
||||
// Add to list.
|
||||
gameList->add(new Game(param.title(), gamePath.c_str()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -584,9 +630,20 @@ void MainWindow::restoreGeometry()
|
|||
bool MainWindow::requireEmulatorStopped()
|
||||
{
|
||||
if (m_kernel) {
|
||||
QMessageBox::critical(this, "Error", "This function is not available while a game is running.");
|
||||
return false;
|
||||
QMessageBox killPrompt(this);
|
||||
|
||||
killPrompt.setText("Action requires kernel to be stopped to continue.");
|
||||
killPrompt.setInformativeText("Do you want to kill the kernel?");
|
||||
killPrompt.setStandardButtons(QMessageBox::Cancel | QMessageBox::Yes);
|
||||
killPrompt.setDefaultButton(QMessageBox::Cancel);
|
||||
killPrompt.setIcon(QMessageBox::Warning);
|
||||
if (killPrompt.exec() == QMessageBox::Yes) {
|
||||
killKernel();
|
||||
return false; // Kernel was killed
|
||||
}
|
||||
|
||||
return true; // Kernel left running
|
||||
}
|
||||
|
||||
return true;
|
||||
return false; // Kernel isn't running
|
||||
}
|
||||
|
|
|
@ -6,8 +6,12 @@ use thiserror::Error;
|
|||
///
|
||||
/// See https://www.psdevwiki.com/ps4/Param.sfo#Internal_Structure for more information.
|
||||
pub struct Param {
|
||||
title: String,
|
||||
app_ver: Option<String>,
|
||||
category: String,
|
||||
content_id: String,
|
||||
title: Option<String>,
|
||||
title_id: String,
|
||||
version: String,
|
||||
}
|
||||
|
||||
impl Param {
|
||||
|
@ -72,8 +76,12 @@ impl Param {
|
|||
keys.drain(i..);
|
||||
|
||||
// Read entries.
|
||||
let mut app_ver: Option<String> = None;
|
||||
let mut category: Option<String> = None;
|
||||
let mut content_id: Option<String> = None;
|
||||
let mut title: Option<String> = None;
|
||||
let mut title_id: Option<String> = None;
|
||||
let mut version: Option<String> = None;
|
||||
|
||||
for i in 0..entries {
|
||||
// Seek to the entry.
|
||||
|
@ -128,26 +136,77 @@ impl Param {
|
|||
|
||||
// Parse value.
|
||||
match key {
|
||||
b"TITLE" => title = Some(Self::read_utf8(&mut raw, i, format, len, 128)?),
|
||||
b"TITLE_ID" => title_id = Some(Self::read_utf8(&mut raw, i, format, len, 12)?),
|
||||
b"APP_VER" => {
|
||||
app_ver = Some(Self::read_utf8(&mut raw, i, format, len, 8)?);
|
||||
}
|
||||
b"CATEGORY" => {
|
||||
category = Some(Self::read_utf8(&mut raw, i, format, 4, 4)?);
|
||||
}
|
||||
b"CONTENT_ID" => {
|
||||
content_id = Some(Self::read_utf8(&mut raw, i, format, len, 48)?);
|
||||
}
|
||||
b"TITLE" => {
|
||||
title = Some(Self::read_utf8(&mut raw, i, format, len, 128)?);
|
||||
}
|
||||
b"TITLE_ID" => {
|
||||
title_id = Some(Self::read_utf8(&mut raw, i, format, len, 12)?);
|
||||
}
|
||||
b"VERSION" => {
|
||||
version = Some(Self::read_utf8(&mut raw, i, format, len, 8)?);
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
title: title.ok_or(ReadError::MissingTitle)?,
|
||||
// App_Ver for Games and Patches, for DLC, use version. Anything else is abnormal.
|
||||
app_ver: app_ver,
|
||||
category: category.ok_or(ReadError::MissingCategory)?,
|
||||
content_id: content_id.ok_or(ReadError::MissingContentId)?,
|
||||
title: title,
|
||||
title_id: title_id.ok_or(ReadError::MissingTitleId)?,
|
||||
version: version.ok_or(ReadError::MissingVersion)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn title(&self) -> &str {
|
||||
/// Fetches the value APP_VER from given Param.SFO
|
||||
pub fn app_ver(&self) -> &Option<String> {
|
||||
&self.app_ver
|
||||
}
|
||||
|
||||
/// Fetches the value CATEGORY from given Param.SFO
|
||||
pub fn category(&self) -> &str {
|
||||
&self.category
|
||||
}
|
||||
|
||||
/// Fetches the value CONTENT_ID from given Param.SFO
|
||||
pub fn content_id(&self) -> &str {
|
||||
&self.content_id
|
||||
}
|
||||
|
||||
/// Fetches a shortened variant of value CONTENT_ID from given Param.SFO
|
||||
pub fn shortcontent_id(&self) -> &str {
|
||||
self.content_id
|
||||
.split('-')
|
||||
.last()
|
||||
.unwrap_or(&self.content_id)
|
||||
}
|
||||
|
||||
/// Fetches the value TITLE from given Param.SFO
|
||||
pub fn title(&self) -> &Option<String> {
|
||||
&self.title
|
||||
}
|
||||
|
||||
/// Fetches the value TITLE_ID from given Param.SFO
|
||||
pub fn title_id(&self) -> &str {
|
||||
&self.title_id
|
||||
}
|
||||
|
||||
/// Fetches the value VERSION from given Param.SFO
|
||||
pub fn version(&self) -> &str {
|
||||
&self.version
|
||||
}
|
||||
|
||||
fn read_utf8<R: Read>(
|
||||
raw: &mut R,
|
||||
i: u64,
|
||||
|
@ -206,9 +265,15 @@ pub enum ReadError {
|
|||
#[error("entry #{0} has invalid value")]
|
||||
InvalidValue(usize),
|
||||
|
||||
#[error("TITLE is not found")]
|
||||
MissingTitle,
|
||||
#[error("CATEGORY parameter not found")]
|
||||
MissingCategory,
|
||||
|
||||
#[error("TITLE_ID is not found")]
|
||||
#[error("CONTENT_ID parameter not found")]
|
||||
MissingContentId,
|
||||
|
||||
#[error("TITLE_ID parameter not found")]
|
||||
MissingTitleId,
|
||||
|
||||
#[error("VERSION parameter not found")]
|
||||
MissingVersion,
|
||||
}
|
||||
|
|
|
@ -7,6 +7,5 @@
|
|||
|
||||
// Do not throw. Return empty string is case of error.
|
||||
std::string joinPath(const QString &base, const QString &name);
|
||||
|
||||
// v must be native format.
|
||||
std::filesystem::path toPath(const QString &v);
|
||||
|
|
|
@ -8,7 +8,7 @@ use sha2::Digest;
|
|||
use std::error::Error;
|
||||
use std::ffi::{c_void, CStr, CString};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fs::{create_dir_all, File};
|
||||
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};
|
||||
|
@ -191,8 +191,8 @@ impl Pkg {
|
|||
}
|
||||
|
||||
// Open destination file.
|
||||
let mut file = match File::create(&path) {
|
||||
Ok(v) => v,
|
||||
let mut file = match OpenOptions::new().write(true).create_new(true).open(&path) {
|
||||
Ok(file) => file,
|
||||
Err(e) => return Err(ExtractError::CreateEntryFailed(path, e)),
|
||||
};
|
||||
|
||||
|
@ -389,12 +389,11 @@ impl Pkg {
|
|||
status(status_name.as_ptr(), size, 0, ud);
|
||||
|
||||
// Open destination file.
|
||||
let mut dest = std::fs::OpenOptions::new();
|
||||
|
||||
dest.create_new(true);
|
||||
dest.write(true);
|
||||
|
||||
let mut dest = match dest.open(&output) {
|
||||
let mut dest = match OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(&output)
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(ExtractError::CreateFileFailed(output, e)),
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue