/*
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.HLE.VFS.crypto;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import jpcsp.HLE.TPointer;
import jpcsp.HLE.VFS.AbstractProxyVirtualFile;
import jpcsp.HLE.VFS.IVirtualFile;
import jpcsp.crypto.CryptoEngine;
import jpcsp.crypto.PGD;
import jpcsp.util.Utilities;
public class PGDBlockVirtualFile extends AbstractProxyVirtualFile {
private static final int pgdHeaderSize = 0x90;
private byte[] key;
private int dataOffset;
private int dataSize;
private int blockSize;
private boolean headerValid;
private boolean headerPresent;
private byte[] buffer;
private byte[] header;
private PGD pgd;
private boolean sequentialRead;
public PGDBlockVirtualFile(IVirtualFile pgdFile, byte[] key, int dataOffset) {
super(pgdFile);
if (key != null) {
this.key = key.clone();
}
this.dataOffset = dataOffset;
super.ioLseek(dataOffset);
pgd = new CryptoEngine().getPGDEngine();
readHeader();
this.dataOffset += dataOffset;
}
private void readHeader() {
headerValid = false;
headerPresent = false;
byte[] inBuf = new byte[pgdHeaderSize];
super.ioRead(inBuf, 0, pgdHeaderSize);
// Check if the "PGD" header is present
if (inBuf[0] != 0 || inBuf[1] != 'P' || inBuf[2] != 'G' || inBuf[3] != 'D') {
// No "PGD" found in the header,
log.warn(String.format("No PGD header detected %02X %02X %02X %02X ('%c%c%c%c') detected in file '%s'", inBuf[0] & 0xFF, inBuf[1] & 0xFF, inBuf[2] & 0xFF, inBuf[3] & 0xFF, (char) inBuf[0], (char) inBuf[1], (char) inBuf[2], (char) inBuf[3], vFile));
return;
}
headerPresent = true;
// Decrypt 0x30 bytes at offset 0x30 to expose the first header.
byte[] headerBuf = new byte[0x30 + 0x10];
System.arraycopy(inBuf, 0x10, headerBuf, 0, 0x10);
System.arraycopy(inBuf, 0x30, headerBuf, 0x10, 0x30);
if (key == null) {
key = pgd.GetEDATPGDKey(inBuf, pgdHeaderSize);
}
header = pgd.DecryptPGD(headerBuf, headerBuf.length, key, 0);
// Extract the decryption parameters.
IntBuffer decryptedHeader = ByteBuffer.wrap(header).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
dataSize = decryptedHeader.get(5);
blockSize = decryptedHeader.get(6);
dataOffset = decryptedHeader.get(7);
if (log.isDebugEnabled()) {
log.debug(String.format("PGD dataSize=%d, blockSize=%d, dataOffset=%d", dataSize, blockSize, dataOffset));
if (log.isTraceEnabled()) {
log.trace(String.format("PGD Header: %s", Utilities.getMemoryDump(inBuf, 0, pgdHeaderSize)));
log.trace(String.format("Decrypted PGD Header: %s", Utilities.getMemoryDump(header, 0, header.length)));
}
}
if (dataOffset < 0 || dataOffset > super.length() || dataSize < 0) {
// The decrypted PGD header is incorrect...
log.warn(String.format("Incorrect PGD header: dataSize=%d, chunkSize=%d, hashOffset=%d", dataSize, blockSize, dataOffset));
return;
}
buffer = new byte[blockSize + 0x10];
headerValid = true;
sequentialRead = false;
}
public int getBlockSize() {
return blockSize;
}
public boolean isHeaderValid() {
return headerValid;
}
public boolean isHeaderPresent() {
return headerPresent;
}
@Override
public int ioRead(byte[] outputBuffer, int outputOffset, int outputLength) {
int seed = 0;
if (!sequentialRead) {
seed = (int) (getPosition() >> 4);
}
int readLength = blockSize;
super.ioRead(buffer, 0x10, readLength);
System.arraycopy(header, 0, buffer, 0, 0x10);
byte[] decryptedBytes;
if (sequentialRead) {
// This operation is more efficient than a complete DecryptPGD() call
// but can only be used when decrypting sequential packets.
decryptedBytes = pgd.UpdatePGDCipher(buffer, readLength + 0x10);
} else {
pgd.FinishPGDCipher();
decryptedBytes = pgd.DecryptPGD(buffer, readLength + 0x10, key, seed);
sequentialRead = true;
}
int length = Math.min(outputLength, decryptedBytes.length);
System.arraycopy(decryptedBytes, 0, outputBuffer, outputOffset, length);
if (log.isTraceEnabled()) {
log.trace(String.format("PGDBlockVirtualFile.ioRead length=0x%X: %s", length, Utilities.getMemoryDump(decryptedBytes, 0, length)));
}
return length;
}
@Override
public int ioRead(TPointer outputPointer, int outputLength) {
return ioReadBuf(outputPointer, outputLength);
}
@Override
public long ioLseek(long offset) {
long result = super.ioLseek(dataOffset + offset);
if (result >= 0 && result >= dataOffset) {
result -= dataOffset;
}
if (log.isTraceEnabled()) {
log.trace(String.format("PGDBlockVirtualFile.ioLseek offset=0x%X, result=0x%X", offset, result));
}
sequentialRead = false;
return result;
}
@Override
public long getPosition() {
long position = super.getPosition();
if (position >= 0 && position >= dataOffset) {
position -= dataOffset;
}
return position;
}
@Override
public long length() {
return dataSize;
}
}