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