mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2024-12-29 11:06:07 +00:00
SaveLib PR related fixes (#1011)
Some checks are pending
Reuse / reuse (push) Waiting to run
Clang Format / clang-format (push) Waiting to run
Linux-Qt / build (push) Waiting to run
Linux / build (push) Waiting to run
macOS-Qt / build (push) Waiting to run
macOS / build (push) Waiting to run
Windows-Qt / build (push) Waiting to run
Windows / build (push) Waiting to run
Some checks are pending
Reuse / reuse (push) Waiting to run
Clang Format / clang-format (push) Waiting to run
Linux-Qt / build (push) Waiting to run
Linux / build (push) Waiting to run
macOS-Qt / build (push) Waiting to run
macOS / build (push) Waiting to run
Windows-Qt / build (push) Waiting to run
Windows / build (push) Waiting to run
* Safety checks in all SFO readings * SaveData: log backup error and continue & fix possible concurrent file editing * SaveData: Fix issue with old firmwares
This commit is contained in:
parent
edde0a3e7e
commit
581ddfec4d
|
@ -4,6 +4,7 @@
|
|||
#include <cmath>
|
||||
|
||||
#include "app_content.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/io_file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/path_util.h"
|
||||
|
@ -246,7 +247,11 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar
|
|||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||
|
||||
const auto addons_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir);
|
||||
title_id = *param_sfo->GetString("TITLE_ID");
|
||||
if (const auto value = param_sfo->GetString("TITLE_ID"); value.has_value()) {
|
||||
title_id = *value;
|
||||
} else {
|
||||
UNREACHABLE_MSG("Failed to get TITLE_ID");
|
||||
}
|
||||
auto addon_path = addons_dir / title_id;
|
||||
if (std::filesystem::exists(addon_path)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(addon_path)) {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <magic_enum.hpp>
|
||||
|
||||
#include "common/singleton.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/save_data/save_instance.h"
|
||||
#include "imgui/imgui_std.h"
|
||||
|
@ -46,12 +47,14 @@ void SaveDialogResult::CopyTo(OrbisSaveDataDialogResult& result) const {
|
|||
result.mode = this->mode;
|
||||
result.result = this->result;
|
||||
result.buttonId = this->button_id;
|
||||
if (has_item) {
|
||||
if (result.dirName != nullptr) {
|
||||
result.dirName->data.FromString(this->dir_name);
|
||||
}
|
||||
if (result.param != nullptr && this->param.GetString(SaveParams::MAINTITLE).has_value()) {
|
||||
result.param->FromSFO(this->param);
|
||||
}
|
||||
}
|
||||
result.userData = this->user_data;
|
||||
}
|
||||
|
||||
|
@ -63,8 +66,9 @@ SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) {
|
|||
this->enable_back = {param.optionParam->back == OptionBack::ENABLE};
|
||||
}
|
||||
|
||||
static std::string game_serial{*Common::Singleton<PSF>::Instance()->GetString("CONTENT_ID"), 7,
|
||||
9};
|
||||
const auto content_id = Common::Singleton<PSF>::Instance()->GetString("CONTENT_ID");
|
||||
ASSERT_MSG(content_id.has_value(), "Failed to get CONTENT_ID");
|
||||
static std::string game_serial{*content_id, 7, 9};
|
||||
|
||||
const auto item = param.items;
|
||||
this->user_id = item->userId;
|
||||
|
@ -115,9 +119,9 @@ SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) {
|
|||
.dir_name = std::string{dir_name},
|
||||
.icon = icon,
|
||||
|
||||
.title = std::string{*param_sfo.GetString(SaveParams::MAINTITLE)},
|
||||
.subtitle = std::string{*param_sfo.GetString(SaveParams::SUBTITLE)},
|
||||
.details = std::string{*param_sfo.GetString(SaveParams::DETAIL)},
|
||||
.title = std::string{param_sfo.GetString(SaveParams::MAINTITLE).value_or("Unknown")},
|
||||
.subtitle = std::string{param_sfo.GetString(SaveParams::SUBTITLE).value_or("")},
|
||||
.details = std::string{param_sfo.GetString(SaveParams::DETAIL).value_or("")},
|
||||
.date = date_str,
|
||||
.size = size_str,
|
||||
.last_write = param_sfo.GetLastWrite(),
|
||||
|
@ -126,12 +130,12 @@ SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) {
|
|||
});
|
||||
}
|
||||
|
||||
if (type == DialogType::SAVE) {
|
||||
if (type == DialogType::SAVE && item->newItem != nullptr) {
|
||||
RefCountedTexture icon;
|
||||
std::string title{"New Save"};
|
||||
|
||||
const auto new_item = item->newItem;
|
||||
if (new_item != nullptr && new_item->iconBuf && new_item->iconSize) {
|
||||
if (new_item->iconBuf && new_item->iconSize) {
|
||||
auto buf = (u8*)new_item->iconBuf;
|
||||
icon = RefCountedTexture::DecodePngTexture({buf, buf + new_item->iconSize});
|
||||
} else {
|
||||
|
@ -140,7 +144,7 @@ SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) {
|
|||
icon = RefCountedTexture::DecodePngFile(src_icon);
|
||||
}
|
||||
}
|
||||
if (new_item != nullptr && new_item->title != nullptr) {
|
||||
if (new_item->title != nullptr) {
|
||||
title = std::string{new_item->title};
|
||||
}
|
||||
|
||||
|
@ -349,9 +353,12 @@ void SaveDialogUi::Finish(ButtonId buttonId, Result r) {
|
|||
result->result = r;
|
||||
result->button_id = buttonId;
|
||||
result->user_data = this->state->user_data;
|
||||
if (state && state->mode != SaveDataDialogMode::LIST && !state->save_list.empty()) {
|
||||
if (state) {
|
||||
if (state->mode != SaveDataDialogMode::LIST && !state->save_list.empty()) {
|
||||
result->dir_name = state->save_list.front().dir_name;
|
||||
}
|
||||
result->has_item = state->mode == SaveDataDialogMode::LIST || !state->save_list.empty();
|
||||
}
|
||||
}
|
||||
if (status) {
|
||||
*status = Status::FINISHED;
|
||||
|
|
|
@ -201,6 +201,7 @@ struct SaveDialogResult {
|
|||
std::string dir_name{};
|
||||
PSF param{};
|
||||
void* user_data{};
|
||||
bool has_item{false};
|
||||
|
||||
void CopyTo(OrbisSaveDataDialogResult& result) const;
|
||||
};
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save
|
||||
constexpr std::string_view backup_dir = "sce_backup"; // backup folder
|
||||
constexpr std::string_view backup_dir_tmp = "sce_backup_tmp"; // in-progress backup folder
|
||||
constexpr std::string_view backup_dir_old = "sce_backup_old"; // previous backup folder
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
@ -26,6 +27,8 @@ namespace Libraries::SaveData::Backup {
|
|||
static std::jthread g_backup_thread;
|
||||
static std::counting_semaphore g_backup_thread_semaphore{0};
|
||||
|
||||
static std::mutex g_backup_running_mutex;
|
||||
|
||||
static std::mutex g_backup_queue_mutex;
|
||||
static std::deque<BackupRequest> g_backup_queue;
|
||||
static std::deque<BackupRequest> g_result_queue;
|
||||
|
@ -34,38 +37,44 @@ static std::atomic_int g_backup_progress = 0;
|
|||
static std::atomic g_backup_status = WorkerStatus::NotStarted;
|
||||
|
||||
static void backup(const std::filesystem::path& dir_name) {
|
||||
std::unique_lock lk{g_backup_running_mutex};
|
||||
if (!fs::exists(dir_name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto backup_dir = dir_name / ::backup_dir;
|
||||
const auto backup_dir_tmp = dir_name / ::backup_dir_tmp;
|
||||
const auto backup_dir_old = dir_name / ::backup_dir_old;
|
||||
|
||||
fs::remove_all(backup_dir_tmp);
|
||||
fs::remove_all(backup_dir_old);
|
||||
|
||||
std::vector<std::filesystem::path> backup_files;
|
||||
for (const auto& entry : fs::directory_iterator(dir_name)) {
|
||||
const auto filename = entry.path().filename();
|
||||
if (filename != backup_dir && filename != backup_dir_tmp) {
|
||||
if (filename != backup_dir) {
|
||||
backup_files.push_back(entry.path());
|
||||
}
|
||||
}
|
||||
const auto backup_dir = dir_name / ::backup_dir;
|
||||
const auto backup_dir_tmp = dir_name / ::backup_dir_tmp;
|
||||
|
||||
g_backup_progress = 0;
|
||||
|
||||
int total_count = static_cast<int>(backup_files.size());
|
||||
int current_count = 0;
|
||||
|
||||
fs::remove_all(backup_dir_tmp);
|
||||
fs::create_directory(backup_dir_tmp);
|
||||
for (const auto& file : backup_files) {
|
||||
fs::copy(file, backup_dir_tmp / file.filename(), fs::copy_options::recursive);
|
||||
current_count++;
|
||||
g_backup_progress = current_count * 100 / total_count;
|
||||
}
|
||||
bool has_existing = fs::exists(backup_dir);
|
||||
if (has_existing) {
|
||||
fs::rename(backup_dir, dir_name / "sce_backup_old");
|
||||
bool has_existing_backup = fs::exists(backup_dir);
|
||||
if (has_existing_backup) {
|
||||
fs::rename(backup_dir, backup_dir_old);
|
||||
}
|
||||
fs::rename(backup_dir_tmp, backup_dir);
|
||||
if (has_existing) {
|
||||
fs::remove_all(dir_name / "sce_backup_old");
|
||||
if (has_existing_backup) {
|
||||
fs::remove_all(backup_dir_old);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,7 +93,11 @@ static void BackupThreadBody() {
|
|||
}
|
||||
g_backup_status = WorkerStatus::Running;
|
||||
LOG_INFO(Lib_SaveData, "Backing up the following directory: {}", req.save_path.string());
|
||||
try {
|
||||
backup(req.save_path);
|
||||
} catch (const std::filesystem::filesystem_error& err) {
|
||||
LOG_ERROR(Lib_SaveData, "Failed to backup {}: {}", req.save_path.string(), err.what());
|
||||
}
|
||||
LOG_DEBUG(Lib_SaveData, "Backing up the following directory: {} finished",
|
||||
req.save_path.string());
|
||||
{
|
||||
|
@ -146,6 +159,7 @@ bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,
|
|||
|
||||
bool Restore(const std::filesystem::path& save_path) {
|
||||
LOG_INFO(Lib_SaveData, "Restoring backup for {}", save_path.string());
|
||||
std::unique_lock lk{g_backup_running_mutex};
|
||||
if (!fs::exists(save_path) || !fs::exists(save_path / backup_dir)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
|
|
|
@ -308,7 +308,9 @@ static std::array<std::optional<SaveInstance>, 16> g_mount_slots;
|
|||
static void initialize() {
|
||||
g_initialized = true;
|
||||
static auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||
g_game_serial = std::string(*param_sfo->GetString("CONTENT_ID"), 7, 9);
|
||||
const auto content_id = param_sfo->GetString("CONTENT_ID");
|
||||
ASSERT_MSG(content_id.has_value(), "Failed to get CONTENT_ID");
|
||||
g_game_serial = std::string(*content_id, 7, 9);
|
||||
}
|
||||
|
||||
// game_00other | game*other
|
||||
|
@ -479,9 +481,9 @@ static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup
|
|||
|
||||
void OrbisSaveDataParam::FromSFO(const PSF& sfo) {
|
||||
memset(this, 0, sizeof(OrbisSaveDataParam));
|
||||
title.FromString(*sfo.GetString(SaveParams::MAINTITLE));
|
||||
subTitle.FromString(*sfo.GetString(SaveParams::SUBTITLE));
|
||||
detail.FromString(*sfo.GetString(SaveParams::DETAIL));
|
||||
title.FromString(sfo.GetString(SaveParams::MAINTITLE).value_or("Unknown"));
|
||||
subTitle.FromString(sfo.GetString(SaveParams::SUBTITLE).value_or(""));
|
||||
detail.FromString(sfo.GetString(SaveParams::DETAIL).value_or(""));
|
||||
userParam = sfo.GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0);
|
||||
const auto time_since_epoch = sfo.GetLastWrite().time_since_epoch();
|
||||
mtime = chrono::duration_cast<chrono::seconds>(time_since_epoch).count();
|
||||
|
@ -1019,7 +1021,7 @@ Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint
|
|||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
const size_t s = param_sfo->GetString(key)->copy(param, paramBufSize - 1);
|
||||
const size_t s = param_sfo->GetString(key).value_or("").copy(param, paramBufSize - 1);
|
||||
param[s] = '\0'; // null terminate
|
||||
if (gotSize != nullptr) {
|
||||
*gotSize = s + 1;
|
||||
|
|
|
@ -102,7 +102,9 @@ void Emulator::Run(const std::filesystem::path& file) {
|
|||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||
const bool success = param_sfo->Open(sce_sys_folder / "param.sfo");
|
||||
ASSERT_MSG(success, "Failed to open param.sfo");
|
||||
id = std::string(*param_sfo->GetString("CONTENT_ID"), 7, 9);
|
||||
const auto content_id = param_sfo->GetString("CONTENT_ID");
|
||||
ASSERT_MSG(content_id.has_value(), "Failed to get CONTENT_ID");
|
||||
id = std::string(*content_id, 7, 9);
|
||||
Libraries::NpTrophy::game_serial = id;
|
||||
const auto trophyDir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles";
|
||||
|
@ -115,10 +117,10 @@ void Emulator::Run(const std::filesystem::path& file) {
|
|||
#ifdef ENABLE_QT_GUI
|
||||
MemoryPatcher::g_game_serial = id;
|
||||
#endif
|
||||
title = *param_sfo->GetString("TITLE");
|
||||
title = param_sfo->GetString("TITLE").value_or("Unknown title");
|
||||
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
|
||||
u32 fw_version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000);
|
||||
app_version = *param_sfo->GetString("APP_VER");
|
||||
app_version = param_sfo->GetString("APP_VER").value_or("Unknown version");
|
||||
LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version);
|
||||
} else if (entry.path().filename() == "playgo-chunk.dat") {
|
||||
auto* playgo = Common::Singleton<PlaygoFile>::Instance();
|
||||
|
|
|
@ -32,16 +32,31 @@ public:
|
|||
QString iconpath = QString::fromStdString(game.icon_path);
|
||||
game.icon = QImage(iconpath);
|
||||
game.pic_path = game.path + "/sce_sys/pic1.png";
|
||||
game.name = *psf.GetString("TITLE");
|
||||
game.serial = *psf.GetString("TITLE_ID");
|
||||
game.region =
|
||||
GameListUtils::GetRegion(psf.GetString("CONTENT_ID")->at(0)).toStdString();
|
||||
u32 fw_int = *psf.GetInteger("SYSTEM_VER");
|
||||
if (const auto title = psf.GetString("TITLE"); title.has_value()) {
|
||||
game.name = *title;
|
||||
}
|
||||
if (const auto title_id = psf.GetString("TITLE_ID"); title_id.has_value()) {
|
||||
game.serial = *title_id;
|
||||
}
|
||||
if (const auto content_id = psf.GetString("CONTENT_ID");
|
||||
content_id.has_value() && !content_id->empty()) {
|
||||
game.region = GameListUtils::GetRegion(content_id->at(0)).toStdString();
|
||||
}
|
||||
if (const auto fw_int_opt = psf.GetInteger("SYSTEM_VER"); fw_int_opt.has_value()) {
|
||||
auto fw_int = *fw_int_opt;
|
||||
if (fw_int == 0) {
|
||||
game.fw = "0.00";
|
||||
} else {
|
||||
QString fw = QString::number(fw_int, 16);
|
||||
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
|
||||
QString fw_ = fw.length() > 7
|
||||
? QString::number(fw_int, 16).left(3).insert(2, '.')
|
||||
: fw.left(3).insert(1, '.');
|
||||
game.fw = (fw_int == 0) ? "0.00" : fw_.toStdString();
|
||||
game.version = *psf.GetString("APP_VER");
|
||||
game.fw = fw_.toStdString();
|
||||
}
|
||||
}
|
||||
if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) {
|
||||
game.version = *app_ver;
|
||||
}
|
||||
}
|
||||
return game;
|
||||
}
|
||||
|
|
|
@ -96,25 +96,37 @@ public:
|
|||
QTableWidgetItem* valueItem;
|
||||
switch (entry.param_fmt) {
|
||||
case PSFEntryFmt::Binary: {
|
||||
|
||||
const auto bin = *psf.GetBinary(entry.key);
|
||||
const auto bin = psf.GetBinary(entry.key);
|
||||
if (!bin.has_value()) {
|
||||
valueItem = new QTableWidgetItem(QString("Unknown"));
|
||||
} else {
|
||||
std::string text;
|
||||
text.reserve(bin.size() * 2);
|
||||
for (const auto& c : bin) {
|
||||
text.reserve(bin->size() * 2);
|
||||
for (const auto& c : *bin) {
|
||||
static constexpr char hex[] = "0123456789ABCDEF";
|
||||
text.push_back(hex[c >> 4 & 0xF]);
|
||||
text.push_back(hex[c & 0xF]);
|
||||
}
|
||||
valueItem = new QTableWidgetItem(QString::fromStdString(text));
|
||||
}
|
||||
} break;
|
||||
case PSFEntryFmt::Text: {
|
||||
auto text = *psf.GetString(entry.key);
|
||||
valueItem = new QTableWidgetItem(QString::fromStdString(std::string{text}));
|
||||
auto text = psf.GetString(entry.key);
|
||||
if (!text.has_value()) {
|
||||
valueItem = new QTableWidgetItem(QString("Unknown"));
|
||||
} else {
|
||||
valueItem =
|
||||
new QTableWidgetItem(QString::fromStdString(std::string{*text}));
|
||||
}
|
||||
} break;
|
||||
case PSFEntryFmt::Integer: {
|
||||
auto integer = *psf.GetInteger(entry.key);
|
||||
auto integer = psf.GetInteger(entry.key);
|
||||
if (!integer.has_value()) {
|
||||
valueItem = new QTableWidgetItem(QString("Unknown"));
|
||||
} else {
|
||||
valueItem =
|
||||
new QTableWidgetItem(QString("0x") + QString::number(integer, 16));
|
||||
new QTableWidgetItem(QString("0x") + QString::number(*integer, 16));
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
|
|
|
@ -636,9 +636,19 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
|
|||
QMessageBox msgBox;
|
||||
msgBox.setWindowTitle(tr("PKG Extraction"));
|
||||
|
||||
psf.Open(pkg.sfo);
|
||||
if (!psf.Open(pkg.sfo)) {
|
||||
QMessageBox::critical(this, tr("PKG ERROR"),
|
||||
"Could not read SFO. Check log for details");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string content_id{*psf.GetString("CONTENT_ID")};
|
||||
std::string content_id;
|
||||
if (auto value = psf.GetString("CONTENT_ID"); value.has_value()) {
|
||||
content_id = std::string{*value};
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no CONTENT_ID");
|
||||
return;
|
||||
}
|
||||
std::string entitlement_label = Common::SplitString(content_id, '-')[2];
|
||||
|
||||
auto addon_extract_path = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) /
|
||||
|
@ -647,11 +657,21 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
|
|||
auto category = psf.GetString("CATEGORY");
|
||||
|
||||
if (pkgType.contains("PATCH")) {
|
||||
QString pkg_app_version =
|
||||
QString::fromStdString(std::string{*psf.GetString("APP_VER")});
|
||||
QString pkg_app_version;
|
||||
if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) {
|
||||
pkg_app_version = QString::fromStdString(std::string{*app_ver});
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no APP_VER");
|
||||
return;
|
||||
}
|
||||
psf.Open(extract_path / "sce_sys" / "param.sfo");
|
||||
QString game_app_version =
|
||||
QString::fromStdString(std::string{*psf.GetString("APP_VER")});
|
||||
QString game_app_version;
|
||||
if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) {
|
||||
game_app_version = QString::fromStdString(std::string{*app_ver});
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no APP_VER");
|
||||
return;
|
||||
}
|
||||
double appD = game_app_version.toDouble();
|
||||
double pkgD = pkg_app_version.toDouble();
|
||||
if (pkgD == appD) {
|
||||
|
|
|
@ -110,12 +110,16 @@ void PKGViewer::ProcessPKGInfo() {
|
|||
#endif
|
||||
package.Open(path);
|
||||
psf.Open(package.sfo);
|
||||
QString title_name = QString::fromStdString(std::string{*psf.GetString("TITLE")});
|
||||
QString title_id = QString::fromStdString(std::string{*psf.GetString("TITLE_ID")});
|
||||
QString app_type = game_list_util.GetAppType(*psf.GetInteger("APP_TYPE"));
|
||||
QString app_version = QString::fromStdString(std::string{*psf.GetString("APP_VER")});
|
||||
QString title_category = QString::fromStdString(std::string{*psf.GetString("CATEGORY")});
|
||||
QString pkg_size = game_list_util.FormatSize(package.GetPkgHeader().pkg_size);
|
||||
QString title_name =
|
||||
QString::fromStdString(std::string{psf.GetString("TITLE").value_or("Unknown")});
|
||||
QString title_id =
|
||||
QString::fromStdString(std::string{psf.GetString("TITLE_ID").value_or("Unknown")});
|
||||
QString app_type = GameListUtils::GetAppType(psf.GetInteger("APP_TYPE").value_or(0));
|
||||
QString app_version =
|
||||
QString::fromStdString(std::string{psf.GetString("APP_VER").value_or("Unknown")});
|
||||
QString title_category =
|
||||
QString::fromStdString(std::string{psf.GetString("CATEGORY").value_or("Unknown")});
|
||||
QString pkg_size = GameListUtils::FormatSize(package.GetPkgHeader().pkg_size);
|
||||
pkg_content_flag = package.GetPkgHeader().pkg_content_flags;
|
||||
QString flagss = "";
|
||||
for (const auto& flag : package.flagNames) {
|
||||
|
@ -126,11 +130,17 @@ void PKGViewer::ProcessPKGInfo() {
|
|||
}
|
||||
}
|
||||
|
||||
u32 fw_int = *psf.GetInteger("SYSTEM_VER");
|
||||
QString fw_ = "Unknown";
|
||||
if (const auto fw_int_opt = psf.GetInteger("SYSTEM_VER"); fw_int_opt.has_value()) {
|
||||
const u32 fw_int = *fw_int_opt;
|
||||
if (fw_int == 0) {
|
||||
fw_ = "0.00";
|
||||
} else {
|
||||
QString fw = QString::number(fw_int, 16);
|
||||
QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
|
||||
fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.')
|
||||
: fw.left(3).insert(1, '.');
|
||||
fw_ = (fw_int == 0) ? "0.00" : fw_;
|
||||
}
|
||||
}
|
||||
char region = package.GetPkgHeader().pkg_content_id[0];
|
||||
QString pkg_info = "";
|
||||
if (title_category == "gd" && !flagss.contains("PATCH")) {
|
||||
|
|
Loading…
Reference in a new issue