CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/FileSystems/BlockDevices.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include <cstdio>18#include <cstring>19#include <algorithm>2021#include "Common/Data/Text/I18n.h"22#include "Common/File/FileUtil.h"23#include "Common/System/OSD.h"24#include "Common/Log.h"25#include "Common/Swap.h"26#include "Common/File/FileUtil.h"27#include "Common/File/DirListing.h"28#include "Core/Loaders.h"29#include "Core/FileSystems/BlockDevices.h"30#include "libchdr/chd.h"3132extern "C"33{34#include "zlib.h"35#include "ext/libkirk/amctrl.h"36#include "ext/libkirk/kirk_engine.h"37};3839std::mutex NPDRMDemoBlockDevice::mutex_;4041BlockDevice *constructBlockDevice(FileLoader *fileLoader) {42if (!fileLoader->Exists()) {43return nullptr;44}45if (fileLoader->IsDirectory()) {46ERROR_LOG(Log::Loader, "Can't open directory directly as block device: %s", fileLoader->GetPath().c_str());47return nullptr;48}4950char buffer[8]{};51size_t size = fileLoader->ReadAt(0, 1, 8, buffer);52if (size != 8) {53// Bad or empty file54return nullptr;55}5657// Check for CISO58if (!memcmp(buffer, "CISO", 4)) {59return new CISOFileBlockDevice(fileLoader);60} else if (!memcmp(buffer, "\x00PBP", 4)) {61uint32_t psarOffset = 0;62size = fileLoader->ReadAt(0x24, 1, 4, &psarOffset);63if (size == 4 && psarOffset < fileLoader->FileSize())64return new NPDRMDemoBlockDevice(fileLoader);65} else if (!memcmp(buffer, "MComprHD", 8)) {66return new CHDFileBlockDevice(fileLoader);67}6869// Should be just a regular ISO file. Let's open it as a plain block device and let the other systems take over.70return new FileBlockDevice(fileLoader);71}7273void BlockDevice::NotifyReadError() {74if (!reportedError_) {75auto err = GetI18NCategory(I18NCat::ERRORS);76g_OSD.Show(OSDType::MESSAGE_WARNING, err->T("Game disc read error - ISO corrupt"), fileLoader_->GetPath().ToVisualString(), 6.0f);77reportedError_ = true;78}79}8081FileBlockDevice::FileBlockDevice(FileLoader *fileLoader)82: BlockDevice(fileLoader) {83filesize_ = fileLoader->FileSize();84}8586FileBlockDevice::~FileBlockDevice() {87}8889bool FileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached) {90FileLoader::Flags flags = uncached ? FileLoader::Flags::HINT_UNCACHED : FileLoader::Flags::NONE;91size_t retval = fileLoader_->ReadAt((u64)blockNumber * (u64)GetBlockSize(), 1, 2048, outPtr, flags);92if (retval != 2048) {93DEBUG_LOG(Log::FileSystem, "Could not read 2048 byte block, at block offset %d. Only got %d bytes", blockNumber, (int)retval);94return false;95}9697return true;98}99100bool FileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {101size_t retval = fileLoader_->ReadAt((u64)minBlock * (u64)GetBlockSize(), 2048, count, outPtr);102if (retval != (size_t)count) {103ERROR_LOG(Log::FileSystem, "Could not read %d blocks, at block offset %d. Only got %d blocks", count, minBlock, (int)retval);104return false;105}106return true;107}108109// .CSO format110111// compressed ISO(9660) header format112typedef struct ciso_header113{114unsigned char magic[4]; // +00 : 'C','I','S','O'115u32_le header_size; // +04 : header size (==0x18)116u64_le total_bytes; // +08 : number of original data size117u32_le block_size; // +10 : number of compressed block size118unsigned char ver; // +14 : version 01119unsigned char align; // +15 : align of index value120unsigned char rsv_06[2]; // +16 : reserved121#if 0122// INDEX BLOCK123unsigned int index[0]; // +18 : block[0] index124unsigned int index[1]; // +1C : block[1] index125:126:127unsigned int index[last]; // +?? : block[last]128unsigned int index[last+1]; // +?? : end of last data point129// DATA BLOCK130unsigned char data[]; // +?? : compressed or plain sector data131#endif132} CISO_H;133134135// TODO: Need much better error handling.136137static const u32 CSO_READ_BUFFER_SIZE = 256 * 1024;138139CISOFileBlockDevice::CISOFileBlockDevice(FileLoader *fileLoader)140: BlockDevice(fileLoader)141{142// CISO format is fairly simple, but most tools do not write the header_size.143144CISO_H hdr;145size_t readSize = fileLoader->ReadAt(0, sizeof(CISO_H), 1, &hdr);146if (readSize != 1 || memcmp(hdr.magic, "CISO", 4) != 0) {147WARN_LOG(Log::Loader, "Invalid CSO!");148}149if (hdr.ver > 1) {150WARN_LOG(Log::Loader, "CSO version too high!");151}152153frameSize = hdr.block_size;154if ((frameSize & (frameSize - 1)) != 0)155ERROR_LOG(Log::Loader, "CSO block size %i unsupported, must be a power of two", frameSize);156else if (frameSize < 0x800)157ERROR_LOG(Log::Loader, "CSO block size %i unsupported, must be at least one sector", frameSize);158159// Determine the translation from block to frame.160blockShift = 0;161for (u32 i = frameSize; i > 0x800; i >>= 1)162++blockShift;163164indexShift = hdr.align;165const u64 totalSize = hdr.total_bytes;166numFrames = (u32)((totalSize + frameSize - 1) / frameSize);167numBlocks = (u32)(totalSize / GetBlockSize());168VERBOSE_LOG(Log::Loader, "CSO numBlocks=%i numFrames=%i align=%i", numBlocks, numFrames, indexShift);169170// We might read a bit of alignment too, so be prepared.171if (frameSize + (1 << indexShift) < CSO_READ_BUFFER_SIZE)172readBuffer = new u8[CSO_READ_BUFFER_SIZE];173else174readBuffer = new u8[frameSize + (1 << indexShift)];175zlibBuffer = new u8[frameSize + (1 << indexShift)];176zlibBufferFrame = numFrames;177178const u32 indexSize = numFrames + 1;179const size_t headerEnd = hdr.ver > 1 ? (size_t)hdr.header_size : sizeof(hdr);180181#if COMMON_LITTLE_ENDIAN182index = new u32[indexSize];183if (fileLoader->ReadAt(headerEnd, sizeof(u32), indexSize, index) != indexSize) {184NotifyReadError();185memset(index, 0, indexSize * sizeof(u32));186}187#else188index = new u32[indexSize];189u32_le *indexTemp = new u32_le[indexSize];190191if (fileLoader->ReadAt(headerEnd, sizeof(u32), indexSize, indexTemp) != indexSize) {192NotifyReadError();193memset(indexTemp, 0, indexSize * sizeof(u32_le));194}195196for (u32 i = 0; i < indexSize; i++)197index[i] = indexTemp[i];198199delete[] indexTemp;200#endif201202ver_ = hdr.ver;203204// Double check that the CSO is not truncated. In most cases, this will be the exact size.205u64 fileSize = fileLoader->FileSize();206u64 lastIndexPos = index[indexSize - 1] & 0x7FFFFFFF;207u64 expectedFileSize = lastIndexPos << indexShift;208if (expectedFileSize > fileSize) {209ERROR_LOG(Log::Loader, "Expected CSO to at least be %lld bytes, but file is %lld bytes. File: '%s'",210expectedFileSize, fileSize, fileLoader->GetPath().c_str());211NotifyReadError();212}213}214215CISOFileBlockDevice::~CISOFileBlockDevice()216{217delete [] index;218delete [] readBuffer;219delete [] zlibBuffer;220}221222bool CISOFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached)223{224FileLoader::Flags flags = uncached ? FileLoader::Flags::HINT_UNCACHED : FileLoader::Flags::NONE;225if ((u32)blockNumber >= numBlocks) {226memset(outPtr, 0, GetBlockSize());227return false;228}229230const u32 frameNumber = blockNumber >> blockShift;231const u32 idx = index[frameNumber];232const u32 indexPos = idx & 0x7FFFFFFF;233const u32 nextIndexPos = index[frameNumber + 1] & 0x7FFFFFFF;234z_stream z{};235236const u64 compressedReadPos = (u64)indexPos << indexShift;237const u64 compressedReadEnd = (u64)nextIndexPos << indexShift;238const size_t compressedReadSize = (size_t)(compressedReadEnd - compressedReadPos);239const u32 compressedOffset = (blockNumber & ((1 << blockShift) - 1)) * GetBlockSize();240241bool plain = (idx & 0x80000000) != 0;242if (ver_ >= 2) {243// CSO v2+ requires blocks be uncompressed if large enough to be. High bit means other things.244plain = compressedReadSize >= frameSize;245}246if (plain) {247int readSize = (u32)fileLoader_->ReadAt(compressedReadPos + compressedOffset, 1, GetBlockSize(), outPtr, flags);248if (readSize < GetBlockSize())249memset(outPtr + readSize, 0, GetBlockSize() - readSize);250} else if (zlibBufferFrame == frameNumber) {251// We already have it. Just apply the offset and copy.252memcpy(outPtr, zlibBuffer + compressedOffset, GetBlockSize());253} else {254const u32 readSize = (u32)fileLoader_->ReadAt(compressedReadPos, 1, compressedReadSize, readBuffer, flags);255256z.zalloc = Z_NULL;257z.zfree = Z_NULL;258z.opaque = Z_NULL;259if (inflateInit2(&z, -15) != Z_OK) {260ERROR_LOG(Log::Loader, "GetBlockSize() ERROR: %s\n", (z.msg) ? z.msg : "?");261NotifyReadError();262return false;263}264z.avail_in = readSize;265z.next_out = frameSize == (u32)GetBlockSize() ? outPtr : zlibBuffer;266z.avail_out = frameSize;267z.next_in = readBuffer;268269int status = inflate(&z, Z_FINISH);270if (status != Z_STREAM_END) {271ERROR_LOG(Log::Loader, "block %d: inflate : %s[%d]\n", blockNumber, (z.msg) ? z.msg : "error", status);272NotifyReadError();273inflateEnd(&z);274memset(outPtr, 0, GetBlockSize());275return false;276}277if (z.total_out != frameSize) {278ERROR_LOG(Log::Loader, "block %d: block size error %d != %d\n", blockNumber, (u32)z.total_out, frameSize);279NotifyReadError();280inflateEnd(&z);281memset(outPtr, 0, GetBlockSize());282return false;283}284inflateEnd(&z);285286if (frameSize != (u32)GetBlockSize()) {287zlibBufferFrame = frameNumber;288memcpy(outPtr, zlibBuffer + compressedOffset, GetBlockSize());289}290}291return true;292}293294bool CISOFileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {295if (count == 1) {296return ReadBlock(minBlock, outPtr);297}298if (minBlock >= numBlocks) {299memset(outPtr, 0, GetBlockSize() * count);300return false;301}302303const u32 lastBlock = std::min(minBlock + count, numBlocks) - 1;304const u32 missingBlocks = (lastBlock + 1 - minBlock) - count;305if (lastBlock < minBlock + count) {306memset(outPtr + GetBlockSize() * (count - missingBlocks), 0, GetBlockSize() * missingBlocks);307}308309const u32 minFrameNumber = minBlock >> blockShift;310const u32 lastFrameNumber = lastBlock >> blockShift;311const u32 afterLastIndexPos = index[lastFrameNumber + 1] & 0x7FFFFFFF;312const u64 totalReadEnd = (u64)afterLastIndexPos << indexShift;313314z_stream z{};315if (inflateInit2(&z, -15) != Z_OK) {316ERROR_LOG(Log::Loader, "Unable to initialize inflate: %s\n", (z.msg) ? z.msg : "?");317return false;318}319320u64 readBufferStart = 0;321u64 readBufferEnd = 0;322u32 block = minBlock;323const u32 blocksPerFrame = 1 << blockShift;324for (u32 frame = minFrameNumber; frame <= lastFrameNumber; ++frame) {325const u32 idx = index[frame];326const u32 indexPos = idx & 0x7FFFFFFF;327const u32 nextIndexPos = index[frame + 1] & 0x7FFFFFFF;328329const u64 frameReadPos = (u64)indexPos << indexShift;330const u64 frameReadEnd = (u64)nextIndexPos << indexShift;331const u32 frameReadSize = (u32)(frameReadEnd - frameReadPos);332const u32 frameBlockOffset = block & ((1 << blockShift) - 1);333const u32 frameBlocks = std::min(lastBlock - block + 1, blocksPerFrame - frameBlockOffset);334335if (frameReadEnd > readBufferEnd) {336const s64 maxNeeded = totalReadEnd - frameReadPos;337const size_t chunkSize = (size_t)std::min(maxNeeded, (s64)std::max(frameReadSize, CSO_READ_BUFFER_SIZE));338339const u32 readSize = (u32)fileLoader_->ReadAt(frameReadPos, 1, chunkSize, readBuffer);340if (readSize < chunkSize) {341memset(readBuffer + readSize, 0, chunkSize - readSize);342}343344readBufferStart = frameReadPos;345readBufferEnd = frameReadPos + readSize;346}347348u8 *rawBuffer = &readBuffer[frameReadPos - readBufferStart];349const int plain = idx & 0x80000000;350if (plain) {351memcpy(outPtr, rawBuffer + frameBlockOffset * GetBlockSize(), frameBlocks * GetBlockSize());352} else {353z.avail_in = frameReadSize;354z.next_out = frameBlocks == blocksPerFrame ? outPtr : zlibBuffer;355z.avail_out = frameSize;356z.next_in = rawBuffer;357358int status = inflate(&z, Z_FINISH);359if (status != Z_STREAM_END) {360ERROR_LOG(Log::Loader, "Inflate frame %d: failed - %s[%d]\n", frame, (z.msg) ? z.msg : "error", status);361NotifyReadError();362memset(outPtr, 0, frameBlocks * GetBlockSize());363} else if (z.total_out != frameSize) {364ERROR_LOG(Log::Loader, "Inflate frame %d: block size error %d != %d\n", frame, (u32)z.total_out, frameSize);365NotifyReadError();366memset(outPtr, 0, frameBlocks * GetBlockSize());367} else if (frameBlocks != blocksPerFrame) {368memcpy(outPtr, zlibBuffer + frameBlockOffset * GetBlockSize(), frameBlocks * GetBlockSize());369// In case we end up reusing it in a single read later.370zlibBufferFrame = frame;371}372373inflateReset(&z);374}375376block += frameBlocks;377outPtr += frameBlocks * GetBlockSize();378}379380inflateEnd(&z);381return true;382}383384NPDRMDemoBlockDevice::NPDRMDemoBlockDevice(FileLoader *fileLoader)385: BlockDevice(fileLoader)386{387std::lock_guard<std::mutex> guard(mutex_);388MAC_KEY mkey;389CIPHER_KEY ckey;390u8 np_header[256];391u32 tableOffset, tableSize;392u32 lbaStart, lbaEnd;393394fileLoader_->ReadAt(0x24, 1, 4, &psarOffset);395size_t readSize = fileLoader_->ReadAt(psarOffset, 1, 256, &np_header);396if (readSize != 256){397ERROR_LOG(Log::Loader, "Invalid NPUMDIMG header!");398}399400kirk_init();401402// getkey403sceDrmBBMacInit(&mkey, 3);404sceDrmBBMacUpdate(&mkey, np_header, 0xc0);405bbmac_getkey(&mkey, np_header+0xc0, vkey);406407// decrypt NP header408memcpy(hkey, np_header+0xa0, 0x10);409sceDrmBBCipherInit(&ckey, 1, 2, hkey, vkey, 0);410sceDrmBBCipherUpdate(&ckey, np_header+0x40, 0x60);411sceDrmBBCipherFinal(&ckey);412413lbaStart = *(u32*)(np_header+0x54); // LBA start414lbaEnd = *(u32*)(np_header+0x64); // LBA end415lbaSize = (lbaEnd-lbaStart+1); // LBA size of ISO416blockLBAs = *(u32*)(np_header+0x0c); // block size in LBA417blockSize = blockLBAs*2048;418numBlocks = (lbaSize+blockLBAs-1)/blockLBAs; // total blocks;419420blockBuf = new u8[blockSize];421tempBuf = new u8[blockSize];422423tableOffset = *(u32*)(np_header+0x6c); // table offset424425tableSize = numBlocks*32;426table = new table_info[numBlocks];427428readSize = fileLoader_->ReadAt(psarOffset + tableOffset, 1, tableSize, table);429if(readSize!=tableSize){430ERROR_LOG(Log::Loader, "Invalid NPUMDIMG table!");431}432433u32 *p = (u32*)table;434u32 i, k0, k1, k2, k3;435for(i=0; i<numBlocks; i++){436k0 = p[0]^p[1];437k1 = p[1]^p[2];438k2 = p[0]^p[3];439k3 = p[2]^p[3];440p[4] ^= k3;441p[5] ^= k1;442p[6] ^= k2;443p[7] ^= k0;444p += 8;445}446447currentBlock = -1;448}449450NPDRMDemoBlockDevice::~NPDRMDemoBlockDevice()451{452std::lock_guard<std::mutex> guard(mutex_);453delete [] table;454delete [] tempBuf;455delete [] blockBuf;456}457458int lzrc_decompress(void *out, int out_len, void *in, int in_len);459460bool NPDRMDemoBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached)461{462FileLoader::Flags flags = uncached ? FileLoader::Flags::HINT_UNCACHED : FileLoader::Flags::NONE;463std::lock_guard<std::mutex> guard(mutex_);464CIPHER_KEY ckey;465int block, lba, lzsize;466size_t readSize;467u8 *readBuf;468469lba = blockNumber-currentBlock;470if(lba>=0 && lba<blockLBAs){471memcpy(outPtr, blockBuf+lba*2048, 2048);472return true;473}474475block = blockNumber/blockLBAs;476lba = blockNumber%blockLBAs;477currentBlock = block*blockLBAs;478479if(table[block].unk_1c!=0){480if((u32)block==(numBlocks-1))481return true; // demos make by fake_np482else483return false;484}485486if(table[block].size<blockSize)487readBuf = tempBuf;488else489readBuf = blockBuf;490491readSize = fileLoader_->ReadAt(psarOffset+table[block].offset, 1, table[block].size, readBuf, flags);492if(readSize != (size_t)table[block].size){493if((u32)block==(numBlocks-1))494return true;495else496return false;497}498499if((table[block].flag&1)==0){500// skip mac check501}502503if((table[block].flag&4)==0){504sceDrmBBCipherInit(&ckey, 1, 2, hkey, vkey, table[block].offset>>4);505sceDrmBBCipherUpdate(&ckey, readBuf, table[block].size);506sceDrmBBCipherFinal(&ckey);507}508509if(table[block].size<blockSize){510lzsize = lzrc_decompress(blockBuf, 0x00100000, readBuf, table[block].size);511if(lzsize!=blockSize){512ERROR_LOG(Log::Loader, "LZRC decompress error! lzsize=%d\n", lzsize);513NotifyReadError();514return false;515}516}517518memcpy(outPtr, blockBuf+lba*2048, 2048);519520return true;521}522523/*524* CHD file525*/526static const UINT8 nullsha1[CHD_SHA1_BYTES] = { 0 };527528struct CHDImpl {529chd_file *chd = nullptr;530const chd_header *header = nullptr;531};532533struct ExtendedCoreFile {534core_file core; // Must be the first struct member, for some tricky pointer casts.535uint64_t seekPos;536};537538CHDFileBlockDevice::CHDFileBlockDevice(FileLoader *fileLoader)539: BlockDevice(fileLoader), impl_(new CHDImpl())540{541Path paths[8];542paths[0] = fileLoader->GetPath();543int depth = 0;544545core_file_ = new ExtendedCoreFile();546core_file_->core.argp = fileLoader;547core_file_->core.fsize = [](core_file *file) -> uint64_t {548FileLoader *loader = (FileLoader *)file->argp;549return loader->FileSize();550};551core_file_->core.fseek = [](core_file *file, int64_t offset, int seekType) -> int {552ExtendedCoreFile *coreFile = (ExtendedCoreFile *)file;553switch (seekType) {554case SEEK_SET:555coreFile->seekPos = offset;556break;557case SEEK_CUR:558coreFile->seekPos += offset;559break;560case SEEK_END:561{562FileLoader *loader = (FileLoader *)file->argp;563coreFile->seekPos = loader->FileSize() + offset;564break;565}566default:567break;568}569return 0;570};571core_file_->core.fread = [](void *out_data, size_t size, size_t count, core_file *file) {572ExtendedCoreFile *coreFile = (ExtendedCoreFile *)file;573FileLoader *loader = (FileLoader *)file->argp;574uint64_t totalSize = size * count;575loader->ReadAt(coreFile->seekPos, totalSize, out_data);576coreFile->seekPos += totalSize;577return size * count;578};579core_file_->core.fclose = [](core_file *file) {580ExtendedCoreFile *coreFile = (ExtendedCoreFile *)file;581delete coreFile;582return 0;583};584585/*586// TODO: Support parent/child CHD files.587588// Default, in case of failure589numBlocks = 0;590591chd_header childHeader;592593chd_error err = chd_read_header(paths[0].c_str(), &childHeader);594if (err != CHDERR_NONE) {595ERROR_LOG(Log::Loader, "Error loading CHD header for '%s': %s", paths[0].c_str(), chd_error_string(err));596NotifyReadError();597return;598}599600if (memcmp(nullsha1, childHeader.parentsha1, sizeof(childHeader.sha1)) != 0) {601chd_header parentHeader;602603// Look for parent CHD in current directory604Path chdDir = paths[0].NavigateUp();605606std::vector<File::FileInfo> files;607if (File::GetFilesInDir(chdDir, &files)) {608parentHeader.length = 0;609610for (const auto &file : files) {611std::string extension = file.fullName.GetFileExtension();612if (extension != ".chd") {613continue;614}615616if (chd_read_header(filepath.c_str(), &parentHeader) == CHDERR_NONE &&617memcmp(parentHeader.sha1, childHeader.parentsha1, sizeof(parentHeader.sha1)) == 0) {618// ERROR_LOG(Log::Loader, "Checking '%s'", filepath.c_str());619paths[++depth] = filepath;620break;621}622}623624// Check if parentHeader was opened625if (parentHeader.length == 0) {626ERROR_LOG(Log::Loader, "Error loading CHD '%s': parents not found", fileLoader->GetPath().c_str());627NotifyReadError();628return;629}630memcpy(childHeader.parentsha1, parentHeader.parentsha1, sizeof(childHeader.parentsha1));631} while (memcmp(nullsha1, childHeader.parentsha1, sizeof(childHeader.sha1)) != 0);632}633*/634635chd_file *file = nullptr;636chd_error err = chd_open_core_file(&core_file_->core, CHD_OPEN_READ, NULL, &file);637if (err != CHDERR_NONE) {638ERROR_LOG(Log::Loader, "Error loading CHD '%s': %s", paths[depth].c_str(), chd_error_string(err));639NotifyReadError();640return;641}642643impl_->chd = file;644impl_->header = chd_get_header(impl_->chd);645646readBuffer = new u8[impl_->header->hunkbytes];647currentHunk = -1;648blocksPerHunk = impl_->header->hunkbytes / impl_->header->unitbytes;649numBlocks = impl_->header->unitcount;650}651652CHDFileBlockDevice::~CHDFileBlockDevice()653{654if (impl_->chd) {655chd_close(impl_->chd);656delete[] readBuffer;657}658}659660bool CHDFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached)661{662if (!impl_->chd) {663ERROR_LOG(Log::Loader, "ReadBlock: CHD not open. %s", fileLoader_->GetPath().c_str());664return false;665}666if ((u32)blockNumber >= numBlocks) {667memset(outPtr, 0, GetBlockSize());668return false;669}670u32 hunk = blockNumber / blocksPerHunk;671u32 blockInHunk = blockNumber % blocksPerHunk;672673if (currentHunk != hunk) {674chd_error err = chd_read(impl_->chd, hunk, readBuffer);675if (err != CHDERR_NONE) {676ERROR_LOG(Log::Loader, "CHD read failed: %d %d %s", blockNumber, hunk, chd_error_string(err));677NotifyReadError();678}679currentHunk = hunk;680}681memcpy(outPtr, readBuffer + blockInHunk * impl_->header->unitbytes, GetBlockSize());682return true;683}684685bool CHDFileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {686if (minBlock >= numBlocks) {687memset(outPtr, 0, GetBlockSize() * count);688return false;689}690691for (int i = 0; i < count; i++) {692if (!ReadBlock(minBlock + i, outPtr + i * GetBlockSize())) {693return false;694}695}696return true;697}698699700