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:
parent
cdd230c040
commit
080807e557
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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" />
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
Loading…
Reference in New Issue