Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/util/cd_image_mds.cpp
4212 views
1
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "assert.h"
5
#include "cd_image.h"
6
7
#include "common/error.h"
8
#include "common/file_system.h"
9
#include "common/log.h"
10
#include "common/path.h"
11
12
#include <algorithm>
13
#include <cerrno>
14
#include <map>
15
16
LOG_CHANNEL(CDImage);
17
18
namespace {
19
20
#pragma pack(push, 1)
21
struct TrackEntry
22
{
23
u8 track_type;
24
u8 has_subchannel_data;
25
u8 unk1;
26
u8 unk2;
27
u8 track_number;
28
u8 unk3[4];
29
u8 start_m;
30
u8 start_s;
31
u8 start_f;
32
u32 extra_offset;
33
u8 unk4[24];
34
u32 track_offset_in_mdf;
35
u8 unk5[36];
36
};
37
static_assert(sizeof(TrackEntry) == 0x50, "TrackEntry is 0x50 bytes");
38
#pragma pack(pop)
39
40
class CDImageMds : public CDImage
41
{
42
public:
43
CDImageMds();
44
~CDImageMds() override;
45
46
bool OpenAndParse(const char* filename, Error* error);
47
48
s64 GetSizeOnDisk() const override;
49
50
protected:
51
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
52
53
private:
54
std::FILE* m_mdf_file = nullptr;
55
u64 m_mdf_file_position = 0;
56
};
57
58
} // namespace
59
60
CDImageMds::CDImageMds() = default;
61
62
CDImageMds::~CDImageMds()
63
{
64
if (m_mdf_file)
65
std::fclose(m_mdf_file);
66
}
67
68
bool CDImageMds::OpenAndParse(const char* filename, Error* error)
69
{
70
std::FILE* mds_fp = FileSystem::OpenSharedCFile(filename, "rb", FileSystem::FileShareMode::DenyWrite, error);
71
if (!mds_fp)
72
{
73
Error::AddPrefixFmt(error, "Failed to open mds '{}': ", Path::GetFileName(filename));
74
return false;
75
}
76
77
std::optional<DynamicHeapArray<u8>> mds_data_opt(FileSystem::ReadBinaryFile(mds_fp));
78
std::fclose(mds_fp);
79
if (!mds_data_opt.has_value() || mds_data_opt->size() < 0x54)
80
{
81
ERROR_LOG("Failed to read mds file '{}'", Path::GetFileName(filename));
82
Error::SetStringFmt(error, "Failed to read mds file '{}'", filename);
83
return false;
84
}
85
86
std::string mdf_filename(Path::ReplaceExtension(filename, "mdf"));
87
m_mdf_file = FileSystem::OpenSharedCFile(mdf_filename.c_str(), "rb", FileSystem::FileShareMode::DenyWrite, error);
88
if (!m_mdf_file)
89
{
90
Error::AddPrefixFmt(error, "Failed to open mdf file '{}': ", Path::GetFileName(mdf_filename));
91
return false;
92
}
93
94
const DynamicHeapArray<u8>& mds = mds_data_opt.value();
95
static constexpr char expected_signature[] = "MEDIA DESCRIPTOR";
96
if (std::memcmp(&mds[0], expected_signature, sizeof(expected_signature) - 1) != 0)
97
{
98
ERROR_LOG("Incorrect signature in '{}'", Path::GetFileName(filename));
99
Error::SetStringFmt(error, "Incorrect signature in '{}'", Path::GetFileName(filename));
100
return false;
101
}
102
103
u32 session_offset;
104
std::memcpy(&session_offset, &mds[0x50], sizeof(session_offset));
105
if ((session_offset + 24) > mds.size())
106
{
107
ERROR_LOG("Invalid session offset in '{}'", Path::GetFileName(filename));
108
Error::SetStringFmt(error, "Invalid session offset in '{}'", Path::GetFileName(filename));
109
return false;
110
}
111
112
u16 track_count;
113
u32 track_offset;
114
std::memcpy(&track_count, &mds[session_offset + 14], sizeof(track_count));
115
std::memcpy(&track_offset, &mds[session_offset + 20], sizeof(track_offset));
116
if (track_count > 99 || track_offset >= mds.size())
117
{
118
ERROR_LOG("Invalid track count/block offset {}/{} in '{}'", track_count, track_offset, Path::GetFileName(filename));
119
Error::SetStringFmt(error, "Invalid track count/block offset {}/{} in '{}'", track_count, track_offset,
120
Path::GetFileName(filename));
121
return false;
122
}
123
124
while ((track_offset + sizeof(TrackEntry)) <= mds.size())
125
{
126
TrackEntry track;
127
std::memcpy(&track, &mds[track_offset], sizeof(track));
128
if (track.track_number < 0xA0)
129
break;
130
131
track_offset += sizeof(TrackEntry);
132
}
133
134
for (u32 track_number = 1; track_number <= track_count; track_number++)
135
{
136
if ((track_offset + sizeof(TrackEntry)) > mds.size())
137
{
138
ERROR_LOG("End of file in '{}' at track {}", Path::GetFileName(filename), track_number);
139
Error::SetStringFmt(error, "End of file in '{}' at track {}", Path::GetFileName(filename), track_number);
140
return false;
141
}
142
143
TrackEntry track;
144
std::memcpy(&track, &mds[track_offset], sizeof(track));
145
track_offset += sizeof(TrackEntry);
146
147
if (PackedBCDToBinary(track.track_number) != track_number)
148
{
149
ERROR_LOG("Unexpected track number 0x{:02X} in track {}", track.track_number, track_number);
150
Error::SetStringFmt(error, "Unexpected track number 0x{:02X} in track {}", track.track_number, track_number);
151
return false;
152
}
153
154
const bool contains_subchannel = (track.has_subchannel_data != 0);
155
const u32 track_sector_size = (contains_subchannel ? 2448 : RAW_SECTOR_SIZE);
156
const TrackMode mode = (track.track_type == 0xA9) ? TrackMode::Audio : TrackMode::Mode2Raw;
157
158
if ((track.extra_offset + sizeof(u32) + sizeof(u32)) > mds.size())
159
{
160
ERROR_LOG("Invalid extra offset {} in track {}", track.extra_offset, track_number);
161
Error::SetStringFmt(error, "Invalid extra offset {} in track {}", track.extra_offset, track_number);
162
return false;
163
}
164
165
u32 track_start_lba = Position::FromBCD(track.start_m, track.start_s, track.start_f).ToLBA();
166
u32 track_file_offset = track.track_offset_in_mdf;
167
168
u32 track_pregap;
169
u32 track_length;
170
std::memcpy(&track_pregap, &mds[track.extra_offset], sizeof(track_pregap));
171
std::memcpy(&track_length, &mds[track.extra_offset + sizeof(u32)], sizeof(track_length));
172
173
// precompute subchannel q flags for the whole track
174
// todo: pull from mds?
175
SubChannelQ::Control control{};
176
control.data = mode != TrackMode::Audio;
177
178
// create the index for the pregap
179
if (track_pregap > 0)
180
{
181
if (track_pregap > track_start_lba)
182
{
183
ERROR_LOG("Track pregap {} is too large for start lba {}", track_pregap, track_start_lba);
184
Error::SetStringFmt(error, "Track pregap {} is too large for start lba {}", track_pregap, track_start_lba);
185
return false;
186
}
187
188
Index pregap_index = {};
189
pregap_index.start_lba_on_disc = track_start_lba - track_pregap;
190
pregap_index.start_lba_in_track = static_cast<LBA>(-static_cast<s32>(track_pregap));
191
pregap_index.length = track_pregap;
192
pregap_index.track_number = track_number;
193
pregap_index.index_number = 0;
194
pregap_index.mode = mode;
195
pregap_index.submode = CDImage::SubchannelMode::None;
196
pregap_index.control.bits = control.bits;
197
pregap_index.is_pregap = true;
198
199
const bool pregap_in_file = (track_number > 1);
200
if (pregap_in_file)
201
{
202
pregap_index.file_index = 0;
203
pregap_index.file_offset = track_file_offset;
204
pregap_index.file_sector_size = track_sector_size;
205
track_file_offset += track_pregap * track_sector_size;
206
}
207
208
m_indices.push_back(pregap_index);
209
}
210
211
// add the track itself
212
m_tracks.push_back(Track{static_cast<u32>(track_number), track_start_lba, static_cast<u32>(m_indices.size()),
213
static_cast<u32>(track_length), mode, SubchannelMode::None, control});
214
215
// how many indices in this track?
216
Index last_index;
217
last_index.start_lba_on_disc = track_start_lba;
218
last_index.start_lba_in_track = 0;
219
last_index.track_number = track_number;
220
last_index.index_number = 1;
221
last_index.file_index = 0;
222
last_index.file_sector_size = track_sector_size;
223
last_index.file_offset = track_file_offset;
224
last_index.mode = mode;
225
last_index.submode = CDImage::SubchannelMode::None;
226
last_index.control.bits = control.bits;
227
last_index.is_pregap = false;
228
last_index.length = track_length;
229
m_indices.push_back(last_index);
230
}
231
232
if (m_tracks.empty())
233
{
234
ERROR_LOG("File '{}' contains no tracks", Path::GetFileName(filename));
235
Error::SetStringFmt(error, "File '{}' contains no tracks", Path::GetFileName(filename));
236
return false;
237
}
238
239
m_lba_count = m_tracks.back().start_lba + m_tracks.back().length;
240
AddLeadOutIndex();
241
242
return Seek(1, Position{0, 0, 0});
243
}
244
245
bool CDImageMds::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
246
{
247
const u64 file_position = index.file_offset + (static_cast<u64>(lba_in_index) * index.file_sector_size);
248
if (m_mdf_file_position != file_position)
249
{
250
if (std::fseek(m_mdf_file, static_cast<long>(file_position), SEEK_SET) != 0)
251
return false;
252
253
m_mdf_file_position = file_position;
254
}
255
256
// we don't want the subchannel data
257
const u32 read_size = RAW_SECTOR_SIZE;
258
if (std::fread(buffer, read_size, 1, m_mdf_file) != 1)
259
{
260
std::fseek(m_mdf_file, static_cast<long>(m_mdf_file_position), SEEK_SET);
261
return false;
262
}
263
264
m_mdf_file_position += read_size;
265
return true;
266
}
267
268
s64 CDImageMds::GetSizeOnDisk() const
269
{
270
return FileSystem::FSize64(m_mdf_file);
271
}
272
273
std::unique_ptr<CDImage> CDImage::OpenMdsImage(const char* path, Error* error)
274
{
275
std::unique_ptr<CDImageMds> image = std::make_unique<CDImageMds>();
276
if (!image->OpenAndParse(path, error))
277
return {};
278
279
return image;
280
}
281
282