diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index 56e7a178c..b924082e3 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -296,7 +296,6 @@ static Command s_command = Command::None; static Command s_command_second_response = Command::None; static DriveState s_drive_state = DriveState::Idle; static DiscRegion s_disc_region = DiscRegion::Other; -static bool s_ps1_disc = false; static StatusRegister s_status = {}; static SecondaryStatusRegister s_secondary_status = {}; @@ -666,7 +665,7 @@ DiscRegion CDROM::GetDiscRegion() bool CDROM::IsMediaPS1Disc() { - return s_ps1_disc; + return (s_disc_region != DiscRegion::NonPS1); } bool CDROM::IsMediaAudioCD() @@ -715,35 +714,21 @@ bool CDROM::CanReadMedia() return (s_drive_state != DriveState::ShellOpening && m_reader.HasMedia()); } -void CDROM::InsertMedia(std::unique_ptr media) +void CDROM::InsertMedia(std::unique_ptr media, DiscRegion region) { if (CanReadMedia()) RemoveMedia(true); - // check if it's a valid PS1 disc - std::string exe_name; - std::vector exe_buffer; - s_ps1_disc = System::ReadExecutableFromImage(media.get(), &exe_name, &exe_buffer); + Log_InfoPrintf("Inserting new media, disc region: %s, console region: %s", + Settings::GetDiscRegionName(region), Settings::GetConsoleRegionName(System::GetRegion())); - if (s_ps1_disc) - { - // set the region from the system area of the disc - s_disc_region = System::GetRegionForImage(media.get()); - Log_InfoPrintf("Inserting new media, disc region: %s, console region: %s", - Settings::GetDiscRegionName(s_disc_region), Settings::GetConsoleRegionName(System::GetRegion())); - } - else - { - s_disc_region = DiscRegion::Other; - Log_InfoPrint("Inserting new media, non-PS1 disc"); - } + s_disc_region = region; + m_reader.SetMedia(std::move(media)); + SetHoldPosition(0, true); // motor automatically spins up if (s_drive_state != DriveState::ShellOpening) StartMotor(); - - m_reader.SetMedia(std::move(media)); - SetHoldPosition(0, true); } std::unique_ptr CDROM::RemoveMedia(bool for_disc_swap) @@ -764,8 +749,7 @@ std::unique_ptr CDROM::RemoveMedia(bool for_disc_swap) s_secondary_status.motor_on = false; s_secondary_status.shell_open = true; s_secondary_status.ClearActiveBits(); - s_disc_region = DiscRegion::Other; - s_ps1_disc = false; + s_disc_region = DiscRegion::NonPS1; // If the drive was doing anything, we need to abort the command. ClearDriveState(); @@ -2683,7 +2667,7 @@ void CDROM::DoIDRead() static constexpr u32 REGION_STRING_LENGTH = 4; static constexpr std::array, static_cast(DiscRegion::Count)> - region_strings = {{{'S', 'C', 'E', 'I'}, {'S', 'C', 'E', 'A'}, {'S', 'C', 'E', 'E'}, {0, 0, 0, 0}}}; + region_strings = {{{'S', 'C', 'E', 'I'}, {'S', 'C', 'E', 'A'}, {'S', 'C', 'E', 'E'}, {0, 0, 0, 0}, {0, 0, 0, 0}}}; s_async_response_fifo.PushRange(region_strings[static_cast(s_disc_region)].data(), REGION_STRING_LENGTH); SetAsyncInterrupt((flags_byte != 0) ? Interrupt::Error : Interrupt::Complete); diff --git a/src/core/cdrom.h b/src/core/cdrom.h index 4ab7d517b..15c942e35 100644 --- a/src/core/cdrom.h +++ b/src/core/cdrom.h @@ -25,7 +25,7 @@ bool IsMediaPS1Disc(); bool IsMediaAudioCD(); bool DoesMediaRegionMatchConsole(); -void InsertMedia(std::unique_ptr media); +void InsertMedia(std::unique_ptr media, DiscRegion region); std::unique_ptr RemoveMedia(bool for_disc_swap); bool PrecacheMedia(); diff --git a/src/core/game_database.cpp b/src/core/game_database.cpp index 7e36690fe..1b45201a6 100644 --- a/src/core/game_database.cpp +++ b/src/core/game_database.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "game_database.h" @@ -35,7 +35,6 @@ enum : u32 }; static Entry* GetMutableEntry(const std::string_view& serial); -static const Entry* GetEntryForId(const std::string_view& code); static bool LoadFromCache(); static bool SaveToCache(); @@ -111,6 +110,9 @@ void GameDatabase::Unload() const GameDatabase::Entry* GameDatabase::GetEntryForId(const std::string_view& code) { + if (code.empty()) + return nullptr; + EnsureLoaded(); auto iter = UnorderedStringMapFind(s_code_lookup, code); @@ -144,7 +146,8 @@ std::string GameDatabase::GetSerialForPath(const char* path) const GameDatabase::Entry* GameDatabase::GetEntryForDisc(CDImage* image) { - std::string id(System::GetGameIdFromImage(image, false)); + std::string id; + System::GetGameDetailsFromImage(image, &id, nullptr); if (!id.empty()) { const Entry* entry = GetEntryForId(id); @@ -152,15 +155,7 @@ const GameDatabase::Entry* GameDatabase::GetEntryForDisc(CDImage* image) return entry; } - std::string hash_id(System::GetGameHashIdFromImage(image)); - if (!hash_id.empty()) - { - const Entry* entry = GetEntryForId(hash_id); - if (entry) - return entry; - } - - Log_WarningPrintf("No entry found for disc (exe code: '%s', hash code: '%s')", id.c_str(), hash_id.c_str()); + Log_WarningPrintf("No entry found for disc '%s'", id.c_str()); return nullptr; } @@ -498,7 +493,7 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes "Controller in port %u (%s) is not supported for %s.\nSupported controllers: " "%s\nPlease configure a supported controller from the list above."), i + 1u, Host::TranslateString("ControllerType", Settings::GetControllerTypeDisplayName(ctype)).GetCharArray(), - System::GetRunningTitle().c_str(), supported_controller_string.GetCharArray()); + System::GetGameTitle().c_str(), supported_controller_string.GetCharArray()); } } } diff --git a/src/core/game_database.h b/src/core/game_database.h index ee9d0ad77..a4dfe6e02 100644 --- a/src/core/game_database.h +++ b/src/core/game_database.h @@ -88,6 +88,7 @@ void EnsureLoaded(); void Unload(); const Entry* GetEntryForDisc(CDImage* image); +const Entry* GetEntryForId(const std::string_view& code); const Entry* GetEntryForSerial(const std::string_view& serial); std::string GetSerialForDisc(CDImage* image); std::string GetSerialForPath(const char* path); diff --git a/src/core/settings.cpp b/src/core/settings.cpp index bd9c67648..68fb136b2 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -788,10 +788,11 @@ const char* Settings::GetConsoleRegionDisplayName(ConsoleRegion region) return s_console_region_display_names[static_cast(region)]; } -static std::array s_disc_region_names = {{"NTSC-J", "NTSC-U", "PAL", "Other"}}; -static std::array s_disc_region_display_names = { +static std::array s_disc_region_names = {{"NTSC-J", "NTSC-U", "PAL", "Other", "Non-PS1"}}; +static std::array s_disc_region_display_names = { {TRANSLATABLE("DiscRegion", "NTSC-J (Japan)"), TRANSLATABLE("DiscRegion", "NTSC-U/C (US, Canada)"), - TRANSLATABLE("DiscRegion", "PAL (Europe, Australia)"), TRANSLATABLE("DiscRegion", "Other")}}; + TRANSLATABLE("DiscRegion", "PAL (Europe, Australia)"), TRANSLATABLE("DiscRegion", "Other"), + TRANSLATABLE("DiscRegion", "Non-PS1")}}; std::optional Settings::ParseDiscRegionName(const char* str) { diff --git a/src/core/system.cpp b/src/core/system.cpp index 3b82f717e..9c79e1638 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "system.h" @@ -91,7 +91,6 @@ static bool LoadMemoryState(const MemorySaveState& mss); 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); @@ -128,6 +127,7 @@ static void SetTimerResolutionIncreased(bool enabled); } // namespace System static constexpr const float PERFORMANCE_COUNTER_UPDATE_INTERVAL = 1.0f; +static constexpr const char FALLBACK_EXE_NAME[] = "PSX.EXE"; static std::unique_ptr s_game_settings_interface; static std::unique_ptr s_input_settings_interface; @@ -147,6 +147,7 @@ static BIOS::Hash s_bios_hash = {}; static std::string s_running_game_path; static std::string s_running_game_serial; static std::string s_running_game_title; +static System::GameHash s_running_game_hash; static bool s_running_unknown_game; static float s_throttle_frequency = 60.0f; @@ -315,20 +316,25 @@ void System::IncrementInternalFrameNumber() s_internal_frame_number++; } -const std::string& System::GetRunningPath() +const std::string& System::GetDiscPath() { return s_running_game_path; } -const std::string& System::GetRunningSerial() +const std::string& System::GetGameSerial() { return s_running_game_serial; } -const std::string& System::GetRunningTitle() +const std::string& System::GetGameTitle() { return s_running_game_title; } +System::GameHash System::GetGameHash() +{ + return s_running_game_hash; +} + bool System::IsRunningUnknownGame() { return s_running_unknown_game; @@ -447,6 +453,7 @@ ConsoleRegion System::GetConsoleRegionForDiscRegion(DiscRegion region) case DiscRegion::NTSC_U: case DiscRegion::Other: + case DiscRegion::NonPS1: default: return ConsoleRegion::NTSC_U; @@ -455,70 +462,86 @@ ConsoleRegion System::GetConsoleRegionForDiscRegion(DiscRegion region) } } -std::string System::GetGameSerialForPath(const char* image_path, bool fallback_to_hash) +std::string System::GetGameHashId(GameHash hash) { - std::unique_ptr cdi = CDImage::Open(image_path, false, nullptr); - if (!cdi) - return {}; - - return GetGameIdFromImage(cdi.get(), fallback_to_hash); + return StringUtil::StdStringFromFormat("HASH-%" PRIX64, hash); } -std::string System::GetGameIdFromImage(CDImage* cdi, bool fallback_to_hash) -{ - std::string code(GetExecutableNameForImage(cdi)); - if (!code.empty()) - { - // SCES_123.45 -> SCES-12345 - for (std::string::size_type pos = 0; pos < code.size();) - { - if (code[pos] == '.') - { - code.erase(pos, 1); - continue; - } - - if (code[pos] == '_') - code[pos] = '-'; - else - code[pos] = static_cast(std::toupper(code[pos])); - - pos++; - } - - return code; - } - - if (!fallback_to_hash) - return {}; - - return GetGameHashIdFromImage(cdi); -} - -std::string System::GetGameHashIdFromImage(CDImage* cdi) +bool System::GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash* out_hash) { ISOReader iso; if (!iso.Open(cdi, 1)) - return {}; + { + if (out_id) + out_id->clear(); + if (out_hash) + *out_hash = 0; + return false; + } + std::string id; std::string exe_name; std::vector exe_buffer; - if (!ReadExecutableFromImage(cdi, &exe_name, &exe_buffer)) - return {}; + if (!ReadExecutableFromImage(iso, &exe_name, &exe_buffer)) + { + if (out_id) + out_id->clear(); + if (out_hash) + *out_hash = 0; + return false; + } + // 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 u64 hash = XXH64_digest(state); + const GameHash hash = XXH64_digest(state); XXH64_freeState(state); + Log_DevPrintf("Hash for '%s' - %" PRIX64, exe_name.c_str(), hash); - Log_InfoPrintf("Hash for '%s' - %" PRIX64, exe_name.c_str(), hash); - return StringUtil::StdStringFromFormat("HASH-%" PRIX64, hash); + if (exe_name != FALLBACK_EXE_NAME) + { + // Strip off any subdirectories. + const std::string::size_type slash = exe_name.rfind('\\'); + if (slash != std::string::npos) + id = std::string_view(exe_name).substr(slash + 1); + else + id = exe_name; + + // SCES_123.45 -> SCES-12345 + for (std::string::size_type pos = 0; pos < id.size();) + { + if (id[pos] == '.') + { + id.erase(pos, 1); + continue; + } + + if (id[pos] == '_') + id[pos] = '-'; + else + id[pos] = static_cast(std::toupper(id[pos])); + + pos++; + } + } + + if (out_id) + { + if (id.empty()) + *out_id = GetGameHashId(hash); + else + *out_id = std::move(id); + } + + if (out_hash) + *out_hash = hash; + + return true; } std::string System::GetExecutableNameForImage(ISOReader& iso, bool strip_subdirectories) @@ -526,7 +549,7 @@ std::string System::GetExecutableNameForImage(ISOReader& iso, bool strip_subdire // Read SYSTEM.CNF std::vector system_cnf_data; if (!iso.ReadFile("SYSTEM.CNF", &system_cnf_data)) - return {}; + return FALLBACK_EXE_NAME; // Parse lines std::vector> lines; @@ -568,7 +591,10 @@ std::string System::GetExecutableNameForImage(ISOReader& iso, bool strip_subdire auto iter = std::find_if(lines.begin(), lines.end(), [](const auto& it) { return StringUtil::Strcasecmp(it.first.c_str(), "boot") == 0; }); if (iter == lines.end()) - return {}; + { + // Fallback to PSX.EXE + return FALLBACK_EXE_NAME; + } std::string code = iter->second; std::string::size_type pos; @@ -608,49 +634,17 @@ std::string System::GetExecutableNameForImage(ISOReader& iso, bool strip_subdire return code; } -std::string System::GetExecutableNameForImage(CDImage* cdi) +std::string System::GetExecutableNameForImage(CDImage* cdi, bool strip_subdirectories) { ISOReader iso; if (!iso.Open(cdi, 1)) return {}; - return GetExecutableNameForImage(iso, true); -} - -bool System::ReadExecutableFromImage(ISOReader& iso, std::string* out_executable_name, - std::vector* out_executable_data) -{ - bool result = false; - - std::string executable_path(GetExecutableNameForImage(iso, false)); - Log_DevPrintf("Executable path: '%s'", executable_path.c_str()); - if (!executable_path.empty()) - { - result = iso.ReadFile(executable_path.c_str(), out_executable_data); - if (!result) - Log_ErrorPrintf("Failed to read executable '%s' from disc", executable_path.c_str()); - } - - if (!result) - { - // fallback to PSX.EXE - executable_path = "PSX.EXE"; - result = iso.ReadFile(executable_path.c_str(), out_executable_data); - if (!result) - Log_ErrorPrint("Failed to read fallback PSX.EXE from disc"); - } - - if (!result) - return false; - - if (out_executable_name) - *out_executable_name = std::move(executable_path); - - return true; + return GetExecutableNameForImage(iso, strip_subdirectories); } bool System::ReadExecutableFromImage(CDImage* cdi, std::string* out_executable_name, - std::vector* out_executable_data) + std::vector* out_executable_data) { ISOReader iso; if (!iso.Open(cdi, 1)) @@ -659,6 +653,25 @@ bool System::ReadExecutableFromImage(CDImage* cdi, std::string* out_executable_n return ReadExecutableFromImage(iso, out_executable_name, out_executable_data); } +bool System::ReadExecutableFromImage(ISOReader& iso, std::string* out_executable_name, std::vector* out_executable_data) +{ + const std::string executable_path = GetExecutableNameForImage(iso, false); + Log_DevPrintf("Executable path: '%s'", executable_path.c_str()); + if (!executable_path.empty() && out_executable_data) + { + if (!iso.ReadFile(executable_path.c_str(), out_executable_data)) + { + Log_ErrorPrintf("Failed to read executable '%s' from disc", executable_path.c_str()); + return false; + } + } + + if (out_executable_name) + *out_executable_name = std::move(executable_path); + + return true; +} + DiscRegion System::GetRegionForSerial(std::string_view serial) { std::string prefix; @@ -705,15 +718,25 @@ DiscRegion System::GetRegionFromSystemArea(CDImage* cdi) DiscRegion System::GetRegionForImage(CDImage* cdi) { - DiscRegion system_area_region = GetRegionFromSystemArea(cdi); + const DiscRegion system_area_region = GetRegionFromSystemArea(cdi); if (system_area_region != DiscRegion::Other) return system_area_region; - std::string serial = GetGameIdFromImage(cdi, false); - if (serial.empty()) - return DiscRegion::Other; + ISOReader iso; + if (!iso.Open(cdi, 1)) + return DiscRegion::NonPS1; - return GetRegionForSerial(serial); + // The executable must exist, because this just returns PSX.EXE if it doesn't. + const std::string exename = GetExecutableNameForImage(iso, false); + if (exename.empty() || !iso.FileExists(exename.c_str())) + return DiscRegion::NonPS1; + + // Strip off any subdirectories. + const std::string::size_type slash = exename.rfind('\\'); + if (slash != std::string::npos) + return GetRegionForSerial(std::string_view(exename).substr(slash + 1)); + else + return GetRegionForSerial(exename); } DiscRegion System::GetRegionForExe(const char* path) @@ -1115,7 +1138,8 @@ bool System::BootSystem(SystemBootParameters parameters) // Load CD image up and detect region. Common::Error error; - std::unique_ptr media; + std::unique_ptr disc; + DiscRegion disc_region = DiscRegion::NonPS1; std::string exe_boot; std::string psf_boot; if (!parameters.filename.empty()) @@ -1139,8 +1163,8 @@ bool System::BootSystem(SystemBootParameters parameters) else { Log_InfoPrintf("Loading CD image '%s'...", parameters.filename.c_str()); - media = CDImage::Open(parameters.filename.c_str(), g_settings.cdrom_load_image_patches, &error); - if (!media) + disc = CDImage::Open(parameters.filename.c_str(), g_settings.cdrom_load_image_patches, &error); + if (!disc) { Host::ReportErrorAsync("Error", fmt::format("Failed to load CD image '{}': {}", Path::GetFileName(parameters.filename), error.GetCodeAndMessage())); @@ -1151,7 +1175,7 @@ bool System::BootSystem(SystemBootParameters parameters) if (s_region == ConsoleRegion::Auto) { - const DiscRegion disc_region = GetRegionForImage(media.get()); + disc_region = GetRegionForImage(disc.get()); if (disc_region != DiscRegion::Other) { s_region = GetConsoleRegionForDiscRegion(disc_region); @@ -1178,7 +1202,7 @@ bool System::BootSystem(SystemBootParameters parameters) Log_InfoPrintf("Console Region: %s", Settings::GetConsoleRegionDisplayName(s_region)); // Switch subimage. - if (media && parameters.media_playlist_index != 0 && !media->SwitchSubImage(parameters.media_playlist_index, &error)) + if (disc && parameters.media_playlist_index != 0 && !disc->SwitchSubImage(parameters.media_playlist_index, &error)) { Host::ReportFormattedErrorAsync("Error", "Failed to switch to subimage %u in '%s': %s", parameters.media_playlist_index, parameters.filename.c_str(), @@ -1189,7 +1213,7 @@ bool System::BootSystem(SystemBootParameters parameters) } // Update running game, this will apply settings as well. - UpdateRunningGame(media ? media->GetFileName().c_str() : parameters.filename.c_str(), media.get(), true); + UpdateRunningGame(disc ? disc->GetFileName().c_str() : parameters.filename.c_str(), disc.get(), true); if (!parameters.override_exe.empty()) { @@ -1207,7 +1231,7 @@ bool System::BootSystem(SystemBootParameters parameters) } // Check for SBI. - if (!CheckForSBIFile(media.get())) + if (!CheckForSBIFile(disc.get())) { s_state = State::Shutdown; ClearRunningGame(); @@ -1274,8 +1298,8 @@ bool System::BootSystem(SystemBootParameters parameters) } // Insert CD, and apply fastboot patch if enabled. - if (media) - CDROM::InsertMedia(std::move(media)); + if (disc) + CDROM::InsertMedia(std::move(disc), disc_region); if (CDROM::HasMedia() && (parameters.override_fast_boot.has_value() ? parameters.override_fast_boot.value() : g_settings.bios_patch_fast_boot)) { @@ -1513,6 +1537,7 @@ void System::ClearRunningGame() s_running_game_serial.clear(); s_running_game_path.clear(); s_running_game_title.clear(); + s_running_game_hash = 0; s_running_unknown_game = false; s_cheat_list.reset(); s_state = State::Shutdown; @@ -1967,7 +1992,8 @@ bool System::DoLoadState(ByteStream* state, bool force_software_renderer, bool u CDROM::Reset(); if (media) { - CDROM::InsertMedia(std::move(media)); + const DiscRegion region = GetRegionForImage(media.get()); + CDROM::InsertMedia(std::move(media), region); if (g_settings.cdrom_load_image_to_ram) CDROM::PrecacheMedia(); } @@ -2997,8 +3023,9 @@ bool System::InsertMedia(const char* path) return false; } + const DiscRegion region = GetRegionForImage(image.get()); UpdateRunningGame(path, image.get(), false); - CDROM::InsertMedia(std::move(image)); + CDROM::InsertMedia(std::move(image), region); Log_InfoPrintf("Inserted media from %s (%s, %s)", s_running_game_path.c_str(), s_running_game_serial.c_str(), s_running_game_title.c_str()); if (g_settings.cdrom_load_image_to_ram) @@ -3031,6 +3058,7 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting) s_running_game_path.clear(); s_running_game_serial.clear(); s_running_game_title.clear(); + s_running_game_hash = 0; s_running_unknown_game = true; if (path && std::strlen(path) > 0) @@ -3044,7 +3072,10 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting) } else if (image) { - const GameDatabase::Entry* entry = GameDatabase::GetEntryForDisc(image); + std::string id; + GetGameDetailsFromImage(image, &id, &s_running_game_hash); + + const GameDatabase::Entry* entry = GameDatabase::GetEntryForId(id); if (entry) { s_running_game_serial = entry->serial; @@ -3053,9 +3084,8 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting) } else { - const std::string display_name(FileSystem::GetDisplayNameFromPath(path)); - s_running_game_serial = GetGameIdFromImage(image, true); - s_running_game_title = Path::GetFileTitle(display_name); + s_running_game_serial = std::move(id); + s_running_game_title = Path::GetFileTitle(FileSystem::GetDisplayNameFromPath(path)); } if (image->HasSubImages() && g_settings.memory_card_use_playlist_title) @@ -3182,14 +3212,17 @@ bool System::SwitchMediaSubImage(u32 index) Host::AddFormattedOSDMessage(10.0f, Host::TranslateString("OSDMessage", "Failed to switch to subimage %u in '%s': %s."), index + 1u, image->GetFileName().c_str(), error.GetCodeAndMessage().GetCharArray()); - CDROM::InsertMedia(std::move(image)); + + const DiscRegion region = GetRegionForImage(image.get()); + CDROM::InsertMedia(std::move(image), region); return false; } Host::AddFormattedOSDMessage(20.0f, Host::TranslateString("OSDMessage", "Switched to sub-image %s (%u) in '%s'."), image->GetSubImageMetadata(index, "title").c_str(), index + 1u, image->GetMetadata("title").c_str()); - CDROM::InsertMedia(std::move(image)); + const DiscRegion region = GetRegionForImage(image.get()); + CDROM::InsertMedia(std::move(image), region); ClearMemorySaveStates(); return true; @@ -3840,7 +3873,7 @@ bool System::StartDumpingAudio(const char* filename) std::string auto_filename; if (!filename) { - const auto& serial = System::GetRunningSerial(); + const auto& serial = System::GetGameSerial(); if (serial.empty()) { auto_filename = Path::Combine( @@ -3885,7 +3918,7 @@ bool System::SaveScreenshot(const char* filename /* = nullptr */, bool full_reso std::string auto_filename; if (!filename) { - const auto& code = System::GetRunningSerial(); + const auto& code = System::GetGameSerial(); const char* extension = "png"; if (code.empty()) { @@ -4079,7 +4112,7 @@ std::string System::GetCheatFileName() { std::string ret; - const std::string& title = System::GetRunningTitle(); + const std::string& title = System::GetGameTitle(); if (!title.empty()) ret = Path::Combine(EmuFolders::Cheats, fmt::format("{}.cht", title.c_str())); diff --git a/src/core/system.h b/src/core/system.h index adbec80ca..8318a4881 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #pragma once @@ -88,6 +88,8 @@ enum class State Paused }; +using GameHash = u64; + extern TickCount g_ticks_per_second; /// Returns true if the filename is a PlayStation executable we can inject. @@ -105,12 +107,12 @@ bool IsSaveStateFilename(const std::string_view& path); /// Returns the preferred console type for a disc. ConsoleRegion GetConsoleRegionForDiscRegion(DiscRegion region); -std::string GetExecutableNameForImage(CDImage* cdi); +std::string GetExecutableNameForImage(CDImage* cdi, bool strip_subdirectories); bool ReadExecutableFromImage(CDImage* cdi, std::string* out_executable_name, std::vector* out_executable_data); -std::string GetGameHashIdFromImage(CDImage* cdi); -std::string GetGameIdFromImage(CDImage* cdi, bool fallback_to_hash); -std::string GetGameSerialForPath(const char* image_path, bool fallback_to_hash); +bool IsValidGameImage(CDImage* cdi); +std::string GetGameHashId(GameHash hash); +bool GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash* out_hash); DiscRegion GetRegionForSerial(std::string_view serial); DiscRegion GetRegionFromSystemArea(CDImage* cdi); DiscRegion GetRegionForImage(CDImage* cdi); @@ -177,11 +179,12 @@ u32 GetInternalFrameNumber(); void FrameDone(); void IncrementInternalFrameNumber(); -const std::string& GetRunningPath(); -const std::string& GetRunningSerial(); -const std::string& GetRunningTitle(); - +const std::string& GetDiscPath(); +const std::string& GetGameSerial(); +const std::string& GetGameTitle(); +GameHash GetGameHash(); bool IsRunningUnknownGame(); + const BIOS::ImageInfo* GetBIOSImageInfo(); const BIOS::Hash& GetBIOSHash(); diff --git a/src/core/types.h b/src/core/types.h index fa6139423..49ede88a5 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -37,6 +37,7 @@ enum class DiscRegion : u8 NTSC_U, // SCEA PAL, // SCEE Other, + NonPS1, Count }; diff --git a/src/duckstation-nogui/nogui_host.cpp b/src/duckstation-nogui/nogui_host.cpp index e0d6564e6..2d1799ae7 100644 --- a/src/duckstation-nogui/nogui_host.cpp +++ b/src/duckstation-nogui/nogui_host.cpp @@ -657,7 +657,7 @@ bool NoGUIHost::AcquireHostDisplay(RenderAPI api) Assert(!g_host_display); g_nogui_window->ExecuteInMessageLoop([api]() { - if (g_nogui_window->CreatePlatformWindow(GetWindowTitle(System::GetRunningTitle()))) + if (g_nogui_window->CreatePlatformWindow(GetWindowTitle(System::GetGameTitle()))) { const std::optional wi(g_nogui_window->GetPlatformWindowInfo()); if (wi.has_value()) diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index b7174b3fc..edc486c0d 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -1284,8 +1284,8 @@ void MainWindow::onViewGamePropertiesActionTriggered() if (!s_system_valid) return; - const std::string& path = System::GetRunningPath(); - const std::string& serial = System::GetRunningSerial(); + const std::string& path = System::GetDiscPath(); + const std::string& serial = System::GetGameSerial(); if (path.empty() || serial.empty()) return; diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 3a7fd50ad..320f3671e 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -1116,11 +1116,11 @@ void EmuThread::loadState(bool global, qint32 slot) } // shouldn't even get here if we don't have a running game - if (!global && System::GetRunningSerial().empty()) + if (!global && System::GetGameSerial().empty()) return; bootOrLoadState(global ? System::GetGlobalSaveStateFileName(slot) : - System::GetGameSaveStateFileName(System::GetRunningSerial(), slot)); + System::GetGameSaveStateFileName(System::GetGameSerial(), slot)); } void EmuThread::saveState(const QString& filename, bool block_until_done /* = false */) @@ -1147,11 +1147,11 @@ void EmuThread::saveState(bool global, qint32 slot, bool block_until_done /* = f return; } - if (!global && System::GetRunningSerial().empty()) + if (!global && System::GetGameSerial().empty()) return; System::SaveState((global ? System::GetGlobalSaveStateFileName(slot) : - System::GetGameSaveStateFileName(System::GetRunningSerial(), slot)) + System::GetGameSaveStateFileName(System::GetGameSerial(), slot)) .c_str(), g_settings.create_save_state_backups); } diff --git a/src/duckstation-qt/qtutils.cpp b/src/duckstation-qt/qtutils.cpp index 2ebdd1a96..d624a10d3 100644 --- a/src/duckstation-qt/qtutils.cpp +++ b/src/duckstation-qt/qtutils.cpp @@ -848,6 +848,7 @@ QIcon GetIconForRegion(DiscRegion region) case DiscRegion::NTSC_U: return QIcon(QStringLiteral(":/icons/flag-uc.svg")); case DiscRegion::Other: + case DiscRegion::NonPS1: default: return QIcon::fromTheme(QStringLiteral("file-unknow-line")); } diff --git a/src/frontend-common/achievements.cpp b/src/frontend-common/achievements.cpp index 74fbe3b56..940f039af 100644 --- a/src/frontend-common/achievements.cpp +++ b/src/frontend-common/achievements.cpp @@ -470,7 +470,7 @@ void Achievements::Initialize() s_logged_in = (!s_username.empty() && !s_api_token.empty()); if (System::IsValid()) - GameChanged(System::GetRunningPath(), nullptr); + GameChanged(System::GetDiscPath(), nullptr); } void Achievements::UpdateSettings(const Settings& old_config) @@ -2142,7 +2142,7 @@ void Achievements::RAIntegration::RACallbackRebuildMenu() void Achievements::RAIntegration::RACallbackEstimateTitle(char* buf) { - StringUtil::Strlcpy(buf, System::GetRunningTitle(), 256); + StringUtil::Strlcpy(buf, System::GetGameTitle(), 256); } void Achievements::RAIntegration::RACallbackResetEmulator() diff --git a/src/frontend-common/common_host.cpp b/src/frontend-common/common_host.cpp index 1bcca55de..6ceb7d9a0 100644 --- a/src/frontend-common/common_host.cpp +++ b/src/frontend-common/common_host.cpp @@ -570,8 +570,8 @@ void CommonHost::UpdateDiscordPresence(bool rich_presence_only) SmallString details_string; if (!System::IsShutdown()) { - details_string.AppendFormattedString("%s (%s)", System::GetRunningTitle().c_str(), - System::GetRunningSerial().c_str()); + details_string.AppendFormattedString("%s (%s)", System::GetGameTitle().c_str(), + System::GetGameSerial().c_str()); } else { @@ -635,7 +635,7 @@ static void HotkeyLoadStateSlot(bool global, s32 slot) if (!System::IsValid()) return; - if (!global && System::GetRunningSerial().empty()) + if (!global && System::GetGameSerial().empty()) { Host::AddKeyedOSDMessage("LoadState", TRANSLATABLE("OSDMessage", "Cannot load state for game without serial."), 5.0f); @@ -643,7 +643,7 @@ static void HotkeyLoadStateSlot(bool global, s32 slot) } std::string path(global ? System::GetGlobalSaveStateFileName(slot) : - System::GetGameSaveStateFileName(System::GetRunningSerial(), slot)); + System::GetGameSaveStateFileName(System::GetGameSerial(), slot)); if (!FileSystem::FileExists(path.c_str())) { Host::AddKeyedOSDMessage("LoadState", @@ -659,7 +659,7 @@ static void HotkeySaveStateSlot(bool global, s32 slot) if (!System::IsValid()) return; - if (!global && System::GetRunningSerial().empty()) + if (!global && System::GetGameSerial().empty()) { Host::AddKeyedOSDMessage("LoadState", TRANSLATABLE("OSDMessage", "Cannot save state for game without serial."), 5.0f); @@ -667,7 +667,7 @@ static void HotkeySaveStateSlot(bool global, s32 slot) } std::string path(global ? System::GetGlobalSaveStateFileName(slot) : - System::GetGameSaveStateFileName(System::GetRunningSerial(), slot)); + System::GetGameSaveStateFileName(System::GetGameSerial(), slot)); System::SaveState(path.c_str(), g_settings.create_save_state_backups); } diff --git a/src/frontend-common/fullscreen_ui.cpp b/src/frontend-common/fullscreen_ui.cpp index 415e8523f..c90e5c54e 100644 --- a/src/frontend-common/fullscreen_ui.cpp +++ b/src/frontend-common/fullscreen_ui.cpp @@ -654,8 +654,8 @@ void FullscreenUI::OnRunningGameChanged() if (!IsInitialized()) return; - const std::string& path = System::GetRunningPath(); - const std::string& serial = System::GetRunningSerial(); + const std::string& path = System::GetDiscPath(); + const std::string& serial = System::GetGameSerial(); if (!serial.empty()) s_current_game_subtitle = fmt::format("{0} - {1}", serial, Path::GetFileName(path)); else @@ -963,7 +963,7 @@ void FullscreenUI::DoChangeDiscFromFile() }; OpenFileSelector(ICON_FA_COMPACT_DISC " Select Disc Image", false, std::move(callback), GetDiscImageFilters(), - std::string(Path::GetDirectory(System::GetRunningPath()))); + std::string(Path::GetDirectory(System::GetDiscPath()))); } void FullscreenUI::DoChangeDisc() @@ -1010,7 +1010,7 @@ void FullscreenUI::DoCheatsMenu() { if (!System::LoadCheatListFromDatabase() || ((cl = System::GetCheatList()) == nullptr)) { - Host::AddKeyedOSDMessage("load_cheat_list", fmt::format("No cheats found for {}.", System::GetRunningTitle()), + Host::AddKeyedOSDMessage("load_cheat_list", fmt::format("No cheats found for {}.", System::GetGameTitle()), 10.0f); ReturnToMainWindow(); return; @@ -2325,14 +2325,14 @@ void FullscreenUI::SwitchToGameSettingsForSerial(const std::string_view& serial) void FullscreenUI::SwitchToGameSettings() { - if (System::GetRunningSerial().empty()) + if (System::GetGameSerial().empty()) return; auto lock = GameList::GetLock(); - const GameList::Entry* entry = GameList::GetEntryForPath(System::GetRunningPath().c_str()); + const GameList::Entry* entry = GameList::GetEntryForPath(System::GetDiscPath().c_str()); if (!entry) { - SwitchToGameSettingsForSerial(System::GetRunningSerial()); + SwitchToGameSettingsForSerial(System::GetGameSerial()); return; } @@ -4561,12 +4561,12 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type) // title info { - const std::string& title = System::GetRunningTitle(); - const std::string& serial = System::GetRunningSerial(); + const std::string& title = System::GetGameTitle(); + const std::string& serial = System::GetGameSerial(); if (!serial.empty()) buffer.Format("%s - ", serial.c_str()); - buffer.AppendString(Path::GetFileName(System::GetRunningPath())); + buffer.AppendString(Path::GetFileName(System::GetDiscPath())); const ImVec2 title_size( g_large_font->CalcTextSizeA(g_large_font->FontSize, std::numeric_limits::max(), -1.0f, title.c_str())); @@ -4624,7 +4624,7 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type) DrawShadowedText(dl, g_large_font, time_pos, IM_COL32(255, 255, 255, 255), buffer.GetCharArray(), buffer.GetCharArray() + buffer.GetLength()); - const std::string& serial = System::GetRunningSerial(); + const std::string& serial = System::GetGameSerial(); if (!serial.empty()) { const std::time_t cached_played_time = GameList::GetCachedPlayedTimeForSerial(serial); @@ -4674,7 +4674,7 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type) case PauseSubMenu::None: { // NOTE: Menu close must come first, because otherwise VM destruction options will race. - const bool has_game = System::IsValid() && !System::GetRunningSerial().empty(); + const bool has_game = System::IsValid() && !System::GetGameSerial().empty(); if (ActiveButton(ICON_FA_PLAY " Resume Game", false) || WantsToCloseMenu()) ClosePauseMenu(); @@ -4698,7 +4698,7 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type) } if (ActiveButton(ICON_FA_FROWN_OPEN " Cheat List", false, - !System::GetRunningSerial().empty() && !Achievements::ChallengeModeActive())) + !System::GetGameSerial().empty() && !Achievements::ChallengeModeActive())) { s_current_main_window = MainWindowType::None; DoCheatsMenu(); @@ -4944,7 +4944,7 @@ bool FullscreenUI::OpenSaveStateSelector(bool is_loading) s_save_state_selector_game_path = {}; s_save_state_selector_loading = is_loading; s_save_state_selector_resuming = false; - if (PopulateSaveStateListEntries(System::GetRunningTitle().c_str(), System::GetRunningSerial().c_str()) > 0) + if (PopulateSaveStateListEntries(System::GetGameTitle().c_str(), System::GetGameSerial().c_str()) > 0) { s_save_state_selector_open = true; return true; @@ -5380,7 +5380,7 @@ void FullscreenUI::DoSaveState(s32 slot, bool global) return; std::string filename(global ? System::GetGlobalSaveStateFileName(slot) : - System::GetGameSaveStateFileName(System::GetRunningSerial(), slot)); + System::GetGameSaveStateFileName(System::GetGameSerial(), slot)); System::SaveState(filename.c_str(), g_settings.create_save_state_backups); }); } @@ -6172,7 +6172,7 @@ GPUTexture* FullscreenUI::GetCoverForCurrentGame() { auto lock = GameList::GetLock(); - const GameList::Entry* entry = GameList::GetEntryForPath(System::GetRunningPath().c_str()); + const GameList::Entry* entry = GameList::GetEntryForPath(System::GetDiscPath().c_str()); if (!entry) return s_fallback_disc_texture.get(); diff --git a/src/frontend-common/game_list.cpp b/src/frontend-common/game_list.cpp index 9def6ad0e..a44d32919 100644 --- a/src/frontend-common/game_list.cpp +++ b/src/frontend-common/game_list.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "game_list.h" @@ -18,13 +18,14 @@ #include "core/psf_loader.h" #include "core/settings.h" #include "core/system.h" +#include "tinyxml2.h" #include "util/cd_image.h" #include #include #include #include #include -#include +#include #include #include Log_SetChannel(GameList); @@ -37,7 +38,7 @@ namespace GameList { enum : u32 { GAME_LIST_CACHE_SIGNATURE = 0x45434C47, - GAME_LIST_CACHE_VERSION = 32, + GAME_LIST_CACHE_VERSION = 33, PLAYED_TIME_SERIAL_LENGTH = 32, PLAYED_TIME_LAST_TIME_LENGTH = 20, // uint64 @@ -55,6 +56,8 @@ struct PlayedTimeEntry using CacheMap = UnorderedStringMap; using PlayedTimeMap = UnorderedStringMap; +static_assert(std::is_same_v); + static bool GetExeListEntry(const std::string& path, Entry* entry); static bool GetPsfListEntry(const std::string& path, Entry* entry); static bool GetDiscListEntry(const std::string& path, Entry* entry); @@ -204,8 +207,11 @@ bool GameList::GetDiscListEntry(const std::string& path, Entry* entry) entry->type = EntryType::Disc; entry->compatibility = GameDatabase::CompatibilityRating::Unknown; + std::string id; + System::GetGameDetailsFromImage(cdi.get(), &id, &entry->hash); + // try the database first - const GameDatabase::Entry* dentry = GameDatabase::GetEntryForDisc(cdi.get()); + const GameDatabase::Entry* dentry = GameDatabase::GetEntryForId(id); if (dentry) { // pull from database @@ -227,7 +233,7 @@ bool GameList::GetDiscListEntry(const std::string& path, Entry* entry) const std::string display_name(FileSystem::GetDisplayNameFromPath(path)); // no game code, so use the filename title - entry->serial = System::GetGameIdFromImage(cdi.get(), true); + entry->serial = std::move(id); entry->title = Path::GetFileTitle(display_name); entry->compatibility = GameDatabase::CompatibilityRating::Unknown; entry->release_date = 0; @@ -239,9 +245,7 @@ bool GameList::GetDiscListEntry(const std::string& path, Entry* entry) } // region detection - entry->region = System::GetRegionFromSystemArea(cdi.get()); - if (entry->region == DiscRegion::Other) - entry->region = System::GetRegionForSerial(entry->serial); + entry->region = System::GetRegionForImage(cdi.get()); if (cdi->HasSubImages()) { @@ -310,12 +314,12 @@ bool GameList::LoadEntriesFromCache(ByteStream* stream) if (!stream->ReadU8(&type) || !stream->ReadU8(®ion) || !stream->ReadSizePrefixedString(&path) || !stream->ReadSizePrefixedString(&ge.serial) || !stream->ReadSizePrefixedString(&ge.title) || !stream->ReadSizePrefixedString(&ge.genre) || !stream->ReadSizePrefixedString(&ge.publisher) || - !stream->ReadSizePrefixedString(&ge.developer) || !stream->ReadU64(&ge.total_size) || - !stream->ReadU64(reinterpret_cast(&ge.last_modified_time)) || !stream->ReadU64(&ge.release_date) || - !stream->ReadU32(&ge.supported_controllers) || !stream->ReadU8(&ge.min_players) || - !stream->ReadU8(&ge.max_players) || !stream->ReadU8(&ge.min_blocks) || !stream->ReadU8(&ge.max_blocks) || - !stream->ReadU8(&compatibility_rating) || region >= static_cast(DiscRegion::Count) || - type >= static_cast(EntryType::Count) || + !stream->ReadSizePrefixedString(&ge.developer) || !stream->ReadU64(&ge.hash) || + !stream->ReadU64(&ge.total_size) || !stream->ReadU64(reinterpret_cast(&ge.last_modified_time)) || + !stream->ReadU64(&ge.release_date) || !stream->ReadU32(&ge.supported_controllers) || + !stream->ReadU8(&ge.min_players) || !stream->ReadU8(&ge.max_players) || !stream->ReadU8(&ge.min_blocks) || + !stream->ReadU8(&ge.max_blocks) || !stream->ReadU8(&compatibility_rating) || + region >= static_cast(DiscRegion::Count) || type >= static_cast(EntryType::Count) || compatibility_rating >= static_cast(GameDatabase::CompatibilityRating::Count)) { Log_WarningPrintf("Game list cache entry is corrupted"); @@ -348,6 +352,7 @@ bool GameList::WriteEntryToCache(const Entry* entry) result &= s_cache_write_stream->WriteSizePrefixedString(entry->genre); result &= s_cache_write_stream->WriteSizePrefixedString(entry->publisher); result &= s_cache_write_stream->WriteSizePrefixedString(entry->developer); + result &= s_cache_write_stream->WriteU64(entry->hash); result &= s_cache_write_stream->WriteU64(entry->total_size); result &= s_cache_write_stream->WriteU64(entry->last_modified_time); result &= s_cache_write_stream->WriteU64(entry->release_date); diff --git a/src/frontend-common/game_list.h b/src/frontend-common/game_list.h index e075d174a..61c69a99e 100644 --- a/src/frontend-common/game_list.h +++ b/src/frontend-common/game_list.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #pragma once @@ -37,6 +37,7 @@ struct Entry std::string genre; std::string publisher; std::string developer; + u64 hash = 0; u64 total_size = 0; std::time_t last_modified_time = 0; std::time_t last_played_time = 0; diff --git a/src/frontend-common/imgui_overlays.cpp b/src/frontend-common/imgui_overlays.cpp index 0a8c2d368..1cf32a698 100644 --- a/src/frontend-common/imgui_overlays.cpp +++ b/src/frontend-common/imgui_overlays.cpp @@ -621,11 +621,11 @@ void SaveStateSelectorUI::RefreshList() if (System::IsShutdown()) return; - if (!System::GetRunningSerial().empty()) + if (!System::GetGameSerial().empty()) { for (s32 i = 1; i <= System::PER_GAME_SAVE_STATE_SLOTS; i++) { - std::string path(System::GetGameSaveStateFileName(System::GetRunningSerial(), i)); + std::string path(System::GetGameSaveStateFileName(System::GetGameSerial(), i)); std::optional ssi = System::GetExtendedSaveStateInfo(path.c_str()); ListEntry li; diff --git a/src/util/iso_reader.cpp b/src/util/iso_reader.cpp index 5ba6ff67a..2797ed474 100644 --- a/src/util/iso_reader.cpp +++ b/src/util/iso_reader.cpp @@ -293,3 +293,9 @@ bool ISOReader::ReadFile(const char* path, std::vector* data) data->resize(de->length_le); return true; } + +bool ISOReader::FileExists(const char* path) +{ + auto de = LocateFile(path); + return de.has_value(); +} diff --git a/src/util/iso_reader.h b/src/util/iso_reader.h index d045e25c2..c757869f3 100644 --- a/src/util/iso_reader.h +++ b/src/util/iso_reader.h @@ -143,6 +143,7 @@ public: std::vector GetFilesInDirectory(const char* path); bool ReadFile(const char* path, std::vector* data); + bool FileExists(const char* path); private: bool ReadPVD();