mirror of
https://github.com/libretro/ppsspp.git
synced 2024-12-11 18:33:41 +00:00
504 lines
14 KiB
C++
504 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);
|
|
}
|
|
|
|
u32 BlockDevice::CalculateCRC() {
|
|
u32 crc = crc32(0, Z_NULL, 0);
|
|
|
|
u8 block[2048];
|
|
for (u32 i = 0; i < GetNumBlocks(); ++i) {
|
|
if (!ReadBlock(i, block, true)) {
|
|
ERROR_LOG(HLE, "Failed to read block for CRC");
|
|
return 0;
|
|
}
|
|
crc = crc32(crc, block, 2048);
|
|
}
|
|
|
|
return crc;
|
|
}
|
|
|
|
|
|
FileBlockDevice::FileBlockDevice(FileLoader *fileLoader)
|
|
: fileLoader_(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;
|
|
if (fileLoader_->ReadAt((u64)blockNumber * (u64)GetBlockSize(), 1, 2048, outPtr, flags) != 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, 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();
|
|
|
|
const int plain = idx & 0x80000000;
|
|
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(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;
|
|
}
|
|
|
|
|
|
recursive_mutex NPDRMDemoBlockDevice::mutex_;
|
|
|
|
NPDRMDemoBlockDevice::NPDRMDemoBlockDevice(FileLoader *fileLoader)
|
|
: fileLoader_(fileLoader)
|
|
{
|
|
lock_guard guard(mutex_);
|
|
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()
|
|
{
|
|
lock_guard 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;
|
|
lock_guard guard(mutex_);
|
|
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, 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){
|
|
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;
|
|
}
|