/* 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.crypto; import java.nio.ByteBuffer; import jpcsp.Emulator; import jpcsp.State; import jpcsp.HLE.kernel.types.pspAbstractMemoryMappedStructure; import jpcsp.format.PSF; import jpcsp.util.Utilities; public class SAVEDATA { private static KIRK kirk; private static final byte[] sdseed = { (byte) 'S', (byte) 'A', (byte) 'V', (byte) 'E', (byte) 'D', (byte) 'A', (byte) 'T', (byte) 'A', (byte) 'S', (byte) 'E', (byte) 'E', (byte) 'D', (byte) 'J', (byte) 'P', (byte) 'C', (byte) 'S', (byte) 'P', (byte) '0', (byte) '0', (byte) '0' }; public SAVEDATA() { // Start the KIRK engine with a dummy seed and fuseID. kirk = new KIRK(sdseed, 0x14, 0xDEADC0DE, 0x12345678); } // CHNNLSV SD context structs. public static class SD_Ctx1 extends pspAbstractMemoryMappedStructure { public int mode; public byte[] pad = new byte[16]; public byte[] key = new byte[16]; public int padSize; @Override protected void read() { mode = read32(); read8Array(pad); read8Array(key); padSize = read32(); } @Override protected void write() { write32(mode); write8Array(pad); write8Array(key); write32(padSize); } @Override public int sizeof() { return 40; } @Override public String toString() { return String.format("mode=0x%X, pad=%s, key=%s, padSize=0x%X", mode, Utilities.getMemoryDump(pad, 0, pad.length), Utilities.getMemoryDump(key, 0, key.length), padSize); } } public static class SD_Ctx2 extends pspAbstractMemoryMappedStructure { private int mode; private int unk; private byte[] buf; public SD_Ctx2() { mode = 0; unk = 0; buf = new byte[16]; } @Override protected void read() { mode = read32(); unk = read32(); read8Array(buf); } @Override protected void write() { write32(mode); write32(unk); write8Array(buf); } @Override public int sizeof() { return 24; } @Override public String toString() { return String.format("mode=0x%X, unk=0x%X, buf=%s", mode, unk, Utilities.getMemoryDump(buf, 0, buf.length)); } } private static boolean isNullKey(byte[] key) { if (key != null) { for (int i = 0; i < key.length; i++) { if (key[i] != (byte) 0) { return false; } } } return true; } private byte[] xorHash(byte[] dest, int dest_offset, int[] src, int src_offset, int size) { for (int i = 0; i < size; i++) { dest[dest_offset + i] = (byte) (dest[dest_offset + i] ^ src[src_offset + i]); } return dest; } private byte[] xorKey(byte[] dest, int dest_offset, byte[] src, int src_offset, int size) { for (int i = 0; i < size; i++) { dest[dest_offset + i] = (byte) (dest[dest_offset + i] ^ src[src_offset + i]); } return dest; } private void ScrambleSD(byte[] buf, int size, int seed, int cbc, int kirk_code) { // Set CBC mode. buf[0] = 0; buf[1] = 0; buf[2] = 0; buf[3] = (byte) cbc; // Set unkown parameters to 0. buf[4] = 0; buf[5] = 0; buf[6] = 0; buf[7] = 0; buf[8] = 0; buf[9] = 0; buf[10] = 0; buf[11] = 0; // Set the the key seed to seed. buf[12] = 0; buf[13] = 0; buf[14] = 0; buf[15] = (byte) seed; // Set the the data size to size. buf[16] = (byte) ((size >> 24) & 0xFF); buf[17] = (byte) ((size >> 16) & 0xFF); buf[18] = (byte) ((size >> 8) & 0xFF); buf[19] = (byte) (size & 0xFF); // Ignore PSP_KIRK_CMD_ENCRYPT_FUSE and PSP_KIRK_CMD_DECRYPT_FUSE. if((kirk_code == 0x5) || (kirk_code == 8)) return; ByteBuffer bBuf = ByteBuffer.wrap(buf); kirk.hleUtilsBufferCopyWithRange(bBuf, size, bBuf, size, kirk_code); } private int getModeSeed(int mode) { int seed; switch (mode) { case 0x6: seed = 0x11; break; case 0x4: seed = 0xD; break; case 0x2: seed = 0x5; break; case 0x1: seed = 0x3; break; case 0x3: seed = 0xC; break; default: seed = 0x10; break; } return seed; } private void cryptMember(SD_Ctx2 ctx, byte[] data, int data_offset, int length) { int finalSeed; byte[] dataBuf = new byte[length + 0x14]; byte[] keyBuf1 = new byte[0x10]; byte[] keyBuf2 = new byte[0x10]; byte[] hashBuf = new byte[0x10]; // Copy the hash stored by hleSdCreateList. System.arraycopy(ctx.buf, 0, dataBuf, 0x14, 0x10); if (ctx.mode == 0x1) { // Decryption mode 0x01: decrypt the hash directly with KIRK CMD7. ScrambleSD(dataBuf, 0x10, 0x4, 5, KIRK.PSP_KIRK_CMD_DECRYPT); finalSeed = 0x53; } else if (ctx.mode == 0x2) { // Decryption mode 0x02: decrypt the hash directly with KIRK CMD8. ScrambleSD(dataBuf, 0x10, 0x100, 5, KIRK.PSP_KIRK_CMD_DECRYPT_FUSE); finalSeed = 0x53; } else if (ctx.mode == 0x3) { // Decryption mode 0x03: XOR the hash with SD keys and decrypt with KIRK CMD7. dataBuf = xorHash(dataBuf, 0x14, KeyVault.sdHashKey4, 0, 0x10); ScrambleSD(dataBuf, 0x10, 0xE, 5, KIRK.PSP_KIRK_CMD_DECRYPT); dataBuf = xorHash(dataBuf, 0, KeyVault.sdHashKey3, 0, 0x10); finalSeed = 0x57; } else if (ctx.mode == 0x4) { // Decryption mode 0x04: XOR the hash with SD keys and decrypt with KIRK CMD8. dataBuf = xorHash(dataBuf, 0x14, KeyVault.sdHashKey4, 0, 0x10); ScrambleSD(dataBuf, 0x10, 0x100, 5, KIRK.PSP_KIRK_CMD_DECRYPT_FUSE); dataBuf = xorHash(dataBuf, 0, KeyVault.sdHashKey3, 0, 0x10); finalSeed = 0x57; } else if (ctx.mode == 0x6) { // Decryption mode 0x06: XOR the hash with new SD keys and decrypt with KIRK CMD8. dataBuf = xorHash(dataBuf, 0x14, KeyVault.sdHashKey7, 0, 0x10); ScrambleSD(dataBuf, 0x10, 0x100, 5, KIRK.PSP_KIRK_CMD_DECRYPT_FUSE); dataBuf = xorHash(dataBuf, 0, KeyVault.sdHashKey6, 0, 0x10); finalSeed = 0x64; } else { // Decryption mode 0x05: XOR the hash with new SD keys and decrypt with KIRK CMD7. dataBuf = xorHash(dataBuf, 0x14, KeyVault.sdHashKey7, 0, 0x10); ScrambleSD(dataBuf, 0x10, 0x12, 5, KIRK.PSP_KIRK_CMD_DECRYPT); dataBuf = xorHash(dataBuf, 0, KeyVault.sdHashKey6, 0, 0x10); finalSeed = 0x64; } // Store the calculated key. System.arraycopy(dataBuf, 0, keyBuf2, 0, 0x10); // Apply extra padding if ctx.unk is not 1. if (ctx.unk != 0x1) { System.arraycopy(keyBuf2, 0, keyBuf1, 0, 0xC); keyBuf1[0xC] = (byte) ((ctx.unk - 1) & 0xFF); keyBuf1[0xD] = (byte) (((ctx.unk - 1) >> 8) & 0xFF); keyBuf1[0xE] = (byte) (((ctx.unk - 1) >> 16) & 0xFF); keyBuf1[0xF] = (byte) (((ctx.unk - 1) >> 24) & 0xFF); } // Copy the first 0xC bytes of the obtained key and replicate them // across a new list buffer. As a terminator, add the ctx1.seed parameter's // 4 bytes (endian swapped) to achieve a full numbered list. for (int i = 0x14; i < (length + 0x14); i += 0x10) { System.arraycopy(keyBuf2, 0, dataBuf, i, 0xC); dataBuf[i + 0xC] = (byte) (ctx.unk & 0xFF); dataBuf[i + 0xD] = (byte) ((ctx.unk >> 8) & 0xFF); dataBuf[i + 0xE] = (byte) ((ctx.unk >> 16) & 0xFF); dataBuf[i + 0xF] = (byte) ((ctx.unk >> 24) & 0xFF); ctx.unk++; } // Copy the generated hash to hashBuf. System.arraycopy(dataBuf, length + 0x04, hashBuf, 0, 0x10); // Decrypt the hash with KIRK CMD7. ScrambleSD(dataBuf, length, finalSeed, 5, KIRK.PSP_KIRK_CMD_DECRYPT); // XOR the first 16-bytes of data with the saved key to generate a new hash. dataBuf = xorKey(dataBuf, 0, keyBuf1, 0, 0x10); // Copy back the last hash from the list to the first keyBuf. System.arraycopy(hashBuf, 0, keyBuf1, 0, 0x10); // Finally, XOR the full list with the given data. xorKey(data, data_offset, dataBuf, 0, length); } /* * sceSd - chnnlsv.prx */ public int hleSdSetIndex(SD_Ctx1 ctx, int encMode) { // Set all parameters to 0 and assign the encMode. ctx.mode = encMode; ctx.padSize = 0; for (int i = 0; i < 0x10; i++) { ctx.pad[i] = 0; } for (int i = 0; i < 0x10; i++) { ctx.key[i] = 0; } return 0; } public int hleSdRemoveValue(SD_Ctx1 ctx, byte[] data, int length) { if (ctx.padSize > 0x10 || (length < 0)) { // Invalid key or length. return -1; } else if (((ctx.padSize + length) <= 0x10)) { // The key hasn't been set yet. // Extract the hash from the data and set it as the key. System.arraycopy(data, 0, ctx.pad, ctx.padSize, length); ctx.padSize += length; return 0; } else { // Calculate the seed. int seed = getModeSeed(ctx.mode); // Setup the buffer. byte[] scrambleBuf = new byte[0x800 + 0x14]; // Copy the previous pad key to the buffer. System.arraycopy(ctx.pad, 0, scrambleBuf, 0x14, ctx.padSize); // Calculate new key length. int kLen = ((ctx.padSize + length) & 0x0F); if (kLen == 0) { kLen = 0x10; } // Calculate new data length. int nLen = ctx.padSize; ctx.padSize = kLen; // Copy data's footer to make a new key. int remaining = length - kLen; System.arraycopy(data, remaining, ctx.pad, 0, kLen); // Process the encryption in 0x800 blocks. int blockSize = 0x800; for (int i = 0; i < remaining; i++) { if (nLen == blockSize) { // XOR with result and encrypt with KIRK CMD 4. scrambleBuf = xorKey(scrambleBuf, 0x14, ctx.key, 0, 0x10); ScrambleSD(scrambleBuf, blockSize, seed, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT); System.arraycopy(scrambleBuf, (blockSize + 0x4) - 0x14, ctx.key, 0, 0x10); // Reset length. nLen = 0; } // Keep copying data. scrambleBuf[0x14 + nLen] = data[i]; nLen++; } // Process any leftover data. if (nLen > 0) { scrambleBuf = xorKey(scrambleBuf, 0x14, ctx.key, 0, 0x10); ScrambleSD(scrambleBuf, nLen, seed, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT); System.arraycopy(scrambleBuf, (nLen + 0x4) - 0x14, ctx.key, 0, 0x10); } return 0; } } public int hleSdGetLastIndex(SD_Ctx1 ctx, byte[] hash, byte[] key) { if (ctx.padSize > 0x10) { // Invalid key length. return -1; } // Calculate the seed. int seed = getModeSeed(ctx.mode); // Set up the buffer. byte[] scrambleBuf = new byte[0x800 + 0x14]; // Set up necessary buffers. byte[] keyBuf = new byte[0x10]; byte[] resultBuf = new byte[0x10]; // Encrypt the buffer with KIRK CMD 4. ScrambleSD(scrambleBuf, 0x10, seed, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT); // Store the generated key. System.arraycopy(scrambleBuf, 0, keyBuf, 0, 0x10); // Apply custom padding management to the stored key. byte b = ((keyBuf[0] & (byte) 0x80) != 0) ? (byte) 0x87 : 0; for (int i = 0; i < 0xF; i++) { int b1 = ((keyBuf[i] & 0xFF) << 1); int b2 = ((keyBuf[i + 1] & 0xFF) >> 7); keyBuf[i] = (byte) (b1 | b2); } byte t = (byte) ((keyBuf[0xF] & 0xFF) << 1); keyBuf[0xF] = (byte) (t ^ b); if (ctx.padSize < 0x10) { byte bb = ((keyBuf[0] < 0)) ? (byte) 0x87 : 0; for (int i = 0; i < 0xF; i++) { int bb1 = ((keyBuf[i] & 0xFF) << 1); int bb2 = ((keyBuf[i + 1] & 0xFF) >> 7); keyBuf[i] = (byte) (bb1 | bb2); } byte tt = (byte) ((keyBuf[0xF] & 0xFF) << 1); keyBuf[0xF] = (byte) (tt ^ bb); ctx.pad[ctx.padSize] = (byte) 0x80; if ((ctx.padSize + 1) < 0x10) { for (int i = 0; i < (0x10 - ctx.padSize - 1); i++) { ctx.pad[ctx.padSize + 1 + i] = 0; } } } // XOR previous pad key with new one and copy the result back to the buffer. ctx.pad = xorKey(ctx.pad, 0, keyBuf, 0, 0x10); System.arraycopy(ctx.pad, 0, scrambleBuf, 0x14, 0x10); // Save the previous result key. System.arraycopy(ctx.key, 0, resultBuf, 0, 0x10); // XOR the decrypted key with the result key. scrambleBuf = xorKey(scrambleBuf, 0x14, resultBuf, 0, 0x10); // Encrypt the key with KIRK CMD 4. ScrambleSD(scrambleBuf, 0x10, seed, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT); // Copy back the key into the result buffer. System.arraycopy(scrambleBuf, 0, resultBuf, 0, 0x10); // If ctx.mode is new mode 0x5 or 0x6, XOR with the new hash key 5, else, XOR with hash key 2. if ((ctx.mode == 0x5) || (ctx.mode == 0x6)) { resultBuf = xorHash(resultBuf, 0, KeyVault.sdHashKey5, 0, 0x10); } else if ((ctx.mode == 0x3) || (ctx.mode == 0x4)) { resultBuf = xorHash(resultBuf, 0, KeyVault.sdHashKey2, 0, 0x10); } // If mode is 2, 4 or 6, encrypt again with KIRK CMD 5 and then KIRK CMD 4. if ((ctx.mode == 0x2) || (ctx.mode == 0x4) || (ctx.mode == 0x6)) { // Copy the result buffer into the data buffer. System.arraycopy(resultBuf, 0, scrambleBuf, 0x14, 0x10); // Encrypt with KIRK CMD 5 (seed is always 0x100). ScrambleSD(scrambleBuf, 0x10, 0x100, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT_FUSE); // Copy the encrypted key to the data area of the buffer. System.arraycopy(scrambleBuf, 0, scrambleBuf, 0x14, 0x10); // Encrypt again with KIRK CMD 4. ScrambleSD(scrambleBuf, 0x10, seed, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT); // Copy back into result buffer. System.arraycopy(scrambleBuf, 0, resultBuf, 0, 0x10); } // XOR with the supplied key and encrypt with KIRK CMD 4. if (key != null) { // XOR result buffer with user key. resultBuf = xorKey(resultBuf, 0, key, 0, 0x10); // Copy the result buffer into the data buffer. System.arraycopy(resultBuf, 0, scrambleBuf, 0x14, 0x10); // Encrypt with KIRK CMD 4. ScrambleSD(scrambleBuf, 0x10, seed, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT); // Copy back into the result buffer. System.arraycopy(scrambleBuf, 0, resultBuf, 0, 0x10); } // Copy back the generated hash. System.arraycopy(resultBuf, 0, hash, 0, 0x10); // Clear the context fields. ctx.mode = 0; ctx.padSize = 0; for (int i = 0; i < 0x10; i++) { ctx.pad[i] = 0; } for (int i = 0; i < 0x10; i++) { ctx.key[i] = 0; } return 0; } public int hleSdCleanList(SD_Ctx2 ctx) { ctx.mode = 0; ctx.unk = 0; for (int i = 0; i < 0x10; i++) { ctx.buf[i] = 0; } return 0; } public int hleSdCreateList(SD_Ctx2 ctx, int encMode, int genMode, byte[] data, byte[] key) { // If the key is not a 16-byte key, return an error. if (!isNullKey(key) && key.length < 0x10) { return -1; } // Set the mode and the unknown parameters. ctx.mode = encMode; ctx.unk = 0x1; // Key generator mode 0x1 (encryption): use an encrypted pseudo random number before XORing the data with the given key. if (genMode == 0x1) { byte[] header = new byte[0x24]; byte[] seed = new byte[0x14]; // Generate SHA-1 to act as seed for encryption. ByteBuffer bSeed = ByteBuffer.wrap(seed); kirk.hleUtilsBufferCopyWithRange(bSeed, 0x14, null, 0, KIRK.PSP_KIRK_CMD_PRNG); // Propagate SHA-1 in kirk header. System.arraycopy(bSeed.array(), 0, header, 0, 0x14); System.arraycopy(bSeed.array(), 0, header, 0x14, 0x10); header[0x20] = 0; header[0x21] = 0; header[0x22] = 0; header[0x23] = 0; // Encryption mode 0x1: encrypt with KIRK CMD4 and XOR with the given key. if (ctx.mode == 0x1) { ScrambleSD(header, 0x10, 0x4, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT); System.arraycopy(header, 0, ctx.buf, 0, 0x10); System.arraycopy(header, 0, data, 0, 0x10); // If the key is not null, XOR the hash with it. if (!isNullKey(key)) { ctx.buf = xorKey(ctx.buf, 0, key, 0, 0x10); } return 0; } else if (ctx.mode == 0x2) { // Encryption mode 0x2: encrypt with KIRK CMD5 and XOR with the given key. ScrambleSD(header, 0x10, 0x100, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT_FUSE); System.arraycopy(header, 0, ctx.buf, 0, 0x10); System.arraycopy(header, 0, data, 0, 0x10); // If the key is not null, XOR the hash with it. if (!isNullKey(key)) { ctx.buf = xorKey(ctx.buf, 0, key, 0, 0x10); } return 0; } else if (ctx.mode == 0x3) { // Encryption mode 0x3: XOR with SD keys, encrypt with KIRK CMD4 and XOR with the given key. header = xorHash(header, 0x14, KeyVault.sdHashKey3, 0, 0x10); ScrambleSD(header, 0x10, 0xE, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT); header = xorHash(header, 0, KeyVault.sdHashKey4, 0, 0x10); System.arraycopy(header, 0, ctx.buf, 0, 0x10); System.arraycopy(header, 0, data, 0, 0x10); // If the key is not null, XOR the hash with it. if (!isNullKey(key)) { ctx.buf = xorKey(ctx.buf, 0, key, 0, 0x10); } return 0; } else if (ctx.mode == 0x4) { // Encryption mode 0x4: XOR with SD keys, encrypt with KIRK CMD5 and XOR with the given key. header = xorHash(header, 0x14, KeyVault.sdHashKey3, 0, 0x10); ScrambleSD(header, 0x10, 0x100, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT_FUSE); header = xorHash(header, 0, KeyVault.sdHashKey4, 0, 0x10); System.arraycopy(header, 0, ctx.buf, 0, 0x10); System.arraycopy(header, 0, data, 0, 0x10); // If the key is not null, XOR the hash with it. if (!isNullKey(key)) { ctx.buf = xorKey(ctx.buf, 0, key, 0, 0x10); } return 0; } else if (ctx.mode == 0x6) { // Encryption mode 0x6: XOR with new SD keys, encrypt with KIRK CMD5 and XOR with the given key. header = xorHash(header, 0x14, KeyVault.sdHashKey6, 0, 0x10); ScrambleSD(header, 0x10, 0x100, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT_FUSE); header = xorHash(header, 0, KeyVault.sdHashKey7, 0, 0x10); System.arraycopy(header, 0, ctx.buf, 0, 0x10); System.arraycopy(header, 0, data, 0, 0x10); // If the key is not null, XOR the hash with it. if (!isNullKey(key)) { ctx.buf = xorKey(ctx.buf, 0, key, 0, 0x10); } return 0; } else { // Encryption mode 0x5: XOR with new SD keys, encrypt with KIRK CMD4 and XOR with the given key. header = xorHash(header, 0x14, KeyVault.sdHashKey6, 0, 0x10); ScrambleSD(header, 0x10, 0x12, 0x4, KIRK.PSP_KIRK_CMD_ENCRYPT); header = xorHash(header, 0, KeyVault.sdHashKey7, 0, 0x10); System.arraycopy(header, 0, ctx.buf, 0, 0x10); System.arraycopy(header, 0, data, 0, 0x10); // If the key is not null, XOR the hash with it. if (!isNullKey(key)) { ctx.buf = xorKey(ctx.buf, 0, key, 0, 0x10); } return 0; } } else if (genMode == 0x2) { // Key generator mode 0x02 (decryption): directly XOR the data with the given key. // Grab the data hash (first 16-bytes). System.arraycopy(data, 0, ctx.buf, 0, 0x10); // If the key is not null, XOR the hash with it. if (!isNullKey(key)) { ctx.buf = xorKey(ctx.buf, 0, key, 0, 0x10); } return 0; } else { // Invalid mode. return -1; } } public int hleSdSetMember(SD_Ctx2 ctx, byte[] data, int length) { if (length == 0) { return 0; } if ((length & 0xF) != 0) { return -1; } // Parse the data in 0x800 blocks first. int index = 0; if (length >= 0x800) { for (index = 0; length >= 0x800; index += 0x800) { cryptMember(ctx, data, index, 0x800); length -= 0x800; } } // Finally parse the rest of the data. if (length >= 0x10) { cryptMember(ctx, data, index, length); } return 0; } public byte[] DecryptSavedata(byte[] buf, int size, byte[] key) { // Initialize the context structs. int sdDecMode; SD_Ctx1 ctx1 = new SD_Ctx1(); SD_Ctx2 ctx2 = new SD_Ctx2(); // Setup the buffers. int alignedSize = (((size + 0xF) >> 4) << 4) - 0x10; byte[] decbuf = new byte[size - 0x10]; byte[] tmpbuf = new byte[alignedSize]; // Set the decryption mode. if (isNullKey(key)) { sdDecMode = 1; } else { // After firmware version 2.7.1 the decryption mode used is 5. // Note: Due to a mislabel, 3 games from firmware 2.8.1 (Sonic Rivals, // Star Trek: Tactical Assault and Brothers in Arms: D-Day) // still use the decryption mode 3. if (Emulator.getInstance().getFirmwareVersion() > 271 && !((State.discId.equals("ULUS10195") || State.discId.equals("ULES00622")) || (State.discId.equals("ULUS10193") || State.discId.equals("ULES00608")) || (State.discId.equals("ULUS10150") || State.discId.equals("ULES00623")))) { sdDecMode = 5; } else { sdDecMode = 3; } } // Perform the decryption. hleSdSetIndex(ctx1, sdDecMode); hleSdCreateList(ctx2, sdDecMode, 2, buf, key); hleSdRemoveValue(ctx1, buf, 0x10); System.arraycopy(buf, 0x10, tmpbuf, 0, size - 0x10); hleSdRemoveValue(ctx1, tmpbuf, alignedSize); hleSdSetMember(ctx2, tmpbuf, alignedSize); // Clear context 2. hleSdCleanList(ctx2); // Copy back the data. System.arraycopy(tmpbuf, 0, decbuf, 0, size - 0x10); return decbuf; } public byte[] EncryptSavedata(byte[] buf, int size, byte[] key) { // Initialize the context structs. int sdEncMode; SD_Ctx1 ctx1 = new SD_Ctx1(); SD_Ctx2 ctx2 = new SD_Ctx2(); // Setup the buffers. int alignedSize = ((size + 0xF) >> 4) << 4; byte[] tmpbuf1 = new byte[alignedSize + 0x10]; byte[] tmpbuf2 = new byte[alignedSize]; byte[] hash = new byte[0x10]; // Copy the plain data to tmpbuf. System.arraycopy(buf, 0, tmpbuf1, 0x10, size); // Set the encryption mode. if (isNullKey(key)) { sdEncMode = 1; } else { // After firmware version 2.7.1 the encryption mode used is 5. // Note: Due to a mislabel, 3 games from firmware 2.8.1 (Sonic Rivals, // Star Trek: Tactical Assault and Brothers in Arms: D-Day) // still use the encryption mode 3. if (Emulator.getInstance().getFirmwareVersion() > 271 && !((State.discId.equals("ULUS10195") || State.discId.equals("ULES00622")) || (State.discId.equals("ULUS10193") || State.discId.equals("ULES00608")) || (State.discId.equals("ULUS10150") || State.discId.equals("ULES00623")))) { sdEncMode = 5; } else { sdEncMode = 3; } } // Generate the encryption IV (first 0x10 bytes). hleSdCreateList(ctx2, sdEncMode, 1, tmpbuf1, key); hleSdSetIndex(ctx1, sdEncMode); hleSdRemoveValue(ctx1, tmpbuf1, 0x10); System.arraycopy(tmpbuf1, 0x10, tmpbuf2, 0, alignedSize); hleSdSetMember(ctx2, tmpbuf2, alignedSize); // Clear extra bytes. for (int i = 0; i < (alignedSize - size); i++) { tmpbuf2[size + i] = 0; } // Encrypt the data. hleSdRemoveValue(ctx1, tmpbuf2, alignedSize); // Copy back the encrypted data + IV. for (int i = 0; i < (tmpbuf1.length - 0x10); i++) { tmpbuf1[0x10 + i] = 0; } System.arraycopy(tmpbuf2, 0, tmpbuf1, 0x10, alignedSize); System.arraycopy(tmpbuf1, 0, buf, 0, buf.length); // Clear context 2. hleSdCleanList(ctx2); // Generate a file hash for this data. hleSdGetLastIndex(ctx1, hash, key); return hash; } private byte[] GenerateSavedataHash(byte[] data, int size, int mode) { SD_Ctx1 ctx1 = new SD_Ctx1(); byte[] hash = new byte[0x10]; // Generate a new hash using a key. hleSdSetIndex(ctx1, mode); hleSdRemoveValue(ctx1, data, size); if (hleSdGetLastIndex(ctx1, hash, null) < 0) { for (int i = 0; i < 0x10; i++) { // Generate a dummy hash in case of failure. hash[i] = 1; } } return hash; } public void UpdateSavedataHashes(PSF psf, byte[] data, int size, byte[] params, byte[] key) { // Setup the params, hash and mode. byte[] hash = new byte[0x10]; // Determine the hashing mode. int mode = 0; int check_bit = 1; if (!isNullKey(key)) { if (Emulator.getInstance().getFirmwareVersion() > 271) { mode = 4; } else { mode = 2; } } // Check for previous SAVEDATA_PARAMS. if (params != null) { for (int i = 0; i < params.length; i++) { if (params[i] != 0) { // Extract the mode setup from the already existing data. mode = ((params[0] >> 4) & 0xF); check_bit = ((params[0]) & 0xF); break; } } } // New mode (after firmware 2.7.1). if ((mode & 0x4) == 0x4) { // Generate a type 6 hash. hash = GenerateSavedataHash(data, size, 6); System.arraycopy(hash, 0, data, 0x11B0 + 0x20, 0x10); // Set the SAVEDATA_PARAMS byte to 0x41. data[0x11B0] |= 0x01; data[0x11B0] |= 0x40; // Generate a type 5 hash. hash = GenerateSavedataHash(data, size, 5); System.arraycopy(hash, 0, data, 0x11B0 + 0x70, 0x10); } else if ((mode & 0x2) == 0x2) { // Last old mode (firmware 2.0.0 to 2.7.1). // Generate a type 4 hash. hash = GenerateSavedataHash(data, size, 4); System.arraycopy(hash, 0, data, 0x11B0 + 0x20, 0x10); // Set the SAVEDATA_PARAMS byte to 0x21. data[0x11B0] |= 0x01; data[0x11B0] |= 0x20; // Generate a type 3 hash. hash = GenerateSavedataHash(data, size, 3); System.arraycopy(hash, 0, data, 0x11B0 + 0x70, 0x10); } else { // First old mode (before firmware 2.0.0). // Generate a type 2 hash. hash = GenerateSavedataHash(data, size, 2); System.arraycopy(hash, 0, data, 0x11B0 + 0x20, 0x10); // Set the SAVEDATA_PARAMS byte to 0x01. data[0x11B0] |= 0x01; } if ((check_bit & 0x1) == 0x1) { // Generate a type 1 hash. hash = GenerateSavedataHash(data, size, 1); System.arraycopy(hash, 0, data, 0x11B0 + 0x10, 0x10); } // Output the final PSF file containing the SAVEDATA_PARAMS and file hashes. try { // Update the SAVEDATA_PARAMS. byte[] savedataParams = new byte[0x80]; for(int i = 0; i < 0x80; i++) { savedataParams[i] = data[0x11B0 + i]; } psf.put("SAVEDATA_PARAMS", savedataParams); } catch (Exception e) { // Ignore... } } }