From 7682cd2c10f02bb8858f347ad16b3c9905da3cc8 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 30 May 2024 22:21:51 +1000 Subject: [PATCH] System: Generate hash serials when running PS-EXE Allows for per-game settings. --- src/core/game_list.cpp | 7 +-- src/core/system.cpp | 67 +++++++++++++++++++-------- src/core/system.h | 2 + src/duckstation-qt/mainwindow.cpp | 24 ++++++---- src/duckstation-qt/settingswindow.cpp | 8 ++-- src/duckstation-qt/settingswindow.h | 3 +- 6 files changed, 75 insertions(+), 36 deletions(-) diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index 8af7f20aa..0878051c5 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -155,9 +155,10 @@ bool GameList::GetExeListEntry(const std::string& path, GameList::Entry* entry) return false; } - const std::string display_name(FileSystem::GetDisplayNameFromPath(path)); - entry->serial.clear(); - entry->title = Path::GetFileTitle(display_name); + const System::GameHash hash = System::GetGameHashFromFile(path.c_str()); + + entry->serial = hash ? System::GetGameHashId(hash) : std::string(); + entry->title = Path::GetFileTitle(FileSystem::GetDisplayNameFromPath(path)); entry->region = BIOS::GetPSExeDiscRegion(header); entry->file_size = ZeroExtend64(file_size); entry->uncompressed_size = entry->file_size; diff --git a/src/core/system.cpp b/src/core/system.cpp index a4e6c2eb5..952189c81 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -114,6 +114,8 @@ static bool LoadEXE(const char* filename); static std::string GetExecutableNameForImage(IsoReader& iso, bool strip_subdirectories); static bool ReadExecutableFromImage(IsoReader& iso, std::string* out_executable_name, std::vector* out_executable_data); +static GameHash GetGameHashFromBuffer(std::string_view exe_name, std::span exe_buffer, + const IsoReader::ISOPrimaryVolumeDescriptor& iso_pvd, u32 track_1_length); static bool LoadBIOS(Error* error); static void InternalReset(); @@ -700,15 +702,7 @@ bool System::GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash } // Always compute the hash. - const u32 track_1_length = cdi->GetTrackLength(1); - XXH64_state_t* state = XXH64_createState(); - XXH64_reset(state, 0x4242D00C); - XXH64_update(state, exe_name.c_str(), exe_name.size()); - XXH64_update(state, exe_buffer.data(), exe_buffer.size()); - XXH64_update(state, &iso.GetPVD(), sizeof(IsoReader::ISOPrimaryVolumeDescriptor)); - XXH64_update(state, &track_1_length, sizeof(track_1_length)); - const GameHash hash = XXH64_digest(state); - XXH64_freeState(state); + const GameHash hash = GetGameHashFromBuffer(exe_name, exe_buffer, iso.GetPVD(), cdi->GetTrackLength(1)); DEV_LOG("Hash for '{}' - {:016X}", exe_name, hash); if (exe_name != FALLBACK_EXE_NAME) @@ -752,6 +746,16 @@ bool System::GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash return true; } +System::GameHash System::GetGameHashFromFile(const char* path) +{ + const std::optional> data = FileSystem::ReadBinaryFile(path); + if (!data) + return 0; + + const std::string display_name = FileSystem::GetDisplayNameFromPath(path); + return GetGameHashFromBuffer(display_name, data.value(), IsoReader::ISOPrimaryVolumeDescriptor{}, 0); +} + std::string System::GetExecutableNameForImage(IsoReader& iso, bool strip_subdirectories) { // Read SYSTEM.CNF @@ -881,6 +885,20 @@ bool System::ReadExecutableFromImage(IsoReader& iso, std::string* out_executable return true; } +System::GameHash System::GetGameHashFromBuffer(std::string_view exe_name, std::span exe_buffer, + const IsoReader::ISOPrimaryVolumeDescriptor& iso_pvd, u32 track_1_length) +{ + XXH64_state_t* state = XXH64_createState(); + XXH64_reset(state, 0x4242D00C); + XXH64_update(state, exe_name.data(), exe_name.size()); + XXH64_update(state, exe_buffer.data(), exe_buffer.size()); + XXH64_update(state, &iso_pvd, sizeof(IsoReader::ISOPrimaryVolumeDescriptor)); + XXH64_update(state, &track_1_length, sizeof(track_1_length)); + const GameHash hash = XXH64_digest(state); + XXH64_freeState(state); + return hash; +} + DiscRegion System::GetRegionForSerial(std::string_view serial) { std::string prefix; @@ -1470,12 +1488,12 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) // Load CD image up and detect region. std::unique_ptr disc; DiscRegion disc_region = DiscRegion::NonPS1; - std::string exe_boot; - std::string psf_boot; + bool do_exe_boot = false; + bool do_psf_boot = false; if (!parameters.filename.empty()) { - const bool do_exe_boot = IsExeFileName(parameters.filename); - const bool do_psf_boot = (!do_exe_boot && IsPsfFileName(parameters.filename)); + do_exe_boot = IsExeFileName(parameters.filename); + do_psf_boot = (!do_exe_boot && IsPsfFileName(parameters.filename)); if (do_exe_boot || do_psf_boot) { if (s_region == ConsoleRegion::Auto) @@ -1485,10 +1503,6 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) INFO_LOG("EXE/PSF Region: {}", Settings::GetDiscRegionDisplayName(file_region)); s_region = GetConsoleRegionForDiscRegion(file_region); } - if (do_psf_boot) - psf_boot = std::move(parameters.filename); - else - exe_boot = std::move(parameters.filename); } else { @@ -1544,6 +1558,8 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) // Update running game, this will apply settings as well. UpdateRunningGame(disc ? disc->GetFileName().c_str() : parameters.filename.c_str(), disc.get(), true); + // Get boot EXE override. + std::string exe_boot; if (!parameters.override_exe.empty()) { if (!FileSystem::FileExists(parameters.override_exe.c_str()) || !IsExeFileName(parameters.override_exe)) @@ -1559,6 +1575,10 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) INFO_LOG("Overriding boot executable: '{}'", parameters.override_exe); exe_boot = std::move(parameters.override_exe); } + else if (do_exe_boot) + { + exe_boot = std::move(parameters.filename); + } // Check for SBI. if (!CheckForSBIFile(disc.get(), error)) @@ -1639,9 +1659,9 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) DestroySystem(); return false; } - else if (!psf_boot.empty() && !PSFLoader::Load(psf_boot.c_str())) + else if (do_psf_boot && !PSFLoader::Load(parameters.filename.c_str())) { - Error::SetStringFmt(error, "Failed to load PSF file '{}'", Path::GetFileName(psf_boot)); + Error::SetStringFmt(error, "Failed to load PSF file '{}'", Path::GetFileName(parameters.filename)); DestroySystem(); return false; } @@ -3658,7 +3678,14 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting) { s_running_game_path = path; - if (IsExeFileName(path) || IsPsfFileName(path)) + if (IsExeFileName(path)) + { + s_running_game_title = Path::GetFileTitle(FileSystem::GetDisplayNameFromPath(path)); + s_running_game_hash = GetGameHashFromFile(path); + if (s_running_game_hash != 0) + s_running_game_serial = GetGameHashId(s_running_game_hash); + } + else if (IsPsfFileName(path)) { // TODO: We could pull the title from the PSF. s_running_game_title = Path::GetFileTitle(path); diff --git a/src/core/system.h b/src/core/system.h index 1b4f8c048..0162278f7 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -133,6 +134,7 @@ bool ReadExecutableFromImage(CDImage* cdi, std::string* out_executable_name, std std::string GetGameHashId(GameHash hash); bool GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash* out_hash); +GameHash GetGameHashFromFile(const char* path); DiscRegion GetRegionForSerial(std::string_view serial); DiscRegion GetRegionFromSystemArea(CDImage* cdi); DiscRegion GetRegionForImage(CDImage* cdi); diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 895c0c197..5556c4d2c 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -1347,12 +1347,16 @@ void MainWindow::onViewGamePropertiesActionTriggered() if (!s_system_valid) return; - const std::string& path = System::GetDiscPath(); - const std::string& serial = System::GetGameSerial(); - if (path.empty() || serial.empty()) - return; + Host::RunOnCPUThread([]() { + const std::string& path = System::GetDiscPath(); + const std::string& serial = System::GetGameSerial(); + if (path.empty() || serial.empty()) + return; - SettingsWindow::openGamePropertiesDialog(path, serial, System::GetDiscRegion()); + QtHost::RunOnUIThread([path = path, serial = serial]() { + SettingsWindow::openGamePropertiesDialog(path, System::GetGameTitle(), serial, System::GetDiscRegion()); + }); + }); } void MainWindow::onGitHubRepositoryActionTriggered() @@ -1448,8 +1452,9 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point) { if (!entry->IsDiscSet()) { - connect(menu.addAction(tr("Properties...")), &QAction::triggered, - [entry]() { SettingsWindow::openGamePropertiesDialog(entry->path, entry->serial, entry->region); }); + connect(menu.addAction(tr("Properties...")), &QAction::triggered, [entry]() { + SettingsWindow::openGamePropertiesDialog(entry->path, entry->title, entry->serial, entry->region); + }); connect(menu.addAction(tr("Open Containing Directory...")), &QAction::triggered, [this, entry]() { const QFileInfo fi(QString::fromStdString(entry->path)); @@ -1516,7 +1521,10 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point) auto lock = GameList::GetLock(); const GameList::Entry* first_disc = GameList::GetFirstDiscSetMember(disc_set_name); if (first_disc) - SettingsWindow::openGamePropertiesDialog(first_disc->path, first_disc->serial, first_disc->region); + { + SettingsWindow::openGamePropertiesDialog(first_disc->path, first_disc->title, first_disc->serial, + first_disc->region); + } }); connect(menu.addAction(tr("Set Cover Image...")), &QAction::triggered, diff --git a/src/duckstation-qt/settingswindow.cpp b/src/duckstation-qt/settingswindow.cpp index 5e293dc76..db0e1065c 100644 --- a/src/duckstation-qt/settingswindow.cpp +++ b/src/duckstation-qt/settingswindow.cpp @@ -611,7 +611,8 @@ void SettingsWindow::saveAndReloadGameSettings() g_emu_thread->reloadGameSettings(false); } -void SettingsWindow::openGamePropertiesDialog(const std::string& path, const std::string& serial, DiscRegion region) +void SettingsWindow::openGamePropertiesDialog(const std::string& path, const std::string& title, + const std::string& serial, DiscRegion region) { const GameDatabase::Entry* dentry = nullptr; if (!System::IsExeFileName(path) && !System::IsPsfFileName(path)) @@ -652,9 +653,8 @@ void SettingsWindow::openGamePropertiesDialog(const std::string& path, const std if (FileSystem::FileExists(sif->GetFileName().c_str())) sif->Load(); - const QString window_title(tr("%1 [%2]") - .arg(dentry ? QtUtils::StringViewToQString(dentry->title) : QStringLiteral("")) - .arg(QtUtils::StringViewToQString(real_serial))); + const QString window_title( + tr("%1 [%2]").arg(QString::fromStdString(dentry ? dentry->title : title)).arg(QString::fromStdString(real_serial))); SettingsWindow* dialog = new SettingsWindow(path, real_serial, region, dentry, std::move(sif)); dialog->setWindowTitle(window_title); diff --git a/src/duckstation-qt/settingswindow.h b/src/duckstation-qt/settingswindow.h index c49bdd140..a5e63c58e 100644 --- a/src/duckstation-qt/settingswindow.h +++ b/src/duckstation-qt/settingswindow.h @@ -44,7 +44,8 @@ public: const GameDatabase::Entry* entry, std::unique_ptr sif); ~SettingsWindow(); - static void openGamePropertiesDialog(const std::string& path, const std::string& serial, DiscRegion region); + static void openGamePropertiesDialog(const std::string& path, const std::string& title, const std::string& serial, + DiscRegion region); static void closeGamePropertiesDialogs(); // Helper for externally setting fields in game settings ini.