ppsspp/Core/FileSystems/BlockDevices.cpp
Unknown W. Brackets dd5c91108c Use a background thread to load ISO into RAM.
This way we don't get slow startup.  This will also cache the CSO data,
for example, rather than the raw data, using up less RAM.  It might even
be reasonable to enable on 32-bit.
2015-12-19 15:23:25 -08:00

480 lines
14 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 "Common/FileUtil.h"
#include "Core/Loaders.h"
#include "Core/FileSystems/BlockDevices.h"
#include <cstdio>
#include <cstring>
#include <algorithm>
extern "C"
{
#include "zlib.h"
#include "ext/libkirk/amctrl.h"
#include "ext/libkirk/kirk_engine.h"
};
BlockDevice *constructBlockDevice(FileLoader *fileLoader) {
// Check for CISO
if (!fileLoader->Exists())
return nullptr;
char buffer[4];
size_t size = fileLoader->ReadAt(0, 1, 4, buffer);
fileLoader->Seek(0);
if (!memcmp(buffer, "CISO", 4) && size == 4)
return new CISOFileBlockDevice(fileLoader);
else if (!memcmp(buffer, "\x00PBP", 4) && size == 4)
return new NPDRMDemoBlockDevice(fileLoader);
else
return new FileBlockDevice(fileLoader);
}
FileBlockDevice::FileBlockDevice(FileLoader *fileLoader)
: fileLoader_(fileLoader) {
filesize_ = fileLoader->FileSize();
}
FileBlockDevice::~FileBlockDevice() {
}
bool FileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr) {
if (fileLoader_->ReadAt((u64)blockNumber * (u64)GetBlockSize(), 1, 2048, outPtr) != 2048) {
DEBUG_LOG(FILESYS, "Could not read 2048 bytes from block");
return false;
}
return true;
}
bool FileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) {
if (fileLoader_->ReadAt((u64)minBlock * (u64)GetBlockSize(), 2048, count, outPtr) != (size_t)count) {
ERROR_LOG(FILESYS, "Could not read %d bytes from block", 2048 * count);
return false;
}
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)
: fileLoader_(fileLoader)
{
// CISO format is fairly simple, but most tools do not write the header_size.
CISO_H hdr;
size_t readSize = fileLoader->ReadAt(0, sizeof(CISO_H), 1, &hdr);
if (readSize != 1 || memcmp(hdr.magic, "CISO", 4) != 0)
{
WARN_LOG(LOADER, "Invalid CSO!");
}
else
{
VERBOSE_LOG(LOADER, "Valid CSO!");
}
if (hdr.ver > 1)
{
ERROR_LOG(LOADER, "CSO version too high!");
//ARGH!
}
frameSize = hdr.block_size;
if ((frameSize & (frameSize - 1)) != 0)
ERROR_LOG(LOADER, "CSO block size %i unsupported, must be a power of two", frameSize);
else if (frameSize < 0x800)
ERROR_LOG(LOADER, "CSO block size %i unsupported, must be at least one sector", frameSize);
// 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(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;
#if COMMON_LITTLE_ENDIAN
index = new u32[indexSize];
if (fileLoader->ReadAt(sizeof(hdr), sizeof(u32), indexSize, index) != indexSize)
memset(index, 0, indexSize * sizeof(u32));
#else
index = new u32[indexSize];
u32_le *indexTemp = new u32_le[indexSize];
if (fileLoader->ReadAt(sizeof(hdr), sizeof(u32), indexSize, indexTemp) != indexSize)
{
memset(indexTemp, 0, indexSize * sizeof(u32_le));
}
for (u32 i = 0; i < indexSize; i++)
{
index[i] = indexTemp[i];
}
delete[] indexTemp;
#endif
}
CISOFileBlockDevice::~CISOFileBlockDevice()
{
delete [] index;
delete [] readBuffer;
delete [] zlibBuffer;
}
bool CISOFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr)
{
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();
const int plain = idx & 0x80000000;
if (plain)
{
int readSize = (u32)fileLoader_->ReadAt(compressedReadPos + compressedOffset, 1, GetBlockSize(), outPtr);
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);
z.zalloc = Z_NULL;
z.zfree = Z_NULL;
z.opaque = Z_NULL;
if(inflateInit2(&z, -15) != Z_OK)
{
ERROR_LOG(LOADER, "GetBlockSize() ERROR: %s\n", (z.msg) ? z.msg : "?");
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(LOADER, "block %d: inflate : %s[%d]\n", blockNumber, (z.msg) ? z.msg : "error", status);
inflateEnd(&z);
memset(outPtr, 0, GetBlockSize());
return false;
}
if (z.total_out != frameSize)
{
ERROR_LOG(LOADER, "block %d: block size error %d != %d\n", blockNumber, (u32)z.total_out, frameSize);
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 = (lastBlock + 1 - minBlock) - count;
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;
z.zalloc = Z_NULL;
z.zfree = Z_NULL;
z.opaque = Z_NULL;
if (inflateInit2(&z, -15) != Z_OK) {
ERROR_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];
const int plain = idx & 0x80000000;
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(LOADER, "Inflate frame %d: failed - %s[%d]\n", frame, (z.msg) ? z.msg : "error", status);
memset(outPtr, 0, frameBlocks * GetBlockSize());
} else if (z.total_out != frameSize) {
ERROR_LOG(LOADER, "Inflate frame %d: block size error %d != %d\n", frame, (u32)z.total_out, frameSize);
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)
: fileLoader_(fileLoader)
{
MAC_KEY mkey;
CIPHER_KEY ckey;
u8 np_header[256];
u32 tableOffset, tableSize;
u32 lbaStart, lbaEnd;
fileLoader_->ReadAt(0x24, 1, 4, &psarOffset);
size_t readSize = fileLoader_->ReadAt(psarOffset, 1, 256, &np_header);
if(readSize!=256){
ERROR_LOG(LOADER, "Invalid NPUMDIMG header!");
}
kirk_init();
// getkey
sceDrmBBMacInit(&mkey, 3);
sceDrmBBMacUpdate(&mkey, np_header, 0xc0);
bbmac_getkey(&mkey, np_header+0xc0, vkey);
// decrypt NP header
memcpy(hkey, np_header+0xa0, 0x10);
sceDrmBBCipherInit(&ckey, 1, 2, hkey, vkey, 0);
sceDrmBBCipherUpdate(&ckey, np_header+0x40, 0x60);
sceDrmBBCipherFinal(&ckey);
lbaStart = *(u32*)(np_header+0x54); // LBA start
lbaEnd = *(u32*)(np_header+0x64); // LBA end
lbaSize = (lbaEnd-lbaStart+1); // LBA size of ISO
blockLBAs = *(u32*)(np_header+0x0c); // block size in LBA
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
tableSize = numBlocks*32;
table = new table_info[numBlocks];
readSize = fileLoader_->ReadAt(psarOffset + tableOffset, 1, tableSize, table);
if(readSize!=tableSize){
ERROR_LOG(LOADER, "Invalid NPUMDIMG table!");
}
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;
}
NPDRMDemoBlockDevice::~NPDRMDemoBlockDevice()
{
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)
{
CIPHER_KEY ckey;
int block, lba, lzsize;
size_t readSize;
u8 *readBuf;
lba = blockNumber-currentBlock;
if(lba>=0 && lba<blockLBAs){
memcpy(outPtr, blockBuf+lba*2048, 2048);
return true;
}
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;
}
if(table[block].size<blockSize)
readBuf = tempBuf;
else
readBuf = blockBuf;
readSize = fileLoader_->ReadAt(psarOffset+table[block].offset, 1, table[block].size, readBuf);
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){
sceDrmBBCipherInit(&ckey, 1, 2, hkey, vkey, table[block].offset>>4);
sceDrmBBCipherUpdate(&ckey, readBuf, table[block].size);
sceDrmBBCipherFinal(&ckey);
}
if(table[block].size<blockSize){
lzsize = lzrc_decompress(blockBuf, 0x00100000, readBuf, table[block].size);
if(lzsize!=blockSize){
ERROR_LOG(LOADER, "LZRC decompress error! lzsize=%d\n", lzsize);
return false;
}
}
memcpy(outPtr, blockBuf+lba*2048, 2048);
return true;
}