Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/core/cdrom_subq_replacement.cpp
4212 views
1
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "cdrom_subq_replacement.h"
5
#include "settings.h"
6
7
#include "common/error.h"
8
#include "common/file_system.h"
9
#include "common/log.h"
10
#include "common/path.h"
11
#include "common/small_string.h"
12
13
#include <algorithm>
14
#include <array>
15
#include <memory>
16
17
LOG_CHANNEL(CDROM);
18
19
#pragma pack(push, 1)
20
struct SBIFileEntry
21
{
22
u8 minute_bcd;
23
u8 second_bcd;
24
u8 frame_bcd;
25
u8 type;
26
u8 data[10];
27
};
28
struct LSDFileEntry
29
{
30
u8 minute_bcd;
31
u8 second_bcd;
32
u8 frame_bcd;
33
u8 data[12];
34
};
35
static_assert(sizeof(LSDFileEntry) == 15);
36
#pragma pack(pop)
37
38
CDROMSubQReplacement::CDROMSubQReplacement() = default;
39
40
CDROMSubQReplacement::~CDROMSubQReplacement() = default;
41
42
std::unique_ptr<CDROMSubQReplacement> CDROMSubQReplacement::LoadSBI(const std::string& path, std::FILE* fp,
43
Error* error)
44
{
45
static constexpr char expected_header[] = {'S', 'B', 'I', '\0'};
46
47
char header[4];
48
if (std::fread(header, sizeof(header), 1, fp) != 1 || std::memcmp(header, expected_header, sizeof(header)) != 0)
49
{
50
Error::SetStringFmt(error, "Invalid header in '{}'", Path::GetFileName(path));
51
return {};
52
}
53
54
std::unique_ptr<CDROMSubQReplacement> ret = std::make_unique<CDROMSubQReplacement>();
55
56
SBIFileEntry entry;
57
while (std::fread(&entry, sizeof(entry), 1, fp) == 1)
58
{
59
if (!IsValidPackedBCD(entry.minute_bcd) || !IsValidPackedBCD(entry.second_bcd) ||
60
!IsValidPackedBCD(entry.frame_bcd))
61
{
62
Error::SetStringFmt(error, "Invalid position [{:02x}:{:02x}:{:02x}] in '{}'", entry.minute_bcd, entry.second_bcd,
63
entry.frame_bcd, Path::GetFileName(path));
64
return {};
65
}
66
67
if (entry.type != 1)
68
{
69
Error::SetStringFmt(error, "Invalid type 0x{:02X} in '{}'", entry.type, Path::GetFileName(path));
70
return {};
71
}
72
73
const u32 lba = CDImage::Position::FromBCD(entry.minute_bcd, entry.second_bcd, entry.frame_bcd).ToLBA();
74
75
CDImage::SubChannelQ subq;
76
std::memcpy(subq.data.data(), entry.data, sizeof(entry.data));
77
78
// generate an invalid crc by flipping all bits from the valid crc (will never collide)
79
const u16 crc = subq.ComputeCRC(subq.data) ^ 0xFFFF;
80
subq.data[10] = Truncate8(crc);
81
subq.data[11] = Truncate8(crc >> 8);
82
83
ret->m_replacement_subq.emplace(lba, subq);
84
}
85
86
INFO_LOG("Loaded {} replacement sectors from SBI '{}'", ret->m_replacement_subq.size(), Path::GetFileName(path));
87
return ret;
88
}
89
90
std::unique_ptr<CDROMSubQReplacement> CDROMSubQReplacement::LoadLSD(const std::string& path, std::FILE* fp,
91
Error* error)
92
{
93
std::unique_ptr<CDROMSubQReplacement> ret = std::make_unique<CDROMSubQReplacement>();
94
95
LSDFileEntry entry;
96
while (std::fread(&entry, sizeof(entry), 1, fp) == 1)
97
{
98
if (!IsValidPackedBCD(entry.minute_bcd) || !IsValidPackedBCD(entry.second_bcd) ||
99
!IsValidPackedBCD(entry.frame_bcd))
100
{
101
Error::SetStringFmt(error, "Invalid position [{:02x}:{:02x}:{:02x}] in '{}'", entry.minute_bcd, entry.second_bcd,
102
entry.frame_bcd, Path::GetFileName(path));
103
return {};
104
}
105
106
const u32 lba = CDImage::Position::FromBCD(entry.minute_bcd, entry.second_bcd, entry.frame_bcd).ToLBA();
107
108
CDImage::SubChannelQ subq;
109
std::memcpy(subq.data.data(), entry.data, sizeof(entry.data));
110
111
DEBUG_LOG("{:02x}:{:02x}:{:02x}: CRC {}", entry.minute_bcd, entry.second_bcd, entry.frame_bcd,
112
subq.IsCRCValid() ? "VALID" : "INVALID");
113
ret->m_replacement_subq.emplace(lba, subq);
114
}
115
116
INFO_LOG("Loaded {} replacement sectors from LSD '{}'", ret->m_replacement_subq.size(), path);
117
return ret;
118
}
119
120
bool CDROMSubQReplacement::LoadForImage(std::unique_ptr<CDROMSubQReplacement>* ret, CDImage* image,
121
std::string_view serial, std::string_view title, Error* error)
122
{
123
struct FileLoader
124
{
125
const char* extension;
126
std::unique_ptr<CDROMSubQReplacement> (*func)(const std::string&, std::FILE* fp, Error*);
127
};
128
static constexpr const FileLoader loaders[] = {
129
{"sbi", &CDROMSubQReplacement::LoadSBI},
130
{"lsd", &CDROMSubQReplacement::LoadLSD},
131
};
132
133
const std::string& image_path = image->GetPath();
134
std::string path;
135
136
// Try sbi/lsd in the directory first.
137
if (!CDImage::IsDeviceName(image_path.c_str()))
138
{
139
std::string display_name = FileSystem::GetDisplayNameFromPath(image_path);
140
141
for (const FileLoader& loader : loaders)
142
{
143
path = Path::BuildRelativePath(
144
image_path, SmallString::from_format("{}.{}", Path::GetFileTitle(display_name), loader.extension));
145
if (const FileSystem::ManagedCFilePtr fp = FileSystem::OpenManagedCFile(path.c_str(), "rb"))
146
{
147
*ret = loader.func(path, fp.get(), error);
148
if (!static_cast<bool>(*ret))
149
Error::AddPrefixFmt(error, "Failed to load subchannel data from {}: ", Path::GetFileName(path));
150
151
return static_cast<bool>(*ret);
152
}
153
}
154
155
// For subimages, we need to check the suffix too.
156
if (image->HasSubImages())
157
{
158
for (const FileLoader& loader : loaders)
159
{
160
path = Path::BuildRelativePath(image_path,
161
SmallString::from_format("{}_{}.{}", Path::GetFileTitle(display_name),
162
image->GetCurrentSubImage() + 1, loader.extension));
163
if (const FileSystem::ManagedCFilePtr fp = FileSystem::OpenManagedCFile(path.c_str(), "rb"))
164
{
165
*ret = loader.func(path, fp.get(), error);
166
if (!static_cast<bool>(*ret))
167
Error::AddPrefixFmt(error, "Failed to load subchannel data from {}: ", Path::GetFileName(path));
168
169
return static_cast<bool>(*ret);
170
}
171
}
172
}
173
}
174
175
// If this fails, try the subchannel directory with serial/title.
176
if (!serial.empty())
177
{
178
for (const FileLoader& loader : loaders)
179
{
180
path = Path::Combine(EmuFolders::Subchannels, TinyString::from_format("{}.{}", serial, loader.extension));
181
if (const FileSystem::ManagedCFilePtr fp = FileSystem::OpenManagedCFile(path.c_str(), "rb"))
182
{
183
*ret = loader.func(path, fp.get(), error);
184
if (!static_cast<bool>(*ret))
185
Error::AddPrefixFmt(error, "Failed to load subchannel data from {}: ", Path::GetFileName(path));
186
187
return static_cast<bool>(*ret);
188
}
189
}
190
}
191
192
if (!title.empty())
193
{
194
for (const FileLoader& loader : loaders)
195
{
196
path = Path::Combine(EmuFolders::Subchannels, TinyString::from_format("{}.{}", title, loader.extension));
197
if (const FileSystem::ManagedCFilePtr fp = FileSystem::OpenManagedCFile(path.c_str(), "rb"))
198
{
199
*ret = loader.func(path, fp.get(), error);
200
if (!static_cast<bool>(*ret))
201
Error::AddPrefixFmt(error, "Failed to load subchannel data from {}: ", Path::GetFileName(path));
202
203
return static_cast<bool>(*ret);
204
}
205
}
206
}
207
208
// Nothing.
209
return true;
210
}
211
212
const CDImage::SubChannelQ* CDROMSubQReplacement::GetReplacementSubQ(u32 lba) const
213
{
214
const auto iter = m_replacement_subq.find(lba);
215
return (iter != m_replacement_subq.end()) ? &iter->second : nullptr;
216
}
217
218