CDROM: Move subchannel replacement from CDImage

This will allow you to use SBI/LSD files with real discs, if your
drive does not support reading subchannels, or has an incorrect
skew.
This commit is contained in:
Stenzek 2024-10-25 15:24:43 +10:00
parent cdd230c040
commit 080807e557
No known key found for this signature in database
29 changed files with 433 additions and 497 deletions

View File

@ -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.

View File

@ -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

View File

@ -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<CDROMSubQReplacement> 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<CDImage> media, DiscRegion region)
bool CDROM::InsertMedia(std::unique_ptr<CDImage> media, DiscRegion region, std::string_view serial,
std::string_view title, Error* error)
{
// Load SBI/LSD first.
std::unique_ptr<CDROMSubQReplacement> 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<CDImage> media, DiscRegion region)
if (s_state.show_current_file)
CreateFileMap();
return true;
}
std::unique_ptr<CDImage> 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<CDImage> 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,

View File

@ -2,11 +2,15 @@
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "types.h"
#include <memory>
#include <string>
#include <tuple>
#include <utility>
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<CDImage> media, DiscRegion region);
bool InsertMedia(std::unique_ptr<CDImage> media, DiscRegion region, std::string_view serial, std::string_view title,
Error* error);
std::unique_ptr<CDImage> RemoveMedia(bool for_disc_swap);
bool PrecacheMedia();
bool HasNonStandardOrReplacementSubQ();
void CPUClockChanged();

View File

@ -37,7 +37,7 @@ public:
bool HasMedia() const { return static_cast<bool>(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);

View File

@ -0,0 +1,204 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// 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 <algorithm>
#include <array>
#include <memory>
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> 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<CDROMSubQReplacement> ret = std::make_unique<CDROMSubQReplacement>();
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> CDROMSubQReplacement::LoadLSD(const std::string& path, Error* error)
{
auto fp = FileSystem::OpenManagedCFile(path.c_str(), "rb", error);
if (!fp)
return {};
std::unique_ptr<CDROMSubQReplacement> ret = std::make_unique<CDROMSubQReplacement>();
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<CDROMSubQReplacement>* ret, CDImage* image, std::string_view serial,
std::string_view title, Error* error)
{
struct FileLoader
{
const char* extension;
std::unique_ptr<CDROMSubQReplacement> (*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<bool>(*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<bool>(*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<bool>(*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<bool>(*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;
}

View File

@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "util/cd_image.h"
#include <unordered_map>
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<CDROMSubQReplacement>* 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<u32, CDImage::SubChannelQ>;
static std::unique_ptr<CDROMSubQReplacement> LoadSBI(const std::string& path, Error* error);
static std::unique_ptr<CDROMSubQReplacement> LoadLSD(const std::string& path, Error* error);
ReplacementMap m_replacement_subq;
};

View File

@ -9,6 +9,7 @@
<ClCompile Include="bus.cpp" />
<ClCompile Include="cdrom.cpp" />
<ClCompile Include="cdrom_async_reader.cpp" />
<ClCompile Include="cdrom_subq_replacement.cpp" />
<ClCompile Include="cheats.cpp" />
<ClCompile Include="cpu_core.cpp" />
<ClCompile Include="cpu_disasm.cpp" />
@ -95,6 +96,7 @@
<ClInclude Include="bus.h" />
<ClInclude Include="cdrom.h" />
<ClInclude Include="cdrom_async_reader.h" />
<ClInclude Include="cdrom_subq_replacement.h" />
<ClInclude Include="cheats.h" />
<ClInclude Include="achievements.h" />
<ClInclude Include="cpu_code_cache_private.h" />

View File

@ -69,6 +69,7 @@
<ClCompile Include="gpu_hw_texture_cache.cpp" />
<ClCompile Include="memory_scanner.cpp" />
<ClCompile Include="gpu_dump.cpp" />
<ClCompile Include="cdrom_subq_replacement.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="types.h" />
@ -144,6 +145,7 @@
<ClInclude Include="gpu_hw_texture_cache.h" />
<ClInclude Include="memory_scanner.h" />
<ClInclude Include="gpu_dump.h" />
<ClInclude Include="cdrom_subq_replacement.h" />
</ItemGroup>
<ItemGroup>
<None Include="gpu_sw_rasterizer.inl" />

View File

@ -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;

View File

@ -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;

View File

@ -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<CDImage> 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<CDImage> 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,32 +2803,39 @@ bool System::LoadStateFromBuffer(const SaveStateBuffer& buffer, Error* error, bo
{
Assert(IsValid());
std::unique_ptr<CDImage> media;
std::unique_ptr<CDImage> 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);
if (CDROM::HasMedia() && CDROM::GetMediaPath() == buffer.media_path &&
CDROM::GetCurrentSubImage() == media_subimage_index)
{
INFO_LOG("Re-using same media '{}'", CDROM::GetMediaPath());
}
else if (!buffer.media_path.empty())
else
{
// needs new image
Error local_error;
media = CDImage::Open(buffer.media_path.c_str(), g_settings.cdrom_load_image_patches, error ? error : &local_error);
if (!media)
std::unique_ptr<CDImage> 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 (old_media)
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(),
old_media->GetFileName()),
Path::GetFileName(CDROM::GetMediaPath())),
Host::OSD_CRITICAL_ERROR_DURATION);
media = std::move(old_media);
media_path = media->GetFileName();
media_subimage_index = media->GetCurrentSubImage();
}
else
{
@ -2844,37 +2844,16 @@ bool System::LoadStateFromBuffer(const SaveStateBuffer& buffer, Error* error, bo
return false;
}
}
else if (g_settings.cdrom_load_image_to_ram)
{
CDROM::PrecacheMedia();
}
}
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;
}
else
{
INFO_LOG("Switched to subimage {} in '{}'", media_subimage_index, buffer.media_path.c_str());
}
}
// 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)
{
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<CDImage> 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;

View File

@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -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<u64>(lba_in_index) * index.file_sector_size);

View File

@ -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<u8, 16> m_hunk_buffer;
u32 m_current_hunk_index = static_cast<u32>(-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)

View File

@ -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<TrackFile> 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());

View File

@ -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;

View File

@ -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<u8> 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<u32>(index.file_offset) + (lba_in_index * index.file_sector_size);

View File

@ -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

View File

@ -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<u64>(lba_in_index) * index.file_sector_size);

View File

@ -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;

View File

@ -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<u8> 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<u32>(index.file_offset) + (lba_in_index * index.file_sector_size);

View File

@ -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<CDImage> 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<CDImage> 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

View File

@ -1,167 +0,0 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// 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 <algorithm>
#include <memory>
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;
}

View File

@ -1,37 +0,0 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "cd_image.h"
#include "common/types.h"
#include <array>
#include <cstdio>
#include <unordered_map>
class CDSubChannelReplacement
{
public:
CDSubChannelReplacement();
~CDSubChannelReplacement();
u32 GetReplacementSectorCount() const { return static_cast<u32>(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<u32, CDImage::SubChannelQ>;
bool LoadSBI(const std::string& path);
bool LoadLSD(const std::string& path);
ReplacementMap m_replacement_subq;
};

View File

@ -78,7 +78,6 @@
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="page_fault_handler.h" />
<ClInclude Include="cd_subchannel_replacement.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="platform_misc.h" />
<ClInclude Include="postprocessing.h" />
@ -148,7 +147,6 @@
<ClCompile Include="input_manager.cpp" />
<ClCompile Include="input_source.cpp" />
<ClCompile Include="iso_reader.cpp" />
<ClCompile Include="cd_subchannel_replacement.cpp" />
<ClCompile Include="media_capture.cpp" />
<ClCompile Include="opengl_context.cpp">
<ExcludedFromBuild Condition="'$(Platform)'=='ARM64'">true</ExcludedFromBuild>

View File

@ -5,7 +5,6 @@
<ClInclude Include="audio_stream.h" />
<ClInclude Include="iso_reader.h" />
<ClInclude Include="cd_image.h" />
<ClInclude Include="cd_subchannel_replacement.h" />
<ClInclude Include="wav_writer.h" />
<ClInclude Include="cd_image_hasher.h" />
<ClInclude Include="shiftjis.h" />
@ -81,7 +80,6 @@
<ClCompile Include="cd_image_cue.cpp" />
<ClCompile Include="cd_image_bin.cpp" />
<ClCompile Include="iso_reader.cpp" />
<ClCompile Include="cd_subchannel_replacement.cpp" />
<ClCompile Include="cd_image_chd.cpp" />
<ClCompile Include="wav_writer.cpp" />
<ClCompile Include="cd_image_hasher.cpp" />