mirror of
https://github.com/obhq/obliteration.git
synced 2024-11-23 11:19:56 +00:00
Implements profile saving and loading (#947)
This commit is contained in:
parent
0f85fbb946
commit
ffb3ef9ec8
@ -72,6 +72,7 @@ add_executable(obliteration WIN32 MACOSX_BUNDLE
|
||||
path.cpp
|
||||
pkg_extractor.cpp
|
||||
pkg_installer.cpp
|
||||
profile_models.cpp
|
||||
progress_dialog.cpp
|
||||
resources.cpp
|
||||
resources.qrc
|
||||
|
@ -9,8 +9,8 @@ static QString root()
|
||||
return QDir::toNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
|
||||
}
|
||||
|
||||
QString kernelDebugDump()
|
||||
QString profiles()
|
||||
{
|
||||
auto path = joinPath(root(), "kernel");
|
||||
auto path = joinPath(root(), "profiles");
|
||||
return QString::fromStdString(path);
|
||||
}
|
||||
|
@ -2,4 +2,4 @@
|
||||
|
||||
#include <QString>
|
||||
|
||||
QString kernelDebugDump();
|
||||
QString profiles();
|
||||
|
17
src/core.h
17
src/core.h
@ -20,6 +20,11 @@ enum VmmLog {
|
||||
VmmLog_Info,
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains settings to launch the kernel.
|
||||
*/
|
||||
struct Profile;
|
||||
|
||||
/**
|
||||
* Error object managed by Rust side.
|
||||
*/
|
||||
@ -109,6 +114,18 @@ struct RustError *pkg_extract(const Pkg *pkg, const char *dir, void (*status)(co
|
||||
uint64_t,
|
||||
void*), void *ud);
|
||||
|
||||
struct Profile *profile_new(const char *name);
|
||||
|
||||
struct Profile *profile_load(const char *path, struct RustError **err);
|
||||
|
||||
void profile_free(struct Profile *p);
|
||||
|
||||
char *profile_id(const struct Profile *p);
|
||||
|
||||
const char *profile_name(const struct Profile *p);
|
||||
|
||||
struct RustError *profile_save(const struct Profile *p, const char *path);
|
||||
|
||||
struct RustError *update_firmware(const char *root,
|
||||
const char *fw,
|
||||
void *cx,
|
||||
|
13
src/core.hpp
13
src/core.hpp
@ -40,7 +40,7 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator T *() { return m_ptr; }
|
||||
operator T *() const { return m_ptr; }
|
||||
operator bool() const { return m_ptr != nullptr; }
|
||||
|
||||
T **operator&()
|
||||
@ -49,7 +49,7 @@ public:
|
||||
return &m_ptr;
|
||||
}
|
||||
|
||||
T *get() { return m_ptr; }
|
||||
T *get() const { return m_ptr; }
|
||||
void free();
|
||||
private:
|
||||
T *m_ptr;
|
||||
@ -80,6 +80,15 @@ inline void Rust<Pkg>::free()
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void Rust<Profile>::free()
|
||||
{
|
||||
if (m_ptr) {
|
||||
profile_free(m_ptr);
|
||||
m_ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
inline void Rust<RustError>::free()
|
||||
{
|
||||
|
@ -14,7 +14,10 @@ obfw = { git = "https://github.com/obhq/firmware-dumper.git", features = ["read"
|
||||
obvirt = { path = "../obvirt" }
|
||||
param = { path = "../param" }
|
||||
pkg = { path = "../pkg" }
|
||||
postcard = { version = "1.0.10", features = ["use-std"], default-features = false }
|
||||
serde = { version = "1.0.209", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
uuid = { version = "1.10.0", features = ["serde", "v4"] }
|
||||
|
||||
[target.'cfg(not(target_os = "macos"))'.dependencies]
|
||||
ash = "0.38.0"
|
||||
|
@ -3,6 +3,7 @@ use std::ffi::{c_char, c_void};
|
||||
mod error;
|
||||
mod param;
|
||||
mod pkg;
|
||||
mod profile;
|
||||
mod string;
|
||||
mod system;
|
||||
mod vmm;
|
||||
|
107
src/core/src/profile/mod.rs
Normal file
107
src/core/src/profile/mod.rs
Normal file
@ -0,0 +1,107 @@
|
||||
use crate::error::RustError;
|
||||
use crate::string::strdup;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ffi::{c_char, CStr, CString};
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use std::ptr::null_mut;
|
||||
use std::time::SystemTime;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_new(name: *const c_char) -> *mut Profile {
|
||||
Box::into_raw(Box::new(Profile {
|
||||
id: Uuid::new_v4(),
|
||||
name: CStr::from_ptr(name).to_owned(),
|
||||
created: SystemTime::now(),
|
||||
}))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_load(
|
||||
path: *const c_char,
|
||||
err: *mut *mut RustError,
|
||||
) -> *mut Profile {
|
||||
// Check if path UTF-8.
|
||||
let root = match CStr::from_ptr(path).to_str() {
|
||||
Ok(v) => Path::new(v),
|
||||
Err(_) => {
|
||||
*err = RustError::new("the specified path is not UTF-8");
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Use from_io() once https://github.com/jamesmunns/postcard/issues/162 is implemented.
|
||||
let path = root.join("profile.bin");
|
||||
let data = match std::fs::read(&path) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = RustError::with_source(format_args!("couldn't read {}", path.display()), e);
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
// Load profile.bin.
|
||||
let p = match postcard::from_bytes(&data) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
*err = RustError::with_source(format_args!("couldn't load {}", path.display()), e);
|
||||
return null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
Box::into_raw(Box::new(p))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_free(p: *mut Profile) {
|
||||
drop(Box::from_raw(p));
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_id(p: *const Profile) -> *mut c_char {
|
||||
strdup((*p).id.to_string())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_name(p: *const Profile) -> *const c_char {
|
||||
(*p).name.as_ptr()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn profile_save(p: *const Profile, path: *const c_char) -> *mut RustError {
|
||||
// Check if path UTF-8.
|
||||
let root = match CStr::from_ptr(path).to_str() {
|
||||
Ok(v) => Path::new(v),
|
||||
Err(_) => return RustError::new("the specified path is not UTF-8"),
|
||||
};
|
||||
|
||||
// Create a directory.
|
||||
if let Err(e) = std::fs::create_dir_all(root) {
|
||||
return RustError::with_source("couldn't create the specified path", e);
|
||||
}
|
||||
|
||||
// Create profile.bin.
|
||||
let path = root.join("profile.bin");
|
||||
let file = match File::create(&path) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return RustError::with_source(format_args!("couldn't create {}", path.display()), e)
|
||||
}
|
||||
};
|
||||
|
||||
// Write profile.bin.
|
||||
if let Err(e) = postcard::to_io(&*p, file) {
|
||||
return RustError::with_source(format_args!("couldn't write {}", path.display()), e);
|
||||
}
|
||||
|
||||
null_mut()
|
||||
}
|
||||
|
||||
/// Contains settings to launch the kernel.
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Profile {
|
||||
id: Uuid,
|
||||
name: CString,
|
||||
created: SystemTime,
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
#include "game_models.hpp"
|
||||
#include "game_settings.hpp"
|
||||
#include "game_settings_dialog.hpp"
|
||||
#include "profile_models.hpp"
|
||||
#include "resources.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
@ -17,7 +18,7 @@
|
||||
#include <QUrl>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
LaunchSettings::LaunchSettings(GameListModel *games, QWidget *parent) :
|
||||
LaunchSettings::LaunchSettings(ProfileList *profiles, GameListModel *games, QWidget *parent) :
|
||||
QWidget(parent),
|
||||
m_display(nullptr),
|
||||
m_games(nullptr),
|
||||
@ -26,7 +27,7 @@ LaunchSettings::LaunchSettings(GameListModel *games, QWidget *parent) :
|
||||
auto layout = new QVBoxLayout();
|
||||
|
||||
layout->addWidget(buildSettings(games));
|
||||
layout->addLayout(buildActions());
|
||||
layout->addLayout(buildActions(profiles));
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
@ -63,12 +64,13 @@ QWidget *LaunchSettings::buildSettings(GameListModel *games)
|
||||
return tab;
|
||||
}
|
||||
|
||||
QLayout *LaunchSettings::buildActions()
|
||||
QLayout *LaunchSettings::buildActions(ProfileList *profiles)
|
||||
{
|
||||
auto layout = new QHBoxLayout();
|
||||
|
||||
// Profile list.
|
||||
m_profiles = new QComboBox();
|
||||
m_profiles->setModel(profiles);
|
||||
|
||||
layout->addWidget(m_profiles, 1);
|
||||
|
||||
@ -80,6 +82,16 @@ QLayout *LaunchSettings::buildActions()
|
||||
// Save button.
|
||||
auto save = new QPushButton(loadIcon(":/resources/content-save.svg"), "Save");
|
||||
|
||||
connect(save, &QAbstractButton::clicked, [this]() {
|
||||
auto index = m_profiles->currentIndex();
|
||||
|
||||
if (index >= 0) {
|
||||
auto profiles = reinterpret_cast<ProfileList *>(m_profiles->model());
|
||||
|
||||
emit saveClicked(profiles->get(index));
|
||||
}
|
||||
});
|
||||
|
||||
actions->addButton(save, QDialogButtonBox::ApplyRole);
|
||||
|
||||
// Start button.
|
||||
|
@ -1,9 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "core.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class DisplaySettings;
|
||||
class GameListModel;
|
||||
class ProfileList;
|
||||
class QComboBox;
|
||||
class QLayout;
|
||||
class QTableView;
|
||||
@ -11,13 +14,14 @@ class QTableView;
|
||||
class LaunchSettings final : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
LaunchSettings(GameListModel *games, QWidget *parent = nullptr);
|
||||
LaunchSettings(ProfileList *profiles, GameListModel *games, QWidget *parent = nullptr);
|
||||
~LaunchSettings() override;
|
||||
signals:
|
||||
void saveClicked(Profile *p);
|
||||
void startClicked();
|
||||
private:
|
||||
QWidget *buildSettings(GameListModel *games);
|
||||
QLayout *buildActions();
|
||||
QLayout *buildActions(ProfileList *profiles);
|
||||
|
||||
void requestGamesContextMenu(const QPoint &pos);
|
||||
|
||||
|
@ -111,9 +111,11 @@ int main(int argc, char *argv[])
|
||||
MainWindow win(&vulkan);
|
||||
#endif
|
||||
|
||||
if (!win.loadGames()) {
|
||||
if (!win.loadProfiles() || !win.loadGames()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
win.restoreGeometry();
|
||||
|
||||
return QApplication::exec();
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
#include "main_window.hpp"
|
||||
#include "app_data.hpp"
|
||||
#include "game_models.hpp"
|
||||
#include "launch_settings.hpp"
|
||||
#include "logs_viewer.hpp"
|
||||
#include "path.hpp"
|
||||
#include "pkg_installer.hpp"
|
||||
#include "profile_models.hpp"
|
||||
#include "resources.hpp"
|
||||
#include "screen.hpp"
|
||||
#include "settings.hpp"
|
||||
@ -41,6 +43,7 @@ MainWindow::MainWindow() :
|
||||
MainWindow::MainWindow(QVulkanInstance *vulkan) :
|
||||
#endif
|
||||
m_main(nullptr),
|
||||
m_profiles(nullptr),
|
||||
m_games(nullptr),
|
||||
m_launch(nullptr),
|
||||
m_screen(nullptr)
|
||||
@ -96,9 +99,11 @@ MainWindow::MainWindow(QVulkanInstance *vulkan) :
|
||||
setCentralWidget(m_main);
|
||||
|
||||
// Launch settings.
|
||||
m_profiles = new ProfileList(this);
|
||||
m_games = new GameListModel(this);
|
||||
m_launch = new LaunchSettings(m_games);
|
||||
m_launch = new LaunchSettings(m_profiles, m_games);
|
||||
|
||||
connect(m_launch, &LaunchSettings::saveClicked, this, &MainWindow::saveClicked);
|
||||
connect(m_launch, &LaunchSettings::startClicked, this, &MainWindow::startKernel);
|
||||
|
||||
m_main->addWidget(m_launch);
|
||||
@ -113,15 +118,67 @@ MainWindow::MainWindow(QVulkanInstance *vulkan) :
|
||||
connect(m_screen, &Screen::updateRequestReceived, this, &MainWindow::updateScreen);
|
||||
|
||||
m_main->addWidget(createWindowContainer(m_screen));
|
||||
|
||||
// Show the window.
|
||||
restoreGeometry();
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
}
|
||||
|
||||
bool MainWindow::loadProfiles()
|
||||
{
|
||||
// List profile directories.
|
||||
auto root = profiles();
|
||||
auto dirs = QDir(root).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
|
||||
// Create default profile if the user don't have any profiles.
|
||||
if (dirs.isEmpty()) {
|
||||
Rust<Profile> p;
|
||||
Rust<char> id;
|
||||
|
||||
p = profile_new("Default");
|
||||
id = profile_id(p);
|
||||
|
||||
// Save.
|
||||
auto path = joinPath(root, id.get());
|
||||
Rust<RustError> error;
|
||||
|
||||
error = profile_save(p, path.c_str());
|
||||
|
||||
if (error) {
|
||||
auto text = QString("Failed to save default profile to %1: %2.")
|
||||
.arg(path.c_str())
|
||||
.arg(error_message(error));
|
||||
|
||||
QMessageBox::critical(this, "Error", text);
|
||||
return false;
|
||||
}
|
||||
|
||||
dirs.append(id.get());
|
||||
}
|
||||
|
||||
// Load profiles.
|
||||
for (auto &dir : dirs) {
|
||||
auto path = joinPath(root, dir);
|
||||
Rust<RustError> error;
|
||||
Rust<Profile> profile;
|
||||
|
||||
profile = profile_load(path.c_str(), &error);
|
||||
|
||||
if (!profile) {
|
||||
auto text = QString("Failed to load a profile from %1: %2.")
|
||||
.arg(path.c_str())
|
||||
.arg(error_message(error));
|
||||
|
||||
QMessageBox::critical(this, "Error", text);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_profiles->add(std::move(profile));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MainWindow::loadGames()
|
||||
{
|
||||
// Get game counts.
|
||||
@ -254,7 +311,37 @@ void MainWindow::reportIssue()
|
||||
|
||||
void MainWindow::aboutObliteration()
|
||||
{
|
||||
QMessageBox::about(this, "About Obliteration", "Obliteration is a free and open-source software for playing your PlayStation 4 titles on PC.");
|
||||
QMessageBox::about(
|
||||
this,
|
||||
"About Obliteration",
|
||||
"Obliteration is a free and open-source PlayStation 4 kernel. It will allows you to run "
|
||||
"the PlayStation 4 system software that you have dumped from your PlayStation 4 on your "
|
||||
"PC. This will allows you to play your games forever even if your PlayStation 4 stopped "
|
||||
"working in the future.");
|
||||
}
|
||||
|
||||
void MainWindow::saveClicked(Profile *p)
|
||||
{
|
||||
// Get ID.
|
||||
Rust<char> id;
|
||||
|
||||
id = profile_id(p);
|
||||
|
||||
// Save.
|
||||
auto root = profiles();
|
||||
auto path = joinPath(root, id.get());
|
||||
Rust<RustError> error;
|
||||
|
||||
error = profile_save(p, path.c_str());
|
||||
|
||||
if (error) {
|
||||
auto text = QString("Failed to save %1 profile to %2: %3.")
|
||||
.arg(profile_name(p))
|
||||
.arg(path.c_str())
|
||||
.arg(error_message(error));
|
||||
|
||||
QMessageBox::critical(this, "Error", text);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::startKernel()
|
||||
|
@ -8,6 +8,7 @@
|
||||
class GameListModel;
|
||||
class LaunchSettings;
|
||||
class LogsViewer;
|
||||
class ProfileList;
|
||||
class QStackedWidget;
|
||||
#ifndef __APPLE__
|
||||
class QVulkanInstance;
|
||||
@ -21,9 +22,11 @@ public:
|
||||
#else
|
||||
MainWindow(QVulkanInstance *vulkan);
|
||||
#endif
|
||||
~MainWindow();
|
||||
~MainWindow() override;
|
||||
|
||||
bool loadProfiles();
|
||||
bool loadGames();
|
||||
void restoreGeometry();
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
|
||||
@ -33,18 +36,19 @@ private slots:
|
||||
void viewLogs();
|
||||
void reportIssue();
|
||||
void aboutObliteration();
|
||||
void saveClicked(Profile *p);
|
||||
void startKernel();
|
||||
void updateScreen();
|
||||
|
||||
private:
|
||||
void log(VmmLog type, const QString &msg);
|
||||
bool loadGame(const QString &gameId);
|
||||
void restoreGeometry();
|
||||
bool requireEmulatorStopped();
|
||||
|
||||
static bool vmmHandler(const VmmEvent *ev, void *cx);
|
||||
|
||||
QStackedWidget *m_main;
|
||||
ProfileList *m_profiles;
|
||||
GameListModel *m_games;
|
||||
LaunchSettings *m_launch;
|
||||
Screen *m_screen;
|
||||
|
58
src/profile_models.cpp
Normal file
58
src/profile_models.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include "profile_models.hpp"
|
||||
|
||||
ProfileList::ProfileList(QObject *parent) :
|
||||
QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
ProfileList::~ProfileList()
|
||||
{
|
||||
}
|
||||
|
||||
void ProfileList::add(Rust<Profile> &&p)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), m_items.size(), m_items.size());
|
||||
m_items.push_back(std::move(p));
|
||||
endInsertRows();
|
||||
|
||||
sort(0);
|
||||
}
|
||||
|
||||
int ProfileList::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return m_items.size();
|
||||
}
|
||||
|
||||
QVariant ProfileList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
auto &p = m_items[index.row()];
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
return profile_name(p);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void ProfileList::sort(int column, Qt::SortOrder order)
|
||||
{
|
||||
emit layoutAboutToBeChanged();
|
||||
|
||||
switch (column) {
|
||||
case 0:
|
||||
std::sort(
|
||||
m_items.begin(),
|
||||
m_items.end(),
|
||||
[order](const Rust<Profile> &a, const Rust<Profile> &b) {
|
||||
if (order == Qt::AscendingOrder) {
|
||||
return strcmp(profile_name(a), profile_name(b));
|
||||
} else {
|
||||
return strcmp(profile_name(b), profile_name(a));
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
emit layoutChanged();
|
||||
}
|
21
src/profile_models.hpp
Normal file
21
src/profile_models.hpp
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "core.hpp"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include <vector>
|
||||
|
||||
class ProfileList final : public QAbstractListModel {
|
||||
public:
|
||||
ProfileList(QObject *parent = nullptr);
|
||||
~ProfileList() override;
|
||||
|
||||
void add(Rust<Profile> &&p);
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
Profile *get(size_t i) const { return m_items[i]; }
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
|
||||
private:
|
||||
std::vector<Rust<Profile>> m_items;
|
||||
};
|
Loading…
Reference in New Issue
Block a user