diff --git a/README.md b/README.md index 4acaecab4..a904c7765 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,8 @@ For example, if your disc image was named `Spyro3.cue`, you would place the SBI CHD images with built-in subchannel information are also supported. +If you are playing directly from a disc and your CD/DVD drive does not support subchannel reading, or has a skew with the returned SubQ, you can place the SBI file in the `subchannel` directory under the user directory, with the serial or title of the game. + ### Cheats and patch database DuckStation ships with a built-in cheat and patch database, both provided by the community. Contributions to these are welcome at https://github.com/duckstation/chtdb. diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d0c339cc9..ee052d70a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -14,6 +14,8 @@ add_library(core cdrom.h cdrom_async_reader.cpp cdrom_async_reader.h + cdrom_subq_replacement.cpp + cdrom_subq_replacement.h cheats.cpp cheats.h controller.cpp diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index 3ccf7c78f..613fc4ff9 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -3,6 +3,7 @@ #include "cdrom.h" #include "cdrom_async_reader.h" +#include "cdrom_subq_replacement.h" #include "dma.h" #include "host.h" #include "host_interface_progress_callback.h" @@ -279,11 +280,13 @@ static_assert(sizeof(XA_ADPCMBlockHeader) == 1, "XA-ADPCM block header is one by static TickCount SoftReset(TickCount ticks_late); +static const CDImage::SubChannelQ& GetSectorSubQ(u32 lba, const CDImage::SubChannelQ& real_subq); +static bool CanReadMedia(); + static bool IsDriveIdle(); static bool IsMotorOn(); static bool IsSeeking(); static bool IsReadingOrPlaying(); -static bool CanReadMedia(); static bool HasPendingCommand(); static bool HasPendingInterrupt(); static bool HasPendingAsyncInterrupt(); @@ -383,6 +386,8 @@ struct CDROMState &CDROM::DeliverAsyncInterrupt, nullptr}; TimingEvent drive_event{"CDROM Drive Event", 1, 1, &CDROM::ExecuteDrive, nullptr}; + std::unique_ptr subq_replacement; + GlobalTicks subq_lba_update_tick = 0; GlobalTicks last_interrupt_time = 0; @@ -818,9 +823,19 @@ bool CDROM::HasMedia() return s_reader.HasMedia(); } -const std::string& CDROM::GetMediaFileName() +const std::string& CDROM::GetMediaPath() { - return s_reader.GetMediaFileName(); + return s_reader.GetMediaPath(); +} + +u32 CDROM::GetCurrentSubImage() +{ + return s_reader.HasMedia() ? s_reader.GetMedia()->GetCurrentSubImage() : 0; +} + +bool CDROM::HasNonStandardOrReplacementSubQ() +{ + return ((s_reader.HasMedia() ? s_reader.GetMedia()->HasSubchannelData() : false) || s_state.subq_replacement); } const CDImage* CDROM::GetMedia() @@ -884,14 +899,21 @@ bool CDROM::CanReadMedia() return (s_state.drive_state != DriveState::ShellOpening && s_reader.HasMedia()); } -void CDROM::InsertMedia(std::unique_ptr media, DiscRegion region) +bool CDROM::InsertMedia(std::unique_ptr media, DiscRegion region, std::string_view serial, + std::string_view title, Error* error) { + // Load SBI/LSD first. + std::unique_ptr subq; + if (!media->HasSubchannelData() && !CDROMSubQReplacement::LoadForImage(&subq, media.get(), serial, title, error)) + return false; + if (CanReadMedia()) RemoveMedia(true); INFO_LOG("Inserting new media, disc region: {}, console region: {}", Settings::GetDiscRegionName(region), Settings::GetConsoleRegionName(System::GetRegion())); + s_state.subq_replacement = std::move(subq); s_state.disc_region = region; s_reader.SetMedia(std::move(media)); SetHoldPosition(0, true); @@ -902,12 +924,14 @@ void CDROM::InsertMedia(std::unique_ptr media, DiscRegion region) if (s_state.show_current_file) CreateFileMap(); + + return true; } std::unique_ptr CDROM::RemoveMedia(bool for_disc_swap) { if (!HasMedia()) - return nullptr; + return {}; // Add an additional two seconds to the disc swap, some games don't like it happening too quickly. TickCount stop_ticks = GetTicksForStop(true); @@ -926,6 +950,7 @@ std::unique_ptr CDROM::RemoveMedia(bool for_disc_swap) s_state.secondary_status.shell_open = true; s_state.secondary_status.ClearActiveBits(); s_state.disc_region = DiscRegion::NonPS1; + s_state.subq_replacement.reset(); // If the drive was doing anything, we need to abort the command. ClearDriveState(); @@ -956,7 +981,7 @@ bool CDROM::PrecacheMedia() { Host::AddOSDMessage( fmt::format(TRANSLATE_FS("OSDMessage", "CD image preloading not available for multi-disc image '{}'"), - FileSystem::GetDisplayNameFromPath(s_reader.GetMedia()->GetFileName())), + FileSystem::GetDisplayNameFromPath(s_reader.GetMedia()->GetPath())), Host::OSD_ERROR_DURATION); return false; } @@ -972,6 +997,17 @@ bool CDROM::PrecacheMedia() return true; } +const CDImage::SubChannelQ& CDROM::GetSectorSubQ(u32 lba, const CDImage::SubChannelQ& real_subq) +{ + if (s_state.subq_replacement) + { + const CDImage::SubChannelQ* replacement_subq = s_state.subq_replacement->GetReplacementSubQ(lba); + return replacement_subq ? *replacement_subq : real_subq; + } + + return real_subq; +} + TinyString CDROM::LBAToMSFString(CDImage::LBA lba) { const auto pos = CDImage::Position::FromLBA(lba); @@ -2721,10 +2757,12 @@ void CDROM::UpdateSubQPositionWhileSeeking() current_lba, completed_frac); // access the image directly since we want to preserve the cached data for the seek complete - CDImage::SubChannelQ subq; - if (!s_reader.ReadSectorUncached(current_lba, &subq, nullptr)) + CDImage::SubChannelQ real_subq = {}; + if (!s_reader.ReadSectorUncached(current_lba, &real_subq, nullptr)) ERROR_LOG("Failed to read subq for sector {} for subq position", current_lba); - else if (subq.IsCRCValid()) + + const CDImage::SubChannelQ& subq = GetSectorSubQ(current_lba, real_subq); + if (subq.IsCRCValid()) s_state.last_subq = subq; s_state.current_lba = current_lba; @@ -2776,14 +2814,15 @@ void CDROM::UpdateSubQPosition(bool update_logical) { s_state.current_subq_lba = new_subq_lba; - CDImage::SubChannelQ subq; + CDImage::SubChannelQ real_subq = {}; CDROMAsyncReader::SectorBuffer raw_sector; - if (!s_reader.ReadSectorUncached(new_subq_lba, &subq, update_logical ? &raw_sector : nullptr)) + if (!s_reader.ReadSectorUncached(new_subq_lba, &real_subq, update_logical ? &raw_sector : nullptr)) { ERROR_LOG("Failed to read subq for sector {} for subq position", new_subq_lba); } else { + const CDImage::SubChannelQ& subq = GetSectorSubQ(new_subq_lba, real_subq); if (subq.IsCRCValid()) s_state.last_subq = subq; @@ -2801,10 +2840,12 @@ void CDROM::SetHoldPosition(CDImage::LBA lba, bool update_subq) { if (update_subq && s_state.current_subq_lba != lba && CanReadMedia()) { - CDImage::SubChannelQ subq; - if (!s_reader.ReadSectorUncached(lba, &subq, nullptr)) + CDImage::SubChannelQ real_subq = {}; + if (!s_reader.ReadSectorUncached(lba, &real_subq, nullptr)) ERROR_LOG("Failed to read subq for sector {} for subq position", lba); - else if (subq.IsCRCValid()) + + const CDImage::SubChannelQ& subq = GetSectorSubQ(lba, real_subq); + if (subq.IsCRCValid()) s_state.last_subq = subq; } @@ -2831,13 +2872,13 @@ bool CDROM::CompleteSeek() bool seek_okay = s_reader.WaitForReadToComplete(); if (seek_okay) { - const CDImage::SubChannelQ& subq = s_reader.GetSectorSubQ(); + const CDImage::SubChannelQ& subq = GetSectorSubQ(s_reader.GetLastReadSector(), s_reader.GetSectorSubQ()); if (subq.IsCRCValid()) { // seek and update sub-q for ReadP command s_state.last_subq = subq; const auto [seek_mm, seek_ss, seek_ff] = CDImage::Position::FromLBA(s_reader.GetLastReadSector()).ToBCD(); - seek_okay = (subq.IsCRCValid() && subq.absolute_minute_bcd == seek_mm && subq.absolute_second_bcd == seek_ss && + seek_okay = (subq.absolute_minute_bcd == seek_mm && subq.absolute_second_bcd == seek_ss && subq.absolute_frame_bcd == seek_ff); if (seek_okay) { @@ -3064,7 +3105,7 @@ void CDROM::DoSectorRead() s_state.secondary_status.SetReadingBits(s_state.drive_state == DriveState::Playing); - const CDImage::SubChannelQ& subq = s_reader.GetSectorSubQ(); + const CDImage::SubChannelQ& subq = GetSectorSubQ(s_state.current_lba, s_reader.GetSectorSubQ()); const bool subq_valid = subq.IsCRCValid(); if (subq_valid) { @@ -3128,8 +3169,7 @@ void CDROM::DoSectorRead() else if (subq.track_number_bcd != s_state.play_track_number_bcd) { // we don't want to update the position if the track changes, so we check it before reading the actual sector. - DEV_LOG("Auto pause at the start of track {:02x} (LBA {})", s_state.last_subq.track_number_bcd, - s_state.current_lba); + DEV_LOG("Auto pause at the start of track {:02x} (LBA {})", subq.track_number_bcd, s_state.current_lba); StopReadingWithDataEnd(); return; } @@ -3759,7 +3799,7 @@ void CDROM::CreateFileMap() return; } - DEV_LOG("Creating file map for {}...", media->GetFileName()); + DEV_LOG("Creating file map for {}...", media->GetPath()); s_state.file_map.emplace(iso.GetPVDLBA(), std::make_pair(iso.GetPVDLBA(), std::string("PVD"))); CreateFileMap(iso, std::string_view()); DEV_LOG("Found {} files", s_state.file_map.size()); @@ -3825,13 +3865,12 @@ void CDROM::DrawDebugWindow(float scale) if (media->HasSubImages()) { - ImGui::Text("Filename: %s [Subimage %u of %u] [%u buffered sectors]", media->GetFileName().c_str(), + ImGui::Text("Filename: %s [Subimage %u of %u] [%u buffered sectors]", media->GetPath().c_str(), media->GetCurrentSubImage() + 1u, media->GetSubImageCount(), s_reader.GetBufferedSectorCount()); } else { - ImGui::Text("Filename: %s [%u buffered sectors]", media->GetFileName().c_str(), - s_reader.GetBufferedSectorCount()); + ImGui::Text("Filename: %s [%u buffered sectors]", media->GetPath().c_str(), s_reader.GetBufferedSectorCount()); } ImGui::Text("Disc Position: MSF[%02u:%02u:%02u] LBA[%u]", disc_position.minute, disc_position.second, diff --git a/src/core/cdrom.h b/src/core/cdrom.h index 893d25b69..04e8854e3 100644 --- a/src/core/cdrom.h +++ b/src/core/cdrom.h @@ -2,11 +2,15 @@ // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #pragma once + #include "types.h" + #include #include #include +#include +class Error; class CDImage; class StateWrapper; @@ -18,16 +22,19 @@ void Reset(); bool DoState(StateWrapper& sw); bool HasMedia(); -const std::string& GetMediaFileName(); +const std::string& GetMediaPath(); +u32 GetCurrentSubImage(); const CDImage* GetMedia(); DiscRegion GetDiscRegion(); bool IsMediaPS1Disc(); bool IsMediaAudioCD(); bool DoesMediaRegionMatchConsole(); -void InsertMedia(std::unique_ptr media, DiscRegion region); +bool InsertMedia(std::unique_ptr media, DiscRegion region, std::string_view serial, std::string_view title, + Error* error); std::unique_ptr RemoveMedia(bool for_disc_swap); bool PrecacheMedia(); +bool HasNonStandardOrReplacementSubQ(); void CPUClockChanged(); diff --git a/src/core/cdrom_async_reader.h b/src/core/cdrom_async_reader.h index 5e93405b9..efebc41ec 100644 --- a/src/core/cdrom_async_reader.h +++ b/src/core/cdrom_async_reader.h @@ -37,7 +37,7 @@ public: bool HasMedia() const { return static_cast(m_media); } const CDImage* GetMedia() const { return m_media.get(); } CDImage* GetMedia() { return m_media.get(); } - const std::string& GetMediaFileName() const { return m_media->GetFileName(); } + const std::string& GetMediaPath() const { return m_media->GetPath(); } bool IsUsingThread() const { return m_read_thread.joinable(); } void StartThread(u32 readahead_count = 8); diff --git a/src/core/cdrom_subq_replacement.cpp b/src/core/cdrom_subq_replacement.cpp new file mode 100644 index 000000000..0def0659e --- /dev/null +++ b/src/core/cdrom_subq_replacement.cpp @@ -0,0 +1,204 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#include "cdrom_subq_replacement.h" +#include "settings.h" + +#include "common/error.h" +#include "common/file_system.h" +#include "common/log.h" +#include "common/path.h" +#include "common/small_string.h" + +#include +#include +#include + +LOG_CHANNEL(CDROM); + +#pragma pack(push, 1) +struct SBIFileEntry +{ + u8 minute_bcd; + u8 second_bcd; + u8 frame_bcd; + u8 type; + u8 data[10]; +}; +struct LSDFileEntry +{ + u8 minute_bcd; + u8 second_bcd; + u8 frame_bcd; + u8 data[12]; +}; +static_assert(sizeof(LSDFileEntry) == 15); +#pragma pack(pop) + +CDROMSubQReplacement::CDROMSubQReplacement() = default; + +CDROMSubQReplacement::~CDROMSubQReplacement() = default; + +std::unique_ptr CDROMSubQReplacement::LoadSBI(const std::string& path, Error* error) +{ + auto fp = FileSystem::OpenManagedCFile(path.c_str(), "rb", error); + if (!fp) + return {}; + + static constexpr char expected_header[] = {'S', 'B', 'I', '\0'}; + + char header[4]; + if (std::fread(header, sizeof(header), 1, fp.get()) != 1 || std::memcmp(header, expected_header, sizeof(header)) != 0) + { + Error::SetStringFmt(error, "Invalid header in '{}'", Path::GetFileName(path)); + return {}; + } + + std::unique_ptr ret = std::make_unique(); + + SBIFileEntry entry; + while (std::fread(&entry, sizeof(entry), 1, fp.get()) == 1) + { + if (!IsValidPackedBCD(entry.minute_bcd) || !IsValidPackedBCD(entry.second_bcd) || + !IsValidPackedBCD(entry.frame_bcd)) + { + Error::SetStringFmt(error, "Invalid position [{:02x}:{:02x}:{:02x}] in '{}'", entry.minute_bcd, entry.second_bcd, + entry.frame_bcd, Path::GetFileName(path)); + return {}; + } + + if (entry.type != 1) + { + Error::SetStringFmt(error, "Invalid type 0x{:02X} in '{}'", entry.type, Path::GetFileName(path)); + return {}; + } + + const u32 lba = CDImage::Position::FromBCD(entry.minute_bcd, entry.second_bcd, entry.frame_bcd).ToLBA(); + + CDImage::SubChannelQ subq; + std::memcpy(subq.data.data(), entry.data, sizeof(entry.data)); + + // generate an invalid crc by flipping all bits from the valid crc (will never collide) + const u16 crc = subq.ComputeCRC(subq.data) ^ 0xFFFF; + subq.data[10] = Truncate8(crc); + subq.data[11] = Truncate8(crc >> 8); + + ret->m_replacement_subq.emplace(lba, subq); + } + + INFO_LOG("Loaded {} replacement sectors from SBI '{}'", ret->m_replacement_subq.size(), Path::GetFileName(path)); + return ret; +} + +std::unique_ptr CDROMSubQReplacement::LoadLSD(const std::string& path, Error* error) +{ + auto fp = FileSystem::OpenManagedCFile(path.c_str(), "rb", error); + if (!fp) + return {}; + + std::unique_ptr ret = std::make_unique(); + + LSDFileEntry entry; + while (std::fread(&entry, sizeof(entry), 1, fp.get()) == 1) + { + if (!IsValidPackedBCD(entry.minute_bcd) || !IsValidPackedBCD(entry.second_bcd) || + !IsValidPackedBCD(entry.frame_bcd)) + { + Error::SetStringFmt(error, "Invalid position [{:02x}:{:02x}:{:02x}] in '{}'", entry.minute_bcd, entry.second_bcd, + entry.frame_bcd, Path::GetFileName(path)); + return {}; + } + + const u32 lba = CDImage::Position::FromBCD(entry.minute_bcd, entry.second_bcd, entry.frame_bcd).ToLBA(); + + CDImage::SubChannelQ subq; + std::memcpy(subq.data.data(), entry.data, sizeof(entry.data)); + + DEBUG_LOG("{:02x}:{:02x}:{:02x}: CRC {}", entry.minute_bcd, entry.second_bcd, entry.frame_bcd, + subq.IsCRCValid() ? "VALID" : "INVALID"); + ret->m_replacement_subq.emplace(lba, subq); + } + + INFO_LOG("Loaded {} replacement sectors from LSD '{}'", ret->m_replacement_subq.size(), path); + return ret; +} + +bool CDROMSubQReplacement::LoadForImage(std::unique_ptr* ret, CDImage* image, std::string_view serial, + std::string_view title, Error* error) +{ + struct FileLoader + { + const char* extension; + std::unique_ptr (*func)(const std::string&, Error*); + }; + static constexpr const FileLoader loaders[] = { + {"sbi", &CDROMSubQReplacement::LoadSBI}, + {"lsd", &CDROMSubQReplacement::LoadLSD}, + }; + + const std::string& image_path = image->GetPath(); + std::string path; + + // Try sbi/lsd in the directory first. + for (const FileLoader& loader : loaders) + { + path = Path::ReplaceExtension(image_path, loader.extension); + if (FileSystem::FileExists(path.c_str())) + { + *ret = loader.func(path, error); + return static_cast(*ret); + } + } + + // For subimages, we need to check the suffix too. + if (image->HasSubImages()) + { + for (const FileLoader& loader : loaders) + { + path = Path::BuildRelativePath(image_path, + SmallString::from_format("{}_{}.{}", Path::GetFileName(image_path), + image->GetCurrentSubImage() + 1, loader.extension)); + if (FileSystem::FileExists(path.c_str())) + { + *ret = loader.func(path, error); + return static_cast(*ret); + } + } + } + + // If this fails, try the subchannel directory with serial/title. + if (!serial.empty()) + { + for (const FileLoader& loader : loaders) + { + path = Path::Combine(EmuFolders::Subchannels, TinyString::from_format("{}.{}", serial, loader.extension)); + if (FileSystem::FileExists(path.c_str())) + { + *ret = loader.func(path, error); + return static_cast(*ret); + } + } + } + + if (!title.empty()) + { + for (const FileLoader& loader : loaders) + { + path = Path::Combine(EmuFolders::Subchannels, TinyString::from_format("{}.{}", title, loader.extension)); + if (FileSystem::FileExists(path.c_str())) + { + *ret = loader.func(path, error); + return static_cast(*ret); + } + } + } + + // Nothing. + return true; +} + +const CDImage::SubChannelQ* CDROMSubQReplacement::GetReplacementSubQ(u32 lba) const +{ + const auto iter = m_replacement_subq.find(lba); + return (iter != m_replacement_subq.end()) ? &iter->second : nullptr; +} diff --git a/src/core/cdrom_subq_replacement.h b/src/core/cdrom_subq_replacement.h new file mode 100644 index 000000000..e4aae425a --- /dev/null +++ b/src/core/cdrom_subq_replacement.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#pragma once + +#include "util/cd_image.h" + +#include + +class CDROMSubQReplacement +{ +public: + CDROMSubQReplacement(); + ~CDROMSubQReplacement(); + + // NOTE: Can return true if no sbi is available, false means load/parse error. + static bool LoadForImage(std::unique_ptr* ret, CDImage* image, std::string_view serial, + std::string_view title, Error* error); + + size_t GetReplacementSectorCount() const { return m_replacement_subq.size(); } + + /// Returns the replacement subchannel data for the specified sector. + const CDImage::SubChannelQ* GetReplacementSubQ(u32 lba) const; + +private: + using ReplacementMap = std::unordered_map; + + static std::unique_ptr LoadSBI(const std::string& path, Error* error); + static std::unique_ptr LoadLSD(const std::string& path, Error* error); + + ReplacementMap m_replacement_subq; +}; diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index 14819076d..1aa26de27 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -9,6 +9,7 @@ + @@ -95,6 +96,7 @@ + diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index 53e35e9dd..38904fbac 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -69,6 +69,7 @@ + @@ -144,6 +145,7 @@ + diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 2a7fff5da..0144056e5 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -2142,6 +2142,7 @@ std::string EmuFolders::Resources; std::string EmuFolders::SaveStates; std::string EmuFolders::Screenshots; std::string EmuFolders::Shaders; +std::string EmuFolders::Subchannels; std::string EmuFolders::Textures; std::string EmuFolders::UserResources; std::string EmuFolders::Videos; @@ -2160,6 +2161,7 @@ void EmuFolders::SetDefaults() SaveStates = Path::Combine(DataRoot, "savestates"); Screenshots = Path::Combine(DataRoot, "screenshots"); Shaders = Path::Combine(DataRoot, "shaders"); + Subchannels = Path::Combine(DataRoot, "subchannels"); Textures = Path::Combine(DataRoot, "textures"); UserResources = Path::Combine(DataRoot, "resources"); Videos = Path::Combine(DataRoot, "videos"); @@ -2191,6 +2193,7 @@ void EmuFolders::LoadConfig(SettingsInterface& si) SaveStates = LoadPathFromSettings(si, DataRoot, "Folders", "SaveStates", "savestates"); Screenshots = LoadPathFromSettings(si, DataRoot, "Folders", "Screenshots", "screenshots"); Shaders = LoadPathFromSettings(si, DataRoot, "Folders", "Shaders", "shaders"); + Subchannels = LoadPathFromSettings(si, DataRoot, "Folders", "Subchannels", "subchannels"); Textures = LoadPathFromSettings(si, DataRoot, "Folders", "Textures", "textures"); UserResources = LoadPathFromSettings(si, DataRoot, "Folders", "UserResources", "resources"); Videos = LoadPathFromSettings(si, DataRoot, "Folders", "Videos", "videos"); @@ -2208,6 +2211,7 @@ void EmuFolders::LoadConfig(SettingsInterface& si) DEV_LOG("SaveStates Directory: {}", SaveStates); DEV_LOG("Screenshots Directory: {}", Screenshots); DEV_LOG("Shaders Directory: {}", Shaders); + DEV_LOG("Subchannels Directory: {}", Subchannels); DEV_LOG("Textures Directory: {}", Textures); DEV_LOG("User Resources Directory: {}", UserResources); DEV_LOG("Videos Directory: {}", Videos); @@ -2228,6 +2232,7 @@ void EmuFolders::Save(SettingsInterface& si) si.SetStringValue("Folders", "SaveStates", Path::MakeRelative(SaveStates, DataRoot).c_str()); si.SetStringValue("Folders", "Screenshots", Path::MakeRelative(Screenshots, DataRoot).c_str()); si.SetStringValue("Folders", "Shaders", Path::MakeRelative(Shaders, DataRoot).c_str()); + si.SetStringValue("Folders", "Subchannels", Path::MakeRelative(Subchannels, DataRoot).c_str()); si.SetStringValue("Folders", "Textures", Path::MakeRelative(Textures, DataRoot).c_str()); si.SetStringValue("Folders", "UserResources", Path::MakeRelative(UserResources, DataRoot).c_str()); si.SetStringValue("Folders", "Videos", Path::MakeRelative(Videos, DataRoot).c_str()); @@ -2275,6 +2280,7 @@ bool EmuFolders::EnsureFoldersExist() result = FileSystem::EnsureDirectoryExists( Path::Combine(Shaders, "reshade" FS_OSPATH_SEPARATOR_STR "Textures").c_str(), false) && result; + result = FileSystem::EnsureDirectoryExists(Subchannels.c_str(), false) && result; result = FileSystem::EnsureDirectoryExists(Textures.c_str(), false) && result; result = FileSystem::EnsureDirectoryExists(UserResources.c_str(), false) && result; result = FileSystem::EnsureDirectoryExists(Videos.c_str(), false) && result; diff --git a/src/core/settings.h b/src/core/settings.h index f99113e74..1f82f8fd5 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -578,6 +578,7 @@ extern std::string Resources; extern std::string SaveStates; extern std::string Screenshots; extern std::string Shaders; +extern std::string Subchannels; extern std::string Textures; extern std::string UserResources; extern std::string Videos; diff --git a/src/core/system.cpp b/src/core/system.cpp index 029a78a82..840e32bee 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -157,7 +157,8 @@ static void WarnAboutStateTaints(u32 state_taints); static void WarnAboutUnsafeSettings(); static void LogUnsafeSettingsToConsole(const SmallStringBase& messages); -static bool Initialize(bool force_software_renderer, bool fullscreen, Error* error); +static bool Initialize(std::unique_ptr disc, DiscRegion disc_region, bool force_software_renderer, + bool fullscreen, Error* error); static bool LoadBIOS(Error* error); static bool SetBootMode(BootMode new_boot_mode, DiscRegion disc_region, Error* error); static void InternalReset(); @@ -187,7 +188,7 @@ static void ResetPerformanceCounters(); static bool UpdateGameSettingsLayer(); static void UpdateRunningGame(const std::string_view path, CDImage* image, bool booting); -static bool CheckForSBIFile(CDImage* image, Error* error); +static bool CheckForRequiredSubQ(Error* error); static void UpdateControllers(); static void ResetControllers(); @@ -1810,7 +1811,7 @@ 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); + UpdateRunningGame(disc ? disc->GetPath().c_str() : parameters.filename.c_str(), disc.get(), true); // Get boot EXE override. if (!parameters.override_exe.empty()) @@ -1828,13 +1829,6 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) exe_override = std::move(parameters.override_exe); } - // Check for SBI. - if (!CheckForSBIFile(disc.get(), error)) - { - DestroySystem(); - return false; - } - // Check for resuming with hardcore mode. if (parameters.disable_achievements_hardcore_mode) Achievements::DisableHardcoreMode(); @@ -1871,25 +1865,16 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) } } - // Load BIOS image. - if (!SetBootMode(boot_mode, disc_region, error)) + // Load BIOS image, component setup, check for subchannel in games that need it. + if (!SetBootMode(boot_mode, disc_region, error) || + !Initialize(std::move(disc), disc_region, parameters.force_software_renderer, + parameters.override_fullscreen.value_or(ShouldStartFullscreen()), error) || + !CheckForRequiredSubQ(error)) { DestroySystem(); return false; } - // Component setup. - if (!Initialize(parameters.force_software_renderer, parameters.override_fullscreen.value_or(ShouldStartFullscreen()), - error)) - { - DestroySystem(); - return false; - } - - // Insert disc. - if (disc) - CDROM::InsertMedia(std::move(disc), disc_region); - s_exe_override = std::move(exe_override); UpdateControllers(); @@ -1944,7 +1929,8 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) return true; } -bool System::Initialize(bool force_software_renderer, bool fullscreen, Error* error) +bool System::Initialize(std::unique_ptr disc, DiscRegion disc_region, bool force_software_renderer, + bool fullscreen, Error* error) { g_ticks_per_second = ScaleTicksToOverclock(MASTER_CLOCK); s_max_slice_ticks = ScaleTicksToOverclock(MASTER_CLOCK / 10); @@ -1993,6 +1979,11 @@ bool System::Initialize(bool force_software_renderer, bool fullscreen, Error* er Bus::Initialize(); CPU::Initialize(); + CDROM::Initialize(); + + // CDROM before GPU, that way we don't modeswitch. + if (disc && !CDROM::InsertMedia(std::move(disc), disc_region, s_running_game_serial, s_running_game_title, error)) + return false; if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer, false, fullscreen, error)) return false; @@ -2004,10 +1995,12 @@ bool System::Initialize(bool force_software_renderer, bool fullscreen, Error* er // Was startup cancelled? (e.g. shading compilers took too long and the user closed the application) if (IsStartupCancelled()) + { + Error::SetStringView(error, TRANSLATE_SV("System", "Startup was cancelled.")); return false; + } DMA::Initialize(); - CDROM::Initialize(); Pad::Initialize(); Timers::Initialize(); SPU::Initialize(); @@ -2810,71 +2803,57 @@ bool System::LoadStateFromBuffer(const SaveStateBuffer& buffer, Error* error, bo { Assert(IsValid()); - std::unique_ptr media; - std::unique_ptr old_media = CDROM::RemoveMedia(false); std::string_view media_path = buffer.media_path; - u32 media_subimage_index = buffer.media_subimage_index; - if (old_media && old_media->GetFileName() == buffer.media_path) + u32 media_subimage_index = (buffer.version >= 51) ? buffer.media_subimage_index : 0; + if (!buffer.media_path.empty()) { - INFO_LOG("Re-using same media '{}'", buffer.media_path); - media = std::move(old_media); - } - else if (!buffer.media_path.empty()) - { - Error local_error; - media = CDImage::Open(buffer.media_path.c_str(), g_settings.cdrom_load_image_patches, error ? error : &local_error); - if (!media) + if (CDROM::HasMedia() && CDROM::GetMediaPath() == buffer.media_path && + CDROM::GetCurrentSubImage() == media_subimage_index) { - if (old_media) - { - Host::AddOSDMessage( - fmt::format(TRANSLATE_FS("OSDMessage", "Failed to open CD image from save state '{}': {}.\nUsing " - "existing image '{}', this may result in instability."), - buffer.media_path, error ? error->GetDescription() : local_error.GetDescription(), - old_media->GetFileName()), - Host::OSD_CRITICAL_ERROR_DURATION); - media = std::move(old_media); - media_path = media->GetFileName(); - media_subimage_index = media->GetCurrentSubImage(); - } - else - { - Error::AddPrefixFmt(error, TRANSLATE_FS("System", "Failed to open CD image '{}' used by save state:\n"), - Path::GetFileName(buffer.media_path)); - return false; - } - } - } - - if (media && buffer.version >= 51) - { - const u32 num_subimages = media->HasSubImages() ? media->GetSubImageCount() : 1; - if (media_subimage_index >= num_subimages || - (media->HasSubImages() && media->GetCurrentSubImage() != media_subimage_index && - !media->SwitchSubImage(media_subimage_index, error))) - { - Error::AddPrefixFmt( - error, TRANSLATE_FS("System", "Failed to switch to subimage {} in CD image '{}' used by save state:\n"), - media_subimage_index + 1u, Path::GetFileName(media_path)); - return false; + INFO_LOG("Re-using same media '{}'", CDROM::GetMediaPath()); } else { - INFO_LOG("Switched to subimage {} in '{}'", media_subimage_index, buffer.media_path.c_str()); + // needs new image + Error local_error; + std::unique_ptr new_disc = + CDImage::Open(buffer.media_path.c_str(), g_settings.cdrom_load_image_patches, error ? error : &local_error); + const DiscRegion new_disc_region = + new_disc ? GameList::GetCustomRegionForPath(buffer.media_path).value_or(GetRegionForImage(new_disc.get())) : + DiscRegion::NonPS1; + if (!new_disc || + (media_subimage_index != 0 && new_disc->HasSubImages() && + !new_disc->SwitchSubImage(media_subimage_index, error ? error : &local_error)) || + (UpdateRunningGame(buffer.media_path, new_disc.get(), false), + !CDROM::InsertMedia(std::move(new_disc), new_disc_region, s_running_game_serial, s_running_game_title, + error ? error : &local_error))) + { + if (CDROM::HasMedia()) + { + Host::AddOSDMessage( + fmt::format(TRANSLATE_FS("OSDMessage", "Failed to open CD image from save state '{}': {}.\nUsing " + "existing image '{}', this may result in instability."), + buffer.media_path, error ? error->GetDescription() : local_error.GetDescription(), + Path::GetFileName(CDROM::GetMediaPath())), + Host::OSD_CRITICAL_ERROR_DURATION); + } + else + { + Error::AddPrefixFmt(error, TRANSLATE_FS("System", "Failed to open CD image '{}' used by save state:\n"), + Path::GetFileName(buffer.media_path)); + return false; + } + } + else if (g_settings.cdrom_load_image_to_ram) + { + CDROM::PrecacheMedia(); + } } } - - // Skip updating media if there is none, and none in the state. That way we don't wipe out EXE boot. - if (media) - UpdateRunningGame(media_path, media.get(), false); - - CDROM::Reset(); - if (media) + else { - const DiscRegion region = GameList::GetCustomRegionForPath(media_path).value_or(GetRegionForImage(media.get())); - CDROM::InsertMedia(std::move(media), region); - if (g_settings.cdrom_load_image_to_ram) - CDROM::PrecacheMedia(); + // state has no disc + CDROM::RemoveMedia(false); } // ensure the correct card is loaded @@ -3141,7 +3120,7 @@ bool System::SaveStateToBuffer(SaveStateBuffer* buffer, Error* error, u32 screen if (CDROM::HasMedia()) { - buffer->media_path = CDROM::GetMediaFileName(); + buffer->media_path = CDROM::GetMediaPath(); buffer->media_subimage_index = CDROM::GetMedia()->HasSubImages() ? CDROM::GetMedia()->GetCurrentSubImage() : 0; } @@ -4076,7 +4055,7 @@ std::string System::GetMediaFileName() if (!CDROM::HasMedia()) return {}; - return CDROM::GetMediaFileName(); + return CDROM::GetMediaPath(); } bool System::InsertMedia(const char* path) @@ -4086,7 +4065,10 @@ bool System::InsertMedia(const char* path) Error error; std::unique_ptr image = CDImage::Open(path, g_settings.cdrom_load_image_patches, &error); - if (!image) + const DiscRegion region = + image ? GameList::GetCustomRegionForPath(path).value_or(GetRegionForImage(image.get())) : DiscRegion::NonPS1; + if (!image || (UpdateRunningGame(path, image.get(), false), + !CDROM::InsertMedia(std::move(image), region, s_running_game_serial, s_running_game_title, &error))) { Host::AddIconOSDWarning( "DiscInserted", ICON_FA_COMPACT_DISC, @@ -4095,9 +4077,6 @@ bool System::InsertMedia(const char* path) return false; } - const DiscRegion region = GameList::GetCustomRegionForPath(path).value_or(GetRegionForImage(image.get())); - UpdateRunningGame(path, image.get(), false); - CDROM::InsertMedia(std::move(image), region); INFO_LOG("Inserted media from {} ({}, {})", s_running_game_path, s_running_game_serial, s_running_game_title); if (g_settings.cdrom_load_image_to_ram) CDROM::PrecacheMedia(); @@ -4238,10 +4217,10 @@ void System::UpdateRunningGame(const std::string_view path, CDImage* image, bool Host::OnGameChanged(s_running_game_path, s_running_game_serial, s_running_game_title); } -bool System::CheckForSBIFile(CDImage* image, Error* error) +bool System::CheckForRequiredSubQ(Error* error) { - if (!s_running_game_entry || !s_running_game_entry->HasTrait(GameDatabase::Trait::IsLibCryptProtected) || !image || - image->HasNonStandardSubchannel()) + if (!s_running_game_entry || !s_running_game_entry->HasTrait(GameDatabase::Trait::IsLibCryptProtected) || + CDROM::HasNonStandardOrReplacementSubQ()) { return true; } @@ -4328,25 +4307,36 @@ bool System::SwitchMediaSubImage(u32 index) Assert(image); Error error; - if (!image->SwitchSubImage(index, &error)) + bool okay = image->SwitchSubImage(index, &error); + std::string title, subimage_title; + if (okay) + { + const DiscRegion region = + GameList::GetCustomRegionForPath(image->GetPath()).value_or(GetRegionForImage(image.get())); + subimage_title = image->GetSubImageMetadata(index, "title"); + title = image->GetMetadata("title"); + UpdateRunningGame(image->GetPath(), image.get(), false); + okay = CDROM::InsertMedia(std::move(image), region, s_running_game_serial, s_running_game_title, &error); + } + if (!okay) { Host::AddIconOSDMessage("MediaSwitchSubImage", ICON_FA_COMPACT_DISC, fmt::format(TRANSLATE_FS("System", "Failed to switch to subimage {} in '{}': {}."), - index + 1u, Path::GetFileName(image->GetFileName()), error.GetDescription()), + index + 1u, Path::GetFileName(image->GetPath()), error.GetDescription()), Host::OSD_INFO_DURATION); - const DiscRegion region = GetRegionForImage(image.get()); - CDROM::InsertMedia(std::move(image), region); + // restore old disc + const DiscRegion region = + GameList::GetCustomRegionForPath(image->GetPath()).value_or(GetRegionForImage(image.get())); + UpdateRunningGame(image->GetPath(), image.get(), false); + CDROM::InsertMedia(std::move(image), region, s_running_game_serial, s_running_game_title, nullptr); return false; } Host::AddIconOSDMessage("MediaSwitchSubImage", ICON_FA_COMPACT_DISC, - fmt::format(TRANSLATE_FS("System", "Switched to sub-image {} ({}) in '{}'."), - image->GetSubImageMetadata(index, "title"), index + 1u, - image->GetMetadata("title")), + fmt::format(TRANSLATE_FS("System", "Switched to sub-image {} ({}) in '{}'."), subimage_title, + title, index + 1u, Path::GetFileName(CDROM::GetMediaPath())), Host::OSD_INFO_DURATION); - const DiscRegion region = GetRegionForImage(image.get()); - CDROM::InsertMedia(std::move(image), region); ClearMemorySaveStates(); return true; diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 6aa4f0e40..5ffe0b72f 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -15,8 +15,6 @@ add_library(util cd_image_mds.cpp cd_image_pbp.cpp cd_image_ppf.cpp - cd_subchannel_replacement.cpp - cd_subchannel_replacement.h compress_helpers.cpp compress_helpers.h cue_parser.cpp diff --git a/src/util/cd_image.cpp b/src/util/cd_image.cpp index 6e5d227c1..01adfe839 100644 --- a/src/util/cd_image.cpp +++ b/src/util/cd_image.cpp @@ -361,7 +361,7 @@ bool CDImage::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_ return true; } -bool CDImage::HasNonStandardSubchannel() const +bool CDImage::HasSubchannelData() const { return false; } diff --git a/src/util/cd_image.h b/src/util/cd_image.h index 9003ff121..a14850803 100644 --- a/src/util/cd_image.h +++ b/src/util/cd_image.h @@ -259,7 +259,7 @@ public: ProgressCallback* progress = ProgressCallback::NullProgressCallback); // Accessors. - const std::string& GetFileName() const { return m_filename; } + const std::string& GetPath() const { return m_filename; } LBA GetPositionOnDisc() const { return m_position_on_disc; } Position GetMSFPositionOnDisc() const { return Position::FromLBA(m_position_on_disc); } LBA GetPositionInTrack() const { return m_position_in_track; } @@ -311,7 +311,7 @@ public: virtual bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index); // Returns true if the image has replacement subchannel data. - virtual bool HasNonStandardSubchannel() const; + virtual bool HasSubchannelData() const; // Reads a single sector from an index. virtual bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) = 0; diff --git a/src/util/cd_image_bin.cpp b/src/util/cd_image_bin.cpp index 3dbc4fa15..830664cf0 100644 --- a/src/util/cd_image_bin.cpp +++ b/src/util/cd_image_bin.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "cd_image.h" -#include "cd_subchannel_replacement.h" #include "common/error.h" #include "common/file_system.h" @@ -18,9 +17,6 @@ public: bool Open(const char* filename, Error* error); - bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; - bool HasNonStandardSubchannel() const override; - s64 GetSizeOnDisk() const override; protected: @@ -29,8 +25,6 @@ protected: private: std::FILE* m_fp = nullptr; u64 m_file_position = 0; - - CDSubChannelReplacement m_sbi; }; } // namespace @@ -102,24 +96,9 @@ bool CDImageBin::Open(const char* filename, Error* error) AddLeadOutIndex(); - m_sbi.LoadFromImagePath(filename); - return Seek(1, Position{0, 0, 0}); } -bool CDImageBin::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) -{ - if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq)) - return true; - - return CDImage::ReadSubChannelQ(subq, index, lba_in_index); -} - -bool CDImageBin::HasNonStandardSubchannel() const -{ - return (m_sbi.GetReplacementSectorCount() > 0); -} - bool CDImageBin::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) { const u64 file_position = index.file_offset + (static_cast(lba_in_index) * index.file_sector_size); diff --git a/src/util/cd_image_chd.cpp b/src/util/cd_image_chd.cpp index 0229cc0a8..a315be7e1 100644 --- a/src/util/cd_image_chd.cpp +++ b/src/util/cd_image_chd.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "cd_image.h" -#include "cd_subchannel_replacement.h" #include "common/align.h" #include "common/assert.h" @@ -65,7 +64,7 @@ public: bool Open(const char* filename, Error* error); bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; - bool HasNonStandardSubchannel() const override; + bool HasSubchannelData() const override; PrecacheResult Precache(ProgressCallback* progress) override; bool IsPrecached() const override; s64 GetSizeOnDisk() const override; @@ -90,8 +89,6 @@ private: DynamicHeapArray m_hunk_buffer; u32 m_current_hunk_index = static_cast(-1); bool m_precached = false; - - CDSubChannelReplacement m_sbi; }; } // namespace @@ -415,16 +412,11 @@ bool CDImageCHD::Open(const char* filename, Error* error) m_lba_count = disc_lba; AddLeadOutIndex(); - m_sbi.LoadFromImagePath(filename); - return Seek(1, Position{0, 0, 0}); } bool CDImageCHD::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) { - if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq)) - return true; - if (index.submode == CDImage::SubchannelMode::None) return CDImage::ReadSubChannelQ(subq, index, lba_in_index); @@ -446,10 +438,10 @@ bool CDImageCHD::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_ return true; } -bool CDImageCHD::HasNonStandardSubchannel() const +bool CDImageCHD::HasSubchannelData() const { // Just look at the first track for in-CHD subq. - return (m_sbi.GetReplacementSectorCount() > 0 || m_tracks.front().submode != CDImage::SubchannelMode::None); + return (m_tracks.front().submode != CDImage::SubchannelMode::None); } CDImage::PrecacheResult CDImageCHD::Precache(ProgressCallback* progress) diff --git a/src/util/cd_image_cue.cpp b/src/util/cd_image_cue.cpp index ad12da262..6b1406632 100644 --- a/src/util/cd_image_cue.cpp +++ b/src/util/cd_image_cue.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "cd_image.h" -#include "cd_subchannel_replacement.h" #include "cue_parser.h" #include "common/assert.h" @@ -29,8 +28,6 @@ public: bool OpenAndParse(const char* filename, Error* error); - bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; - bool HasNonStandardSubchannel() const override; s64 GetSizeOnDisk() const override; protected: @@ -45,7 +42,6 @@ private: }; std::vector m_files; - CDSubChannelReplacement m_sbi; }; } // namespace @@ -293,24 +289,9 @@ bool CDImageCueSheet::OpenAndParse(const char* filename, Error* error) m_lba_count = disc_lba; AddLeadOutIndex(); - m_sbi.LoadFromImagePath(filename); - return Seek(1, Position{0, 0, 0}); } -bool CDImageCueSheet::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) -{ - if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq)) - return true; - - return CDImage::ReadSubChannelQ(subq, index, lba_in_index); -} - -bool CDImageCueSheet::HasNonStandardSubchannel() const -{ - return (m_sbi.GetReplacementSectorCount() > 0); -} - bool CDImageCueSheet::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) { DebugAssert(index.file_index < m_files.size()); diff --git a/src/util/cd_image_device.cpp b/src/util/cd_image_device.cpp index c16ce060e..082a8aeb2 100644 --- a/src/util/cd_image_device.cpp +++ b/src/util/cd_image_device.cpp @@ -213,7 +213,7 @@ public: bool Open(const char* filename, Error* error); bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; - bool HasNonStandardSubchannel() const override; + bool HasSubchannelData() const override; protected: bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override; @@ -441,7 +441,7 @@ bool CDImageDeviceWin32::ReadSubChannelQ(SubChannelQ* subq, const Index& index, } } -bool CDImageDeviceWin32::HasNonStandardSubchannel() const +bool CDImageDeviceWin32::HasSubchannelData() const { return m_has_valid_subcode; } @@ -704,7 +704,7 @@ public: bool Open(const char* filename, Error* error); bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; - bool HasNonStandardSubchannel() const override; + bool HasSubchannelData() const override; protected: bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override; @@ -932,7 +932,7 @@ bool CDImageDeviceLinux::ReadSubChannelQ(SubChannelQ* subq, const Index& index, } } -bool CDImageDeviceLinux::HasNonStandardSubchannel() const +bool CDImageDeviceLinux::HasSubchannelData() const { // Can only read subchannel through SPTD. return m_scsi_read_mode >= SCSIReadMode::Full; @@ -1165,7 +1165,7 @@ public: bool Open(const char* filename, Error* error); bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; - bool HasNonStandardSubchannel() const override; + bool HasSubchannelData() const override; protected: bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override; @@ -1440,7 +1440,7 @@ bool CDImageDeviceMacOS::ReadSubChannelQ(SubChannelQ* subq, const Index& index, } } -bool CDImageDeviceMacOS::HasNonStandardSubchannel() const +bool CDImageDeviceMacOS::HasSubchannelData() const { // Can only read subchannel through SPTD. return m_read_mode >= SCSIReadMode::Full; diff --git a/src/util/cd_image_ecm.cpp b/src/util/cd_image_ecm.cpp index c4f8c8480..9c680f435 100644 --- a/src/util/cd_image_ecm.cpp +++ b/src/util/cd_image_ecm.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "cd_image.h" -#include "cd_subchannel_replacement.h" #include "common/assert.h" #include "common/error.h" @@ -27,8 +26,6 @@ public: bool Open(const char* filename, Error* error); - bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; - bool HasNonStandardSubchannel() const override; s64 GetSizeOnDisk() const override; protected: @@ -74,8 +71,6 @@ private: DataMap m_data_map; std::vector m_chunk_buffer; u32 m_chunk_start = 0; - - CDSubChannelReplacement m_sbi; }; } // namespace @@ -260,8 +255,6 @@ bool CDImageEcm::Open(const char* filename, Error* error) AddLeadOutIndex(); - m_sbi.LoadFromImagePath(filename); - m_chunk_buffer.reserve(RAW_SECTOR_SIZE * 2); return Seek(1, Position{0, 0, 0}); } @@ -369,19 +362,6 @@ bool CDImageEcm::ReadChunks(u32 disc_offset, u32 size) return true; } -bool CDImageEcm::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) -{ - if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq)) - return true; - - return CDImage::ReadSubChannelQ(subq, index, lba_in_index); -} - -bool CDImageEcm::HasNonStandardSubchannel() const -{ - return (m_sbi.GetReplacementSectorCount() > 0); -} - bool CDImageEcm::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) { const u32 file_start = static_cast(index.file_offset) + (lba_in_index * index.file_sector_size); diff --git a/src/util/cd_image_m3u.cpp b/src/util/cd_image_m3u.cpp index 12f79219d..42e84aea7 100644 --- a/src/util/cd_image_m3u.cpp +++ b/src/util/cd_image_m3u.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "cd_image.h" -#include "cd_subchannel_replacement.h" #include "common/assert.h" #include "common/error.h" @@ -28,7 +27,7 @@ public: bool Open(const char* path, bool apply_patches, Error* Error); bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; - bool HasNonStandardSubchannel() const override; + bool HasSubchannelData() const override; bool HasSubImages() const override; u32 GetSubImageCount() const override; @@ -115,9 +114,9 @@ bool CDImageM3u::Open(const char* path, bool apply_patches, Error* error) return !m_entries.empty() && SwitchSubImage(0, error); } -bool CDImageM3u::HasNonStandardSubchannel() const +bool CDImageM3u::HasSubchannelData() const { - return m_current_image->HasNonStandardSubchannel(); + return m_current_image->HasSubchannelData(); } bool CDImageM3u::HasSubImages() const diff --git a/src/util/cd_image_mds.cpp b/src/util/cd_image_mds.cpp index 34d4d9367..5dd98e14f 100644 --- a/src/util/cd_image_mds.cpp +++ b/src/util/cd_image_mds.cpp @@ -3,7 +3,6 @@ #include "assert.h" #include "cd_image.h" -#include "cd_subchannel_replacement.h" #include "common/error.h" #include "common/file_system.h" @@ -46,8 +45,6 @@ public: bool OpenAndParse(const char* filename, Error* error); - bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; - bool HasNonStandardSubchannel() const override; s64 GetSizeOnDisk() const override; protected: @@ -56,7 +53,6 @@ protected: private: std::FILE* m_mdf_file = nullptr; u64 m_mdf_file_position = 0; - CDSubChannelReplacement m_sbi; }; } // namespace @@ -243,24 +239,9 @@ bool CDImageMds::OpenAndParse(const char* filename, Error* error) m_lba_count = m_tracks.back().start_lba + m_tracks.back().length; AddLeadOutIndex(); - m_sbi.LoadFromImagePath(filename); - return Seek(1, Position{0, 0, 0}); } -bool CDImageMds::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) -{ - if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq)) - return true; - - return CDImage::ReadSubChannelQ(subq, index, lba_in_index); -} - -bool CDImageMds::HasNonStandardSubchannel() const -{ - return (m_sbi.GetReplacementSectorCount() > 0); -} - bool CDImageMds::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) { const u64 file_position = index.file_offset + (static_cast(lba_in_index) * index.file_sector_size); diff --git a/src/util/cd_image_memory.cpp b/src/util/cd_image_memory.cpp index 847e446b5..7904909b5 100644 --- a/src/util/cd_image_memory.cpp +++ b/src/util/cd_image_memory.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "cd_image.h" -#include "cd_subchannel_replacement.h" #include "common/assert.h" #include "common/file_system.h" @@ -24,9 +23,6 @@ public: bool CopyImage(CDImage* image, ProgressCallback* progress); - bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; - bool HasNonStandardSubchannel() const override; - bool IsPrecached() const override; protected: @@ -35,7 +31,6 @@ protected: private: u8* m_memory = nullptr; u32 m_memory_sectors = 0; - CDSubChannelReplacement m_sbi; }; } // namespace @@ -119,27 +114,12 @@ bool CDImageMemory::CopyImage(CDImage* image, ProgressCallback* progress) } Assert(current_offset == m_memory_sectors); - m_filename = image->GetFileName(); + m_filename = image->GetPath(); m_lba_count = image->GetLBACount(); - m_sbi.LoadFromImagePath(m_filename); - return Seek(1, Position{0, 0, 0}); } -bool CDImageMemory::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) -{ - if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq)) - return true; - - return CDImage::ReadSubChannelQ(subq, index, lba_in_index); -} - -bool CDImageMemory::HasNonStandardSubchannel() const -{ - return (m_sbi.GetReplacementSectorCount() > 0); -} - bool CDImageMemory::IsPrecached() const { return true; diff --git a/src/util/cd_image_pbp.cpp b/src/util/cd_image_pbp.cpp index 7220ee6b5..7335299b3 100644 --- a/src/util/cd_image_pbp.cpp +++ b/src/util/cd_image_pbp.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "cd_image.h" -#include "cd_subchannel_replacement.h" #include "common/assert.h" #include "common/error.h" @@ -134,8 +133,6 @@ public: bool Open(const char* filename, Error* error); - bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; - bool HasNonStandardSubchannel() const override; s64 GetSizeOnDisk() const override; bool HasSubImages() const override; @@ -197,8 +194,6 @@ private: std::vector m_compressed_block; z_stream m_inflate_stream; - - CDSubChannelReplacement m_sbi; }; } // namespace @@ -738,20 +733,6 @@ bool CDImagePBP::OpenDisc(u32 index, Error* error) return false; } - if (m_disc_offsets.size() > 1) - { - // Gross. Have to use the SBI suffix here, otherwise Android won't resolve content URIs... - // Which means that LSD won't be usable with PBP on Android. Oh well. - const std::string display_name = FileSystem::GetDisplayNameFromPath(m_filename); - const std::string offset_path = - Path::BuildRelativePath(m_filename, fmt::format("{}_{}.sbi", Path::StripExtension(display_name), index + 1)); - m_sbi.LoadFromImagePath(offset_path); - } - else - { - m_sbi.LoadFromImagePath(m_filename); - } - m_current_disc = index; return Seek(1, Position{0, 0, 0}); } @@ -817,19 +798,6 @@ bool CDImagePBP::DecompressBlock(const BlockInfo& block_info) return true; } -bool CDImagePBP::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) -{ - if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq)) - return true; - - return CDImage::ReadSubChannelQ(subq, index, lba_in_index); -} - -bool CDImagePBP::HasNonStandardSubchannel() const -{ - return (m_sbi.GetReplacementSectorCount() > 0); -} - bool CDImagePBP::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) { const u32 offset_in_file = static_cast(index.file_offset) + (lba_in_index * index.file_sector_size); diff --git a/src/util/cd_image_ppf.cpp b/src/util/cd_image_ppf.cpp index 22ea204da..8f59721e5 100644 --- a/src/util/cd_image_ppf.cpp +++ b/src/util/cd_image_ppf.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "cd_image.h" -#include "cd_subchannel_replacement.h" #include "common/assert.h" #include "common/file_system.h" @@ -33,7 +32,7 @@ public: bool Open(const char* filename, std::unique_ptr parent_image); bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override; - bool HasNonStandardSubchannel() const override; + bool HasSubchannelData() const override; s64 GetSizeOnDisk() const override; std::string GetMetadata(std::string_view type) const override; @@ -89,7 +88,7 @@ bool CDImagePPF::Open(const char* filename, std::unique_ptr parent_imag m_replacement_offset = parent_image->GetIndex(1).start_lba_on_disc; // copy all the stuff from the parent image - m_filename = parent_image->GetFileName(); + m_filename = parent_image->GetPath(); m_tracks = parent_image->GetTracks(); m_indices = parent_image->GetIndices(); m_parent_image = std::move(parent_image); @@ -416,9 +415,9 @@ bool CDImagePPF::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_ return m_parent_image->ReadSubChannelQ(subq, index, lba_in_index); } -bool CDImagePPF::HasNonStandardSubchannel() const +bool CDImagePPF::HasSubchannelData() const { - return m_parent_image->HasNonStandardSubchannel(); + return m_parent_image->HasSubchannelData(); } std::string CDImagePPF::GetMetadata(std::string_view type) const diff --git a/src/util/cd_subchannel_replacement.cpp b/src/util/cd_subchannel_replacement.cpp deleted file mode 100644 index c24fabfaa..000000000 --- a/src/util/cd_subchannel_replacement.cpp +++ /dev/null @@ -1,167 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin -// SPDX-License-Identifier: CC-BY-NC-ND-4.0 - -#include "cd_subchannel_replacement.h" -#include "common/file_system.h" -#include "common/log.h" -#include "common/path.h" -#include -#include -LOG_CHANNEL(CDSubChannelReplacement); - -#pragma pack(push, 1) -struct SBIFileEntry -{ - u8 minute_bcd; - u8 second_bcd; - u8 frame_bcd; - u8 type; - u8 data[10]; -}; -struct LSDFileEntry -{ - u8 minute_bcd; - u8 second_bcd; - u8 frame_bcd; - u8 data[12]; -}; -static_assert(sizeof(LSDFileEntry) == 15); -#pragma pack(pop) - -CDSubChannelReplacement::CDSubChannelReplacement() = default; - -CDSubChannelReplacement::~CDSubChannelReplacement() = default; - -static constexpr u32 MSFToLBA(u8 minute_bcd, u8 second_bcd, u8 frame_bcd) -{ - const u8 minute = PackedBCDToBinary(minute_bcd); - const u8 second = PackedBCDToBinary(second_bcd); - const u8 frame = PackedBCDToBinary(frame_bcd); - - return (ZeroExtend32(minute) * 60 * 75) + (ZeroExtend32(second) * 75) + ZeroExtend32(frame); -} - -bool CDSubChannelReplacement::LoadSBI(const std::string& path) -{ - auto fp = FileSystem::OpenManagedCFile(path.c_str(), "rb"); - if (!fp) - return false; - - char header[4]; - if (std::fread(header, sizeof(header), 1, fp.get()) != 1) - { - ERROR_LOG("Failed to read header for '{}'", path); - return true; - } - - static constexpr char expected_header[] = {'S', 'B', 'I', '\0'}; - if (std::memcmp(header, expected_header, sizeof(header)) != 0) - { - ERROR_LOG("Invalid header in '{}'", path); - return true; - } - - m_replacement_subq.clear(); - - SBIFileEntry entry; - while (std::fread(&entry, sizeof(entry), 1, fp.get()) == 1) - { - if (!IsValidPackedBCD(entry.minute_bcd) || !IsValidPackedBCD(entry.second_bcd) || - !IsValidPackedBCD(entry.frame_bcd)) - { - ERROR_LOG("Invalid position [{:02x}:{:02x}:{:02x}] in '{}'", entry.minute_bcd, entry.second_bcd, entry.frame_bcd, - path); - return false; - } - - if (entry.type != 1) - { - ERROR_LOG("Invalid type 0x{:02X} in '{}'", entry.type, path); - return false; - } - - const u32 lba = MSFToLBA(entry.minute_bcd, entry.second_bcd, entry.frame_bcd); - - CDImage::SubChannelQ subq; - std::memcpy(subq.data.data(), entry.data, sizeof(entry.data)); - - // generate an invalid crc by flipping all bits from the valid crc (will never collide) - const u16 crc = subq.ComputeCRC(subq.data) ^ 0xFFFF; - subq.data[10] = Truncate8(crc); - subq.data[11] = Truncate8(crc >> 8); - - m_replacement_subq.emplace(lba, subq); - } - - INFO_LOG("Loaded {} replacement sectors from SBI '{}'", m_replacement_subq.size(), path); - return true; -} - -bool CDSubChannelReplacement::LoadLSD(const std::string& path) -{ - auto fp = FileSystem::OpenManagedCFile(path.c_str(), "rb"); - if (!fp) - return false; - - m_replacement_subq.clear(); - - LSDFileEntry entry; - while (std::fread(&entry, sizeof(entry), 1, fp.get()) == 1) - { - if (!IsValidPackedBCD(entry.minute_bcd) || !IsValidPackedBCD(entry.second_bcd) || - !IsValidPackedBCD(entry.frame_bcd)) - { - ERROR_LOG("Invalid position [{:02x}:{:02x}:{:02x}] in '{}'", entry.minute_bcd, entry.second_bcd, entry.frame_bcd, - path); - return false; - } - - const u32 lba = MSFToLBA(entry.minute_bcd, entry.second_bcd, entry.frame_bcd); - - CDImage::SubChannelQ subq; - std::memcpy(subq.data.data(), entry.data, sizeof(entry.data)); - - DEBUG_LOG("{:02x}:{:02x}:{:02x}: CRC {}", entry.minute_bcd, entry.second_bcd, entry.frame_bcd, - subq.IsCRCValid() ? "VALID" : "INVALID"); - m_replacement_subq.emplace(lba, subq); - } - - INFO_LOG("Loaded {} replacement sectors from LSD '{}'", m_replacement_subq.size(), path); - return true; -} - -bool CDSubChannelReplacement::LoadFromImagePath(std::string_view image_path) -{ - if (const std::string filename = Path::ReplaceExtension(image_path, "sbi"); LoadSBI(filename.c_str())) - return true; - - if (const std::string filename = Path::ReplaceExtension(image_path, "lsd"); LoadLSD(filename.c_str())) - return true; - - return false; -} - -void CDSubChannelReplacement::AddReplacementSubChannelQ(u32 lba, const CDImage::SubChannelQ& subq) -{ - auto iter = m_replacement_subq.find(lba); - if (iter != m_replacement_subq.end()) - iter->second.data = subq.data; - else - m_replacement_subq.emplace(lba, subq); -} - -bool CDSubChannelReplacement::GetReplacementSubChannelQ(u8 minute_bcd, u8 second_bcd, u8 frame_bcd, - CDImage::SubChannelQ* subq) const -{ - return GetReplacementSubChannelQ(MSFToLBA(minute_bcd, second_bcd, frame_bcd), subq); -} - -bool CDSubChannelReplacement::GetReplacementSubChannelQ(u32 lba, CDImage::SubChannelQ* subq) const -{ - const auto iter = m_replacement_subq.find(lba); - if (iter == m_replacement_subq.cend()) - return false; - - *subq = iter->second; - return true; -} diff --git a/src/util/cd_subchannel_replacement.h b/src/util/cd_subchannel_replacement.h deleted file mode 100644 index 791a4334c..000000000 --- a/src/util/cd_subchannel_replacement.h +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin -// SPDX-License-Identifier: CC-BY-NC-ND-4.0 - -#pragma once -#include "cd_image.h" -#include "common/types.h" -#include -#include -#include - -class CDSubChannelReplacement -{ -public: - CDSubChannelReplacement(); - ~CDSubChannelReplacement(); - - u32 GetReplacementSectorCount() const { return static_cast(m_replacement_subq.size()); } - - bool LoadFromImagePath(std::string_view image_path); - - /// Adds a sector to the replacement map. - void AddReplacementSubChannelQ(u32 lba, const CDImage::SubChannelQ& subq); - - /// Returns the replacement subchannel data for the specified position (in BCD). - bool GetReplacementSubChannelQ(u8 minute_bcd, u8 second_bcd, u8 frame_bcd, CDImage::SubChannelQ* subq) const; - - /// Returns the replacement subchannel data for the specified sector. - bool GetReplacementSubChannelQ(u32 lba, CDImage::SubChannelQ* subq) const; - -private: - using ReplacementMap = std::unordered_map; - - bool LoadSBI(const std::string& path); - bool LoadLSD(const std::string& path); - - ReplacementMap m_replacement_subq; -}; diff --git a/src/util/util.vcxproj b/src/util/util.vcxproj index ad910df6c..66c9c7964 100644 --- a/src/util/util.vcxproj +++ b/src/util/util.vcxproj @@ -78,7 +78,6 @@ true - @@ -148,7 +147,6 @@ - true diff --git a/src/util/util.vcxproj.filters b/src/util/util.vcxproj.filters index 48eb3ef1e..fd5e6bb63 100644 --- a/src/util/util.vcxproj.filters +++ b/src/util/util.vcxproj.filters @@ -5,7 +5,6 @@ - @@ -81,7 +80,6 @@ -