mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-07-11 01:25:07 +02:00
2d8de4c1bf
See #21886
1127 lines
33 KiB
C++
1127 lines
33 KiB
C++
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
#include "Common/Data/Text/I18n.h"
|
|
#include "Common/System/OSD.h"
|
|
#include "Common/Log.h"
|
|
#include "Common/Swap.h"
|
|
#include "Common/Data/Text/Parsers.h"
|
|
#include "Common/File/FileUtil.h"
|
|
#include "Common/File/DirListing.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Core/Loaders.h"
|
|
#include "Core/FileSystems/BlockDevices.h"
|
|
#include "Core/FileSystems/ISOFileSystem.h"
|
|
#include "Core/Util/PathUtil.h"
|
|
#include "libchdr/chd.h"
|
|
|
|
extern "C"
|
|
{
|
|
#include "zlib.h"
|
|
#include "ext/libkirk/amctrl.h"
|
|
#include "ext/libkirk/kirk_engine.h"
|
|
};
|
|
|
|
static u16 ReadLE16(const u8 *ptr) {
|
|
return ptr[0] | (ptr[1] << 8);
|
|
}
|
|
|
|
static u32 ReadLE32(const u8 *ptr) {
|
|
return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24);
|
|
}
|
|
|
|
static std::string DecodeUDFFileName(const u8 *data, size_t size) {
|
|
if (size == 0)
|
|
return "";
|
|
|
|
std::string result;
|
|
if (data[0] == 8) {
|
|
result.assign((const char *)(data + 1), size - 1);
|
|
} else if (data[0] == 16) {
|
|
for (size_t i = 1; i + 1 < size; i += 2) {
|
|
result.push_back((char)data[i + 1]);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
struct UDFShortAd {
|
|
u32 length = 0;
|
|
u32 position = 0;
|
|
};
|
|
|
|
struct UDFLongAd {
|
|
u32 length = 0;
|
|
u32 position = 0;
|
|
u16 partition = 0;
|
|
};
|
|
|
|
static bool ReadDescriptorSector(FileLoader *fileLoader, u32 sector, std::array<u8, 2048> *out) {
|
|
return fileLoader->ReadAt((u64)sector * 2048, 1, out->size(), out->data()) == out->size();
|
|
}
|
|
|
|
static bool ParseUDFLongAd(const u8 *data, UDFLongAd *out) {
|
|
out->length = ReadLE32(data);
|
|
out->position = ReadLE32(data + 4);
|
|
out->partition = ReadLE16(data + 8);
|
|
return true;
|
|
}
|
|
|
|
static bool ParseUDFShortAd(const u8 *data, UDFShortAd *out) {
|
|
out->length = ReadLE32(data) & 0x3FFFFFFF;
|
|
out->position = ReadLE32(data + 4);
|
|
return out->length != 0;
|
|
}
|
|
|
|
static bool ParseUDFFileEntryExtent(FileLoader *fileLoader, u32 sector, UDFShortAd *extent) {
|
|
std::array<u8, 2048> block{};
|
|
if (!ReadDescriptorSector(fileLoader, sector, &block))
|
|
return false;
|
|
if (ReadLE16(block.data()) != 0x0105)
|
|
return false;
|
|
|
|
u32 extAttrLen = ReadLE32(block.data() + 0xA8);
|
|
u32 allocDescLen = ReadLE32(block.data() + 0xAC);
|
|
u32 allocDescOffset = 0xB0 + extAttrLen;
|
|
if (allocDescLen < 8 || allocDescOffset + 8 > block.size())
|
|
return false;
|
|
|
|
return ParseUDFShortAd(block.data() + allocDescOffset, extent);
|
|
}
|
|
|
|
static bool ParseUDFRootDirectory(FileLoader *fileLoader, u32 sector, u32 partitionStart, std::vector<u8> *dirData) {
|
|
UDFShortAd extent{};
|
|
if (!ParseUDFFileEntryExtent(fileLoader, sector, &extent))
|
|
return false;
|
|
if (extent.length == 0)
|
|
return false;
|
|
|
|
dirData->resize(extent.length);
|
|
const u64 offset = (u64)(partitionStart + extent.position) * 2048;
|
|
return fileLoader->ReadAt(offset, 1, dirData->size(), dirData->data()) == dirData->size();
|
|
}
|
|
|
|
static bool FindUDFRootFileEntry(FileLoader *fileLoader, u32 *rootSector, u32 *partitionStart) {
|
|
std::array<u8, 2048> avdp{};
|
|
if (!ReadDescriptorSector(fileLoader, 256, &avdp))
|
|
return false;
|
|
if (ReadLE16(avdp.data()) != 0x0002)
|
|
return false;
|
|
|
|
u32 mvdsLength = ReadLE32(avdp.data() + 16);
|
|
u32 mvdsLocation = ReadLE32(avdp.data() + 20);
|
|
if (mvdsLength < 2048)
|
|
return false;
|
|
|
|
std::array<u8, 2048> block{};
|
|
bool foundPartition = false;
|
|
bool foundRoot = false;
|
|
u32 fsdLocation = 0;
|
|
u32 fsdPartition = 0;
|
|
|
|
for (u32 sector = mvdsLocation; sector < mvdsLocation + mvdsLength / 2048; ++sector) {
|
|
if (!ReadDescriptorSector(fileLoader, sector, &block))
|
|
return false;
|
|
|
|
switch (ReadLE16(block.data())) {
|
|
case 0x0005:
|
|
// Partition Descriptor.
|
|
fsdPartition = ReadLE32(block.data() + 188);
|
|
foundPartition = true;
|
|
break;
|
|
case 0x0006:
|
|
// Logical Volume Descriptor. The file set descriptor sequence is stored
|
|
// in logicalVolumeContentsUse as an extent_ad.
|
|
fsdLocation = ReadLE32(block.data() + 252);
|
|
foundRoot = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundPartition || !foundRoot)
|
|
return false;
|
|
|
|
std::array<u8, 2048> fsd{};
|
|
if (!ReadDescriptorSector(fileLoader, fsdPartition + fsdLocation, &fsd))
|
|
return false;
|
|
if (ReadLE16(fsd.data()) != 0x0100)
|
|
return false;
|
|
|
|
UDFLongAd rootIcb{};
|
|
ParseUDFLongAd(fsd.data() + 400, &rootIcb);
|
|
if (rootIcb.partition != 0)
|
|
return false;
|
|
|
|
*partitionStart = fsdPartition;
|
|
*rootSector = fsdPartition + rootIcb.position;
|
|
return true;
|
|
}
|
|
|
|
static bool FindUDFLayerFileEntrySectors(FileLoader *fileLoader, u32 rootSector, u32 partitionStart, u32 *layer0Sector, u32 *layer1Sector) {
|
|
std::vector<u8> dirData;
|
|
if (!ParseUDFRootDirectory(fileLoader, rootSector, partitionStart, &dirData))
|
|
return false;
|
|
|
|
bool found0 = false;
|
|
bool found1 = false;
|
|
for (size_t offset = 0; offset + 16 <= dirData.size();) {
|
|
u16 tag = ReadLE16(&dirData[offset]);
|
|
u16 crcLen = ReadLE16(&dirData[offset + 10]);
|
|
size_t entryLen = 16 + crcLen;
|
|
entryLen = (entryLen + 3) & ~size_t(3);
|
|
if (entryLen == 0 || offset + entryLen > dirData.size())
|
|
break;
|
|
|
|
if (tag == 0x0101 && crcLen >= 20) {
|
|
u8 fileIdLen = dirData[offset + 19];
|
|
u16 implUseLen = ReadLE16(&dirData[offset + 36]);
|
|
size_t nameOffset = offset + 38 + implUseLen;
|
|
if (nameOffset + fileIdLen <= dirData.size()) {
|
|
std::string name = DecodeUDFFileName(&dirData[nameOffset], fileIdLen);
|
|
UDFLongAd icb{};
|
|
ParseUDFLongAd(&dirData[offset + 20], &icb);
|
|
if (icb.partition == 0) {
|
|
if (name == "USER_L0.IMG") {
|
|
*layer0Sector = partitionStart + icb.position;
|
|
found0 = true;
|
|
} else if (name == "USER_L1.IMG") {
|
|
*layer1Sector = partitionStart + icb.position;
|
|
found1 = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
offset += entryLen;
|
|
}
|
|
|
|
return found0 || found1;
|
|
}
|
|
|
|
BlockDevice *ConstructBlockDevice(FileLoader *fileLoader, std::string *errorString) {
|
|
if (!fileLoader->Exists()) {
|
|
// Shouldn't get here really.
|
|
*errorString = "File not readable or doesn't exist";
|
|
return nullptr;
|
|
}
|
|
if (fileLoader->IsDirectory()) {
|
|
*errorString = "Can't open directory directly as block device: ";
|
|
*errorString += fileLoader->GetPath().ToString();
|
|
return nullptr;
|
|
}
|
|
if (fileLoader->FileSize() < 8) {
|
|
*errorString = "File is too small to read the header.";
|
|
return nullptr;
|
|
}
|
|
|
|
char buffer[8]{};
|
|
size_t size = fileLoader->ReadAt(0, 1, 8, buffer);
|
|
if (size != 8) {
|
|
// Bad or empty file
|
|
*errorString = "Failed to read 8-byte header";
|
|
return nullptr;
|
|
}
|
|
|
|
BlockDevice *device = nullptr;
|
|
|
|
// Check for CISO
|
|
if (!memcmp(buffer, "CISO", 4)) {
|
|
device = new CISOFileBlockDevice(fileLoader);
|
|
} else if (!memcmp(buffer, "\x00PBP", 4)) {
|
|
uint32_t psarOffset = 0;
|
|
size = fileLoader->ReadAt(0x24, 1, 4, &psarOffset);
|
|
if (size == 4 && psarOffset < fileLoader->FileSize()) {
|
|
device = new NPDRMDemoBlockDevice(fileLoader);
|
|
}
|
|
} else if (!memcmp(buffer, "MComprHD", 8)) {
|
|
device = new CHDFileBlockDevice(fileLoader);
|
|
}
|
|
|
|
if (!device) {
|
|
device = new UDFFileBlockDevice(fileLoader);
|
|
if (!device->IsOK()) {
|
|
delete device;
|
|
device = nullptr;
|
|
}
|
|
}
|
|
|
|
if (!device) {
|
|
device = new ISOContainerFileBlockDevice(fileLoader);
|
|
if (!device->IsOK()) {
|
|
delete device;
|
|
device = nullptr;
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
if (!device) {
|
|
device = new FileBlockDevice(fileLoader);
|
|
}
|
|
|
|
if (!device->IsOK()) {
|
|
*errorString = device->ErrorString();
|
|
delete device;
|
|
return nullptr;
|
|
}
|
|
|
|
return device;
|
|
}
|
|
|
|
void BlockDevice::NotifyReadError() {
|
|
if (!reportedError_) {
|
|
auto err = GetI18NCategory(I18NCat::ERRORS);
|
|
g_OSD.Show(OSDType::MESSAGE_WARNING, err->T("Game disc read error - ISO corrupt"), GetFriendlyPath(fileLoader_->GetPath()), 6.0f);
|
|
reportedError_ = true;
|
|
}
|
|
}
|
|
|
|
FileBlockDevice::FileBlockDevice(FileLoader *fileLoader)
|
|
: BlockDevice(fileLoader) {
|
|
filesize_ = fileLoader->FileSize();
|
|
}
|
|
|
|
FileBlockDevice::~FileBlockDevice() {}
|
|
|
|
bool FileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached) {
|
|
FileLoader::Flags flags = uncached ? FileLoader::Flags::HINT_UNCACHED : FileLoader::Flags::NONE;
|
|
size_t retval = fileLoader_->ReadAt((u64)blockNumber * (u64)GetBlockSize(), 1, 2048, outPtr, flags);
|
|
if (retval != 2048) {
|
|
DEBUG_LOG(Log::FileSystem, "Could not read 2048 byte block, at block offset %d. Only got %d bytes", blockNumber, (int)retval);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {
|
|
size_t retval = fileLoader_->ReadAt((u64)minBlock * (u64)GetBlockSize(), 2048, count, outPtr);
|
|
if (retval != (size_t)count) {
|
|
ERROR_LOG(Log::FileSystem, "Could not read %d blocks, at block offset %d. Only got %d blocks", count, minBlock, (int)retval);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
UDFFileBlockDevice::UDFFileBlockDevice(FileLoader *fileLoader)
|
|
: BlockDevice(fileLoader) {
|
|
u32 partitionStart = 0;
|
|
u32 rootSector = 0;
|
|
if (!FindUDFRootFileEntry(fileLoader, &rootSector, &partitionStart)) {
|
|
errorString_ = "Not a supported UDF disc image";
|
|
return;
|
|
}
|
|
|
|
u32 layer0Sector = 0;
|
|
u32 layer1Sector = 0;
|
|
if (!FindUDFLayerFileEntrySectors(fileLoader, rootSector, partitionStart, &layer0Sector, &layer1Sector) || layer0Sector == 0) {
|
|
errorString_ = "Not a PSP UDF disc image";
|
|
return;
|
|
}
|
|
|
|
UDFShortAd layer0Extent{};
|
|
if (!ParseUDFFileEntryExtent(fileLoader, layer0Sector, &layer0Extent)) {
|
|
errorString_ = "Failed to read USER_L0.IMG entry";
|
|
return;
|
|
}
|
|
layer0_.startBlock = partitionStart + layer0Extent.position;
|
|
layer0_.numBlocks = layer0Extent.length / GetBlockSize();
|
|
numBlocks_ = layer0_.numBlocks;
|
|
|
|
if (layer1Sector != 0) {
|
|
UDFShortAd layer1Extent{};
|
|
if (ParseUDFFileEntryExtent(fileLoader, layer1Sector, &layer1Extent)) {
|
|
layer1_.startBlock = partitionStart + layer1Extent.position;
|
|
layer1_.numBlocks = layer1Extent.length / GetBlockSize();
|
|
numBlocks_ += layer1_.numBlocks;
|
|
}
|
|
}
|
|
|
|
if (numBlocks_ == 0) {
|
|
errorString_ = "UDF disc image had no readable UMD layers";
|
|
return;
|
|
}
|
|
|
|
DEBUG_LOG(Log::Loader, "Detected PSP DVD-R wrapper: USER_L0=%u blocks at %u, USER_L1=%u blocks at %u",
|
|
layer0_.numBlocks, layer0_.startBlock, layer1_.numBlocks, layer1_.startBlock);
|
|
}
|
|
|
|
UDFFileBlockDevice::~UDFFileBlockDevice() = default;
|
|
|
|
bool UDFFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached) {
|
|
if ((u32)blockNumber >= numBlocks_) {
|
|
memset(outPtr, 0, GetBlockSize());
|
|
return false;
|
|
}
|
|
|
|
u32 sourceBlock = 0;
|
|
if ((u32)blockNumber < layer0_.numBlocks) {
|
|
sourceBlock = layer0_.startBlock + blockNumber;
|
|
} else {
|
|
u32 layer1Block = (u32)blockNumber - layer0_.numBlocks;
|
|
if (layer1Block >= layer1_.numBlocks) {
|
|
memset(outPtr, 0, GetBlockSize());
|
|
return false;
|
|
}
|
|
sourceBlock = layer1_.startBlock + layer1Block;
|
|
}
|
|
|
|
FileLoader::Flags flags = uncached ? FileLoader::Flags::HINT_UNCACHED : FileLoader::Flags::NONE;
|
|
size_t retval = fileLoader_->ReadAt((u64)sourceBlock * (u64)GetBlockSize(), 1, GetBlockSize(), outPtr, flags);
|
|
if (retval != GetBlockSize()) {
|
|
DEBUG_LOG(Log::FileSystem, "Could not read UDF-wrapped block %d from source block %u", blockNumber, sourceBlock);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool UDFFileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {
|
|
for (int i = 0; i < count; ++i) {
|
|
if (!ReadBlock(minBlock + i, outPtr)) {
|
|
return false;
|
|
}
|
|
outPtr += GetBlockSize();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ISOContainerFileBlockDevice::ISOContainerFileBlockDevice(FileLoader *fileLoader)
|
|
: BlockDevice(fileLoader) {
|
|
outerBlockDevice_ = std::make_shared<FileBlockDevice>(fileLoader);
|
|
if (!outerBlockDevice_->IsOK()) {
|
|
errorString_ = outerBlockDevice_->ErrorString();
|
|
outerBlockDevice_.reset();
|
|
return;
|
|
}
|
|
|
|
SequentialHandleAllocator alloc;
|
|
ISOFileSystem iso(&alloc, outerBlockDevice_);
|
|
if (!iso.Error().empty()) {
|
|
errorString_ = iso.Error();
|
|
outerBlockDevice_.reset();
|
|
return;
|
|
}
|
|
|
|
PSPFileInfo layer0Info = iso.GetFileInfo("/USER_L0.IMG");
|
|
if (!layer0Info.exists) {
|
|
errorString_ = "Not a PSP ISO container image";
|
|
outerBlockDevice_.reset();
|
|
return;
|
|
}
|
|
|
|
layer0_.startBlock = layer0Info.startSector;
|
|
layer0_.numBlocks = (u32)((layer0Info.size + GetBlockSize() - 1) / GetBlockSize());
|
|
numBlocks_ = layer0_.numBlocks;
|
|
|
|
PSPFileInfo layer1Info = iso.GetFileInfo("/USER_L1.IMG");
|
|
if (layer1Info.exists) {
|
|
layer1_.startBlock = layer1Info.startSector;
|
|
layer1_.numBlocks = (u32)((layer1Info.size + GetBlockSize() - 1) / GetBlockSize());
|
|
numBlocks_ += layer1_.numBlocks;
|
|
}
|
|
|
|
if (numBlocks_ == 0) {
|
|
errorString_ = "ISO container image had no readable UMD layers";
|
|
outerBlockDevice_.reset();
|
|
return;
|
|
}
|
|
|
|
DEBUG_LOG(Log::Loader, "Detected PSP ISO wrapper: USER_L0=%u blocks at %u, USER_L1=%u blocks at %u",
|
|
layer0_.numBlocks, layer0_.startBlock, layer1_.numBlocks, layer1_.startBlock);
|
|
}
|
|
|
|
ISOContainerFileBlockDevice::~ISOContainerFileBlockDevice() = default;
|
|
|
|
bool ISOContainerFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached) {
|
|
if ((u32)blockNumber >= numBlocks_ || !outerBlockDevice_) {
|
|
memset(outPtr, 0, GetBlockSize());
|
|
return false;
|
|
}
|
|
|
|
u32 sourceBlock = 0;
|
|
if ((u32)blockNumber < layer0_.numBlocks) {
|
|
sourceBlock = layer0_.startBlock + blockNumber;
|
|
} else {
|
|
u32 layer1Block = (u32)blockNumber - layer0_.numBlocks;
|
|
if (layer1Block >= layer1_.numBlocks) {
|
|
memset(outPtr, 0, GetBlockSize());
|
|
return false;
|
|
}
|
|
sourceBlock = layer1_.startBlock + layer1Block;
|
|
}
|
|
|
|
return outerBlockDevice_->ReadBlock(sourceBlock, outPtr, uncached);
|
|
}
|
|
|
|
bool ISOContainerFileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {
|
|
for (int i = 0; i < count; ++i) {
|
|
if (!ReadBlock(minBlock + i, outPtr)) {
|
|
return false;
|
|
}
|
|
outPtr += GetBlockSize();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// .CSO format
|
|
|
|
// compressed ISO(9660) header format
|
|
typedef struct ciso_header
|
|
{
|
|
unsigned char magic[4]; // +00 : 'C','I','S','O'
|
|
u32_le header_size; // +04 : header size (==0x18)
|
|
u64_le total_bytes; // +08 : number of original data size
|
|
u32_le block_size; // +10 : number of compressed block size
|
|
unsigned char ver; // +14 : version 01
|
|
unsigned char align; // +15 : align of index value
|
|
unsigned char rsv_06[2]; // +16 : reserved
|
|
#if 0
|
|
// INDEX BLOCK
|
|
unsigned int index[0]; // +18 : block[0] index
|
|
unsigned int index[1]; // +1C : block[1] index
|
|
:
|
|
:
|
|
unsigned int index[last]; // +?? : block[last]
|
|
unsigned int index[last+1]; // +?? : end of last data point
|
|
// DATA BLOCK
|
|
unsigned char data[]; // +?? : compressed or plain sector data
|
|
#endif
|
|
} CISO_H;
|
|
|
|
|
|
// TODO: Need much better error handling.
|
|
|
|
static const u32 CSO_READ_BUFFER_SIZE = 256 * 1024;
|
|
|
|
CISOFileBlockDevice::CISOFileBlockDevice(FileLoader *fileLoader)
|
|
: BlockDevice(fileLoader)
|
|
{
|
|
// CISO format is fairly simple, but most tools do not write the header_size.
|
|
// NOTE: CSOv2 isn't actually a thing. It was partially implemented in maxcso but it has never been in active use.
|
|
|
|
CISO_H hdr;
|
|
size_t readSize = fileLoader->ReadAt(0, sizeof(CISO_H), 1, &hdr);
|
|
if (readSize != 1 || memcmp(hdr.magic, "CISO", 4) != 0) {
|
|
errorString_ = "Invalid CSO!";
|
|
return;
|
|
}
|
|
if (hdr.ver > 1) {
|
|
errorString_ = "CSO version too high!";
|
|
return;
|
|
}
|
|
|
|
frameSize = hdr.block_size;
|
|
if ((frameSize & (frameSize - 1)) != 0) {
|
|
errorString_ = StringFromFormat("CSO block size %i unsupported, must be a power of two", frameSize);
|
|
return;
|
|
} else if (frameSize < 0x800) {
|
|
errorString_ = StringFromFormat("CSO block size %i unsupported, must be at least one sector", frameSize);
|
|
return;
|
|
}
|
|
|
|
// Determine the translation from block to frame.
|
|
blockShift = 0;
|
|
for (u32 i = frameSize; i > 0x800; i >>= 1)
|
|
++blockShift;
|
|
|
|
indexShift = hdr.align;
|
|
const u64 totalSize = hdr.total_bytes;
|
|
numFrames = (u32)((totalSize + frameSize - 1) / frameSize);
|
|
numBlocks = (u32)(totalSize / GetBlockSize());
|
|
VERBOSE_LOG(Log::Loader, "CSO numBlocks=%i numFrames=%i align=%i", numBlocks, numFrames, indexShift);
|
|
|
|
// We might read a bit of alignment too, so be prepared.
|
|
if (frameSize + (1 << indexShift) < CSO_READ_BUFFER_SIZE)
|
|
readBuffer = new u8[CSO_READ_BUFFER_SIZE];
|
|
else
|
|
readBuffer = new u8[frameSize + (1 << indexShift)];
|
|
zlibBuffer = new u8[frameSize + (1 << indexShift)];
|
|
zlibBufferFrame = numFrames;
|
|
|
|
const u32 indexSize = numFrames + 1;
|
|
const size_t headerEnd = hdr.ver > 1 ? (size_t)hdr.header_size : sizeof(hdr);
|
|
|
|
#if COMMON_LITTLE_ENDIAN
|
|
index = new u32[indexSize];
|
|
if (fileLoader->ReadAt(headerEnd, sizeof(u32), indexSize, index) != indexSize) {
|
|
NotifyReadError();
|
|
memset(index, 0, indexSize * sizeof(u32));
|
|
}
|
|
#else
|
|
index = new u32[indexSize];
|
|
u32_le *indexTemp = new u32_le[indexSize];
|
|
|
|
if (fileLoader->ReadAt(headerEnd, sizeof(u32), indexSize, indexTemp) != indexSize) {
|
|
NotifyReadError();
|
|
memset(indexTemp, 0, indexSize * sizeof(u32_le));
|
|
}
|
|
|
|
for (u32 i = 0; i < indexSize; i++)
|
|
index[i] = indexTemp[i];
|
|
|
|
delete[] indexTemp;
|
|
#endif
|
|
|
|
ver_ = hdr.ver;
|
|
|
|
// Double check that the CSO is not truncated. In most cases, this will be the exact size.
|
|
u64 fileSize = fileLoader->FileSize();
|
|
u64 lastIndexPos = index[indexSize - 1] & 0x7FFFFFFF;
|
|
u64 expectedFileSize = lastIndexPos << indexShift;
|
|
if (expectedFileSize > fileSize) {
|
|
errorString_ = StringFromFormat("CSO file incomplete: expected %s, but is %s", NiceSizeFormat(expectedFileSize).c_str(), NiceSizeFormat(fileSize).c_str());
|
|
return;
|
|
}
|
|
|
|
// all ok.
|
|
_dbg_assert_(errorString_.empty());
|
|
}
|
|
|
|
CISOFileBlockDevice::~CISOFileBlockDevice()
|
|
{
|
|
delete [] index;
|
|
delete [] readBuffer;
|
|
delete [] zlibBuffer;
|
|
}
|
|
|
|
bool CISOFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached)
|
|
{
|
|
FileLoader::Flags flags = uncached ? FileLoader::Flags::HINT_UNCACHED : FileLoader::Flags::NONE;
|
|
if ((u32)blockNumber >= numBlocks) {
|
|
memset(outPtr, 0, GetBlockSize());
|
|
return false;
|
|
}
|
|
|
|
const u32 frameNumber = blockNumber >> blockShift;
|
|
const u32 idx = index[frameNumber];
|
|
const u32 indexPos = idx & 0x7FFFFFFF;
|
|
const u32 nextIndexPos = index[frameNumber + 1] & 0x7FFFFFFF;
|
|
z_stream z{};
|
|
|
|
const u64 compressedReadPos = (u64)indexPos << indexShift;
|
|
const u64 compressedReadEnd = (u64)nextIndexPos << indexShift;
|
|
const size_t compressedReadSize = (size_t)(compressedReadEnd - compressedReadPos);
|
|
const u32 compressedOffset = (blockNumber & ((1 << blockShift) - 1)) * GetBlockSize();
|
|
|
|
bool plain = (idx & 0x80000000) != 0;
|
|
if (ver_ >= 2) {
|
|
// CSO v2+ requires blocks be uncompressed if large enough to be. High bit means other things.
|
|
plain = compressedReadSize >= frameSize;
|
|
}
|
|
if (plain) {
|
|
int readSize = (u32)fileLoader_->ReadAt(compressedReadPos + compressedOffset, 1, GetBlockSize(), outPtr, flags);
|
|
if (readSize < GetBlockSize())
|
|
memset(outPtr + readSize, 0, GetBlockSize() - readSize);
|
|
} else if (zlibBufferFrame == frameNumber) {
|
|
// We already have it. Just apply the offset and copy.
|
|
memcpy(outPtr, zlibBuffer + compressedOffset, GetBlockSize());
|
|
} else {
|
|
const u32 readSize = (u32)fileLoader_->ReadAt(compressedReadPos, 1, compressedReadSize, readBuffer, flags);
|
|
|
|
z.zalloc = Z_NULL;
|
|
z.zfree = Z_NULL;
|
|
z.opaque = Z_NULL;
|
|
if (inflateInit2(&z, -15) != Z_OK) {
|
|
ERROR_LOG(Log::Loader, "GetBlockSize() ERROR: %s\n", (z.msg) ? z.msg : "?");
|
|
NotifyReadError();
|
|
return false;
|
|
}
|
|
z.avail_in = readSize;
|
|
z.next_out = frameSize == (u32)GetBlockSize() ? outPtr : zlibBuffer;
|
|
z.avail_out = frameSize;
|
|
z.next_in = readBuffer;
|
|
|
|
int status = inflate(&z, Z_FINISH);
|
|
if (status != Z_STREAM_END) {
|
|
ERROR_LOG(Log::Loader, "block %d: inflate : %s[%d]\n", blockNumber, (z.msg) ? z.msg : "error", status);
|
|
NotifyReadError();
|
|
inflateEnd(&z);
|
|
memset(outPtr, 0, GetBlockSize());
|
|
return false;
|
|
}
|
|
if (z.total_out != frameSize) {
|
|
ERROR_LOG(Log::Loader, "block %d: block size error %d != %d\n", blockNumber, (u32)z.total_out, frameSize);
|
|
NotifyReadError();
|
|
inflateEnd(&z);
|
|
memset(outPtr, 0, GetBlockSize());
|
|
return false;
|
|
}
|
|
inflateEnd(&z);
|
|
|
|
if (frameSize != (u32)GetBlockSize()) {
|
|
zlibBufferFrame = frameNumber;
|
|
memcpy(outPtr, zlibBuffer + compressedOffset, GetBlockSize());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CISOFileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {
|
|
if (count == 1) {
|
|
return ReadBlock(minBlock, outPtr);
|
|
}
|
|
if (minBlock >= numBlocks) {
|
|
memset(outPtr, 0, GetBlockSize() * count);
|
|
return false;
|
|
}
|
|
|
|
const u32 lastBlock = std::min(minBlock + count, numBlocks) - 1;
|
|
const u32 missingBlocks = count - (lastBlock + 1 - minBlock);
|
|
if (lastBlock < minBlock + count) {
|
|
memset(outPtr + GetBlockSize() * (count - missingBlocks), 0, GetBlockSize() * missingBlocks);
|
|
}
|
|
|
|
const u32 minFrameNumber = minBlock >> blockShift;
|
|
const u32 lastFrameNumber = lastBlock >> blockShift;
|
|
const u32 afterLastIndexPos = index[lastFrameNumber + 1] & 0x7FFFFFFF;
|
|
const u64 totalReadEnd = (u64)afterLastIndexPos << indexShift;
|
|
|
|
z_stream z{};
|
|
if (inflateInit2(&z, -15) != Z_OK) {
|
|
ERROR_LOG(Log::Loader, "Unable to initialize inflate: %s\n", (z.msg) ? z.msg : "?");
|
|
return false;
|
|
}
|
|
|
|
u64 readBufferStart = 0;
|
|
u64 readBufferEnd = 0;
|
|
u32 block = minBlock;
|
|
const u32 blocksPerFrame = 1 << blockShift;
|
|
for (u32 frame = minFrameNumber; frame <= lastFrameNumber; ++frame) {
|
|
const u32 idx = index[frame];
|
|
const u32 indexPos = idx & 0x7FFFFFFF;
|
|
const u32 nextIndexPos = index[frame + 1] & 0x7FFFFFFF;
|
|
|
|
const u64 frameReadPos = (u64)indexPos << indexShift;
|
|
const u64 frameReadEnd = (u64)nextIndexPos << indexShift;
|
|
const u32 frameReadSize = (u32)(frameReadEnd - frameReadPos);
|
|
const u32 frameBlockOffset = block & ((1 << blockShift) - 1);
|
|
const u32 frameBlocks = std::min(lastBlock - block + 1, blocksPerFrame - frameBlockOffset);
|
|
|
|
if (frameReadEnd > readBufferEnd) {
|
|
const s64 maxNeeded = totalReadEnd - frameReadPos;
|
|
const size_t chunkSize = (size_t)std::min(maxNeeded, (s64)std::max(frameReadSize, CSO_READ_BUFFER_SIZE));
|
|
|
|
const u32 readSize = (u32)fileLoader_->ReadAt(frameReadPos, 1, chunkSize, readBuffer);
|
|
if (readSize < chunkSize) {
|
|
memset(readBuffer + readSize, 0, chunkSize - readSize);
|
|
}
|
|
|
|
readBufferStart = frameReadPos;
|
|
readBufferEnd = frameReadPos + readSize;
|
|
}
|
|
|
|
u8 *rawBuffer = &readBuffer[frameReadPos - readBufferStart];
|
|
bool plain = (idx & 0x80000000) != 0;
|
|
if (ver_ >= 2) {
|
|
// CSO v2+ requires blocks be uncompressed if large enough to be. High bit means other things.
|
|
plain = frameReadSize >= frameSize;
|
|
}
|
|
|
|
if (plain) {
|
|
memcpy(outPtr, rawBuffer + frameBlockOffset * GetBlockSize(), frameBlocks * GetBlockSize());
|
|
} else {
|
|
z.avail_in = frameReadSize;
|
|
z.next_out = frameBlocks == blocksPerFrame ? outPtr : zlibBuffer;
|
|
z.avail_out = frameSize;
|
|
z.next_in = rawBuffer;
|
|
|
|
int status = inflate(&z, Z_FINISH);
|
|
if (status != Z_STREAM_END) {
|
|
ERROR_LOG(Log::Loader, "Inflate frame %d: failed - %s[%d]\n", frame, (z.msg) ? z.msg : "error", status);
|
|
NotifyReadError();
|
|
memset(outPtr, 0, frameBlocks * GetBlockSize());
|
|
} else if (z.total_out != frameSize) {
|
|
ERROR_LOG(Log::Loader, "Inflate frame %d: block size error %d != %d\n", frame, (u32)z.total_out, frameSize);
|
|
NotifyReadError();
|
|
memset(outPtr, 0, frameBlocks * GetBlockSize());
|
|
} else if (frameBlocks != blocksPerFrame) {
|
|
memcpy(outPtr, zlibBuffer + frameBlockOffset * GetBlockSize(), frameBlocks * GetBlockSize());
|
|
// In case we end up reusing it in a single read later.
|
|
zlibBufferFrame = frame;
|
|
}
|
|
|
|
inflateReset(&z);
|
|
}
|
|
|
|
block += frameBlocks;
|
|
outPtr += frameBlocks * GetBlockSize();
|
|
}
|
|
|
|
inflateEnd(&z);
|
|
return true;
|
|
}
|
|
|
|
NPDRMDemoBlockDevice::NPDRMDemoBlockDevice(FileLoader *fileLoader)
|
|
: BlockDevice(fileLoader)
|
|
{
|
|
MAC_KEY mkey;
|
|
CIPHER_KEY ckey;
|
|
u8 np_header[256];
|
|
u32 tableOffset_, tableSize_;
|
|
|
|
fileLoader_->ReadAt(0x24, 1, 4, &psarOffset);
|
|
if (psarOffset >= fileLoader_->FileSize() - 256) {
|
|
errorString_ = "Unexpected psarOffset";
|
|
return;
|
|
}
|
|
|
|
size_t readSize = fileLoader_->ReadAt(psarOffset, 1, 256, &np_header);
|
|
if (readSize != 256) {
|
|
errorString_ = "Invalid NPUMDIMG header!";
|
|
return;
|
|
}
|
|
|
|
// Check np_header
|
|
if (memcmp(np_header, "NPUMDIMG", 8) != 0) {
|
|
// This is not something we can deal with here. Might be an oversized/misdetected
|
|
// regular PBP.
|
|
errorString_ = "Not a NPDRM PBP ISO";
|
|
return;
|
|
}
|
|
|
|
u32 psar_id;
|
|
fileLoader->ReadAt(psarOffset, 4, 1, &psar_id);
|
|
|
|
INFO_LOG(Log::Loader, "NPDRM: PSAR ID: %08x", psar_id);
|
|
// PS1 PSAR begins with "PSISOIMG0000"
|
|
if (psar_id == 'SISP') {
|
|
lbaSize_ = 0; // Mark invalid
|
|
ERROR_LOG(Log::Loader, "PSX not supported! Should have been caught earlier.");
|
|
errorString_ = "PSX ISOs not supported!";
|
|
return;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
|
|
// Local kirk instance to not clash with other block devices and other decryption things.
|
|
kirk_init(&kirk_);
|
|
|
|
// getkey
|
|
sceDrmBBMacInit(&mkey, 3);
|
|
sceDrmBBMacUpdate(&kirk_, &mkey, np_header, 0xc0);
|
|
bbmac_getkey(&kirk_, &mkey, np_header+0xc0, vkey);
|
|
|
|
// decrypt NP header
|
|
memcpy(hkey, np_header+0xa0, 0x10);
|
|
sceDrmBBCipherInit(&kirk_, &ckey, 1, 2, hkey, vkey, 0);
|
|
sceDrmBBCipherUpdate(&kirk_, &ckey, np_header+0x40, 0x60);
|
|
sceDrmBBCipherFinal(&ckey);
|
|
|
|
u32 lbaStart = *(u32*)(np_header+0x54); // LBA start
|
|
u32 lbaEnd = *(u32*)(np_header+0x64); // LBA end
|
|
lbaSize_ = (lbaEnd - lbaStart + 1); // LBA size of ISO
|
|
blockLBAs_ = *(u32*)(np_header+0x0c); // block size in LBA
|
|
|
|
char psarStr[5]{};
|
|
memcpy(psarStr, &psar_id, 4);
|
|
|
|
// Protect against a badly decrypted header, and send information through the assert about what's being played (implicitly).
|
|
_dbg_assert_msg_(blockLBAs_ <= 4096, "Bad blockLBAs in header: %08x (%s) psar: %s", blockLBAs_, fileLoader->GetPath().ToVisualString().c_str(), psarStr);
|
|
|
|
// When we remove the above assert, let's just try to survive.
|
|
if (blockLBAs_ > 4096) {
|
|
errorString_ = StringFromFormat("Bad blockLBAs in header: %08x (%s) psar: %s", blockLBAs_, GetFriendlyPath(fileLoader->GetPath()).c_str(), psarStr);
|
|
return;
|
|
}
|
|
|
|
blockSize_ = blockLBAs_ * 2048;
|
|
numBlocks_ = (lbaSize_ + blockLBAs_ - 1) / blockLBAs_; // total blocks;
|
|
|
|
blockBuf_ = new u8[blockSize_];
|
|
tempBuf_ = new u8[blockSize_];
|
|
|
|
tableOffset_ = *(u32*)(np_header + 0x6c); // table offset
|
|
if (tableOffset_ > fileLoader_->FileSize()) {
|
|
errorString_ = "Invalid table offset";
|
|
return;
|
|
}
|
|
|
|
tableSize_ = numBlocks_ * 32;
|
|
table_ = new table_info[numBlocks_];
|
|
|
|
readSize = fileLoader_->ReadAt(psarOffset + tableOffset_, 1, tableSize_, table_);
|
|
if (readSize != tableSize_){
|
|
errorString_ = "Invalid NPUMDIMG table!";
|
|
return;
|
|
}
|
|
|
|
u32 *p = (u32*)table_;
|
|
u32 i, k0, k1, k2, k3;
|
|
for (i = 0; i < numBlocks_; i++){
|
|
k0 = p[0]^p[1];
|
|
k1 = p[1]^p[2];
|
|
k2 = p[0]^p[3];
|
|
k3 = p[2]^p[3];
|
|
p[4] ^= k3;
|
|
p[5] ^= k1;
|
|
p[6] ^= k2;
|
|
p[7] ^= k0;
|
|
p += 8;
|
|
}
|
|
|
|
currentBlock_ = -1;
|
|
_dbg_assert_(errorString_.empty());
|
|
}
|
|
|
|
NPDRMDemoBlockDevice::~NPDRMDemoBlockDevice() {
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
delete [] table_;
|
|
delete [] tempBuf_;
|
|
delete [] blockBuf_;
|
|
}
|
|
|
|
int lzrc_decompress(void *out, int out_len, void *in, int in_len);
|
|
|
|
bool NPDRMDemoBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached) {
|
|
FileLoader::Flags flags = uncached ? FileLoader::Flags::HINT_UNCACHED : FileLoader::Flags::NONE;
|
|
std::lock_guard<std::mutex> guard(mutex_);
|
|
|
|
if (blockSize_ == 0) {
|
|
// Wasn't opened successfully.
|
|
return false;
|
|
}
|
|
|
|
int lba = blockNumber - currentBlock_;
|
|
if (lba >= 0 && lba < blockLBAs_){
|
|
memcpy(outPtr, blockBuf_ + lba*2048, 2048);
|
|
return true;
|
|
}
|
|
|
|
int block = blockNumber / blockLBAs_;
|
|
lba = blockNumber % blockLBAs_;
|
|
currentBlock_ = block * blockLBAs_;
|
|
|
|
if (table_[block].unk_1c != 0) {
|
|
if((u32)block == (numBlocks_ - 1))
|
|
return true; // demos make by fake_np
|
|
else
|
|
return false;
|
|
}
|
|
|
|
u8 *readBuf;
|
|
if (table_[block].size < blockSize_)
|
|
readBuf = tempBuf_;
|
|
else
|
|
readBuf = blockBuf_;
|
|
|
|
size_t readSize = fileLoader_->ReadAt(psarOffset+table_[block].offset, 1, table_[block].size, readBuf, flags);
|
|
if (readSize != (size_t)table_[block].size){
|
|
if((u32)block==(numBlocks_-1))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
if ((table_[block].flag & 1) == 0) {
|
|
// skip mac check
|
|
}
|
|
|
|
if ((table_[block].flag & 4) == 0) {
|
|
CIPHER_KEY ckey;
|
|
sceDrmBBCipherInit(&kirk_, &ckey, 1, 2, hkey, vkey, table_[block].offset>>4);
|
|
sceDrmBBCipherUpdate(&kirk_, &ckey, readBuf, table_[block].size);
|
|
sceDrmBBCipherFinal(&ckey);
|
|
}
|
|
|
|
if (table_[block].size < blockSize_) {
|
|
int lzsize = lzrc_decompress(blockBuf_, 0x00100000, readBuf, table_[block].size);
|
|
if(lzsize != blockSize_){
|
|
ERROR_LOG(Log::Loader, "LZRC decompress error! lzsize=%d\n", lzsize);
|
|
NotifyReadError();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
memcpy(outPtr, blockBuf_+lba*2048, 2048);
|
|
return true;
|
|
}
|
|
|
|
struct CHDImpl {
|
|
chd_file *chd = nullptr;
|
|
const chd_header *header = nullptr;
|
|
};
|
|
|
|
struct ExtendedCoreFile {
|
|
core_file core; // Must be the first struct member, for some tricky pointer casts.
|
|
uint64_t seekPos;
|
|
};
|
|
|
|
CHDFileBlockDevice::CHDFileBlockDevice(FileLoader *fileLoader)
|
|
: BlockDevice(fileLoader), impl_(new CHDImpl()) {
|
|
Path paths[8];
|
|
paths[0] = fileLoader->GetPath();
|
|
int depth = 0;
|
|
|
|
core_file_ = new ExtendedCoreFile();
|
|
core_file_->core.argp = fileLoader;
|
|
core_file_->core.fsize = [](core_file *file) -> uint64_t {
|
|
FileLoader *loader = (FileLoader *)file->argp;
|
|
return loader->FileSize();
|
|
};
|
|
core_file_->core.fseek = [](core_file *file, int64_t offset, int seekType) -> int {
|
|
ExtendedCoreFile *coreFile = (ExtendedCoreFile *)file;
|
|
switch (seekType) {
|
|
case SEEK_SET:
|
|
coreFile->seekPos = offset;
|
|
break;
|
|
case SEEK_CUR:
|
|
coreFile->seekPos += offset;
|
|
break;
|
|
case SEEK_END:
|
|
{
|
|
FileLoader *loader = (FileLoader *)file->argp;
|
|
coreFile->seekPos = loader->FileSize() + offset;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
};
|
|
core_file_->core.fread = [](void *out_data, size_t size, size_t count, core_file *file) {
|
|
ExtendedCoreFile *coreFile = (ExtendedCoreFile *)file;
|
|
FileLoader *loader = (FileLoader *)file->argp;
|
|
uint64_t totalSize = size * count;
|
|
loader->ReadAt(coreFile->seekPos, totalSize, out_data);
|
|
coreFile->seekPos += totalSize;
|
|
return size * count;
|
|
};
|
|
core_file_->core.fclose = [](core_file *file) {
|
|
ExtendedCoreFile *coreFile = (ExtendedCoreFile *)file;
|
|
delete coreFile;
|
|
return 0;
|
|
};
|
|
|
|
/*
|
|
// TODO: Support parent/child CHD files.
|
|
|
|
// Default, in case of failure
|
|
numBlocks = 0;
|
|
|
|
chd_header childHeader;
|
|
|
|
chd_error err = chd_read_header(paths[0].c_str(), &childHeader);
|
|
if (err != CHDERR_NONE) {
|
|
ERROR_LOG(Log::Loader, "Error loading CHD header for '%s': %s", paths[0].c_str(), chd_error_string(err));
|
|
NotifyReadError();
|
|
return;
|
|
}
|
|
|
|
// static const UINT8 nullsha1[CHD_SHA1_BYTES] = { 0 };
|
|
if (memcmp(nullsha1, childHeader.parentsha1, sizeof(childHeader.sha1)) != 0) {
|
|
chd_header parentHeader;
|
|
|
|
// Look for parent CHD in current directory
|
|
Path chdDir = paths[0].NavigateUp();
|
|
|
|
std::vector<File::FileInfo> files;
|
|
if (File::GetFilesInDir(chdDir, &files)) {
|
|
parentHeader.length = 0;
|
|
|
|
for (const auto &file : files) {
|
|
std::string extension = file.fullName.GetFileExtension();
|
|
if (extension != ".chd") {
|
|
continue;
|
|
}
|
|
|
|
if (chd_read_header(filepath.c_str(), &parentHeader) == CHDERR_NONE &&
|
|
memcmp(parentHeader.sha1, childHeader.parentsha1, sizeof(parentHeader.sha1)) == 0) {
|
|
// ERROR_LOG(Log::Loader, "Checking '%s'", filepath.c_str());
|
|
paths[++depth] = filepath;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check if parentHeader was opened
|
|
if (parentHeader.length == 0) {
|
|
ERROR_LOG(Log::Loader, "Error loading CHD '%s': parents not found", fileLoader->GetPath().c_str());
|
|
NotifyReadError();
|
|
return;
|
|
}
|
|
memcpy(childHeader.parentsha1, parentHeader.parentsha1, sizeof(childHeader.parentsha1));
|
|
} while (memcmp(nullsha1, childHeader.parentsha1, sizeof(childHeader.sha1)) != 0);
|
|
}
|
|
*/
|
|
|
|
chd_file *file = nullptr;
|
|
chd_error err = chd_open_core_file(&core_file_->core, CHD_OPEN_READ, NULL, &file);
|
|
if (err != CHDERR_NONE) {
|
|
errorString_ = StringFromFormat("CHD error: %s: %s", paths[depth].c_str(), chd_error_string(err));
|
|
return;
|
|
}
|
|
|
|
impl_->chd = file;
|
|
impl_->header = chd_get_header(impl_->chd);
|
|
|
|
readBuffer = new u8[impl_->header->hunkbytes];
|
|
currentHunk = -1;
|
|
blocksPerHunk = impl_->header->hunkbytes / impl_->header->unitbytes;
|
|
numBlocks = impl_->header->unitcount;
|
|
|
|
_dbg_assert_(errorString_.empty());
|
|
}
|
|
|
|
CHDFileBlockDevice::~CHDFileBlockDevice() {
|
|
if (impl_->chd) {
|
|
chd_close(impl_->chd);
|
|
delete[] readBuffer;
|
|
}
|
|
}
|
|
|
|
bool CHDFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached) {
|
|
if (!impl_->chd) {
|
|
ERROR_LOG(Log::Loader, "ReadBlock: CHD not open. %s", fileLoader_->GetPath().c_str());
|
|
return false;
|
|
}
|
|
if ((u32)blockNumber >= numBlocks) {
|
|
memset(outPtr, 0, GetBlockSize());
|
|
return false;
|
|
}
|
|
u32 hunk = blockNumber / blocksPerHunk;
|
|
u32 blockInHunk = blockNumber % blocksPerHunk;
|
|
|
|
if (currentHunk != hunk) {
|
|
chd_error err = chd_read(impl_->chd, hunk, readBuffer);
|
|
if (err != CHDERR_NONE) {
|
|
ERROR_LOG(Log::Loader, "CHD read failed: %d %d %s", blockNumber, hunk, chd_error_string(err));
|
|
NotifyReadError();
|
|
}
|
|
currentHunk = hunk;
|
|
}
|
|
memcpy(outPtr, readBuffer + blockInHunk * impl_->header->unitbytes, GetBlockSize());
|
|
return true;
|
|
}
|
|
|
|
bool CHDFileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {
|
|
if (minBlock >= numBlocks) {
|
|
memset(outPtr, 0, GetBlockSize() * count);
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
if (!ReadBlock(minBlock + i, outPtr + i * GetBlockSize())) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|