Path: blob/master/src/core/cdrom_subq_replacement.cpp
4212 views
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <[email protected]>1// SPDX-License-Identifier: CC-BY-NC-ND-4.023#include "cdrom_subq_replacement.h"4#include "settings.h"56#include "common/error.h"7#include "common/file_system.h"8#include "common/log.h"9#include "common/path.h"10#include "common/small_string.h"1112#include <algorithm>13#include <array>14#include <memory>1516LOG_CHANNEL(CDROM);1718#pragma pack(push, 1)19struct SBIFileEntry20{21u8 minute_bcd;22u8 second_bcd;23u8 frame_bcd;24u8 type;25u8 data[10];26};27struct LSDFileEntry28{29u8 minute_bcd;30u8 second_bcd;31u8 frame_bcd;32u8 data[12];33};34static_assert(sizeof(LSDFileEntry) == 15);35#pragma pack(pop)3637CDROMSubQReplacement::CDROMSubQReplacement() = default;3839CDROMSubQReplacement::~CDROMSubQReplacement() = default;4041std::unique_ptr<CDROMSubQReplacement> CDROMSubQReplacement::LoadSBI(const std::string& path, std::FILE* fp,42Error* error)43{44static constexpr char expected_header[] = {'S', 'B', 'I', '\0'};4546char header[4];47if (std::fread(header, sizeof(header), 1, fp) != 1 || std::memcmp(header, expected_header, sizeof(header)) != 0)48{49Error::SetStringFmt(error, "Invalid header in '{}'", Path::GetFileName(path));50return {};51}5253std::unique_ptr<CDROMSubQReplacement> ret = std::make_unique<CDROMSubQReplacement>();5455SBIFileEntry entry;56while (std::fread(&entry, sizeof(entry), 1, fp) == 1)57{58if (!IsValidPackedBCD(entry.minute_bcd) || !IsValidPackedBCD(entry.second_bcd) ||59!IsValidPackedBCD(entry.frame_bcd))60{61Error::SetStringFmt(error, "Invalid position [{:02x}:{:02x}:{:02x}] in '{}'", entry.minute_bcd, entry.second_bcd,62entry.frame_bcd, Path::GetFileName(path));63return {};64}6566if (entry.type != 1)67{68Error::SetStringFmt(error, "Invalid type 0x{:02X} in '{}'", entry.type, Path::GetFileName(path));69return {};70}7172const u32 lba = CDImage::Position::FromBCD(entry.minute_bcd, entry.second_bcd, entry.frame_bcd).ToLBA();7374CDImage::SubChannelQ subq;75std::memcpy(subq.data.data(), entry.data, sizeof(entry.data));7677// generate an invalid crc by flipping all bits from the valid crc (will never collide)78const u16 crc = subq.ComputeCRC(subq.data) ^ 0xFFFF;79subq.data[10] = Truncate8(crc);80subq.data[11] = Truncate8(crc >> 8);8182ret->m_replacement_subq.emplace(lba, subq);83}8485INFO_LOG("Loaded {} replacement sectors from SBI '{}'", ret->m_replacement_subq.size(), Path::GetFileName(path));86return ret;87}8889std::unique_ptr<CDROMSubQReplacement> CDROMSubQReplacement::LoadLSD(const std::string& path, std::FILE* fp,90Error* error)91{92std::unique_ptr<CDROMSubQReplacement> ret = std::make_unique<CDROMSubQReplacement>();9394LSDFileEntry entry;95while (std::fread(&entry, sizeof(entry), 1, fp) == 1)96{97if (!IsValidPackedBCD(entry.minute_bcd) || !IsValidPackedBCD(entry.second_bcd) ||98!IsValidPackedBCD(entry.frame_bcd))99{100Error::SetStringFmt(error, "Invalid position [{:02x}:{:02x}:{:02x}] in '{}'", entry.minute_bcd, entry.second_bcd,101entry.frame_bcd, Path::GetFileName(path));102return {};103}104105const u32 lba = CDImage::Position::FromBCD(entry.minute_bcd, entry.second_bcd, entry.frame_bcd).ToLBA();106107CDImage::SubChannelQ subq;108std::memcpy(subq.data.data(), entry.data, sizeof(entry.data));109110DEBUG_LOG("{:02x}:{:02x}:{:02x}: CRC {}", entry.minute_bcd, entry.second_bcd, entry.frame_bcd,111subq.IsCRCValid() ? "VALID" : "INVALID");112ret->m_replacement_subq.emplace(lba, subq);113}114115INFO_LOG("Loaded {} replacement sectors from LSD '{}'", ret->m_replacement_subq.size(), path);116return ret;117}118119bool CDROMSubQReplacement::LoadForImage(std::unique_ptr<CDROMSubQReplacement>* ret, CDImage* image,120std::string_view serial, std::string_view title, Error* error)121{122struct FileLoader123{124const char* extension;125std::unique_ptr<CDROMSubQReplacement> (*func)(const std::string&, std::FILE* fp, Error*);126};127static constexpr const FileLoader loaders[] = {128{"sbi", &CDROMSubQReplacement::LoadSBI},129{"lsd", &CDROMSubQReplacement::LoadLSD},130};131132const std::string& image_path = image->GetPath();133std::string path;134135// Try sbi/lsd in the directory first.136if (!CDImage::IsDeviceName(image_path.c_str()))137{138std::string display_name = FileSystem::GetDisplayNameFromPath(image_path);139140for (const FileLoader& loader : loaders)141{142path = Path::BuildRelativePath(143image_path, SmallString::from_format("{}.{}", Path::GetFileTitle(display_name), loader.extension));144if (const FileSystem::ManagedCFilePtr fp = FileSystem::OpenManagedCFile(path.c_str(), "rb"))145{146*ret = loader.func(path, fp.get(), error);147if (!static_cast<bool>(*ret))148Error::AddPrefixFmt(error, "Failed to load subchannel data from {}: ", Path::GetFileName(path));149150return static_cast<bool>(*ret);151}152}153154// For subimages, we need to check the suffix too.155if (image->HasSubImages())156{157for (const FileLoader& loader : loaders)158{159path = Path::BuildRelativePath(image_path,160SmallString::from_format("{}_{}.{}", Path::GetFileTitle(display_name),161image->GetCurrentSubImage() + 1, loader.extension));162if (const FileSystem::ManagedCFilePtr fp = FileSystem::OpenManagedCFile(path.c_str(), "rb"))163{164*ret = loader.func(path, fp.get(), error);165if (!static_cast<bool>(*ret))166Error::AddPrefixFmt(error, "Failed to load subchannel data from {}: ", Path::GetFileName(path));167168return static_cast<bool>(*ret);169}170}171}172}173174// If this fails, try the subchannel directory with serial/title.175if (!serial.empty())176{177for (const FileLoader& loader : loaders)178{179path = Path::Combine(EmuFolders::Subchannels, TinyString::from_format("{}.{}", serial, loader.extension));180if (const FileSystem::ManagedCFilePtr fp = FileSystem::OpenManagedCFile(path.c_str(), "rb"))181{182*ret = loader.func(path, fp.get(), error);183if (!static_cast<bool>(*ret))184Error::AddPrefixFmt(error, "Failed to load subchannel data from {}: ", Path::GetFileName(path));185186return static_cast<bool>(*ret);187}188}189}190191if (!title.empty())192{193for (const FileLoader& loader : loaders)194{195path = Path::Combine(EmuFolders::Subchannels, TinyString::from_format("{}.{}", title, loader.extension));196if (const FileSystem::ManagedCFilePtr fp = FileSystem::OpenManagedCFile(path.c_str(), "rb"))197{198*ret = loader.func(path, fp.get(), error);199if (!static_cast<bool>(*ret))200Error::AddPrefixFmt(error, "Failed to load subchannel data from {}: ", Path::GetFileName(path));201202return static_cast<bool>(*ret);203}204}205}206207// Nothing.208return true;209}210211const CDImage::SubChannelQ* CDROMSubQReplacement::GetReplacementSubQ(u32 lba) const212{213const auto iter = m_replacement_subq.find(lba);214return (iter != m_replacement_subq.end()) ? &iter->second : nullptr;215}216217218