Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/FileSystems/BlockDevices.cpp
5659 views
1
// Copyright (c) 2012- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include <algorithm>
19
#include <cstring>
20
21
#include "Common/Data/Text/I18n.h"
22
#include "Common/System/OSD.h"
23
#include "Common/Log.h"
24
#include "Common/Swap.h"
25
#include "Common/Data/Text/Parsers.h"
26
#include "Common/File/FileUtil.h"
27
#include "Common/File/DirListing.h"
28
#include "Common/StringUtils.h"
29
#include "Core/Loaders.h"
30
#include "Core/FileSystems/BlockDevices.h"
31
#include "libchdr/chd.h"
32
33
extern "C"
34
{
35
#include "zlib.h"
36
#include "ext/libkirk/amctrl.h"
37
#include "ext/libkirk/kirk_engine.h"
38
};
39
40
BlockDevice *ConstructBlockDevice(FileLoader *fileLoader, std::string *errorString) {
41
if (!fileLoader->Exists()) {
42
// Shouldn't get here really.
43
*errorString = "File doesn't exist";
44
return nullptr;
45
}
46
if (fileLoader->IsDirectory()) {
47
*errorString = "Can't open directory directly as block device: ";
48
*errorString += fileLoader->GetPath().ToString();
49
return nullptr;
50
}
51
52
char buffer[8]{};
53
size_t size = fileLoader->ReadAt(0, 1, 8, buffer);
54
if (size != 8) {
55
// Bad or empty file
56
*errorString = "File is empty";
57
return nullptr;
58
}
59
60
BlockDevice *device = nullptr;
61
62
// Check for CISO
63
if (!memcmp(buffer, "CISO", 4)) {
64
device = new CISOFileBlockDevice(fileLoader);
65
} else if (!memcmp(buffer, "\x00PBP", 4)) {
66
uint32_t psarOffset = 0;
67
size = fileLoader->ReadAt(0x24, 1, 4, &psarOffset);
68
if (size == 4 && psarOffset < fileLoader->FileSize())
69
device = new NPDRMDemoBlockDevice(fileLoader);
70
} else if (!memcmp(buffer, "MComprHD", 8)) {
71
device = new CHDFileBlockDevice(fileLoader);
72
}
73
74
// No check above passed, should be just a regular ISO file. Let's open it as a plain block device and let the other systems take over.
75
if (!device) {
76
device = new FileBlockDevice(fileLoader);
77
}
78
79
if (!device->IsOK()) {
80
*errorString = device->ErrorString();
81
delete device;
82
return nullptr;
83
}
84
85
return device;
86
}
87
88
void BlockDevice::NotifyReadError() {
89
if (!reportedError_) {
90
auto err = GetI18NCategory(I18NCat::ERRORS);
91
g_OSD.Show(OSDType::MESSAGE_WARNING, err->T("Game disc read error - ISO corrupt"), fileLoader_->GetPath().ToVisualString(), 6.0f);
92
reportedError_ = true;
93
}
94
}
95
96
FileBlockDevice::FileBlockDevice(FileLoader *fileLoader)
97
: BlockDevice(fileLoader) {
98
filesize_ = fileLoader->FileSize();
99
}
100
101
FileBlockDevice::~FileBlockDevice() {}
102
103
bool FileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached) {
104
FileLoader::Flags flags = uncached ? FileLoader::Flags::HINT_UNCACHED : FileLoader::Flags::NONE;
105
size_t retval = fileLoader_->ReadAt((u64)blockNumber * (u64)GetBlockSize(), 1, 2048, outPtr, flags);
106
if (retval != 2048) {
107
DEBUG_LOG(Log::FileSystem, "Could not read 2048 byte block, at block offset %d. Only got %d bytes", blockNumber, (int)retval);
108
return false;
109
}
110
return true;
111
}
112
113
bool FileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {
114
size_t retval = fileLoader_->ReadAt((u64)minBlock * (u64)GetBlockSize(), 2048, count, outPtr);
115
if (retval != (size_t)count) {
116
ERROR_LOG(Log::FileSystem, "Could not read %d blocks, at block offset %d. Only got %d blocks", count, minBlock, (int)retval);
117
return false;
118
}
119
return true;
120
}
121
122
// .CSO format
123
124
// compressed ISO(9660) header format
125
typedef struct ciso_header
126
{
127
unsigned char magic[4]; // +00 : 'C','I','S','O'
128
u32_le header_size; // +04 : header size (==0x18)
129
u64_le total_bytes; // +08 : number of original data size
130
u32_le block_size; // +10 : number of compressed block size
131
unsigned char ver; // +14 : version 01
132
unsigned char align; // +15 : align of index value
133
unsigned char rsv_06[2]; // +16 : reserved
134
#if 0
135
// INDEX BLOCK
136
unsigned int index[0]; // +18 : block[0] index
137
unsigned int index[1]; // +1C : block[1] index
138
:
139
:
140
unsigned int index[last]; // +?? : block[last]
141
unsigned int index[last+1]; // +?? : end of last data point
142
// DATA BLOCK
143
unsigned char data[]; // +?? : compressed or plain sector data
144
#endif
145
} CISO_H;
146
147
148
// TODO: Need much better error handling.
149
150
static const u32 CSO_READ_BUFFER_SIZE = 256 * 1024;
151
152
CISOFileBlockDevice::CISOFileBlockDevice(FileLoader *fileLoader)
153
: BlockDevice(fileLoader)
154
{
155
// CISO format is fairly simple, but most tools do not write the header_size.
156
157
CISO_H hdr;
158
size_t readSize = fileLoader->ReadAt(0, sizeof(CISO_H), 1, &hdr);
159
if (readSize != 1 || memcmp(hdr.magic, "CISO", 4) != 0) {
160
errorString_ = "Invalid CSO!";
161
return;
162
}
163
if (hdr.ver > 1) {
164
errorString_ = "CSO version too high!";
165
return;
166
}
167
168
frameSize = hdr.block_size;
169
if ((frameSize & (frameSize - 1)) != 0) {
170
errorString_ = StringFromFormat("CSO block size %i unsupported, must be a power of two", frameSize);
171
return;
172
} else if (frameSize < 0x800) {
173
errorString_ = StringFromFormat("CSO block size %i unsupported, must be at least one sector", frameSize);
174
return;
175
}
176
177
// Determine the translation from block to frame.
178
blockShift = 0;
179
for (u32 i = frameSize; i > 0x800; i >>= 1)
180
++blockShift;
181
182
indexShift = hdr.align;
183
const u64 totalSize = hdr.total_bytes;
184
numFrames = (u32)((totalSize + frameSize - 1) / frameSize);
185
numBlocks = (u32)(totalSize / GetBlockSize());
186
VERBOSE_LOG(Log::Loader, "CSO numBlocks=%i numFrames=%i align=%i", numBlocks, numFrames, indexShift);
187
188
// We might read a bit of alignment too, so be prepared.
189
if (frameSize + (1 << indexShift) < CSO_READ_BUFFER_SIZE)
190
readBuffer = new u8[CSO_READ_BUFFER_SIZE];
191
else
192
readBuffer = new u8[frameSize + (1 << indexShift)];
193
zlibBuffer = new u8[frameSize + (1 << indexShift)];
194
zlibBufferFrame = numFrames;
195
196
const u32 indexSize = numFrames + 1;
197
const size_t headerEnd = hdr.ver > 1 ? (size_t)hdr.header_size : sizeof(hdr);
198
199
#if COMMON_LITTLE_ENDIAN
200
index = new u32[indexSize];
201
if (fileLoader->ReadAt(headerEnd, sizeof(u32), indexSize, index) != indexSize) {
202
NotifyReadError();
203
memset(index, 0, indexSize * sizeof(u32));
204
}
205
#else
206
index = new u32[indexSize];
207
u32_le *indexTemp = new u32_le[indexSize];
208
209
if (fileLoader->ReadAt(headerEnd, sizeof(u32), indexSize, indexTemp) != indexSize) {
210
NotifyReadError();
211
memset(indexTemp, 0, indexSize * sizeof(u32_le));
212
}
213
214
for (u32 i = 0; i < indexSize; i++)
215
index[i] = indexTemp[i];
216
217
delete[] indexTemp;
218
#endif
219
220
ver_ = hdr.ver;
221
222
// Double check that the CSO is not truncated. In most cases, this will be the exact size.
223
u64 fileSize = fileLoader->FileSize();
224
u64 lastIndexPos = index[indexSize - 1] & 0x7FFFFFFF;
225
u64 expectedFileSize = lastIndexPos << indexShift;
226
if (expectedFileSize > fileSize) {
227
errorString_ = StringFromFormat("CSO file incomplete: expected %s, but is %s", NiceSizeFormat(expectedFileSize).c_str(), NiceSizeFormat(fileSize).c_str());
228
return;
229
}
230
231
// all ok.
232
_dbg_assert_(errorString_.empty());
233
}
234
235
CISOFileBlockDevice::~CISOFileBlockDevice()
236
{
237
delete [] index;
238
delete [] readBuffer;
239
delete [] zlibBuffer;
240
}
241
242
bool CISOFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached)
243
{
244
FileLoader::Flags flags = uncached ? FileLoader::Flags::HINT_UNCACHED : FileLoader::Flags::NONE;
245
if ((u32)blockNumber >= numBlocks) {
246
memset(outPtr, 0, GetBlockSize());
247
return false;
248
}
249
250
const u32 frameNumber = blockNumber >> blockShift;
251
const u32 idx = index[frameNumber];
252
const u32 indexPos = idx & 0x7FFFFFFF;
253
const u32 nextIndexPos = index[frameNumber + 1] & 0x7FFFFFFF;
254
z_stream z{};
255
256
const u64 compressedReadPos = (u64)indexPos << indexShift;
257
const u64 compressedReadEnd = (u64)nextIndexPos << indexShift;
258
const size_t compressedReadSize = (size_t)(compressedReadEnd - compressedReadPos);
259
const u32 compressedOffset = (blockNumber & ((1 << blockShift) - 1)) * GetBlockSize();
260
261
bool plain = (idx & 0x80000000) != 0;
262
if (ver_ >= 2) {
263
// CSO v2+ requires blocks be uncompressed if large enough to be. High bit means other things.
264
plain = compressedReadSize >= frameSize;
265
}
266
if (plain) {
267
int readSize = (u32)fileLoader_->ReadAt(compressedReadPos + compressedOffset, 1, GetBlockSize(), outPtr, flags);
268
if (readSize < GetBlockSize())
269
memset(outPtr + readSize, 0, GetBlockSize() - readSize);
270
} else if (zlibBufferFrame == frameNumber) {
271
// We already have it. Just apply the offset and copy.
272
memcpy(outPtr, zlibBuffer + compressedOffset, GetBlockSize());
273
} else {
274
const u32 readSize = (u32)fileLoader_->ReadAt(compressedReadPos, 1, compressedReadSize, readBuffer, flags);
275
276
z.zalloc = Z_NULL;
277
z.zfree = Z_NULL;
278
z.opaque = Z_NULL;
279
if (inflateInit2(&z, -15) != Z_OK) {
280
ERROR_LOG(Log::Loader, "GetBlockSize() ERROR: %s\n", (z.msg) ? z.msg : "?");
281
NotifyReadError();
282
return false;
283
}
284
z.avail_in = readSize;
285
z.next_out = frameSize == (u32)GetBlockSize() ? outPtr : zlibBuffer;
286
z.avail_out = frameSize;
287
z.next_in = readBuffer;
288
289
int status = inflate(&z, Z_FINISH);
290
if (status != Z_STREAM_END) {
291
ERROR_LOG(Log::Loader, "block %d: inflate : %s[%d]\n", blockNumber, (z.msg) ? z.msg : "error", status);
292
NotifyReadError();
293
inflateEnd(&z);
294
memset(outPtr, 0, GetBlockSize());
295
return false;
296
}
297
if (z.total_out != frameSize) {
298
ERROR_LOG(Log::Loader, "block %d: block size error %d != %d\n", blockNumber, (u32)z.total_out, frameSize);
299
NotifyReadError();
300
inflateEnd(&z);
301
memset(outPtr, 0, GetBlockSize());
302
return false;
303
}
304
inflateEnd(&z);
305
306
if (frameSize != (u32)GetBlockSize()) {
307
zlibBufferFrame = frameNumber;
308
memcpy(outPtr, zlibBuffer + compressedOffset, GetBlockSize());
309
}
310
}
311
return true;
312
}
313
314
bool CISOFileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {
315
if (count == 1) {
316
return ReadBlock(minBlock, outPtr);
317
}
318
if (minBlock >= numBlocks) {
319
memset(outPtr, 0, GetBlockSize() * count);
320
return false;
321
}
322
323
const u32 lastBlock = std::min(minBlock + count, numBlocks) - 1;
324
const u32 missingBlocks = (lastBlock + 1 - minBlock) - count;
325
if (lastBlock < minBlock + count) {
326
memset(outPtr + GetBlockSize() * (count - missingBlocks), 0, GetBlockSize() * missingBlocks);
327
}
328
329
const u32 minFrameNumber = minBlock >> blockShift;
330
const u32 lastFrameNumber = lastBlock >> blockShift;
331
const u32 afterLastIndexPos = index[lastFrameNumber + 1] & 0x7FFFFFFF;
332
const u64 totalReadEnd = (u64)afterLastIndexPos << indexShift;
333
334
z_stream z{};
335
if (inflateInit2(&z, -15) != Z_OK) {
336
ERROR_LOG(Log::Loader, "Unable to initialize inflate: %s\n", (z.msg) ? z.msg : "?");
337
return false;
338
}
339
340
u64 readBufferStart = 0;
341
u64 readBufferEnd = 0;
342
u32 block = minBlock;
343
const u32 blocksPerFrame = 1 << blockShift;
344
for (u32 frame = minFrameNumber; frame <= lastFrameNumber; ++frame) {
345
const u32 idx = index[frame];
346
const u32 indexPos = idx & 0x7FFFFFFF;
347
const u32 nextIndexPos = index[frame + 1] & 0x7FFFFFFF;
348
349
const u64 frameReadPos = (u64)indexPos << indexShift;
350
const u64 frameReadEnd = (u64)nextIndexPos << indexShift;
351
const u32 frameReadSize = (u32)(frameReadEnd - frameReadPos);
352
const u32 frameBlockOffset = block & ((1 << blockShift) - 1);
353
const u32 frameBlocks = std::min(lastBlock - block + 1, blocksPerFrame - frameBlockOffset);
354
355
if (frameReadEnd > readBufferEnd) {
356
const s64 maxNeeded = totalReadEnd - frameReadPos;
357
const size_t chunkSize = (size_t)std::min(maxNeeded, (s64)std::max(frameReadSize, CSO_READ_BUFFER_SIZE));
358
359
const u32 readSize = (u32)fileLoader_->ReadAt(frameReadPos, 1, chunkSize, readBuffer);
360
if (readSize < chunkSize) {
361
memset(readBuffer + readSize, 0, chunkSize - readSize);
362
}
363
364
readBufferStart = frameReadPos;
365
readBufferEnd = frameReadPos + readSize;
366
}
367
368
u8 *rawBuffer = &readBuffer[frameReadPos - readBufferStart];
369
const int plain = idx & 0x80000000;
370
if (plain) {
371
memcpy(outPtr, rawBuffer + frameBlockOffset * GetBlockSize(), frameBlocks * GetBlockSize());
372
} else {
373
z.avail_in = frameReadSize;
374
z.next_out = frameBlocks == blocksPerFrame ? outPtr : zlibBuffer;
375
z.avail_out = frameSize;
376
z.next_in = rawBuffer;
377
378
int status = inflate(&z, Z_FINISH);
379
if (status != Z_STREAM_END) {
380
ERROR_LOG(Log::Loader, "Inflate frame %d: failed - %s[%d]\n", frame, (z.msg) ? z.msg : "error", status);
381
NotifyReadError();
382
memset(outPtr, 0, frameBlocks * GetBlockSize());
383
} else if (z.total_out != frameSize) {
384
ERROR_LOG(Log::Loader, "Inflate frame %d: block size error %d != %d\n", frame, (u32)z.total_out, frameSize);
385
NotifyReadError();
386
memset(outPtr, 0, frameBlocks * GetBlockSize());
387
} else if (frameBlocks != blocksPerFrame) {
388
memcpy(outPtr, zlibBuffer + frameBlockOffset * GetBlockSize(), frameBlocks * GetBlockSize());
389
// In case we end up reusing it in a single read later.
390
zlibBufferFrame = frame;
391
}
392
393
inflateReset(&z);
394
}
395
396
block += frameBlocks;
397
outPtr += frameBlocks * GetBlockSize();
398
}
399
400
inflateEnd(&z);
401
return true;
402
}
403
404
NPDRMDemoBlockDevice::NPDRMDemoBlockDevice(FileLoader *fileLoader)
405
: BlockDevice(fileLoader)
406
{
407
MAC_KEY mkey;
408
CIPHER_KEY ckey;
409
u8 np_header[256];
410
u32 tableOffset_, tableSize_;
411
412
fileLoader_->ReadAt(0x24, 1, 4, &psarOffset);
413
size_t readSize = fileLoader_->ReadAt(psarOffset, 1, 256, &np_header);
414
if (readSize != 256){
415
errorString_ = "Invalid NPUMDIMG header!";
416
return;
417
}
418
419
u32 psar_id;
420
fileLoader->ReadAt(psarOffset, 4, 1, &psar_id);
421
422
INFO_LOG(Log::Loader, "NPDRM: PSAR ID: %08x", psar_id);
423
// PS1 PSAR begins with "PSISOIMG0000"
424
if (psar_id == 'SISP') {
425
lbaSize_ = 0; // Mark invalid
426
ERROR_LOG(Log::Loader, "PSX not supported! Should have been caught earlier.");
427
errorString_ = "PSX ISOs not supported!";
428
return;
429
}
430
431
std::lock_guard<std::mutex> guard(mutex_);
432
433
// Local kirk instance to not clash with other block devices and other decryption things.
434
kirk_init(&kirk_);
435
436
// getkey
437
sceDrmBBMacInit(&mkey, 3);
438
sceDrmBBMacUpdate(&kirk_, &mkey, np_header, 0xc0);
439
bbmac_getkey(&kirk_, &mkey, np_header+0xc0, vkey);
440
441
// decrypt NP header
442
memcpy(hkey, np_header+0xa0, 0x10);
443
sceDrmBBCipherInit(&kirk_, &ckey, 1, 2, hkey, vkey, 0);
444
sceDrmBBCipherUpdate(&kirk_, &ckey, np_header+0x40, 0x60);
445
sceDrmBBCipherFinal(&ckey);
446
447
u32 lbaStart = *(u32*)(np_header+0x54); // LBA start
448
u32 lbaEnd = *(u32*)(np_header+0x64); // LBA end
449
lbaSize_ = (lbaEnd-lbaStart+1); // LBA size of ISO
450
blockLBAs_ = *(u32*)(np_header+0x0c); // block size in LBA
451
452
char psarStr[5] = {};
453
memcpy(psarStr, &psar_id, 4);
454
455
// Protect against a badly decrypted header, and send information through the assert about what's being played (implicitly).
456
_dbg_assert_msg_(blockLBAs_ <= 4096, "Bad blockLBAs in header: %08x (%s) psar: %s", blockLBAs_, fileLoader->GetPath().ToVisualString().c_str(), psarStr);
457
458
// When we remove the above assert, let's just try to survive.
459
if (blockLBAs_ > 4096) {
460
errorString_ = StringFromFormat("Bad blockLBAs in header: %08x (%s) psar: %s", blockLBAs_, fileLoader->GetPath().ToVisualString().c_str(), psarStr);
461
return;
462
}
463
464
blockSize_ = blockLBAs_ * 2048;
465
numBlocks_ = (lbaSize_ + blockLBAs_-1) / blockLBAs_; // total blocks;
466
467
blockBuf_ = new u8[blockSize_];
468
tempBuf_ = new u8[blockSize_];
469
470
tableOffset_ = *(u32*)(np_header+0x6c); // table offset
471
472
tableSize_ = numBlocks_ * 32;
473
table_ = new table_info[numBlocks_];
474
475
readSize = fileLoader_->ReadAt(psarOffset + tableOffset_, 1, tableSize_, table_);
476
if (readSize != tableSize_){
477
errorString_ = "Invalid NPUMDIMG table!";
478
return;
479
}
480
481
u32 *p = (u32*)table_;
482
u32 i, k0, k1, k2, k3;
483
for (i=0; i<numBlocks_; i++){
484
k0 = p[0]^p[1];
485
k1 = p[1]^p[2];
486
k2 = p[0]^p[3];
487
k3 = p[2]^p[3];
488
p[4] ^= k3;
489
p[5] ^= k1;
490
p[6] ^= k2;
491
p[7] ^= k0;
492
p += 8;
493
}
494
495
currentBlock_ = -1;
496
_dbg_assert_(errorString_.empty());
497
}
498
499
NPDRMDemoBlockDevice::~NPDRMDemoBlockDevice() {
500
std::lock_guard<std::mutex> guard(mutex_);
501
delete [] table_;
502
delete [] tempBuf_;
503
delete [] blockBuf_;
504
}
505
506
int lzrc_decompress(void *out, int out_len, void *in, int in_len);
507
508
bool NPDRMDemoBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached) {
509
FileLoader::Flags flags = uncached ? FileLoader::Flags::HINT_UNCACHED : FileLoader::Flags::NONE;
510
std::lock_guard<std::mutex> guard(mutex_);
511
512
if (blockSize_ == 0) {
513
// Wasn't opened successfully.
514
return false;
515
}
516
517
int lba = blockNumber - currentBlock_;
518
if (lba >= 0 && lba < blockLBAs_){
519
memcpy(outPtr, blockBuf_ + lba*2048, 2048);
520
return true;
521
}
522
523
int block = blockNumber / blockLBAs_;
524
lba = blockNumber % blockLBAs_;
525
currentBlock_ = block * blockLBAs_;
526
527
if (table_[block].unk_1c != 0) {
528
if((u32)block == (numBlocks_ - 1))
529
return true; // demos make by fake_np
530
else
531
return false;
532
}
533
534
u8 *readBuf;
535
if (table_[block].size < blockSize_)
536
readBuf = tempBuf_;
537
else
538
readBuf = blockBuf_;
539
540
size_t readSize = fileLoader_->ReadAt(psarOffset+table_[block].offset, 1, table_[block].size, readBuf, flags);
541
if (readSize != (size_t)table_[block].size){
542
if((u32)block==(numBlocks_-1))
543
return true;
544
else
545
return false;
546
}
547
548
if ((table_[block].flag & 1) == 0) {
549
// skip mac check
550
}
551
552
if ((table_[block].flag & 4) == 0) {
553
CIPHER_KEY ckey;
554
sceDrmBBCipherInit(&kirk_, &ckey, 1, 2, hkey, vkey, table_[block].offset>>4);
555
sceDrmBBCipherUpdate(&kirk_, &ckey, readBuf, table_[block].size);
556
sceDrmBBCipherFinal(&ckey);
557
}
558
559
if (table_[block].size < blockSize_) {
560
int lzsize = lzrc_decompress(blockBuf_, 0x00100000, readBuf, table_[block].size);
561
if(lzsize != blockSize_){
562
ERROR_LOG(Log::Loader, "LZRC decompress error! lzsize=%d\n", lzsize);
563
NotifyReadError();
564
return false;
565
}
566
}
567
568
memcpy(outPtr, blockBuf_+lba*2048, 2048);
569
return true;
570
}
571
572
// static const UINT8 nullsha1[CHD_SHA1_BYTES] = { 0 };
573
574
struct CHDImpl {
575
chd_file *chd = nullptr;
576
const chd_header *header = nullptr;
577
};
578
579
struct ExtendedCoreFile {
580
core_file core; // Must be the first struct member, for some tricky pointer casts.
581
uint64_t seekPos;
582
};
583
584
CHDFileBlockDevice::CHDFileBlockDevice(FileLoader *fileLoader)
585
: BlockDevice(fileLoader), impl_(new CHDImpl()) {
586
Path paths[8];
587
paths[0] = fileLoader->GetPath();
588
int depth = 0;
589
590
core_file_ = new ExtendedCoreFile();
591
core_file_->core.argp = fileLoader;
592
core_file_->core.fsize = [](core_file *file) -> uint64_t {
593
FileLoader *loader = (FileLoader *)file->argp;
594
return loader->FileSize();
595
};
596
core_file_->core.fseek = [](core_file *file, int64_t offset, int seekType) -> int {
597
ExtendedCoreFile *coreFile = (ExtendedCoreFile *)file;
598
switch (seekType) {
599
case SEEK_SET:
600
coreFile->seekPos = offset;
601
break;
602
case SEEK_CUR:
603
coreFile->seekPos += offset;
604
break;
605
case SEEK_END:
606
{
607
FileLoader *loader = (FileLoader *)file->argp;
608
coreFile->seekPos = loader->FileSize() + offset;
609
break;
610
}
611
default:
612
break;
613
}
614
return 0;
615
};
616
core_file_->core.fread = [](void *out_data, size_t size, size_t count, core_file *file) {
617
ExtendedCoreFile *coreFile = (ExtendedCoreFile *)file;
618
FileLoader *loader = (FileLoader *)file->argp;
619
uint64_t totalSize = size * count;
620
loader->ReadAt(coreFile->seekPos, totalSize, out_data);
621
coreFile->seekPos += totalSize;
622
return size * count;
623
};
624
core_file_->core.fclose = [](core_file *file) {
625
ExtendedCoreFile *coreFile = (ExtendedCoreFile *)file;
626
delete coreFile;
627
return 0;
628
};
629
630
/*
631
// TODO: Support parent/child CHD files.
632
633
// Default, in case of failure
634
numBlocks = 0;
635
636
chd_header childHeader;
637
638
chd_error err = chd_read_header(paths[0].c_str(), &childHeader);
639
if (err != CHDERR_NONE) {
640
ERROR_LOG(Log::Loader, "Error loading CHD header for '%s': %s", paths[0].c_str(), chd_error_string(err));
641
NotifyReadError();
642
return;
643
}
644
645
if (memcmp(nullsha1, childHeader.parentsha1, sizeof(childHeader.sha1)) != 0) {
646
chd_header parentHeader;
647
648
// Look for parent CHD in current directory
649
Path chdDir = paths[0].NavigateUp();
650
651
std::vector<File::FileInfo> files;
652
if (File::GetFilesInDir(chdDir, &files)) {
653
parentHeader.length = 0;
654
655
for (const auto &file : files) {
656
std::string extension = file.fullName.GetFileExtension();
657
if (extension != ".chd") {
658
continue;
659
}
660
661
if (chd_read_header(filepath.c_str(), &parentHeader) == CHDERR_NONE &&
662
memcmp(parentHeader.sha1, childHeader.parentsha1, sizeof(parentHeader.sha1)) == 0) {
663
// ERROR_LOG(Log::Loader, "Checking '%s'", filepath.c_str());
664
paths[++depth] = filepath;
665
break;
666
}
667
}
668
669
// Check if parentHeader was opened
670
if (parentHeader.length == 0) {
671
ERROR_LOG(Log::Loader, "Error loading CHD '%s': parents not found", fileLoader->GetPath().c_str());
672
NotifyReadError();
673
return;
674
}
675
memcpy(childHeader.parentsha1, parentHeader.parentsha1, sizeof(childHeader.parentsha1));
676
} while (memcmp(nullsha1, childHeader.parentsha1, sizeof(childHeader.sha1)) != 0);
677
}
678
*/
679
680
chd_file *file = nullptr;
681
chd_error err = chd_open_core_file(&core_file_->core, CHD_OPEN_READ, NULL, &file);
682
if (err != CHDERR_NONE) {
683
errorString_ = StringFromFormat("CHD error: %s", paths[depth].c_str(), chd_error_string(err));
684
return;
685
}
686
687
impl_->chd = file;
688
impl_->header = chd_get_header(impl_->chd);
689
690
readBuffer = new u8[impl_->header->hunkbytes];
691
currentHunk = -1;
692
blocksPerHunk = impl_->header->hunkbytes / impl_->header->unitbytes;
693
numBlocks = impl_->header->unitcount;
694
695
_dbg_assert_(errorString_.empty());
696
}
697
698
CHDFileBlockDevice::~CHDFileBlockDevice() {
699
if (impl_->chd) {
700
chd_close(impl_->chd);
701
delete[] readBuffer;
702
}
703
}
704
705
bool CHDFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached) {
706
if (!impl_->chd) {
707
ERROR_LOG(Log::Loader, "ReadBlock: CHD not open. %s", fileLoader_->GetPath().c_str());
708
return false;
709
}
710
if ((u32)blockNumber >= numBlocks) {
711
memset(outPtr, 0, GetBlockSize());
712
return false;
713
}
714
u32 hunk = blockNumber / blocksPerHunk;
715
u32 blockInHunk = blockNumber % blocksPerHunk;
716
717
if (currentHunk != hunk) {
718
chd_error err = chd_read(impl_->chd, hunk, readBuffer);
719
if (err != CHDERR_NONE) {
720
ERROR_LOG(Log::Loader, "CHD read failed: %d %d %s", blockNumber, hunk, chd_error_string(err));
721
NotifyReadError();
722
}
723
currentHunk = hunk;
724
}
725
memcpy(outPtr, readBuffer + blockInHunk * impl_->header->unitbytes, GetBlockSize());
726
return true;
727
}
728
729
bool CHDFileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {
730
if (minBlock >= numBlocks) {
731
memset(outPtr, 0, GetBlockSize() * count);
732
return false;
733
}
734
735
for (int i = 0; i < count; i++) {
736
if (!ReadBlock(minBlock + i, outPtr + i * GetBlockSize())) {
737
return false;
738
}
739
}
740
return true;
741
}
742
743