/*
This file is part of jpcsp.
Jpcsp 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, either version 3 of the License, or
(at your option) any later version.
Jpcsp 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 for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.filesystems.umdiso;
import static jpcsp.util.Tlzrc.lzrc_decompress;
import static jpcsp.util.Utilities.endianSwap32;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.Arrays;
import jpcsp.crypto.AMCTRL;
import jpcsp.crypto.AMCTRL.BBCipher_Ctx;
import jpcsp.crypto.AMCTRL.BBMac_Ctx;
import jpcsp.crypto.CryptoEngine;
import jpcsp.util.Utilities;
public class PBPFileSectorDevice extends AbstractFileSectorDevice implements IBrowser {
private int lbaSize;
private int blockSize;
private int blockLBAs;
private int numBlocks;
private int numSectors;
private byte[] vkey;
private byte[] hkey = new byte[16];
private TableInfo[] table;
private int currentBlock;
private AMCTRL amctrl;
private byte[] blockBuffer;
private byte[] tempBuffer;
private int offsetParamSFO;
private int offsetIcon0;
private int offsetIcon1;
private int offsetPic0;
private int offsetPic1;
private int offsetSnd0;
private int offsetPspData;
private int offsetPsarData;
private static class TableInfo {
public static final int FLAG_IS_UNCRYPTED = 4;
public byte[] mac = new byte[16];
public int offset;
public int size;
public int flags;
public int unknown;
}
public PBPFileSectorDevice(RandomAccessFile fileAccess) {
super(fileAccess);
try {
int magic = endianSwap32(fileAccess.readInt());
int version = endianSwap32(fileAccess.readInt());
offsetParamSFO = endianSwap32(fileAccess.readInt());
offsetIcon0 = endianSwap32(fileAccess.readInt());
offsetIcon1 = endianSwap32(fileAccess.readInt());
offsetPic0 = endianSwap32(fileAccess.readInt());
offsetPic1 = endianSwap32(fileAccess.readInt());
offsetSnd0 = endianSwap32(fileAccess.readInt());
offsetPspData = endianSwap32(fileAccess.readInt());
offsetPsarData = endianSwap32(fileAccess.readInt());
if (magic != 0x50425000) {
throw new IOException(String.format("Invalid PBP header 0x%08X", magic));
}
if (version != 0x00010000 && version != 0x00000100 && version != 0x00010001) {
throw new IOException(String.format("Invalid PBP version 0x%08X", version));
}
fileAccess.seek(offsetPsarData);
byte[] header = new byte[256];
int readSize = fileAccess.read(header);
if (readSize != header.length) {
int psarDataLength = (int) (fileAccess.length() - offsetPsarData);
if (psarDataLength != 0 && psarDataLength != 16) {
throw new IOException(String.format("Invalid PBP header"));
}
} else if (header[0] == 'N' && header[1] == 'P' && header[2] == 'U' && header[3] == 'M' && header[4] == 'D' && header[5] == 'I' && header[6] == 'M' && header[7] == 'G') {
CryptoEngine cryptoEngine = new CryptoEngine();
amctrl = cryptoEngine.getAMCTRLEngine();
BBMac_Ctx macContext = new BBMac_Ctx();
BBCipher_Ctx cipherContext = new BBCipher_Ctx();
// getKey
amctrl.hleDrmBBMacInit(macContext, 3);
amctrl.hleDrmBBMacUpdate(macContext, header, 0xC0);
byte[] macKeyC0 = new byte[16];
System.arraycopy(header, 0xC0, macKeyC0, 0, macKeyC0.length);
vkey = amctrl.GetKeyFromBBMac(macContext, macKeyC0);
// decrypt NP header
byte[] cipherData = new byte[0x60];
System.arraycopy(header, 0x40, cipherData, 0, cipherData.length);
System.arraycopy(header, 0xA0, hkey, 0, hkey.length);
amctrl.hleDrmBBCipherInit(cipherContext, 1, 2, hkey, vkey);
amctrl.hleDrmBBCipherUpdate(cipherContext, cipherData, cipherData.length);
amctrl.hleDrmBBCipherFinal(cipherContext);
int lbaStart = Utilities.readUnaligned32(cipherData, 0x14);
int lbaEnd = Utilities.readUnaligned32(cipherData, 0x24);
numSectors = lbaEnd + 1;
lbaSize = numSectors - lbaStart;
blockLBAs = Utilities.readUnaligned32(header, 0x0C);
blockSize = blockLBAs * sectorLength;
numBlocks = (lbaSize + blockLBAs - 1) / blockLBAs;
blockBuffer = new byte[blockSize];
tempBuffer = new byte[blockSize];
table = new TableInfo[numBlocks];
int tableOffset = Utilities.readUnaligned32(cipherData, 0x2C);
fileAccess.seek(offsetPsarData + tableOffset);
byte[] tableBytes = new byte[numBlocks * 32];
readSize = fileAccess.read(tableBytes);
if (readSize != tableBytes.length) {
log.error(String.format("Could not read table with size %d (readSize=%d)", tableBytes.length, readSize));
}
IntBuffer tableInts = ByteBuffer.wrap(tableBytes).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
for (int i = 0; i < numBlocks; i++) {
int p0 = tableInts.get();
int p1 = tableInts.get();
int p2 = tableInts.get();
int p3 = tableInts.get();
int p4 = tableInts.get();
int p5 = tableInts.get();
int p6 = tableInts.get();
int p7 = tableInts.get();
int k0 = p0 ^ p1;
int k1 = p1 ^ p2;
int k2 = p0 ^ p3;
int k3 = p2 ^ p3;
TableInfo tableInfo = new TableInfo();
System.arraycopy(tableBytes, i * 32, tableInfo.mac, 0, tableInfo.mac.length);
tableInfo.offset = p4 ^ k3;
tableInfo.size = p5 ^ k1;
tableInfo.flags = p6 ^ k2;
tableInfo.unknown = p7 ^ k0;
table[i] = tableInfo;
}
currentBlock = -1;
}
} catch (IOException e) {
log.error("Reading PBP", e);
}
}
@Override
public int getNumSectors() {
return numSectors;
}
@Override
public void readSector(int sectorNumber, byte[] buffer, int offset) throws IOException {
int lba = sectorNumber - currentBlock;
if (table == null) {
Arrays.fill(buffer, offset, offset + sectorLength, (byte) 0);
} else if (currentBlock >= 0 && lba >= 0 && lba < blockLBAs) {
System.arraycopy(blockBuffer, lba * sectorLength, buffer, offset, sectorLength);
} else {
int block = sectorNumber / blockLBAs;
lba = sectorNumber % blockLBAs;
currentBlock = block * blockLBAs;
if (table[block].unknown == 0) {
fileAccess.seek(offsetPsarData + table[block].offset);
byte [] readBuffer;
if (table[block].size < blockSize) {
// For compressed blocks, decode into a temporary buffer
readBuffer = tempBuffer;
} else {
readBuffer = blockBuffer;
}
int readSize = fileAccess.read(readBuffer, 0, table[block].size);
if (readSize == table[block].size) {
if ((table[block].flags & TableInfo.FLAG_IS_UNCRYPTED) == 0) {
BBCipher_Ctx cipherContext = new BBCipher_Ctx();
amctrl.hleDrmBBCipherInit(cipherContext, 1, 2, hkey, vkey, table[block].offset >> 4);
amctrl.hleDrmBBCipherUpdate(cipherContext, readBuffer, table[block].size);
amctrl.hleDrmBBCipherFinal(cipherContext);
}
// Compressed block?
if (table[block].size < blockSize) {
int lzsize = lzrc_decompress(blockBuffer, blockBuffer.length, readBuffer, table[block].size);
if (lzsize != blockSize) {
log.error(String.format("LZRC decompress error: decompressedSized=%d, should be %d", lzsize, blockSize));
}
}
System.arraycopy(blockBuffer, lba * sectorLength, buffer, offset, sectorLength);
}
}
}
}
private byte[] read(int offset, int length) throws IOException {
if (length <= 0) {
return null;
}
byte[] buffer = new byte[length];
fileAccess.seek(offset & 0xFFFFFFFFL);
int read = fileAccess.read(buffer);
if (read < 0) {
return null;
}
// Read less than expected?
if (read < length) {
// Shrink the buffer to the read size
byte[] newBuffer = new byte[read];
System.arraycopy(buffer, 0, newBuffer, 0, read);
buffer = newBuffer;
}
return buffer;
}
@Override
public byte[] readParamSFO() throws IOException {
return read(offsetParamSFO, offsetIcon0 - offsetParamSFO);
}
@Override
public byte[] readIcon0() throws IOException {
return read(offsetIcon0, offsetIcon1 - offsetIcon0);
}
@Override
public byte[] readIcon1() throws IOException {
return read(offsetIcon1, offsetPic0 - offsetIcon1);
}
@Override
public byte[] readPic0() throws IOException {
return read(offsetPic0, offsetPic1 - offsetPic0);
}
@Override
public byte[] readPic1() throws IOException {
return read(offsetPic1, offsetSnd0 - offsetPic1);
}
@Override
public byte[] readSnd0() throws IOException {
return read(offsetSnd0, offsetPspData - offsetSnd0);
}
@Override
public byte[] readPspData() throws IOException {
return read(offsetPspData, offsetPsarData - offsetPspData);
}
@Override
public byte[] readPsarData() throws IOException {
return read(offsetPsarData, (int) (fileAccess.length() - offsetPsarData));
}
}