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