Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/util/cd_image_ppf.cpp
7322 views
1
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "cd_image.h"
5
6
#include "common/assert.h"
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
#include <span>
16
#include <unordered_map>
17
#include <vector>
18
19
LOG_CHANNEL(CDImage);
20
21
namespace {
22
23
enum : u32
24
{
25
DESC_SIZE = 50,
26
BLOCKCHECK_SIZE = 1024
27
};
28
29
class CDImagePPF : public CDImage
30
{
31
public:
32
CDImagePPF();
33
~CDImagePPF() override;
34
35
bool Open(const char* filename, std::unique_ptr<CDImage> parent_image, Error* error);
36
37
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
38
bool HasSubchannelData() const override;
39
s64 GetSizeOnDisk() const override;
40
41
std::string GetSubImageTitle(u32 index) const override;
42
43
PrecacheResult Precache(ProgressCallback* progress, Error* error) override;
44
45
protected:
46
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
47
48
private:
49
bool ReadV1Patch(std::FILE* fp, Error* error);
50
bool ReadV2Patch(std::FILE* fp, Error* error);
51
bool ReadV3Patch(std::FILE* fp, Error* error);
52
u32 ReadFileIDDiz(std::FILE* fp, u32 version);
53
54
bool AddPatch(u64 offset, std::span<const u8> patch, std::span<const u8> undo_data, Error* error);
55
56
std::unique_ptr<CDImage> m_parent_image;
57
std::vector<u8> m_replacement_data;
58
std::unordered_map<u32, u32> m_replacement_map;
59
s64 m_patch_size = 0;
60
u32 m_replacement_offset = 0;
61
};
62
63
} // namespace
64
65
CDImagePPF::CDImagePPF() = default;
66
67
CDImagePPF::~CDImagePPF() = default;
68
69
bool CDImagePPF::Open(const char* filename, std::unique_ptr<CDImage> parent_image, Error* error)
70
{
71
auto fp = FileSystem::OpenManagedSharedCFile(filename, "rb", FileSystem::FileShareMode::DenyWrite, error);
72
if (!fp)
73
{
74
Error::AddPrefixFmt(error, "Failed to open '{}'", Path::GetFileName(filename));
75
return false;
76
}
77
78
m_patch_size = FileSystem::FSize64(fp.get());
79
80
u32 magic;
81
if (std::fread(&magic, sizeof(magic), 1, fp.get()) != 1)
82
{
83
Error::SetErrno(error, "Failed to read PPF magic: ", errno);
84
return false;
85
}
86
87
// work out the offset from the start of the parent image which we need to patch
88
// i.e. the two second implicit pregap on data sectors
89
if (parent_image->GetTrack(1).mode != TrackMode::Audio)
90
m_replacement_offset = parent_image->GetIndex(1).start_lba_on_disc;
91
92
// copy all the stuff from the parent image
93
m_filename = parent_image->GetPath();
94
m_tracks = parent_image->GetTracks();
95
m_indices = parent_image->GetIndices();
96
m_parent_image = std::move(parent_image);
97
98
if (magic == 0x33465050) // PPF3
99
return ReadV3Patch(fp.get(), error);
100
else if (magic == 0x32465050) // PPF2
101
return ReadV2Patch(fp.get(), error);
102
else if (magic == 0x31465050) // PPF1
103
return ReadV1Patch(fp.get(), error);
104
105
Error::SetStringFmt(error, "Unknown PPF magic {:08X}", magic);
106
return false;
107
}
108
109
u32 CDImagePPF::ReadFileIDDiz(std::FILE* fp, u32 version)
110
{
111
const int lenidx = (version == 2) ? 4 : 2;
112
113
u32 magic;
114
if (std::fseek(fp, -(lenidx + 4), SEEK_END) != 0 || std::fread(&magic, sizeof(magic), 1, fp) != 1) [[unlikely]]
115
{
116
WARNING_LOG("Failed to read diz magic");
117
return 0;
118
}
119
120
if (magic != 0x5A49442E) // .DIZ
121
return 0;
122
123
u32 dlen = 0;
124
if (std::fseek(fp, -lenidx, SEEK_END) != 0 || std::fread(&dlen, lenidx, 1, fp) != 1) [[unlikely]]
125
{
126
WARNING_LOG("Failed to read diz length");
127
return 0;
128
}
129
130
if (dlen > static_cast<u32>(std::ftell(fp))) [[unlikely]]
131
{
132
WARNING_LOG("diz length out of range");
133
return 0;
134
}
135
136
std::string fdiz;
137
fdiz.resize(dlen);
138
if (std::fseek(fp, -(lenidx + 16 + static_cast<int>(dlen)), SEEK_END) != 0 ||
139
std::fread(fdiz.data(), 1, dlen, fp) != dlen) [[unlikely]]
140
{
141
WARNING_LOG("Failed to read fdiz");
142
return 0;
143
}
144
145
INFO_LOG("File_Id.diz: {}", fdiz);
146
return dlen;
147
}
148
149
bool CDImagePPF::ReadV1Patch(std::FILE* fp, Error* error)
150
{
151
char desc[DESC_SIZE + 1] = {};
152
if (std::fseek(fp, 6, SEEK_SET) != 0 || std::fread(desc, sizeof(char), DESC_SIZE, fp) != DESC_SIZE) [[unlikely]]
153
{
154
Error::SetErrno(error, "Failed to read description: ", errno);
155
return false;
156
}
157
158
u32 filelen;
159
if (std::fseek(fp, 0, SEEK_END) != 0 || (filelen = static_cast<u32>(std::ftell(fp))) == 0 || filelen < 56)
160
[[unlikely]]
161
{
162
Error::SetErrno(error, "Invalid ppf file: ", errno);
163
return false;
164
}
165
166
u32 count = filelen - 56;
167
if (count <= 0)
168
{
169
Error::SetStringView(error, "Invalid count/filelen");
170
return false;
171
}
172
173
if (std::fseek(fp, 56, SEEK_SET) != 0)
174
{
175
Error::SetErrno(error, "Failed to seek to patch data: ", errno);
176
return false;
177
}
178
179
std::vector<u8> temp;
180
while (count > 0)
181
{
182
u32 offset;
183
u8 chunk_size;
184
if (std::fread(&offset, sizeof(offset), 1, fp) != 1 || std::fread(&chunk_size, sizeof(chunk_size), 1, fp) != 1)
185
[[unlikely]]
186
{
187
Error::SetErrno(error, "Incomplete ppf: ", errno);
188
return false;
189
}
190
191
temp.resize(chunk_size);
192
if (std::fread(temp.data(), 1, chunk_size, fp) != chunk_size) [[unlikely]]
193
{
194
Error::SetErrno(error, "Failed to read patch data: ", errno);
195
return false;
196
}
197
198
if (!AddPatch(offset, temp, {}, error)) [[unlikely]]
199
return false;
200
201
count -= sizeof(offset) + sizeof(chunk_size) + chunk_size;
202
}
203
204
INFO_LOG("Loaded {} replacement sectors from version 1 PPF", m_replacement_map.size());
205
return true;
206
}
207
208
bool CDImagePPF::ReadV2Patch(std::FILE* fp, Error* error)
209
{
210
char desc[DESC_SIZE + 1] = {};
211
if (std::fseek(fp, 6, SEEK_SET) != 0 || std::fread(desc, sizeof(char), DESC_SIZE, fp) != DESC_SIZE) [[unlikely]]
212
{
213
Error::SetErrno(error, "Failed to read description: ", errno);
214
return false;
215
}
216
217
INFO_LOG("Patch description: {}", desc);
218
219
const u32 idlen = ReadFileIDDiz(fp, 2);
220
221
u32 origlen;
222
if (std::fseek(fp, 56, SEEK_SET) != 0 || std::fread(&origlen, sizeof(origlen), 1, fp) != 1) [[unlikely]]
223
{
224
Error::SetErrno(error, "Failed to read size: ", errno);
225
return false;
226
}
227
228
std::vector<u8> temp;
229
temp.resize(BLOCKCHECK_SIZE);
230
if (std::fread(temp.data(), 1, BLOCKCHECK_SIZE, fp) != BLOCKCHECK_SIZE) [[unlikely]]
231
{
232
Error::SetErrno(error, "Failed to read blockcheck data: ", errno);
233
return false;
234
}
235
236
// do blockcheck
237
{
238
u32 blockcheck_src_sector = 16 + m_replacement_offset;
239
u32 blockcheck_src_offset = 32;
240
241
std::vector<u8> src_sector(RAW_SECTOR_SIZE);
242
if (m_parent_image->Seek(blockcheck_src_sector) && m_parent_image->ReadRawSector(src_sector.data(), nullptr))
243
{
244
if (std::memcmp(&src_sector[blockcheck_src_offset], temp.data(), BLOCKCHECK_SIZE) != 0)
245
WARNING_LOG("Blockcheck failed. The patch may not apply correctly.");
246
}
247
else
248
{
249
WARNING_LOG("Failed to read blockcheck sector {}", blockcheck_src_sector);
250
}
251
}
252
253
u32 filelen;
254
if (std::fseek(fp, 0, SEEK_END) != 0 || (filelen = static_cast<u32>(std::ftell(fp))) == 0 || filelen < 1084)
255
[[unlikely]]
256
{
257
Error::SetErrno(error, "Invalid ppf file: ", errno);
258
return false;
259
}
260
261
u32 count = filelen - 1084;
262
if (idlen > 0)
263
count -= (idlen + 38);
264
265
if (count <= 0)
266
return false;
267
268
if (std::fseek(fp, 1084, SEEK_SET) != 0)
269
return false;
270
271
while (count > 0)
272
{
273
u32 offset;
274
u8 chunk_size;
275
if (std::fread(&offset, sizeof(offset), 1, fp) != 1 || std::fread(&chunk_size, sizeof(chunk_size), 1, fp) != 1)
276
[[unlikely]]
277
{
278
Error::SetErrno(error, "Incomplete ppf: ", errno);
279
return false;
280
}
281
282
temp.resize(chunk_size);
283
if (std::fread(temp.data(), 1, chunk_size, fp) != chunk_size) [[unlikely]]
284
{
285
Error::SetErrno(error, "Failed to read patch data: ", errno);
286
return false;
287
}
288
289
if (!AddPatch(offset, temp, {}, error))
290
return false;
291
292
count -= sizeof(offset) + sizeof(chunk_size) + chunk_size;
293
}
294
295
INFO_LOG("Loaded {} replacement sectors from version 2 PPF", m_replacement_map.size());
296
return true;
297
}
298
299
bool CDImagePPF::ReadV3Patch(std::FILE* fp, Error* error)
300
{
301
char desc[DESC_SIZE + 1] = {};
302
if (std::fseek(fp, 6, SEEK_SET) != 0 || std::fread(desc, sizeof(char), DESC_SIZE, fp) != DESC_SIZE)
303
{
304
Error::SetErrno(error, "Failed to read description: ", errno);
305
return false;
306
}
307
308
INFO_LOG("Patch description: {}", desc);
309
310
u32 idlen = ReadFileIDDiz(fp, 3);
311
312
u8 image_type;
313
u8 block_check;
314
u8 undo;
315
if (std::fseek(fp, 56, SEEK_SET) != 0 || std::fread(&image_type, sizeof(image_type), 1, fp) != 1 ||
316
std::fread(&block_check, sizeof(block_check), 1, fp) != 1 || std::fread(&undo, sizeof(undo), 1, fp) != 1)
317
{
318
Error::SetErrno(error, "Failed to read headers: ", errno);
319
return false;
320
}
321
322
// TODO: Blockcheck
323
324
std::fseek(fp, 0, SEEK_END);
325
u32 count = static_cast<u32>(std::ftell(fp));
326
327
u32 seekpos = (block_check) ? 1084 : 60;
328
if (seekpos >= count)
329
{
330
Error::SetStringView(error, "File is too short");
331
return false;
332
}
333
334
count -= seekpos;
335
if (idlen > 0)
336
{
337
const u32 extralen = idlen + 18 + 16 + 2;
338
if (count < extralen)
339
{
340
Error::SetStringView(error, "File is too short (diz)");
341
return false;
342
}
343
344
count -= extralen;
345
}
346
347
if (std::fseek(fp, seekpos, SEEK_SET) != 0)
348
{
349
Error::SetErrno(error, "Failed to seek to patch data: ", errno);
350
return false;
351
}
352
353
std::vector<u8> temp;
354
355
while (count > 0)
356
{
357
u64 offset;
358
u8 chunk_size;
359
if (std::fread(&offset, sizeof(offset), 1, fp) != 1 || std::fread(&chunk_size, sizeof(chunk_size), 1, fp) != 1)
360
{
361
Error::SetErrno(error, "Incomplete ppf: ", errno);
362
return false;
363
}
364
365
// undo data is stored after patch data if present
366
const size_t read_chunk_size = undo ? (static_cast<size_t>(chunk_size) * 2) : chunk_size;
367
temp.resize(read_chunk_size);
368
if (std::fread(temp.data(), 1, read_chunk_size, fp) != read_chunk_size)
369
{
370
Error::SetErrno(error, "Failed to read patch data: ", errno);
371
return false;
372
}
373
374
std::span<const u8> patch_span = temp;
375
std::span<const u8> undo_span;
376
if (undo)
377
{
378
undo_span = patch_span.subspan(chunk_size, chunk_size);
379
patch_span = patch_span.subspan(0, chunk_size);
380
}
381
382
if (!AddPatch(offset, patch_span, undo_span, error))
383
return false;
384
385
count -= sizeof(offset) + sizeof(chunk_size) + static_cast<u32>(read_chunk_size);
386
}
387
388
INFO_LOG("Loaded {} replacement sectors from version 3 PPF", m_replacement_map.size());
389
return true;
390
}
391
392
bool CDImagePPF::AddPatch(u64 offset, std::span<const u8> patch, std::span<const u8> undo_data, Error* error)
393
{
394
DEBUG_LOG("Starting applying patch{} of {} bytes at at offset {}", patch.empty() ? "" : " with undo data",
395
patch.size(), offset);
396
397
DebugAssert(undo_data.empty() || patch.size() == undo_data.size());
398
399
u32 remaining_patch_size = static_cast<u32>(patch.size());
400
u32 patch_offset = 0;
401
while (remaining_patch_size > 0)
402
{
403
const u32 sector_index = Truncate32(offset / RAW_SECTOR_SIZE) + m_replacement_offset;
404
const u32 sector_offset = Truncate32(offset % RAW_SECTOR_SIZE);
405
if (sector_index >= m_parent_image->GetLBACount())
406
{
407
WARNING_LOG("Ignoring out-of-range sector {} (max {})", sector_index, m_parent_image->GetLBACount());
408
return true;
409
}
410
411
const u32 bytes_to_patch = std::min(static_cast<u32>(patch.size()), RAW_SECTOR_SIZE - sector_offset);
412
413
auto iter = m_replacement_map.find(sector_index);
414
if (iter == m_replacement_map.end())
415
{
416
const u32 replacement_buffer_start = static_cast<u32>(m_replacement_data.size());
417
m_replacement_data.resize(m_replacement_data.size() + RAW_SECTOR_SIZE);
418
if (!m_parent_image->Seek(sector_index) ||
419
!m_parent_image->ReadRawSector(&m_replacement_data[replacement_buffer_start], nullptr))
420
{
421
Error::SetStringFmt(error, "Failed to read sector {} from parent image", sector_index);
422
return false;
423
}
424
425
iter = m_replacement_map.emplace(sector_index, replacement_buffer_start).first;
426
}
427
428
// verify undo data, but don't reject if it doesn't match
429
if (!undo_data.empty())
430
{
431
if (std::memcmp(&undo_data[patch_offset], &m_replacement_data[iter->second + sector_offset], bytes_to_patch) != 0)
432
{
433
WARNING_LOG("Original file data does not match undo data for patch at offset {} size {}", offset,
434
bytes_to_patch);
435
}
436
}
437
438
// patch it!
439
DEBUG_LOG(" Patching {} bytes at sector {} offset {}", bytes_to_patch, sector_index, sector_offset);
440
std::memcpy(&m_replacement_data[iter->second + sector_offset], &patch[patch_offset], bytes_to_patch);
441
offset += bytes_to_patch;
442
patch_offset += bytes_to_patch;
443
remaining_patch_size -= bytes_to_patch;
444
}
445
446
return true;
447
}
448
449
bool CDImagePPF::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
450
{
451
return m_parent_image->ReadSubChannelQ(subq, index, lba_in_index);
452
}
453
454
bool CDImagePPF::HasSubchannelData() const
455
{
456
return m_parent_image->HasSubchannelData();
457
}
458
459
std::string CDImagePPF::GetSubImageTitle(u32 index) const
460
{
461
// We only support a single sub-image for patched games.
462
return (index == 0) ? m_parent_image->GetSubImageTitle(index) : std::string();
463
}
464
465
CDImage::PrecacheResult CDImagePPF::Precache(ProgressCallback* progress, Error* error)
466
{
467
return m_parent_image->Precache(progress, error);
468
}
469
470
bool CDImagePPF::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
471
{
472
DebugAssert(index.file_index == 0);
473
474
const u32 sector_number = index.start_lba_on_disc + lba_in_index;
475
const auto it = m_replacement_map.find(sector_number);
476
if (it == m_replacement_map.end())
477
return m_parent_image->ReadSectorFromIndex(buffer, index, lba_in_index);
478
479
std::memcpy(buffer, &m_replacement_data[it->second], RAW_SECTOR_SIZE);
480
return true;
481
}
482
483
s64 CDImagePPF::GetSizeOnDisk() const
484
{
485
return m_patch_size + m_parent_image->GetSizeOnDisk();
486
}
487
488
std::unique_ptr<CDImage> CDImage::OverlayPPFPatch(const char* path, std::unique_ptr<CDImage> parent_image, Error* error)
489
{
490
std::unique_ptr<CDImagePPF> ppf_image = std::make_unique<CDImagePPF>();
491
if (!ppf_image->Open(path, std::move(parent_image), error))
492
return {};
493
494
return ppf_image;
495
}
496
497