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