diff --git a/src/core/libraries/app_content/app_content.cpp b/src/core/libraries/app_content/app_content.cpp index 81ce044f..754343ee 100644 --- a/src/core/libraries/app_content/app_content.cpp +++ b/src/core/libraries/app_content/app_content.cpp @@ -4,6 +4,7 @@ #include #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::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)) { diff --git a/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp b/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp index 9281e0dc..79fc1b58 100644 --- a/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp +++ b/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp @@ -6,6 +6,7 @@ #include #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,11 +47,13 @@ void SaveDialogResult::CopyTo(OrbisSaveDataDialogResult& result) const { result.mode = this->mode; result.result = this->result; result.buttonId = this->button_id; - 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); + 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::Instance()->GetString("CONTENT_ID"), 7, - 9}; + const auto content_id = Common::Singleton::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,8 +353,11 @@ 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()) { - result->dir_name = state->save_list.front().dir_name; + 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) { diff --git a/src/core/libraries/save_data/dialog/savedatadialog_ui.h b/src/core/libraries/save_data/dialog/savedatadialog_ui.h index 8b9a68e1..f8a2dccd 100644 --- a/src/core/libraries/save_data/dialog/savedatadialog_ui.h +++ b/src/core/libraries/save_data/dialog/savedatadialog_ui.h @@ -201,6 +201,7 @@ struct SaveDialogResult { std::string dir_name{}; PSF param{}; void* user_data{}; + bool has_item{false}; void CopyTo(OrbisSaveDataDialogResult& result) const; }; diff --git a/src/core/libraries/save_data/save_backup.cpp b/src/core/libraries/save_data/save_backup.cpp index 93af373a..adc68690 100644 --- a/src/core/libraries/save_data/save_backup.cpp +++ b/src/core/libraries/save_data/save_backup.cpp @@ -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 g_backup_queue; static std::deque 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 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(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()); - backup(req.save_path); + 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; } diff --git a/src/core/libraries/save_data/save_backup.h b/src/core/libraries/save_data/save_backup.h index f0aef369..98f752c5 100644 --- a/src/core/libraries/save_data/save_backup.h +++ b/src/core/libraries/save_data/save_backup.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include "common/types.h" diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 839ec335..efe8c673 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -308,7 +308,9 @@ static std::array, 16> g_mount_slots; static void initialize() { g_initialized = true; static auto* param_sfo = Common::Singleton::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(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; diff --git a/src/emulator.cpp b/src/emulator.cpp index 581d0da8..d920e77c 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -102,7 +102,9 @@ void Emulator::Run(const std::filesystem::path& file) { auto* param_sfo = Common::Singleton::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::Instance(); diff --git a/src/qt_gui/game_info.h b/src/qt_gui/game_info.h index 2d08bc08..a4bcd20e 100644 --- a/src/qt_gui/game_info.h +++ b/src/qt_gui/game_info.h @@ -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"); - QString fw = QString::number(fw_int, 16); - 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"); + 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, '.') + : fw.left(3).insert(1, '.'); + game.fw = fw_.toStdString(); + } + } + if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) { + game.version = *app_ver; + } } return game; } diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index bd3961dd..a2f7f28f 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -96,25 +96,37 @@ public: QTableWidgetItem* valueItem; switch (entry.param_fmt) { case PSFEntryFmt::Binary: { - - const auto bin = *psf.GetBinary(entry.key); - std::string text; - 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]); + 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) { + static constexpr char hex[] = "0123456789ABCDEF"; + text.push_back(hex[c >> 4 & 0xF]); + text.push_back(hex[c & 0xF]); + } + valueItem = new QTableWidgetItem(QString::fromStdString(text)); } - 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); - valueItem = - new QTableWidgetItem(QString("0x") + QString::number(integer, 16)); + 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)); + } } break; } diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index cb0129c8..c6036066 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -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) { diff --git a/src/qt_gui/pkg_viewer.cpp b/src/qt_gui/pkg_viewer.cpp index d41d37db..8f20f692 100644 --- a/src/qt_gui/pkg_viewer.cpp +++ b/src/qt_gui/pkg_viewer.cpp @@ -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 = QString::number(fw_int, 16); - QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.') + 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); + 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")) {