diff --git a/README.md b/README.md
index b0cb625..0c279a2 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,19 @@
-# nxdumptool
+todo:
+
+ hfs0 methods
+ tik gamecard
+
+
+
+
+
+
+
+
+
+
+
+# nxdumptool
Nintendo Switch Dump Tool
diff --git a/source/keys.c b/source/keys.c
deleted file mode 100644
index c855136..0000000
--- a/source/keys.c
+++ /dev/null
@@ -1,1163 +0,0 @@
-#include
-#include
-#include
-#include
-
-#include "fatfs/ff.h"
-#include "keys.h"
-#include "util.h"
-#include "ui.h"
-#include "es.h"
-#include "save.h"
-
-/* Extern variables */
-
-extern int breaks;
-extern int font_height;
-
-extern u8 *dumpBuf;
-
-extern char strbuf[NAME_BUF_LEN];
-
-/* Statically allocated variables */
-
-nca_keyset_t nca_keyset;
-
-static SetCalRsa2048DeviceKey eticket_data;
-static bool setcal_eticket_retrieved = false;
-
-static keyLocation FSRodata = {
- FS_TID,
- SEG_RODATA,
- NULL,
- 0
-};
-
-static keyLocation FSData = {
- FS_TID,
- SEG_DATA,
- NULL,
- 0
-};
-
-static const keyInfo header_kek_source = {
- "header_kek_source",
- { 0x18, 0x88, 0xCA, 0xED, 0x55, 0x51, 0xB3, 0xED, 0xE0, 0x14, 0x99, 0xE8, 0x7C, 0xE0, 0xD8, 0x68,
- 0x27, 0xF8, 0x08, 0x20, 0xEF, 0xB2, 0x75, 0x92, 0x10, 0x55, 0xAA, 0x4E, 0x2A, 0xBD, 0xFF, 0xC2 },
- 0x10
-};
-
-static const keyInfo header_key_source = {
- "header_key_source",
- { 0x8F, 0x78, 0x3E, 0x46, 0x85, 0x2D, 0xF6, 0xBE, 0x0B, 0xA4, 0xE1, 0x92, 0x73, 0xC4, 0xAD, 0xBA,
- 0xEE, 0x16, 0x38, 0x00, 0x43, 0xE1, 0xB8, 0xC4, 0x18, 0xC4, 0x08, 0x9A, 0x8B, 0xD6, 0x4A, 0xA6 },
- 0x20
-};
-
-static const keyInfo key_area_key_application_source = {
- "key_area_key_application_source",
- { 0x04, 0xAD, 0x66, 0x14, 0x3C, 0x72, 0x6B, 0x2A, 0x13, 0x9F, 0xB6, 0xB2, 0x11, 0x28, 0xB4, 0x6F,
- 0x56, 0xC5, 0x53, 0xB2, 0xB3, 0x88, 0x71, 0x10, 0x30, 0x42, 0x98, 0xD8, 0xD0, 0x09, 0x2D, 0x9E },
- 0x10
-};
-
-static const keyInfo key_area_key_ocean_source = {
- "key_area_key_ocean_source",
- { 0xFD, 0x43, 0x40, 0x00, 0xC8, 0xFF, 0x2B, 0x26, 0xF8, 0xE9, 0xA9, 0xD2, 0xD2, 0xC1, 0x2F, 0x6B,
- 0xE5, 0x77, 0x3C, 0xBB, 0x9D, 0xC8, 0x63, 0x00, 0xE1, 0xBD, 0x99, 0xF8, 0xEA, 0x33, 0xA4, 0x17 },
- 0x10
-};
-
-static const keyInfo key_area_key_system_source = {
- "key_area_key_system_source",
- { 0x1F, 0x17, 0xB1, 0xFD, 0x51, 0xAD, 0x1C, 0x23, 0x79, 0xB5, 0x8F, 0x15, 0x2C, 0xA4, 0x91, 0x2E,
- 0xC2, 0x10, 0x64, 0x41, 0xE5, 0x17, 0x22, 0xF3, 0x87, 0x00, 0xD5, 0x93, 0x7A, 0x11, 0x62, 0xF7 },
- 0x10
-};
-
-/* Empty string hash */
-static const u8 null_hash[0x20] = {
- 0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC, 0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24,
- 0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55
-};
-
-void freeProcessMemory(keyLocation *location)
-{
- if (location && location->data)
- {
- free(location->data);
- location->data = NULL;
- }
-}
-
-bool retrieveProcessMemory(keyLocation *location)
-{
- if (!location || !location->titleID || !location->mask)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve process memory.", __func__);
- return false;
- }
-
- Result result;
- Handle debug_handle = INVALID_HANDLE;
-
- u64 d[8];
- memset(d, 0, 8 * sizeof(u64));
-
- if ((location->titleID > 0x0100000000000005) && (location->titleID != 0x0100000000000028))
- {
- // If not a kernel process, get PID from pm:dmnt
- u64 pid;
-
- result = pmdmntGetProcessId(&pid, location->titleID);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: pmdmntGetProcessId failed! (0x%08X)", __func__, result);
- return false;
- }
-
- result = svcDebugActiveProcess(&debug_handle, pid);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcDebugActiveProcess failed! (0x%08X)", __func__, result);
- return false;
- }
-
- result = svcGetDebugEvent((u8*)&d, debug_handle);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcGetDebugEvent failed! (0x%08X)", __func__, result);
- return false;
- }
- } else {
- // Otherwise, query svc for the process list
- u64 pids[300];
- u32 num_processes;
-
- result = svcGetProcessList(&num_processes, pids, 300);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcGetProcessList failed! (0x%08X)", __func__, result);
- return false;
- }
-
- u32 i;
-
- for(i = 0; i < (num_processes - 1); i++)
- {
- if (R_SUCCEEDED(svcDebugActiveProcess(&debug_handle, pids[i])) && R_SUCCEEDED(svcGetDebugEvent((u8*)&d, debug_handle)) && (d[2] == location->titleID)) break;
- if (debug_handle) svcCloseHandle(debug_handle);
- }
-
- if (i == (num_processes - 1))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to retrieve debug handle for process with Title ID %016lX!", __func__, location->titleID);
- if (debug_handle) svcCloseHandle(debug_handle);
- return false;
- }
- }
-
- MemoryInfo mem_info;
- memset(&mem_info, 0, sizeof(MemoryInfo));
-
- u32 page_info;
- u64 addr = 0;
- u8 segment;
- u64 last_text_addr = 0;
- u8 *dataTmp = NULL;
-
- bool success = true;
-
- // Locate "real" .text segment as Atmosphere emuMMC has two
- for(;;)
- {
- result = svcQueryDebugProcessMemory(&mem_info, &page_info, debug_handle, addr);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcQueryDebugProcessMemory failed! (0x%08X)", __func__, result);
- success = false;
- break;
- }
-
- if ((mem_info.perm & Perm_X) && ((mem_info.type & 0xFF) >= MemType_CodeStatic) && ((mem_info.type & 0xFF) < MemType_Heap)) last_text_addr = mem_info.addr;
-
- addr = (mem_info.addr + mem_info.size);
- if (!addr) break;
- }
-
- if (!success)
- {
- svcCloseHandle(debug_handle);
- return success;
- }
-
- addr = last_text_addr;
-
- for(segment = 1; segment < BIT(3);)
- {
- result = svcQueryDebugProcessMemory(&mem_info, &page_info, debug_handle, addr);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcQueryDebugProcessMemory failed! (0x%08X)", __func__, result);
- success = false;
- break;
- }
-
- // Weird code to allow for bitmasking segments
- if ((mem_info.perm & Perm_R) && ((mem_info.type & 0xFF) >= MemType_CodeStatic) && ((mem_info.type & 0xFF) < MemType_Heap) && ((segment <<= 1) >> 1 & location->mask) > 0)
- {
- // If location->data == NULL, realloc will essentially act as a malloc
- dataTmp = realloc(location->data, location->dataSize + mem_info.size);
- if (!dataTmp)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to resize key location data buffer to %lu bytes.", __func__, location->dataSize + mem_info.size);
- success = false;
- break;
- }
-
- location->data = dataTmp;
- dataTmp = NULL;
-
- memset(location->data + location->dataSize, 0, mem_info.size);
-
- result = svcReadDebugProcessMemory(location->data + location->dataSize, debug_handle, mem_info.addr, mem_info.size);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcReadDebugProcessMemory failed! (0x%08X)", __func__, result);
- success = false;
- break;
- }
-
- location->dataSize += mem_info.size;
- }
-
- addr = (mem_info.addr + mem_info.size);
- if (addr == 0) break;
- }
-
- svcCloseHandle(debug_handle);
-
- if (success)
- {
- if (!location->data || !location->dataSize) success = false;
- }
-
- return success;
-}
-
-bool findKeyInProcessMemory(const keyLocation *location, const keyInfo *findKey, u8 *out)
-{
- if (!location || !location->data || !location->dataSize || !findKey || !strlen(findKey->name) || !findKey->size)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to locate key in process memory.", __func__);
- return false;
- }
-
- u64 i;
- u8 temp_hash[SHA256_HASH_SIZE];
- bool found = false;
-
- // Hash every key-length-sized byte chunk in data until it matches a key hash
- for(i = 0; i < location->dataSize; i++)
- {
- if (!found && (location->dataSize - i) < findKey->size) break;
-
- sha256CalculateHash(temp_hash, location->data + i, findKey->size);
-
- if (!memcmp(temp_hash, findKey->hash, SHA256_HASH_SIZE))
- {
- // Jackpot
- memcpy(out, location->data + i, findKey->size);
- found = true;
- break;
- }
- }
-
- if (!found) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to locate key \"%s\" in process memory!", __func__, findKey->name);
-
- return found;
-}
-
-bool findFSRodataKeys(keyLocation *location)
-{
- if (!location || location->titleID != FS_TID || location->mask != SEG_RODATA || !location->data || !location->dataSize)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to locate keys in FS .rodata segment.", __func__);
- return false;
- }
-
- if (!findKeyInProcessMemory(location, &header_kek_source, nca_keyset.header_kek_source)) return false;
- nca_keyset.memory_key_cnt++;
-
- if (!findKeyInProcessMemory(location, &key_area_key_application_source, nca_keyset.key_area_key_application_source)) return false;
- nca_keyset.memory_key_cnt++;
-
- if (!findKeyInProcessMemory(location, &key_area_key_ocean_source, nca_keyset.key_area_key_ocean_source)) return false;
- nca_keyset.memory_key_cnt++;
-
- if (!findKeyInProcessMemory(location, &key_area_key_system_source, nca_keyset.key_area_key_system_source)) return false;
- nca_keyset.memory_key_cnt++;
-
- return true;
-}
-
-bool loadMemoryKeys()
-{
- if (nca_keyset.memory_key_cnt > 0) return true;
-
- Result result;
- bool proceed;
-
- if (!retrieveProcessMemory(&FSRodata)) return false;
- proceed = findFSRodataKeys(&FSRodata);
- freeProcessMemory(&FSRodata);
- if (!proceed) return false;
-
- if (!retrieveProcessMemory(&FSData)) return false;
- proceed = findKeyInProcessMemory(&FSData, &header_key_source, nca_keyset.header_key_source);
- freeProcessMemory(&FSData);
- if (!proceed) return false;
- nca_keyset.memory_key_cnt++;
-
- // Derive NCA header key
- result = splCryptoInitialize();
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize the spl:crypto service! (0x%08X)", __func__, result);
- return false;
- }
-
- result = splCryptoGenerateAesKek(nca_keyset.header_kek_source, 0, 0, nca_keyset.header_kek);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splCryptoGenerateAesKek(header_kek_source) failed! (0x%08X)", __func__, result);
- splCryptoExit();
- return false;
- }
-
- nca_keyset.memory_key_cnt++;
-
- result = splCryptoGenerateAesKey(nca_keyset.header_kek, nca_keyset.header_key_source + 0x00, nca_keyset.header_key + 0x00);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splCryptoGenerateAesKey(header_key_source + 0x00) failed! (0x%08X)", __func__, result);
- splCryptoExit();
- return false;
- }
-
- result = splCryptoGenerateAesKey(nca_keyset.header_kek, nca_keyset.header_key_source + 0x10, nca_keyset.header_key + 0x10);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splCryptoGenerateAesKey(header_key_source + 0x10) failed! (0x%08X)", __func__, result);
- splCryptoExit();
- return false;
- }
-
- nca_keyset.memory_key_cnt++;
-
- nca_keyset.total_key_cnt += nca_keyset.memory_key_cnt;
-
- splCryptoExit();
-
- return true;
-}
-
-bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out)
-{
- if (!dec_nca_header || dec_nca_header->kaek_ind > 2 || !out)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to decrypt NCA key area.", __func__);
- return false;
- }
-
- Result result;
-
- u8 i;
- u8 tmp_kek[0x10];
-
- u8 crypto_type = (dec_nca_header->crypto_type2 > dec_nca_header->crypto_type ? dec_nca_header->crypto_type2 : dec_nca_header->crypto_type);
- if (crypto_type > 0x20)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA keyblob index!", __func__);
- return false;
- }
-
- u8 *kek_source = (dec_nca_header->kaek_ind == 0 ? nca_keyset.key_area_key_application_source : (dec_nca_header->kaek_ind == 1 ? nca_keyset.key_area_key_ocean_source : nca_keyset.key_area_key_system_source));
-
- result = splCryptoInitialize();
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize the spl:crypto service! (0x%08X)", __func__, result);
- return false;
- }
-
- result = splCryptoGenerateAesKek(kek_source, crypto_type, 0, tmp_kek);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splCryptoGenerateAesKek(kek_source) failed! (0x%08X)", __func__, result);
- splCryptoExit();
- return false;
- }
-
- bool success = true;
- u8 decrypted_nca_keys[NCA_KEY_AREA_KEY_CNT][NCA_KEY_AREA_KEY_SIZE];
-
- for(i = 0; i < NCA_KEY_AREA_KEY_CNT; i++)
- {
- result = splCryptoGenerateAesKey(tmp_kek, dec_nca_header->nca_keys[i], decrypted_nca_keys[i]);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splCryptoGenerateAesKey(nca_kaek_%02u) failed! (0x%08X)", __func__, i, result);
- success = false;
- break;
- }
- }
-
- splCryptoExit();
-
- memcpy(out, decrypted_nca_keys, NCA_KEY_AREA_SIZE);
-
- return success;
-}
-
-/**
- * Reads a line from file f and parses out the key and value from it.
- * The format of a line must match /^ *[A-Za-z0-9_] *[,=] *.+$/.
- * If a line ends in \r, the final \r is stripped.
- * The input file is assumed to have been opened with the 'b' flag.
- * The input file is assumed to contain only ASCII.
- *
- * A line cannot exceed 512 bytes in length.
- * Lines that are excessively long will be silently truncated.
- *
- * On success, *key and *value will be set to point to the key and value in
- * the input line, respectively.
- * *key and *value may also be NULL in case of empty lines.
- * On failure, *key and *value will be set to NULL.
- * End of file is considered failure.
- *
- * Because *key and *value will point to a static buffer, their contents must be
- * copied before calling this function again.
- * For the same reason, this function is not thread-safe.
- *
- * The key will be converted to lowercase.
- * An empty key is considered a parse error, but an empty value is returned as
- * success.
- *
- * This function assumes that the file can be trusted not to contain any NUL in
- * the contents.
- *
- * Whitespace (' ', ASCII 0x20, as well as '\t', ASCII 0x09) at the beginning of
- * the line, at the end of the line as well as around = (or ,) will be ignored.
- *
- * @param f the file to read
- * @param key pointer to change to point to the key
- * @param value pointer to change to point to the value
- * @return 0 on success,
- * 1 on end of file,
- * -1 on parse error (line too long, line malformed)
- * -2 on I/O error
- */
-static int get_kv(FILE *f, char **key, char **value)
-{
-#define SKIP_SPACE(p) do {\
- for (; (*p == ' ' || *p == '\t'); ++p);\
-} while(0);
-
- static char line[512];
- char *k, *v, *p, *end;
-
- *key = *value = NULL;
-
- errno = 0;
-
- if (fgets(line, (int)sizeof(line), f) == NULL)
- {
- if (feof(f))
- {
- return 1;
- } else {
- return -2;
- }
- }
-
- if (errno != 0) return -2;
-
- if (*line == '\n' || *line == '\r' || *line == '\0') return 0;
-
- /* Not finding \r or \n is not a problem.
- * The line might just be exactly 512 characters long, we have no way to
- * tell.
- * Additionally, it's possible that the last line of a file is not actually
- * a line (i.e., does not end in '\n'); we do want to handle those.
- */
- if ((p = strchr(line, '\r')) != NULL || (p = strchr(line, '\n')) != NULL)
- {
- end = p;
- *p = '\0';
- } else {
- end = (line + strlen(line) + 1);
- }
-
- p = line;
- SKIP_SPACE(p);
- k = p;
-
- /* Validate key and convert to lower case. */
- for (; *p != ' ' && *p != ',' && *p != '\t' && *p != '='; ++p)
- {
- if (*p == '\0') return -1;
-
- if (*p >= 'A' && *p <= 'Z')
- {
- *p = 'a' + (*p - 'A');
- continue;
- }
-
- if (*p != '_' && (*p < '0' && *p > '9') && (*p < 'a' && *p > 'z')) return -1;
- }
-
- /* Bail if the final ++p put us at the end of string */
- if (*p == '\0') return -1;
-
- /* We should be at the end of key now and either whitespace or [,=]
- * follows.
- */
- if (*p == '=' || *p == ',')
- {
- *p++ = '\0';
- } else {
- *p++ = '\0';
- SKIP_SPACE(p);
- if (*p != '=' && *p != ',') return -1;
- *p++ = '\0';
- }
-
- /* Empty key is an error. */
- if (*k == '\0') return -1;
-
- SKIP_SPACE(p);
- v = p;
-
- /* Skip trailing whitespace */
- for (p = end - 1; *p == '\t' || *p == ' '; --p);
-
- *(p + 1) = '\0';
-
- *key = k;
- *value = v;
-
- return 0;
-
-#undef SKIP_SPACE
-}
-
-static char hextoi(char c)
-{
- if ('a' <= c && c <= 'f') return (c - 'a' + 0xA);
- if ('A' <= c && c <= 'F') return (c - 'A' + 0xA);
- if ('0' <= c && c <= '9') return (c - '0');
- return 'z';
-}
-
-int parse_hex_key(unsigned char *key, const char *hex, unsigned int len)
-{
- u32 i;
- size_t hex_str_len = (2 * len);
-
- if (strlen(hex) != hex_str_len)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: key (%s) must be %u hex digits!", __func__, hex, hex_str_len);
- return 0;
- }
-
- memset(key, 0, len);
-
- for(i = 0; i < hex_str_len; i++)
- {
- char val = hextoi(hex[i]);
- if (val == 'z')
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: key (%s) must be %u hex digits!", __func__, hex, hex_str_len);
- return 0;
- }
-
- if ((i & 1) == 0) val <<= 4;
- key[i >> 1] |= val;
- }
-
- return 1;
-}
-
-int readKeysFromFile(FILE *f)
-{
- u32 i;
- int ret;
- char *key, *value;
- char test_name[0x100];
- bool common_eticket = false, personalized_eticket = false;
-
- while((ret = get_kv(f, &key, &value)) != 1 && ret != -2)
- {
- if (ret == 0)
- {
- if (key == NULL || value == NULL) continue;
-
- if (!common_eticket && !personalized_eticket && strcasecmp(key, "eticket_rsa_kek") == 0)
- {
- if (!parse_hex_key(nca_keyset.eticket_rsa_kek, value, sizeof(nca_keyset.eticket_rsa_kek))) return 0;
- nca_keyset.ext_key_cnt++;
- common_eticket = true;
- } else
- if (!personalized_eticket && strcasecmp(key, "eticket_rsa_kek_personalized") == 0)
- {
- /* Use the personalized eTicket RSA kek if available */
- /* This only appears on consoles that use the new PRODINFO key generation scheme */
- if (!parse_hex_key(nca_keyset.eticket_rsa_kek, value, sizeof(nca_keyset.eticket_rsa_kek))) return 0;
- if (!common_eticket) nca_keyset.ext_key_cnt++;
- personalized_eticket = true;
- } else {
- memset(test_name, 0, sizeof(test_name));
-
- for(i = 0; i < 0x20; i++)
- {
- snprintf(test_name, sizeof(test_name), "titlekek_%02x", i);
- if (strcasecmp(key, test_name) == 0)
- {
- if (!parse_hex_key(nca_keyset.titlekeks[i], value, sizeof(nca_keyset.titlekeks[i]))) return 0;
- nca_keyset.ext_key_cnt++;
- break;
- }
-
- snprintf(test_name, sizeof(test_name), "key_area_key_application_%02x", i);
- if (strcasecmp(key, test_name) == 0)
- {
- if (!parse_hex_key(nca_keyset.key_area_keys[i][0], value, sizeof(nca_keyset.key_area_keys[i][0]))) return 0;
- nca_keyset.ext_key_cnt++;
- break;
- }
-
- snprintf(test_name, sizeof(test_name), "key_area_key_ocean_%02x", i);
- if (strcasecmp(key, test_name) == 0)
- {
- if (!parse_hex_key(nca_keyset.key_area_keys[i][1], value, sizeof(nca_keyset.key_area_keys[i][1]))) return 0;
- nca_keyset.ext_key_cnt++;
- break;
- }
-
- snprintf(test_name, sizeof(test_name), "key_area_key_system_%02x", i);
- if (strcasecmp(key, test_name) == 0)
- {
- if (!parse_hex_key(nca_keyset.key_area_keys[i][2], value, sizeof(nca_keyset.key_area_keys[i][2]))) return 0;
- nca_keyset.ext_key_cnt++;
- break;
- }
- }
- }
- }
- }
-
- if (!nca_keyset.ext_key_cnt) return -1;
-
- nca_keyset.total_key_cnt += nca_keyset.ext_key_cnt;
-
- return 1;
-}
-
-bool loadExternalKeys()
-{
- // Check if the keyset has been already loaded
- if (nca_keyset.ext_key_cnt > 0) return true;
-
- // Open keys file
- FILE *keysFile = fopen(KEYS_FILE_PATH, "rb");
- if (!keysFile)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to open \"%s\" to retrieve \"eticket_rsa_kek\", titlekeks and KAEKs!", __func__, KEYS_FILE_PATH);
- return false;
- }
-
- // Load keys
- int ret = readKeysFromFile(keysFile);
- fclose(keysFile);
-
- if (ret < 1)
- {
- if (ret == -1) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to parse necessary keys from \"%s\"! (keys file empty?)", __func__, KEYS_FILE_PATH);
- return false;
- }
-
- return true;
-}
-
-bool testKeyPair(const void *E, const void *D, const void *N)
-{
- if (!E || !D || !N)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to test RSA key pair.", __func__);
- return false;
- }
-
- Result result;
- u8 X[0x100] = {0}, Y[0x100] = {0}, Z[0x100] = {0};
- size_t i;
-
- // 0xCAFEBABE
- X[0xFC] = 0xCA;
- X[0xFD] = 0xFE;
- X[0xFE] = 0xBA;
- X[0xFF] = 0xBE;
-
- result = splUserExpMod(X, N, D, 0x100, Y);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splUserExpMod failed! (testKeyPair #1) (0x%08X)", __func__, result);
- return false;
- }
-
- result = splUserExpMod(Y, N, E, 4, Z);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splUserExpMod failed! (testKeyPair #2) (0x%08X)", __func__, result);
- return false;
- }
-
- for(i = 0; i < 0x100; i++)
- {
- if (X[i] != Z[i])
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid RSA key pair!", __func__);
- return false;
- }
- }
-
- return true;
-}
-
-void mgf1(const u8 *data, size_t data_length, u8 *mask, size_t mask_length)
-{
- if (!data || !data_length || !mask || !mask_length) return;
-
- u8 *data_counter = calloc(data_length + 4, sizeof(u8));
- if (!data_counter) return;
-
- memcpy(data_counter, data, data_length);
-
- sha256CalculateHash(mask, data_counter, data_length + 4);
-
- u32 i, j;
- for(i = 1; i < ((mask_length / 0x20) + 1); i++)
- {
- for(j = 0; j < 4; j++) data_counter[data_length + 3 - j] = ((i >> (8 * j)) & 0xFF);
-
- if ((i * 0x20) <= mask_length)
- {
- sha256CalculateHash(mask + (i * 0x20), data_counter, data_length + 4);
- } else {
- u8 temp_mask[0x20];
- sha256CalculateHash(temp_mask, data_counter, data_length + 4);
- memcpy(mask + (i * 0x20), temp_mask, mask_length - (i * 0x20));
- }
- }
-
- free(data_counter);
-}
-
-int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_enc_key, u8 *out_dec_key)
-{
- int ret = -1;
-
- if (!dec_nca_header || dec_nca_header->kaek_ind > 2 || (!out_tik && !out_dec_key && !out_enc_key))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve NCA ticket and/or titlekey.", __func__);
- return ret;
- }
-
- u32 i, j;
- bool has_rights_id = false;
-
- for(i = 0; i < 0x10; i++)
- {
- if (dec_nca_header->rights_id[i] != 0)
- {
- has_rights_id = true;
- break;
- }
- }
-
- if (!has_rights_id)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA doesn't use titlekey crypto.", __func__);
- return ret;
- }
-
- u8 crypto_type = (dec_nca_header->crypto_type2 > dec_nca_header->crypto_type ? dec_nca_header->crypto_type2 : dec_nca_header->crypto_type);
- if (crypto_type) crypto_type--;
-
- if (crypto_type >= 0x20)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA keyblob index.", __func__);
- return ret;
- }
-
- Result result;
- u32 common_count, personalized_count, ids_written;
- FsRightsId *common_rights_ids = NULL, *personalized_rights_ids = NULL;
-
- bool foundRightsId = false;
- u8 rightsIdType = 0; // 1 = Common, 2 = Personalized
-
- Aes128CtrContext eticket_aes_ctx;
- unsigned char ctr[0x10];
-
- u8 *D = NULL, *N = NULL, *E = NULL;
-
- FRESULT fr;
- FIL eTicketSave;
-
- save_ctx_t *save_ctx = NULL;
- allocation_table_storage_ctx_t fat_storage;
- save_fs_list_entry_t entry;
- const char ticket_bin_path[SAVE_FS_LIST_MAX_NAME_LENGTH] = "/ticket.bin";
-
- u32 buf_size = (ETICKET_ENTRY_SIZE * 0x10);
- u32 br = buf_size;
- u64 total_br = 0;
-
- char tmp[NAME_BUF_LEN / 2] = {'\0'};
-
- bool foundEticket = false, proceed = true;
-
- u8 titlekey[0x10];
- Aes128Context titlekey_aes_ctx;
-
- result = esInitialize();
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize the ES service! (0x%08X)", __func__, result);
- return ret;
- }
-
- result = esCountCommonTicket(&common_count);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: esCountCommonTicket failed! (0x%08X)", __func__, result);
- esExit();
- return ret;
- }
-
- result = esCountPersonalizedTicket(&personalized_count);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: esCountPersonalizedTicket failed! (0x%08X)", __func__, result);
- esExit();
- return ret;
- }
-
- if (!common_count && !personalized_count)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: no tickets available!", __func__);
- esExit();
- return ret;
- }
-
- if (common_count)
- {
- common_rights_ids = calloc(common_count, sizeof(FsRightsId));
- if (!common_rights_ids)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for common tickets' rights IDs!", __func__);
- esExit();
- return ret;
- }
-
- result = esListCommonTicket(&ids_written, common_rights_ids, common_count * sizeof(FsRightsId));
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: esListCommonTicket failed! (0x%08X)", __func__, result);
- free(common_rights_ids);
- esExit();
- return ret;
- }
-
- for(i = 0; i < common_count; i++)
- {
- if (!memcmp(common_rights_ids[i].c, dec_nca_header->rights_id, 0x10))
- {
- foundRightsId = true;
- rightsIdType = 1; // Common
- break;
- }
- }
-
- free(common_rights_ids);
- }
-
- if (!foundRightsId && personalized_count)
- {
- personalized_rights_ids = calloc(personalized_count, sizeof(FsRightsId));
- if (!personalized_rights_ids)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for personalized tickets' rights IDs!", __func__);
- esExit();
- return ret;
- }
-
- result = esListPersonalizedTicket(&ids_written, personalized_rights_ids, personalized_count * sizeof(FsRightsId));
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: esListPersonalizedTicket failed! (0x%08X)", __func__, result);
- free(personalized_rights_ids);
- esExit();
- return ret;
- }
-
- for(i = 0; i < personalized_count; i++)
- {
- if (!memcmp(personalized_rights_ids[i].c, dec_nca_header->rights_id, 0x10))
- {
- foundRightsId = true;
- rightsIdType = 2; // Personalized
- break;
- }
- }
-
- free(personalized_rights_ids);
- }
-
- esExit();
-
- if (!foundRightsId || (rightsIdType != 1 && rightsIdType != 2))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA rights ID unavailable in this console!", __func__);
- ret = -2;
- dumpSharedTikSavedata();
- return ret;
- }
-
- // Load external keys
- if (!loadExternalKeys()) return ret;
-
- if (!setcal_eticket_retrieved)
- {
- // Get extended eTicket RSA key from PRODINFO
- memset(&eticket_data, 0, sizeof(SetCalRsa2048DeviceKey));
-
- result = setcalInitialize();
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize the set:cal service! (0x%08X)", __func__, result);
- return ret;
- }
-
- result = setcalGetEticketDeviceKey(&eticket_data);
-
- setcalExit();
-
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: setcalGetEticketDeviceKey failed! (0x%08X)", __func__, result);
- return ret;
- }
-
- // Decrypt eTicket RSA key
- memcpy(ctr, eticket_data.key, ETICKET_DEVKEY_RSA_CTR_SIZE);
- aes128CtrContextCreate(&eticket_aes_ctx, nca_keyset.eticket_rsa_kek, ctr);
- aes128CtrCrypt(&eticket_aes_ctx, eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET, eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET, ETICKET_DEVKEY_RSA_SIZE);
-
- // Public exponent must use RSA-2048 SHA-1 signature method
- // The value is stored use big endian byte order
- if (__builtin_bswap32(*((u32*)(eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET + 0x200))) != SIGTYPE_RSA2048_SHA1)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid public RSA exponent for eTicket data! Wrong keys?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__);
- return ret;
- }
- }
-
- D = (eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET);
- N = (eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET + 0x100);
- E = (eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET + 0x200);
-
- if (!setcal_eticket_retrieved)
- {
- if (!testKeyPair(E, D, N)) return ret;
- setcal_eticket_retrieved = true;
- }
-
- // FatFs is used to mount the BIS System partition and read the ES savedata files to avoid 0xE02 (file already in use) errors
- fr = f_open(&eTicketSave, (rightsIdType == 1 ? BIS_COMMON_TIK_SAVE_NAME : BIS_PERSONALIZED_TIK_SAVE_NAME), FA_READ | FA_OPEN_EXISTING);
- if (fr)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open ES %s eTicket save! (%u)", __func__, (rightsIdType == 1 ? "common" : "personalized"), fr);
- return ret;
- }
-
- save_ctx = calloc(1, sizeof(save_ctx_t));
- if (!save_ctx)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for ticket savefile context!");
- f_close(&eTicketSave);
- return ret;
- }
-
- save_ctx->file = &eTicketSave;
- save_ctx->tool_ctx.action = 0;
-
- if (!save_process(save_ctx))
- {
- snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to process ticket savefile!", __func__);
- strcat(strbuf, tmp);
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf);
- free(save_ctx);
- f_close(&eTicketSave);
- return ret;
- }
-
- if (!save_hierarchical_file_table_get_file_entry_by_path(&save_ctx->save_filesystem_core.file_table, ticket_bin_path, &entry))
- {
- snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to get file entry for \"%s\" in ticket savefile!", __func__, ticket_bin_path);
- strcat(strbuf, tmp);
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf);
- save_free_contexts(save_ctx);
- free(save_ctx);
- f_close(&eTicketSave);
- return ret;
- }
-
- if (!save_open_fat_storage(&save_ctx->save_filesystem_core, &fat_storage, entry.value.save_file_info.start_block))
- {
- snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to open FAT storage at block 0x%X for \"%s\" in ticket savefile!", __func__, entry.value.save_file_info.start_block, ticket_bin_path);
- strcat(strbuf, tmp);
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf);
- save_free_contexts(save_ctx);
- free(save_ctx);
- f_close(&eTicketSave);
- return ret;
- }
-
- while(br == buf_size && total_br < entry.value.save_file_info.length)
- {
- br = save_allocation_table_storage_read(&fat_storage, dumpBuf, total_br, buf_size);
- if (br != buf_size)
- {
- snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read %u bytes chunk at offset 0x%lX from \"%s\" in ticket savefile!", __func__, buf_size, total_br, ticket_bin_path);
- strcat(strbuf, tmp);
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf);
- proceed = false;
- break;
- }
-
- if (dumpBuf[0] == 0) break;
-
- total_br += br;
-
- for(i = 0; i < buf_size; i += ETICKET_ENTRY_SIZE)
- {
- // Only read eTicket entries with RSA-2048 SHA-256 signature method
- // Also check if our current eTicket entry matches our rights ID
- if (*((u32*)(dumpBuf + i)) != SIGTYPE_RSA2048_SHA256 || memcmp(dumpBuf + i + ETICKET_RIGHTSID_OFFSET, dec_nca_header->rights_id, 0x10) != 0) continue;
-
- foundEticket = true;
-
- if (rightsIdType == 1)
- {
- // Common
- memcpy(titlekey, dumpBuf + i + ETICKET_TITLEKEY_OFFSET, 0x10);
- } else {
- // Personalized
- u8 M[0x100], salt[0x20], db[0xDF];
-
- u8 *titleKeyBlock = (dumpBuf + i + ETICKET_TITLEKEY_OFFSET);
-
- result = splUserExpMod(titleKeyBlock, N, D, 0x100, M);
- if (R_FAILED(result))
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splUserExpMod failed! (titleKeyBlock) (0x%08X)", __func__, result);
- proceed = false;
- break;
- }
-
- // Decrypt the titlekey
- mgf1(M + 0x21, 0xDF, salt, 0x20);
- for(j = 0; j < 0x20; j++) salt[j] ^= M[j + 1];
-
- mgf1(salt, 0x20, db, 0xDF);
- for(j = 0; j < 0xDF; j++) db[j] ^= M[j + 0x21];
-
- // Verify if it starts with a null string hash
- if (memcmp(db, null_hash, 0x20) != 0)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: titlekey decryption failed! Wrong keys?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__);
- proceed = false;
- break;
- }
-
- memcpy(titlekey, db + 0xCF, 0x10);
- }
-
- break;
- }
-
- if (foundEticket) break;
- }
-
- save_free_contexts(save_ctx);
- free(save_ctx);
- f_close(&eTicketSave);
-
- if (!proceed) return ret;
-
- if (!foundEticket)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find a matching eTicket entry for NCA rights ID!", __func__);
- ret = -2;
- dumpSharedTikSavedata();
- return ret;
- }
-
- ret = 0;
-
- // Copy ticket data to output pointer
- if (out_tik != NULL) memcpy(out_tik, dumpBuf + i, ETICKET_TIK_FILE_SIZE);
-
- // Copy encrypted titlekey to output pointer
- // It is used in personalized -> common ticket conversion
- if (out_enc_key != NULL) memcpy(out_enc_key, titlekey, 0x10);
-
- // Generate decrypted titlekey ready to use for section decryption
- // It is also used in ticket-less dumps as the NCA key area slot #2 key (before encryption)
- if (out_dec_key != NULL)
- {
- aes128ContextCreate(&titlekey_aes_ctx, nca_keyset.titlekeks[crypto_type], false);
- aes128DecryptBlock(&titlekey_aes_ctx, out_dec_key, titlekey);
- }
-
- return ret;
-}
-
-bool generateEncryptedNcaKeyAreaWithTitlekey(nca_header_t *dec_nca_header, u8 *decrypted_nca_keys)
-{
- if (!dec_nca_header || dec_nca_header->kaek_ind > 2 || !decrypted_nca_keys || !nca_keyset.ext_key_cnt)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to generate encrypted NCA key area using titlekey!", __func__);
- return false;
- }
-
- u8 i;
- Aes128Context key_area_ctx;
-
- u8 crypto_type = (dec_nca_header->crypto_type2 > dec_nca_header->crypto_type ? dec_nca_header->crypto_type2 : dec_nca_header->crypto_type);
- if (crypto_type) crypto_type--;
-
- if (crypto_type >= 0x20)
- {
- uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA keyblob index.", __func__);
- return false;
- }
-
- aes128ContextCreate(&key_area_ctx, nca_keyset.key_area_keys[crypto_type][dec_nca_header->kaek_ind], true);
-
- for(i = 0; i < NCA_KEY_AREA_KEY_CNT; i++) aes128EncryptBlock(&key_area_ctx, dec_nca_header->nca_keys[i], decrypted_nca_keys + (i * NCA_KEY_AREA_KEY_SIZE));
-
- return true;
-}
diff --git a/source/keys.h b/source/keys.h
deleted file mode 100644
index 50d083f..0000000
--- a/source/keys.h
+++ /dev/null
@@ -1,65 +0,0 @@
-#pragma once
-
-#ifndef __KEYS_H__
-#define __KEYS_H__
-
-#include
-#include "nca.h"
-
-#define FS_TID (u64)0x0100000000000000
-
-#define SEG_TEXT BIT(0)
-#define SEG_RODATA BIT(1)
-#define SEG_DATA BIT(2)
-
-#define ETICKET_DEVKEY_RSA_CTR_SIZE 0x10
-#define ETICKET_DEVKEY_RSA_OFFSET ETICKET_DEVKEY_RSA_CTR_SIZE
-#define ETICKET_DEVKEY_RSA_SIZE 0x230
-
-#define SIGTYPE_RSA2048_SHA1 (u32)0x10001
-#define SIGTYPE_RSA2048_SHA256 (u32)0x10004
-
-typedef struct {
- u64 titleID;
- u8 mask;
- u8 *data;
- u64 dataSize;
-} keyLocation;
-
-typedef struct {
- char name[128];
- u8 hash[SHA256_HASH_SIZE];
- u64 size;
-} keyInfo;
-
-typedef struct {
- u16 memory_key_cnt; /* Key counter for keys retrieved from memory. */
- u16 ext_key_cnt; /* Key counter for keys retrieved from keysfile. */
- u32 total_key_cnt; /* Total key counter. */
-
- // Needed to decrypt the NCA header using AES-128-XTS
- u8 header_kek_source[0x10]; /* Seed for header kek. Retrieved from the .rodata section in the FS sysmodule. */
- u8 header_key_source[0x20]; /* Seed for NCA header key. Retrieved from the .data section in the FS sysmodule. */
- u8 header_kek[0x10]; /* NCA header kek. Generated from header_kek_source. */
- u8 header_key[0x20]; /* NCA header key. Generated from header_kek and header_key_source. */
-
- // Needed to derive the KAEK used to decrypt the NCA key area
- u8 key_area_key_application_source[0x10]; /* Seed for kaek 0. Retrieved from the .rodata section in the FS sysmodule. */
- u8 key_area_key_ocean_source[0x10]; /* Seed for kaek 1. Retrieved from the .rodata section in the FS sysmodule. */
- u8 key_area_key_system_source[0x10]; /* Seed for kaek 2. Retrieved from the .rodata section in the FS sysmodule. */
-
- // Needed to decrypt the title key block from an eTicket. Retrieved from the Lockpick_RCM keys file.
- u8 eticket_rsa_kek[0x10]; /* eTicket RSA kek. */
- u8 titlekeks[0x20][0x10]; /* Title key encryption keys. */
-
- // Needed to reencrypt the NCA key area for tik-less NSP dumps. Retrieved from the Lockpick_RCM keys file.
- u8 key_area_keys[0x20][3][0x10]; /* Key area encryption keys. */
-} nca_keyset_t;
-
-bool loadMemoryKeys();
-bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out);
-bool loadExternalKeys();
-int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_enc_key, u8 *out_dec_key);
-bool generateEncryptedNcaKeyAreaWithTitlekey(nca_header_t *dec_nca_header, u8 *decrypted_nca_keys);
-
-#endif
diff --git a/source/new/es.c b/source/new/es.c
index ab514d7..75d2aff 100644
--- a/source/new/es.c
+++ b/source/new/es.c
@@ -6,18 +6,10 @@
#include "es.h"
#include "service_guard.h"
-static Service g_esSrv;
+static Service g_esSrv = {0};
NX_GENERATE_SERVICE_GUARD(es);
-Result _esInitialize() {
- return smGetService(&g_esSrv, "es");
-}
-
-void _esCleanup() {
- serviceClose(&g_esSrv);
-}
-
Result esCountCommonTicket(s32 *out_count)
{
struct {
@@ -50,7 +42,7 @@ Result esListCommonTicket(s32 *out_entries_written, FsRightsId *out_ids, s32 cou
Result rc = serviceDispatchInOut(&g_esSrv, 11, *out_entries_written, out,
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
- .buffers = { { out_ids, count * sizeof(FsRightsId) } },
+ .buffers = { { out_ids, count * sizeof(FsRightsId) } }
);
if (R_SUCCEEDED(rc) && out_entries_written) *out_entries_written = out.num_rights_ids_written;
@@ -66,10 +58,20 @@ Result esListPersonalizedTicket(s32 *out_entries_written, FsRightsId *out_ids, s
Result rc = serviceDispatchInOut(&g_esSrv, 12, *out_entries_written, out,
.buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
- .buffers = { { out_ids, count * sizeof(FsRightsId) } },
+ .buffers = { { out_ids, count * sizeof(FsRightsId) } }
);
if (R_SUCCEEDED(rc) && out_entries_written) *out_entries_written = out.num_rights_ids_written;
return rc;
}
+
+NX_INLINE Result _esInitialize(void)
+{
+ return smGetService(&g_esSrv, "es");
+}
+
+static void _esCleanup(void)
+{
+ serviceClose(&g_esSrv);
+}
diff --git a/source/new/es.h b/source/new/es.h
index d58f1d3..2029a45 100644
--- a/source/new/es.h
+++ b/source/new/es.h
@@ -5,8 +5,8 @@
#include
-Result esInitialize();
-void esExit();
+Result esInitialize(void);
+void esExit(void);
Result esCountCommonTicket(s32 *out_count);
Result esCountPersonalizedTicket(s32 *out_count);
diff --git a/source/new/fs_ext.c b/source/new/fs_ext.c
index ea3e1f2..5ca6b87 100644
--- a/source/new/fs_ext.c
+++ b/source/new/fs_ext.c
@@ -4,13 +4,13 @@
#include "fs_ext.h"
-// IFileSystemProxy
+/* IFileSystemProxy */
Result fsOpenGameCardStorage(FsStorage *out, const FsGameCardHandle *handle, u32 partition)
{
struct {
- u32 handle;
+ FsGameCardHandle handle;
u32 partition;
- } in = { handle->value, partition };
+ } in = { *handle, partition };
return serviceDispatchIn(fsGetServiceSession(), 30, in,
.out_num_objects = 1,
@@ -26,22 +26,37 @@ Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier *out)
);
}
-// IDeviceOperator
+/* IDeviceOperator */
Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator *d, const FsGameCardHandle *handle, u32 *out_title_version, u64 *out_title_id)
{
struct {
- u32 handle;
- } in = { handle->value };
+ FsGameCardHandle handle;
+ } in = { *handle };
struct {
- u32 title_ver;
+ u32 title_version;
u64 title_id;
} out;
Result rc = serviceDispatchInOut(&d->s, 203, in, out);
- if (R_SUCCEEDED(rc) && out_title_version) *out_title_version = out.title_ver;
+ if (R_SUCCEEDED(rc) && out_title_version) *out_title_version = out.title_version;
if (R_SUCCEEDED(rc) && out_title_id) *out_title_id = out.title_id;
return rc;
}
+
+Result fsDeviceOperatorGetGameCardDeviceCertificate(FsDeviceOperator *d, const FsGameCardHandle *handle, FsGameCardCertificate *out)
+{
+ struct {
+ FsGameCardHandle handle;
+ u64 buf_size;
+ } in = { *handle, sizeof(FsGameCardCertificate) };
+
+ Result rc = serviceDispatchIn(&d->s, 206, in,
+ .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
+ .buffers = { { out, sizeof(FsGameCardCertificate) } }
+ );
+
+ return rc;
+}
diff --git a/source/new/fs_ext.h b/source/new/fs_ext.h
index daa7c14..0a9d8cb 100644
--- a/source/new/fs_ext.h
+++ b/source/new/fs_ext.h
@@ -6,11 +6,24 @@
#include
#include
-// IFileSystemProxy
+/// Located at offset 0x7000 in the gamecard image.
+typedef struct {
+ u8 signature[0x100]; ///< RSA-2048 PKCS #1 signature over the rest of the data.
+ u32 magic; ///< "CERT"
+ u8 reserved_1[0x4];
+ u8 kek_index;
+ u8 reserved_2[0x7];
+ u8 device_id[0x10];
+ u8 reserved_3[0x10];
+ u8 encrypted_data[0xD0];
+} FsGameCardCertificate;
+
+/* IFileSystemProxy */
Result fsOpenGameCardStorage(FsStorage *out, const FsGameCardHandle *handle, u32 partition);
Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier *out);
-// IDeviceOperator
+/* IDeviceOperator */
Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator *d, const FsGameCardHandle *handle, u32 *out_title_version, u64 *out_title_id);
+Result fsDeviceOperatorGetGameCardDeviceCertificate(FsDeviceOperator *d, const FsGameCardHandle *handle, FsGameCardCertificate *out);
#endif /* __FS_EXT_H__ */
diff --git a/source/new/gamecard.c b/source/new/gamecard.c
new file mode 100644
index 0000000..973c5a6
--- /dev/null
+++ b/source/new/gamecard.c
@@ -0,0 +1,763 @@
+#include
+#include
+#include
+#include
+
+#include "gamecard.h"
+#include "service_guard.h"
+#include "utils.h"
+
+#define GAMECARD_ACCESS_WAIT_TIME 3 /* Seconds */
+
+#define GAMECARD_UPDATE_TID (u64)0x0100000000000816
+
+#define GAMECARD_READ_BUFFER_SIZE 0x800000 /* 8 MiB */
+
+#define GAMECARD_ECC_BLOCK_SIZE 0x200
+#define GAMECARD_ECC_DATA_SIZE 0x24
+
+typedef struct {
+ u64 offset; ///< Relative to the start of the gamecard header.
+ u64 size; ///< Whole partition size.
+ u8 *header; ///< GameCardHashFileSystemHeader + GameCardHashFileSystemEntry + Name Table.
+} GameCardHashFileSystemPartitionInfo;
+
+static FsDeviceOperator g_deviceOperator = {0};
+static FsEventNotifier g_gameCardEventNotifier = {0};
+static Event g_gameCardKernelEvent = {0};
+static bool g_openDeviceOperator = false, g_openEventNotifier = false, g_loadKernelEvent = false;
+
+static thrd_t g_gameCardDetectionThread;
+static UEvent g_gameCardDetectionThreadExitEvent = {0};
+static mtx_t g_gameCardSharedDataMutex;
+static bool g_gameCardDetectionThreadCreated = false, g_gameCardInserted = false, g_gameCardInfoLoaded = false;
+
+static FsGameCardHandle g_gameCardHandle = {0};
+static FsStorage g_gameCardStorageNormal = {0}, g_gameCardStorageSecure = {0};
+static u8 *g_gameCardReadBuf = NULL;
+
+static GameCardHeader g_gameCardHeader = {0};
+static u64 g_gameCardStorageNormalAreaSize = 0, g_gameCardStorageSecureAreaSize = 0;
+
+static u8 *g_gameCardHfsRootHeader = NULL; /* GameCardHashFileSystemHeader + GameCardHashFileSystemEntry + Name Table */
+static GameCardHashFileSystemPartitionInfo *g_gameCardHfsPartitions = NULL;
+
+static bool gamecardCreateDetectionThread(void);
+static void gamecardDestroyDetectionThread(void);
+static int gamecardDetectionThreadFunc(void *arg);
+
+static inline bool gamecardCheckIfInserted(void);
+
+static void gamecardLoadInfo(void);
+static void gamecardFreeInfo(void);
+
+static bool gamecardGetHandle(void);
+static inline void gamecardCloseHandle(void);
+
+static bool gamecardOpenStorageAreas(void);
+static bool _gamecardStorageRead(void *out, u64 out_size, u64 offset, bool lock);
+static void gamecardCloseStorageAreas(void);
+
+static bool gamecardGetSizesFromStorageAreas(void);
+
+/* Service guard used to generate thread-safe initialize + exit functions */
+NX_GENERATE_SERVICE_GUARD(gamecard);
+
+bool gamecardCheckReadyStatus(void)
+{
+ mtx_lock(&g_gameCardSharedDataMutex);
+ bool status = (g_gameCardInserted && g_gameCardInfoLoaded);
+ mtx_unlock(&g_gameCardSharedDataMutex);
+ return status;
+}
+
+bool gamecardStorageRead(void *out, u64 out_size, u64 offset)
+{
+ return _gamecardStorageRead(out, out_size, offset, true);
+}
+
+bool gamecardGetHeader(GameCardHeader *out)
+{
+ bool ret = false;
+
+ mtx_lock(&g_gameCardSharedDataMutex);
+ if (g_gameCardInserted && g_gameCardInfoLoaded && out)
+ {
+ memcpy(out, &g_gameCardHeader, sizeof(GameCardHeader));
+ ret = true;
+ }
+ mtx_unlock(&g_gameCardSharedDataMutex);
+
+ return ret;
+}
+
+bool gamecardGetTotalRomSize(u64 *out)
+{
+ bool ret = false;
+
+ mtx_lock(&g_gameCardSharedDataMutex);
+ if (g_gameCardInserted && g_gameCardInfoLoaded && out)
+ {
+ *out = (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize);
+ ret = true;
+ }
+ mtx_unlock(&g_gameCardSharedDataMutex);
+
+ return ret;
+}
+
+bool gamecardGetTrimmedRomSize(u64 *out)
+{
+ bool ret = false;
+
+ mtx_lock(&g_gameCardSharedDataMutex);
+ if (g_gameCardInserted && g_gameCardInfoLoaded && out)
+ {
+ *out = (sizeof(GameCardHeader) + ((u64)g_gameCardHeader.valid_data_end_address * GAMECARD_MEDIA_UNIT_SIZE));
+ ret = true;
+ }
+ mtx_unlock(&g_gameCardSharedDataMutex);
+
+ return ret;
+}
+
+bool gamecardGetCertificate(FsGameCardCertificate *out)
+{
+ Result rc = 0;
+ bool ret = false;
+
+ mtx_lock(&g_gameCardSharedDataMutex);
+ if (g_gameCardInserted && g_gameCardHandle.value && out)
+ {
+ rc = fsDeviceOperatorGetGameCardDeviceCertificate(&g_deviceOperator, &g_gameCardHandle, out);
+ if (R_FAILED(rc)) LOGFILE("fsDeviceOperatorGetGameCardDeviceCertificate failed! (0x%08X)", rc);
+ ret = R_SUCCEEDED(rc);
+ }
+ mtx_unlock(&g_gameCardSharedDataMutex);
+
+ return ret;
+}
+
+bool gamecardGetBundledFirmwareUpdateVersion(u32 *out)
+{
+ Result rc = 0;
+ u64 update_id = 0;
+ u32 update_version = 0;
+ bool ret = false;
+
+ mtx_lock(&g_gameCardSharedDataMutex);
+ if (g_gameCardInserted && g_gameCardHandle.value && out)
+ {
+ rc = fsDeviceOperatorUpdatePartitionInfo(&g_deviceOperator, &g_gameCardHandle, &update_version, &update_id);
+ if (R_FAILED(rc)) LOGFILE("fsDeviceOperatorUpdatePartitionInfo failed! (0x%08X)", rc);
+ ret = (R_SUCCEEDED(rc) && update_id == GAMECARD_UPDATE_TID);
+ if (ret) *out = update_version;
+ }
+ mtx_unlock(&g_gameCardSharedDataMutex);
+
+ return ret;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+NX_INLINE Result _gamecardInitialize(void)
+{
+ Result rc = 0;
+
+ /* Allocate memory for the gamecard read buffer */
+ g_gameCardReadBuf = malloc(GAMECARD_READ_BUFFER_SIZE);
+ if (!g_gameCardReadBuf)
+ {
+ LOGFILE("Unable to allocate memory for the gamecard read buffer!");
+ rc = MAKERESULT(Module_Libnx, LibnxError_HeapAllocFailed);
+ goto out;
+ }
+
+ /* Open device operator */
+ rc = fsOpenDeviceOperator(&g_deviceOperator);
+ if (R_FAILED(rc))
+ {
+ LOGFILE("fsOpenDeviceOperator failed! (0x%08X)", rc);
+ goto out;
+ }
+
+ g_openDeviceOperator = true;
+
+ /* Open gamecard detection event notifier */
+ rc = fsOpenGameCardDetectionEventNotifier(&g_gameCardEventNotifier);
+ if (R_FAILED(rc))
+ {
+ LOGFILE("fsOpenGameCardDetectionEventNotifier failed! (0x%08X)", rc);
+ goto out;
+ }
+
+ g_openEventNotifier = true;
+
+ /* Retrieve gamecard detection kernel event */
+ rc = fsEventNotifierGetEventHandle(&g_gameCardEventNotifier, &g_gameCardKernelEvent, true);
+ if (R_FAILED(rc))
+ {
+ LOGFILE("fsEventNotifierGetEventHandle failed! (0x%08X)", rc);
+ goto out;
+ }
+
+ g_loadKernelEvent = true;
+
+ /* Create usermode exit event */
+ ueventCreate(&g_gameCardDetectionThreadExitEvent, false);
+
+ /* Create gamecard detection thread */
+ g_gameCardDetectionThreadCreated = gamecardCreateDetectionThread();
+ if (!g_gameCardDetectionThreadCreated)
+ {
+ LOGFILE("Failed to create gamecard detection thread!");
+ rc = MAKERESULT(Module_Libnx, LibnxError_IoError);
+ }
+
+out:
+ return rc;
+}
+
+static void _gamecardCleanup(void)
+{
+ /* Destroy gamecard detection thread */
+ if (g_gameCardDetectionThreadCreated)
+ {
+ gamecardDestroyDetectionThread();
+ g_gameCardDetectionThreadCreated = false;
+ }
+
+ /* Close gamecard detection kernel event */
+ if (g_loadKernelEvent)
+ {
+ eventClose(&g_gameCardKernelEvent);
+ g_loadKernelEvent = false;
+ }
+
+ /* Close gamecard detection event notifier */
+ if (g_openEventNotifier)
+ {
+ fsEventNotifierClose(&g_gameCardEventNotifier);
+ g_openEventNotifier = false;
+ }
+
+ /* Close device operator */
+ if (g_openDeviceOperator)
+ {
+ fsDeviceOperatorClose(&g_deviceOperator);
+ g_openDeviceOperator = false;
+ }
+
+ /* Free gamecard read buffer */
+ if (g_gameCardReadBuf)
+ {
+ free(g_gameCardReadBuf);
+ g_gameCardReadBuf = NULL;
+ }
+}
+
+static bool gamecardCreateDetectionThread(void)
+{
+ if (mtx_init(&g_gameCardSharedDataMutex, mtx_plain) != thrd_success)
+ {
+ LOGFILE("Failed to initialize gamecard shared data mutex!");
+ return false;
+ }
+
+ if (thrd_create(&g_gameCardDetectionThread, gamecardDetectionThreadFunc, NULL) != thrd_success)
+ {
+ LOGFILE("Failed to create gamecard detection thread!");
+ mtx_destroy(&g_gameCardSharedDataMutex);
+ return false;
+ }
+
+ return true;
+}
+
+static void gamecardDestroyDetectionThread(void)
+{
+ /* Signal the exit event to terminate the gamecard detection thread */
+ ueventSignal(&g_gameCardDetectionThreadExitEvent);
+
+ /* Wait for the gamecard detection thread to exit */
+ thrd_join(g_gameCardDetectionThread, NULL);
+
+ /* Destroy mutex */
+ mtx_destroy(&g_gameCardSharedDataMutex);
+}
+
+static int gamecardDetectionThreadFunc(void *arg)
+{
+ (void)arg;
+
+ Result rc = 0;
+ int idx = 0;
+ bool prev_status = false;
+
+ Waiter gamecard_event_waiter = waiterForEvent(&g_gameCardKernelEvent);
+ Waiter exit_event_waiter = waiterForUEvent(&g_gameCardDetectionThreadExitEvent);
+
+ mtx_lock(&g_gameCardSharedDataMutex);
+
+ /* Retrieve initial gamecard insertion status */
+ g_gameCardInserted = prev_status = gamecardCheckIfInserted();
+
+ /* Load gamecard info right away if a gamecard is inserted and if a handle can be retrieved */
+ if (g_gameCardInserted && gamecardGetHandle()) gamecardLoadInfo();
+
+ mtx_unlock(&g_gameCardSharedDataMutex);
+
+ while(true)
+ {
+ /* Wait until an event is triggered */
+ rc = waitMulti(&idx, -1, gamecard_event_waiter, exit_event_waiter);
+ if (R_FAILED(rc)) continue;
+
+ /* Exit event triggered */
+ if (idx == 1) break;
+
+ /* Retrieve current gamecard insertion status */
+ /* Only proceed if we're dealing with a status change */
+ mtx_lock(&g_gameCardSharedDataMutex);
+
+ g_gameCardInserted = gamecardCheckIfInserted();
+
+ if (!prev_status && g_gameCardInserted)
+ {
+ /* Don't access the gamecard immediately to avoid conflicts with HOS / sysmodules */
+ SLEEP(GAMECARD_ACCESS_WAIT_TIME);
+
+ /* Load gamecard info if a gamecard is inserted and if a handle can be retrieved */
+ if (gamecardGetHandle()) gamecardLoadInfo();
+ } else {
+ /* Free gamecard info and close gamecard handle */
+ gamecardFreeInfo();
+ gamecardCloseHandle();
+ }
+
+ prev_status = g_gameCardInserted;
+
+ mtx_unlock(&g_gameCardSharedDataMutex);
+ }
+
+ /* Free gamecard info and close gamecard handle */
+ mtx_lock(&g_gameCardSharedDataMutex);
+ gamecardFreeInfo();
+ gamecardCloseHandle();
+ g_gameCardInserted = false;
+ mtx_unlock(&g_gameCardSharedDataMutex);
+
+ return 0;
+}
+
+static inline bool gamecardCheckIfInserted(void)
+{
+ bool inserted = false;
+ Result rc = fsDeviceOperatorIsGameCardInserted(&g_deviceOperator, &inserted);
+ if (R_FAILED(rc)) LOGFILE("fsDeviceOperatorIsGameCardInserted failed! (0x%08X)", rc);
+ return (R_SUCCEEDED(rc) && inserted);
+}
+
+static void gamecardLoadInfo(void)
+{
+ if (g_gameCardInfoLoaded) return;
+
+ GameCardHashFileSystemHeader *fs_header = NULL;
+ GameCardHashFileSystemEntry *fs_entry = NULL;
+
+ /* Open gamecard storage areas */
+ if (!gamecardOpenStorageAreas())
+ {
+ LOGFILE("Failed to open gamecard storage areas!");
+ goto out;
+ }
+
+ /* Read gamecard header */
+ if (!_gamecardStorageRead(&g_gameCardHeader, sizeof(GameCardHeader), 0, false))
+ {
+ LOGFILE("Failed to read gamecard header!");
+ goto out;
+ }
+
+ /* Check magic word from gamecard header */
+ if (__builtin_bswap32(g_gameCardHeader.magic) != GAMECARD_HEAD_MAGIC)
+ {
+ LOGFILE("Invalid gamecard header magic word! (0x%08X)", __builtin_bswap32(g_gameCardHeader.magic));
+ goto out;
+ }
+
+ if (utilsGetCustomFirmwareType() == UtilsCustomFirmwareType_SXOS)
+ {
+ /* Total size for the secure storage area is maxed out under SX OS */
+ /* Let's try to calculate it manually */
+ u64 capacity = gamecardGetCapacity(&g_gameCardHeader);
+ if (!capacity)
+ {
+ LOGFILE("Invalid gamecard capacity value! (0x%02X)", g_gameCardHeader.rom_size);
+ goto out;
+ }
+
+ g_gameCardStorageSecureAreaSize = ((capacity - ((capacity / GAMECARD_ECC_BLOCK_SIZE) * GAMECARD_ECC_DATA_SIZE)) - g_gameCardStorageNormalAreaSize);
+ }
+
+ /* Allocate memory for the root hash FS header */
+ g_gameCardHfsRootHeader = calloc(g_gameCardHeader.partition_fs_header_size, sizeof(u8));
+ if (!g_gameCardHfsRootHeader)
+ {
+ LOGFILE("Unable to allocate memory for the root hash FS header!");
+ goto out;
+ }
+
+ /* Read root hash FS header */
+ if (!_gamecardStorageRead(g_gameCardHfsRootHeader, g_gameCardHeader.partition_fs_header_size, g_gameCardHeader.partition_fs_header_address, false))
+ {
+ LOGFILE("Failed to read root hash FS header from offset 0x%lX!", g_gameCardHeader.partition_fs_header_address);
+ goto out;
+ }
+
+ fs_header = (GameCardHashFileSystemHeader*)g_gameCardHfsRootHeader;
+
+ if (__builtin_bswap32(fs_header->magic) != GAMECARD_HFS0_MAGIC)
+ {
+ LOGFILE("Invalid magic word in root hash FS header! (0x%08X)", __builtin_bswap32(fs_header->magic));
+ goto out;
+ }
+
+ if (!fs_header->entry_count || !fs_header->name_table_size || \
+ (sizeof(GameCardHashFileSystemHeader) + (fs_header->entry_count * sizeof(GameCardHashFileSystemEntry)) + fs_header->name_table_size) > g_gameCardHeader.partition_fs_header_size)
+ {
+ LOGFILE("Invalid file count and/or name table size in root hash FS header!");
+ goto out;
+ }
+
+ /* Allocate memory for the hash FS partitions info */
+ g_gameCardHfsPartitions = calloc(fs_header->entry_count, sizeof(GameCardHashFileSystemEntry));
+ if (!g_gameCardHfsPartitions)
+ {
+ LOGFILE("Unable to allocate memory for the hash FS partitions info!");
+ goto out;
+ }
+
+ /* Read hash FS partitions */
+ for(u32 i = 0; i < fs_header->entry_count; i++)
+ {
+ fs_entry = (GameCardHashFileSystemEntry*)(g_gameCardHfsRootHeader + sizeof(GameCardHashFileSystemHeader) + (i * sizeof(GameCardHashFileSystemEntry)));
+
+ if (!fs_entry->size)
+ {
+ LOGFILE("Invalid size for hash FS partition #%u!", i);
+ goto out;
+ }
+
+ g_gameCardHfsPartitions[i].offset = (g_gameCardHeader.partition_fs_header_address + g_gameCardHeader.partition_fs_header_size + fs_entry->offset);
+ g_gameCardHfsPartitions[i].size = fs_entry->size;
+
+ /* Partially read the current hash FS partition header */
+ GameCardHashFileSystemHeader partition_header = {0};
+ if (!_gamecardStorageRead(&partition_header, sizeof(GameCardHashFileSystemHeader), g_gameCardHfsPartitions[i].offset, false))
+ {
+ LOGFILE("Failed to partially read hash FS partition #%u header from offset 0x%lX!", i, g_gameCardHfsPartitions[i].offset);
+ goto out;
+ }
+
+ if (__builtin_bswap32(partition_header.magic) != GAMECARD_HFS0_MAGIC)
+ {
+ LOGFILE("Invalid magic word in hash FS partition #%u header! (0x%08X)", i, __builtin_bswap32(partition_header.magic));
+ goto out;
+ }
+
+ if (!partition_header.name_table_size)
+ {
+ LOGFILE("Invalid name table size in hash FS partition #%u header!", i);
+ goto out;
+ }
+
+ /* Calculate the full header size for the current hash FS partition */
+ u64 partition_header_size = (sizeof(GameCardHashFileSystemHeader) + (partition_header.entry_count * sizeof(GameCardHashFileSystemEntry)) + partition_header.name_table_size);
+
+ /* Allocate memory for the hash FS partition header */
+ g_gameCardHfsPartitions[i].header = calloc(partition_header_size, sizeof(u8));
+ if (!g_gameCardHfsPartitions[i].header)
+ {
+ LOGFILE("Unable to allocate memory for the hash FS partition #%u header!", i);
+ goto out;
+ }
+
+ /* Finally, read the full hash FS partition header */
+ if (!_gamecardStorageRead(g_gameCardHfsPartitions[i].header, partition_header_size, g_gameCardHfsPartitions[i].offset, false))
+ {
+ LOGFILE("Failed to read full hash FS partition #%u header from offset 0x%lX!", i, g_gameCardHfsPartitions[i].offset);
+ goto out;
+ }
+ }
+
+ g_gameCardInfoLoaded = true;
+
+out:
+ if (!g_gameCardInfoLoaded) gamecardFreeInfo();
+}
+
+static void gamecardFreeInfo(void)
+{
+ memset(&g_gameCardHeader, 0, sizeof(GameCardHeader));
+
+ if (g_gameCardHfsRootHeader)
+ {
+ if (g_gameCardHfsPartitions)
+ {
+ GameCardHashFileSystemHeader *fs_header = (GameCardHashFileSystemHeader*)g_gameCardHfsRootHeader;
+
+ for(u32 i = 0; i < fs_header->entry_count; i++)
+ {
+ if (g_gameCardHfsPartitions[i].header) free(g_gameCardHfsPartitions[i].header);
+ }
+ }
+
+ free(g_gameCardHfsRootHeader);
+ g_gameCardHfsRootHeader = NULL;
+ }
+
+ if (g_gameCardHfsPartitions)
+ {
+ free(g_gameCardHfsPartitions);
+ g_gameCardHfsPartitions = NULL;
+ }
+
+ gamecardCloseStorageAreas();
+
+ g_gameCardInfoLoaded = false;
+}
+
+static bool gamecardGetHandle(void)
+{
+ if (!g_gameCardInserted)
+ {
+ LOGFILE("Gamecard not inserted!");
+ return false;
+ }
+
+ if (g_gameCardInfoLoaded && g_gameCardHandle.value) return true;
+
+ Result rc1 = 0, rc2 = 0;
+ FsStorage tmp_storage = {0};
+
+ /* 10 tries */
+ for(u8 i = 0; i < 10; i++)
+ {
+ /* First try to open a gamecard storage area using the current gamecard handle */
+ rc1 = fsOpenGameCardStorage(&tmp_storage, &g_gameCardHandle, 0);
+ if (R_SUCCEEDED(rc1))
+ {
+ fsStorageClose(&tmp_storage);
+ break;
+ }
+
+ /* If the previous call failed, we may have an invalid handle, so let's close the current one and try to retrieve a new one */
+ gamecardCloseHandle();
+ rc2 = fsDeviceOperatorGetGameCardHandle(&g_deviceOperator, &g_gameCardHandle);
+ }
+
+ if (R_FAILED(rc1) || R_FAILED(rc2))
+ {
+ /* Close leftover gamecard handle */
+ gamecardCloseHandle();
+
+ if (R_FAILED(rc1)) LOGFILE("fsOpenGameCardStorage failed! (0x%08X)", rc1);
+ if (R_FAILED(rc2)) LOGFILE("fsDeviceOperatorGetGameCardHandle failed! (0x%08X)", rc2);
+
+ return false;
+ }
+
+ return true;
+}
+
+static inline void gamecardCloseHandle(void)
+{
+ svcCloseHandle(g_gameCardHandle.value);
+ g_gameCardHandle.value = 0;
+}
+
+static bool gamecardOpenStorageAreas(void)
+{
+ if (!g_gameCardInserted || !g_gameCardHandle.value)
+ {
+ LOGFILE("Invalid parameters!");
+ return false;
+ }
+
+ if (g_gamecardInfoLoaded && serviceIsActive(&(g_gameCardStorageNormal.s)) && serviceIsActive(&(g_gameCardStorageSecure.s))) return true;
+
+ gamecardCloseStorageAreas();
+
+ Result rc = 0;
+ bool success = false;
+
+ rc = fsOpenGameCardStorage(&g_gameCardStorageNormal, &g_gameCardHandle, 0);
+ if (R_FAILED(rc))
+ {
+ LOGFILE("fsOpenGameCardStorage failed! (0x%08X) (normal)", rc);
+ goto out;
+ }
+
+ rc = fsOpenGameCardStorage(&g_gameCardStorageSecure, &g_gameCardHandle, 1);
+ if (R_FAILED(rc))
+ {
+ LOGFILE("fsOpenGameCardStorage failed! (0x%08X) (secure)", rc);
+ goto out;
+ }
+
+ if (!gamecardGetSizesFromStorageAreas())
+ {
+ LOGFILE("Failed to retrieve sizes from storage areas!");
+ goto out;
+ }
+
+ success = true;
+
+out:
+ if (!success) gamecardCloseStorageAreas();
+
+ return success;
+}
+
+static bool _gamecardStorageRead(void *out, u64 out_size, u64 offset, bool lock)
+{
+ if (lock) mtx_lock(&g_gameCardSharedDataMutex);
+
+ bool success = false;
+
+ if (!g_gameCardInserted || !serviceIsActive(&(g_gameCardStorageNormal.s)) || !g_gameCardStorageNormalAreaSize || !serviceIsActive(&(g_gameCardStorageSecure.s)) || \
+ !g_gameCardStorageSecureAreaSize || !out || !out_size || offset >= (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize) || \
+ (offset + out_size) > (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize))
+ {
+ LOGFILE("Invalid parameters!");
+ goto out;
+ }
+
+ Result rc = 0;
+ u8 *out_u8 = (u8*)out;
+
+ /* Handle reads between the end of the normal storage area and the start of the secure storage area */
+ if (offset < g_gameCardStorageNormalAreaSize && (offset + out_size) > g_gameCardStorageNormalAreaSize)
+ {
+ /* Calculate normal storage area size difference */
+ u64 diff_size = (g_gameCardStorageNormalAreaSize - offset);
+
+ if (!_gamecardStorageRead(out_u8, diff_size, offset, false)) goto out;
+
+ /* Adjust variables to start reading right from the start of the secure storage area */
+ out_u8 += diff_size;
+ offset = g_gameCardStorageNormalAreaSize;
+ out_size -= diff_size;
+ }
+
+ /* Calculate appropiate storage area offset and retrieve the right storage area pointer */
+ const char *area = (offset < g_gameCardStorageNormalAreaSize ? "normal" : "secure");
+ u64 base_offset = (offset < g_gameCardStorageNormalAreaSize ? offset : (offset - g_gameCardStorageNormalAreaSize));
+ FsStorage *storage = (offset < g_gameCardStorageNormalAreaSize ? &g_gameCardStorageNormal : &g_gameCardStorageSecure);
+
+ if (!(base_offset % GAMECARD_MEDIA_UNIT_SIZE) && !(out_size % GAMECARD_MEDIA_UNIT_SIZE))
+ {
+ /* Optimization for reads that are already aligned to GAMECARD_MEDIA_UNIT_SIZE bytes */
+ rc = fsStorageRead(storage, base_offset, out_u8, out_size);
+ if (R_FAILED(rc))
+ {
+ LOGFILE("fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (aligned)", out_size, base_offset, area, rc);
+ goto out;
+ }
+
+ success = true;
+ } else {
+ /* Fix offset and/or size to avoid unaligned reads */
+ u64 block_start_offset = (base_offset - (base_offset % GAMECARD_MEDIA_UNIT_SIZE));
+ u64 block_end_offset = round_up(base_offset + out_size, GAMECARD_MEDIA_UNIT_SIZE);
+ u64 block_size = (block_end_offset - block_start_offset);
+
+ u64 chunk_size = (block_size > GAMECARD_READ_BUFFER_SIZE ? GAMECARD_READ_BUFFER_SIZE : block_size);
+ u64 out_chunk_size = (block_size > GAMECARD_READ_BUFFER_SIZE ? (GAMECARD_READ_BUFFER_SIZE - (base_offset - block_start_offset)) : out_size);
+
+ rc = fsStorageRead(storage, block_start_offset, g_gameCardReadBuf, chunk_size);
+ if (!R_FAILED(rc))
+ {
+ LOGFILE("fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (unaligned)", chunk_size, block_start_offset, area, rc);
+ goto out;
+ }
+
+ memcpy(out_u8, g_gameCardReadBuf + (base_offset - block_start_offset), out_chunk_size);
+
+ success = (block_size > GAMECARD_READ_BUFFER_SIZE ? _gamecardStorageRead(out_u8 + out_chunk_size, out_size - out_chunk_size, base_offset + out_chunk_size, false) : true);
+ }
+
+out:
+ if (lock) mtx_unlock(&g_gameCardSharedDataMutex);
+
+ return success;
+}
+
+static void gamecardCloseStorageAreas(void)
+{
+ if (serviceIsActive(&(g_gameCardStorageNormal.s)))
+ {
+ fsStorageClose(&g_gameCardStorageNormal);
+ memset(&g_gameCardStorageNormal, 0, sizeof(FsStorage));
+ }
+
+ g_gameCardStorageNormalAreaSize = 0;
+
+ if (serviceIsActive(&(g_gameCardStorageSecure.s)))
+ {
+ fsStorageClose(&g_gameCardStorageSecure);
+ memset(&g_gameCardStorageSecure, 0, sizeof(FsStorage));
+ }
+
+ g_gameCardStorageSecureAreaSize = 0;
+}
+
+static bool gamecardGetSizesFromStorageAreas(void)
+{
+ if (!g_gameCardInserted || !serviceIsActive(&(g_gameCardStorageNormal.s)) || !serviceIsActive(&(g_gameCardStorageSecure.s)))
+ {
+ LOGFILE("Invalid parameters!");
+ return false;
+ }
+
+ Result rc = 0;
+
+ rc = fsStorageGetSize(&g_gameCardStorageNormal, (s64*)&g_gameCardStorageNormalAreaSize);
+ if (R_FAILED(rc))
+ {
+ LOGFILE("fsStorageGetSize failed! (0x%08X) (normal)", rc);
+ return false;
+ }
+
+ rc = fsStorageGetSize(&g_gameCardStorageSecure, (s64*)&g_gameCardStorageSecureAreaSize);
+ if (R_FAILED(rc))
+ {
+ LOGFILE("fsStorageGetSize failed! (0x%08X) (secure)", rc);
+ g_gameCardStorageNormalAreaSize = 0;
+ return false;
+ }
+
+ return true;
+}
diff --git a/source/new/gamecard.h b/source/new/gamecard.h
new file mode 100644
index 0000000..1e761f5
--- /dev/null
+++ b/source/new/gamecard.h
@@ -0,0 +1,174 @@
+#pragma once
+
+#ifndef __GAMECARD_H__
+#define __GAMECARD_H__
+
+#include "fs_ext.h"
+
+#define GAMECARD_HEAD_MAGIC 0x48454144 /* "HEAD" */
+#define GAMECARD_CERT_MAGIC 0x43455254 /* "CERT" */
+#define GAMECARD_HFS0_MAGIC 0x48465330 /* "HFS0" */
+
+#define GAMECARD_MEDIA_UNIT_SIZE 0x200
+
+typedef enum {
+ GameCardKekIndex_Version0 = 0,
+ GameCardKekIndex_VersionForDev = 1
+} GameCardKekIndex;
+
+typedef struct {
+ u8 kek_index : 4; ///< GameCardKekIndex.
+ u8 titlekey_dec_index : 4;
+} GameCardKeyFlags;
+
+typedef enum {
+ GameCardRomSize_1GB = 0xFA,
+ GameCardRomSize_2GB = 0xF8,
+ GameCardRomSize_4GB = 0xF0,
+ GameCardRomSize_8GB = 0xE0,
+ GameCardRomSize_16GB = 0xE1,
+ GameCardRomSize_32GB = 0xE2
+} GameCardRomSize;
+
+typedef struct {
+ u8 autoboot : 1;
+ u8 history_erase : 1;
+ u8 repair_tool : 1;
+ u8 different_region_cup_to_terra_device : 1;
+ u8 different_region_cup_to_global_device : 1;
+} GameCardFlags;
+
+typedef enum {
+ GameCardSelSec_ForT1 = 0,
+ GameCardSelSec_ForT2 = 1
+} GameCardSelSec;
+
+typedef enum {
+ GameCardFwVersion_Dev = 0,
+ GameCardFwVersion_Prod = 1,
+ GameCardFwVersion_Since400NUP = 2
+} GameCardFwVersion;
+
+typedef enum {
+ GameCardAccCtrl_25MHz = 0xA10011,
+ GameCardAccCtrl_50MHz = 0xA10010
+} GameCardAccCtrl;
+
+typedef enum {
+ GameCardCompatibilityType_Normal = 0,
+ GameCardCompatibilityType_Terra = 1
+} GameCardCompatibilityType;
+
+typedef struct {
+ u64 fw_version; ///< GameCardFwVersion.
+ u32 acc_ctrl; ///< GameCardAccCtrl.
+ u32 wait_1_time_read; ///< Always 0x1388.
+ u32 wait_2_time_read; ///< Always 0.
+ u32 wait_1_time_write; ///< Always 0.
+ u32 wait_2_time_write; ///< Always 0.
+ u32 fw_mode;
+ u32 upp_version;
+ u8 compatibility_type; ///< GameCardCompatibilityType.
+ u8 reserved_1[0x3];
+ u64 upp_hash;
+ u64 upp_id; ///< Must match GAMECARD_UPDATE_TID.
+ u8 reserved_2[0x38];
+} GameCardExtendedHeader;
+
+typedef struct {
+ u8 signature[0x100]; ///< RSA-2048 PKCS #1 signature over the rest of the header.
+ u32 magic; ///< "HEAD".
+ u32 secure_area_start_address; ///< Expressed in GAMECARD_MEDIA_UNIT_SIZE blocks.
+ u32 backup_area_start_address; ///< Always 0xFFFFFFFF.
+ GameCardKeyFlags key_flags;
+ u8 rom_size; ///< GameCardRomSize.
+ u8 header_version;
+ GameCardFlags flags;
+ u64 package_id;
+ u32 valid_data_end_address; ///< Expressed in GAMECARD_MEDIA_UNIT_SIZE blocks.
+ u8 reserved[0x4];
+ u8 iv[0x10];
+ u64 partition_fs_header_address; ///< Root HFS0 header offset.
+ u64 partition_fs_header_size; ///< Root HFS0 header size.
+ u8 partition_fs_header_hash[SHA256_HASH_SIZE];
+ u8 initial_data_hash[SHA256_HASH_SIZE];
+ u32 sel_sec; ///< GameCardSelSec.
+ u32 sel_t1_key_index;
+ u32 sel_key_index;
+ u32 normal_area_end_address; ///< Expressed in GAMECARD_MEDIA_UNIT_SIZE blocks.
+ GameCardExtendedHeader extended_header; ///< Encrypted using AES-128-CBC with 'xci_header_key', which can't dumped through current methods.
+} GameCardHeader;
+
+typedef struct {
+ u32 magic; ///< "HFS0".
+ u32 entry_count;
+ u32 name_table_size;
+ u8 reserved[0x4];
+} GameCardHashFileSystemHeader;
+
+typedef struct {
+ u64 offset;
+ u64 size;
+ u32 name_offset;
+ u32 hash_target_size;
+ u64 hash_target_offset;
+ u8 hash[SHA256_HASH_SIZE];
+} GameCardHashFileSystemEntry;
+
+/// Initializes data needed to access raw gamecard storage areas.
+/// Also spans a background thread to automatically detect gamecard status changes and to cache data from the inserted gamecard.
+Result gamecardInitialize(void);
+
+/// Deinitializes data generated by gamecardInitialize().
+/// This includes destroying the background gamecard detection thread and freeing all cached gamecard data.
+void gamecardExit(void);
+
+/// Used to check if a gamecard has been inserted and if info could be loaded from it.
+bool gamecardCheckReadyStatus(void);
+
+/// Used to read data from the inserted gamecard.
+/// All required handles are managed internally.
+/// offset + out_size should never exceed the value returned by gamecardGetTotalRomSize().
+bool gamecardStorageRead(void *out, u64 out_size, u64 offset);
+
+/// Miscellaneous functions.
+bool gamecardGetHeader(GameCardHeader *out);
+bool gamecardGetTotalRomSize(u64 *out);
+bool gamecardGetTrimmedRomSize(u64 *out);
+bool gamecardGetCertificate(FsGameCardCertificate *out);
+bool gamecardGetBundledFirmwareUpdateVersion(u32 *out);
+
+static inline u64 gamecardGetCapacity(GameCardHeader *header)
+{
+ if (!header) return 0;
+
+ u64 capacity = 0;
+
+ switch(header->rom_size)
+ {
+ case GameCardRomSize_1GB:
+ capacity = (u64)0x40000000;
+ break;
+ case GameCardRomSize_2GB:
+ capacity = (u64)0x80000000;
+ break;
+ case GameCardRomSize_4GB:
+ capacity = (u64)0x100000000;
+ break;
+ case GameCardRomSize_8GB:
+ capacity = (u64)0x200000000;
+ break;
+ case GameCardRomSize_16GB:
+ capacity = (u64)0x400000000;
+ break;
+ case GameCardRomSize_32GB:
+ capacity = (u64)0x800000000;
+ break;
+ default:
+ break;
+ }
+
+ return out;
+}
+
+#endif /* __GAMECARD_H__ */
diff --git a/source/new/nca.c b/source/new/nca.c
index ae0a85b..8285159 100644
--- a/source/new/nca.c
+++ b/source/new/nca.c
@@ -4,17 +4,21 @@
#include "nca.h"
#include "keys.h"
+#include "rsa.h"
#include "utils.h"
+static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = {
+ 0x9A, 0xBB, 0xD2, 0x11, 0x86, 0x00, 0x21, 0x9D, 0x7A, 0xDC, 0x5B, 0x43, 0x95, 0xF8, 0x4E, 0xFD,
+ 0xFF, 0x6B, 0x25, 0xEF, 0x9F, 0x96, 0x85, 0x28, 0x18, 0x9E, 0x76, 0xB0, 0x92, 0xF0, 0x6A, 0xCB
+};
+static bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx);
+static void ncaUpdateAesCtrIv(u8 *ctr, u64 offset);
+static void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset);
-
-
-
-
-size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, bool encrypt)
+size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, size_t sector_size, bool encrypt)
{
- if (!ctx || !dst || !src || !size || (size % NCA_AES_XTS_SECTOR_SIZE) != 0)
+ if (!ctx || !dst || !src || !size || !sector_size || (size % sector_size) != 0)
{
LOGFILE("Invalid parameters!");
return 0;
@@ -26,19 +30,19 @@ size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src,
u8 *dst_u8 = (u8*)dst;
const u8 *src_u8 = (const u8*)src;
- for(i = 0; i < size; i += NCA_AES_XTS_SECTOR_SIZE, cur_sector++)
+ for(i = 0; i < size; i += sector_size, cur_sector++)
{
/* We have to force a sector reset on each new sector to actually enable Nintendo AES-XTS cipher tweak */
aes128XtsContextResetSector(ctx, cur_sector, true);
if (encrypt)
{
- crypt_res = aes128XtsEncrypt(ctx, dst_u8 + i, src_u8 + i, NCA_AES_XTS_SECTOR_SIZE);
+ crypt_res = aes128XtsEncrypt(ctx, dst_u8 + i, src_u8 + i, sector_size);
} else {
- crypt_res = aes128XtsDecrypt(ctx, dst_u8 + i, src_u8 + i, NCA_AES_XTS_SECTOR_SIZE);
+ crypt_res = aes128XtsDecrypt(ctx, dst_u8 + i, src_u8 + i, sector_size);
}
- if (crypt_res != NCA_AES_XTS_SECTOR_SIZE) break;
+ if (crypt_res != sector_size) break;
}
return i;
@@ -47,6 +51,19 @@ size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src,
+
+
+
+
+
+
+
+
+
+
+
+
+
bool ncaDecryptKeyArea(NcaContext *ctx)
{
if (!ctx)
@@ -56,8 +73,15 @@ bool ncaDecryptKeyArea(NcaContext *ctx)
}
Result rc = 0;
- u8 tmp_kek[0x10] = {0};
const u8 *kek_src = NULL;
+ u8 key_count, tmp_kek[0x10] = {0};
+
+ /* Check if we're dealing with a NCA0 with a plain text key area */
+ if (ctx->format_version == NcaVersion_Nca0 && !ncaCheckIfVersion0KeyAreaIsEncrypted(ctx))
+ {
+ memcpy(ctx->decrypted_keys, ctx->header.encrypted_keys, 0x40);
+ return true;
+ }
kek_src = keysGetKeyAreaEncryptionKeySource(ctx->header.kaek_index);
if (!kek_src)
@@ -73,7 +97,9 @@ bool ncaDecryptKeyArea(NcaContext *ctx)
return false;
}
- for(u8 i = 0; i < 4; i++)
+ key_count = (ctx->format_version == NcaVersion_Nca0 ? 2 : 4);
+
+ for(u8 i = 0; i < key_count; i++)
{
rc = splCryptoGenerateAesKey(tmp_kek, ctx->header.encrypted_keys[i], ctx->decrypted_keys[i]);
if (R_FAILED(rc))
@@ -86,7 +112,7 @@ bool ncaDecryptKeyArea(NcaContext *ctx)
return true;
}
-bool ncaEncryptNcaKeyArea(NcaContext *ctx)
+bool ncaEncryptKeyArea(NcaContext *ctx)
{
if (!ctx)
{
@@ -94,8 +120,16 @@ bool ncaEncryptNcaKeyArea(NcaContext *ctx)
return false;
}
- Aes128Context key_area_ctx = {0};
+ u8 key_count;
const u8 *kaek = NULL;
+ Aes128Context key_area_ctx = {0};
+
+ /* Check if we're dealing with a NCA0 with a plain text key area */
+ if (ctx->format_version == NcaVersion_Nca0 && !ncaCheckIfVersion0KeyAreaIsEncrypted(ctx))
+ {
+ memcpy(ctx->header.encrypted_keys, ctx->decrypted_keys, 0x40);
+ return true;
+ }
kaek = keysGetKeyAreaEncryptionKey(ctx->key_generation, ctx->header.kaek_index);
if (!kaek)
@@ -104,20 +138,14 @@ bool ncaEncryptNcaKeyArea(NcaContext *ctx)
return false;
}
+ key_count = (ctx->format_version == NcaVersion_Nca0 ? 2 : 4);
+
aes128ContextCreate(&key_area_ctx, kaek, true);
- for(u8 i = 0; i < 4; i++) aes128EncryptBlock(&key_area_ctx, ctx->header.encrypted_keys[i], ctx->decrypted_keys[i]);
+ for(u8 i = 0; i < key_count; i++) aes128EncryptBlock(&key_area_ctx, ctx->header.encrypted_keys[i], ctx->decrypted_keys[i]);
return true;
}
-
-
-
-
-
-
-
-
bool ncaDecryptHeader(NcaContext *ctx)
{
if (!ctx)
@@ -129,13 +157,13 @@ bool ncaDecryptHeader(NcaContext *ctx)
u32 i, magic = 0;
size_t crypt_res = 0;
const u8 *header_key = NULL;
- Aes128XtsContext hdr_aes_ctx = {0};
+ Aes128XtsContext hdr_aes_ctx = {0}, nca0_fs_header_ctx = {0};
header_key = keysGetNcaHeaderKey();
aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + 0x10, false);
- crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, false);
+ crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false);
if (crypt_res != NCA_HEADER_LENGTH)
{
LOGFILE("Invalid output length for decrypted NCA header! (0x%X != 0x%lX)", NCA_HEADER_LENGTH, crypt_res);
@@ -147,21 +175,55 @@ bool ncaDecryptHeader(NcaContext *ctx)
switch(magic)
{
case NCA_NCA3_MAGIC:
- crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_FULL_HEADER_LENGTH, 0, false);
+ ctx->format_version = NcaVersion_Nca3;
+
+ crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_FULL_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false);
+ if (crypt_res != NCA_FULL_HEADER_LENGTH)
+ {
+ LOGFILE("Error decrypting full NCA3 header!");
+ return false;
+ }
+
break;
case NCA_NCA2_MAGIC:
- for(i = 0; i < 4; i++)
+ ctx->format_version = NcaVersion_Nca2;
+
+ for(i = 0; i < NCA_SECTION_HEADER_CNT; i++)
{
- if (ctx->header.fs_entries[i].enable_entry)
+ if (!ctx->header.fs_entries[i].enable_entry) continue;
+
+ crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false);
+ if (crypt_res != NCA_FS_HEADER_LENGTH)
{
- crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), sizeof(NcaFsHeader), 0, false);
- if (crypt_res != sizeof(NcaFsHeader)) break;
- } else {
- memset(&(ctx->header.fs_headers[i]), 0, sizeof(NcaFsHeader));
+ LOGFILE("Error decrypting NCA2 FS section header #%u!", i);
+ return false;
}
}
+
break;
case NCA_NCA0_MAGIC:
+ ctx->format_version = NcaVersion_Nca0;
+
+ /* We first need to decrypt the key area from the NCA0 header in order to access its FS section headers */
+ if (!ncaDecryptKeyArea(ctx))
+ {
+ LOGFILE("Error decrypting key area from NCA0 header!");
+ return false;
+ }
+
+ aes128XtsContextCreate(&nca0_fs_header_ctx, ctx->decrypted_keys[0], ctx->decrypted_keys[1], false);
+
+ for(i = 0; i < NCA_SECTION_HEADER_CNT; i++)
+ {
+ if (!ctx->header.fs_entries[i].enable_entry) continue;
+
+
+
+
+
+
+
+ }
break;
default:
@@ -169,10 +231,71 @@ bool ncaDecryptHeader(NcaContext *ctx)
return false;
}
+ /* Fill additional context info */
+ ctx->key_generation = ncaGetKeyGenerationValue(ctx);
+ ctx->rights_id_available = ncaCheckRightsIdAvailability(ctx);
+ return true;
+}
+
+bool ncaEncryptHeader(NcaContext *ctx)
+{
+ if (!ctx)
+ {
+ LOGFILE("Invalid NCA context!");
+ return false;
+ }
+ u32 i;
+ size_t crypt_res = 0;
+ const u8 *header_key = NULL;
+ Aes128XtsContext hdr_aes_ctx = {0};
+ header_key = keysGetNcaHeaderKey();
+ aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + 0x10, true);
+
+ switch(ctx->format_version)
+ {
+ case NcaVersion_Nca3:
+ crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_FULL_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true);
+ if (crypt_res != NCA_FULL_HEADER_LENGTH)
+ {
+ LOGFILE("Error encrypting full NCA3 header!");
+ return false;
+ }
+
+ break;
+ case NcaVersion_Nca2:
+ crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true);
+ if (crypt_res != NCA_HEADER_LENGTH)
+ {
+ LOGFILE("Error encrypting partial NCA2 header!");
+ return false;
+ }
+
+ for(i = 0; i < NCA_SECTION_HEADER_CNT; i++)
+ {
+ if (!ctx->header.fs_entries[i].enable_entry) continue;
+
+ crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true);
+ if (crypt_res != NCA_FS_HEADER_LENGTH)
+ {
+ LOGFILE("Error encrypting NCA2 FS section header #%u!", i);
+ return false;
+ }
+ }
+
+ break;
+ case NcaVersion_Nca0:
+ /* There's nothing else to do */
+ break;
+ default:
+ LOGFILE("Invalid NCA format version! (0x%02X)", ctx->format_version);
+ return false;
+ }
+
+ return true;
}
@@ -183,6 +306,24 @@ bool ncaDecryptHeader(NcaContext *ctx)
+
+
+
+
+
+
+static bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx)
+{
+ if (!ctx || ctx->format_version != NcaVersion_Nca0) return false;
+
+ u8 nca0_key_area_hash[SHA256_HASH_SIZE] = {0};
+ sha256CalculateHash(nca0_key_area_hash, ctx->header.encrypted_keys, 0x40);
+
+ if (!memcmp(nca0_key_area_hash, g_nca0KeyAreaHash, SHA256_HASH_SIZE)) return false;
+
+ return true;
+}
+
static void ncaUpdateAesCtrIv(u8 *ctr, u64 offset)
{
if (!ctr) return;
@@ -213,4 +354,4 @@ static void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset)
ctr[0x8 - i - 1] = (u8)(ctr_val & 0xFF);
ctr_val >>= 8;
}
-}
\ No newline at end of file
+}
diff --git a/source/new/nca.h b/source/new/nca.h
index 0986243..51c90e5 100644
--- a/source/new/nca.h
+++ b/source/new/nca.h
@@ -22,19 +22,12 @@
#define NCA_IVFC_BLOCK_SIZE(x) (1 << (x))
-
-
-
typedef enum {
- NcaVersion_Nca0Beta = 0,
- NcaVersion_Nca0 = 1,
- NcaVersion_Nca2 = 2,
- NcaVersion_Nca3 = 3
+ NcaVersion_Nca0 = 0,
+ NcaVersion_Nca2 = 1,
+ NcaVersion_Nca3 = 2
} NcaVersion;
-
-
-
typedef enum {
NcaDistributionType_Download = 0,
NcaDistributionType_GameCard = 1
@@ -51,7 +44,7 @@ typedef enum {
typedef enum {
NcaKeyGenerationOld_100_230 = 0,
- NcaKeyGenerationOld_300 = 1
+ NcaKeyGenerationOld_300 = 2
} NcaKeyGenerationOld;
typedef enum {
@@ -60,7 +53,7 @@ typedef enum {
NcaKeyAreaEncryptionKeyIndex_System = 2
} NcaKeyAreaEncryptionKeyIndex;
-/// 'NcaKeyGeneration_Latest' will always point to the last known key generation value
+/// 'NcaKeyGeneration_Latest' will always point to the last known key generation value.
typedef enum {
NcaKeyGeneration_301_302 = 3,
NcaKeyGeneration_400_410 = 4,
@@ -75,8 +68,8 @@ typedef enum {
} NcaKeyGeneration;
typedef struct {
- u32 start_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks
- u32 end_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks
+ u32 start_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks.
+ u32 end_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks.
u8 enable_entry;
u8 reserved[0x7];
} NcaFsEntry;
@@ -97,8 +90,8 @@ typedef enum {
typedef enum {
NcaHashType_Auto = 0,
NcaHashType_None = 1,
- NcaHashType_HierarchicalSha256 = 2, ///< Used by NcaFsType_PartitionFs
- NcaHashType_HierarchicalIntegrity = 3 ///< Used by NcaFsType_RomFs
+ NcaHashType_HierarchicalSha256 = 2, ///< Used by NcaFsType_PartitionFs.
+ NcaHashType_HierarchicalIntegrity = 3 ///< Used by NcaFsType_RomFs.
} NcaHashType;
typedef enum {
@@ -114,7 +107,7 @@ typedef struct {
u64 size;
} NcaHierarchicalSha256LayerInfo;
-/// Used for NcaFsType_PartitionFs and NCA0 RomFS
+/// Used for NcaFsType_PartitionFs and NCA0 RomFS.
typedef struct {
u8 master_hash[SHA256_HASH_SIZE];
u32 hash_block_size;
@@ -130,9 +123,9 @@ typedef struct {
u8 reserved[0x4];
} NcaHierarchicalIntegrityLayerInfo;
-/// Used for NcaFsType_RomFs
+/// Used for NcaFsType_RomFs.
typedef struct {
- u32 magic; ///< "IVFC"
+ u32 magic; ///< "IVFC".
u32 version;
u32 master_hash_size;
u32 layer_count;
@@ -145,12 +138,12 @@ typedef struct {
typedef struct {
union {
struct {
- ///< Used if hash_type == NcaHashType_HierarchicalSha256 (NcaFsType_PartitionFs)
+ ///< Used if hash_type == NcaHashType_HierarchicalSha256 (NcaFsType_PartitionFs).
NcaHierarchicalSha256 hierarchical_sha256;
u8 reserved_1[0xB0];
};
struct {
- ///< Used if hash_type == NcaHashType_HierarchicalIntegrity (NcaFsType_RomFs)
+ ///< Used if hash_type == NcaHashType_HierarchicalIntegrity (NcaFsType_RomFs).
NcaHierarchicalIntegrity hierarchical_integrity;
u8 reserved_2[0x18];
};
@@ -158,13 +151,13 @@ typedef struct {
} NcaHashInfo;
typedef struct {
- u32 magic; ///< "BKTR"
+ u32 magic; ///< "BKTR".
u32 bucket_count;
u32 entry_count;
u8 reserved[0x4];
} NcaBucketTreeHeader;
-/// Only used for NcaEncryptionType_AesCtrEx (PatchRomFs)
+/// Only used for NcaEncryptionType_AesCtrEx (PatchRomFs).
typedef struct {
u64 indirect_offset;
u64 indirect_size;
@@ -174,16 +167,16 @@ typedef struct {
NcaBucketTreeHeader aes_ctr_ex_header;
} NcaPatchInfo;
-/// Format unknown
+/// Format unknown.
typedef struct {
u8 unknown[0x30];
} NcaSparseInfo;
typedef struct {
u16 version;
- u8 fs_type; ///< NcaFsType
- u8 hash_type; ///< NcaHashType
- u8 encryption_type; ///< NcaEncryptionType
+ u8 fs_type; ///< NcaFsType.
+ u8 hash_type; ///< NcaHashType.
+ u8 encryption_type; ///< NcaEncryptionType.
u8 reserved_1[0x3];
NcaHashInfo hash_info;
NcaPatchInfo patch_info;
@@ -201,11 +194,11 @@ typedef struct {
typedef struct {
u8 main_signature[0x100]; ///< RSA-PSS signature over header with fixed key.
u8 acid_signature[0x100]; ///< RSA-PSS signature over header with key in NPDM.
- u32 magic; ///< "NCA0" / "NCA2" / "NCA3"
- u8 distribution_type; ///< NcaDistributionType
- u8 content_type; ///< NcaContentType
- u8 key_generation_old; ///< NcaKeyGenerationOld
- u8 kaek_index; ///< NcaKeyAreaEncryptionKeyIndex
+ u32 magic; ///< "NCA0" / "NCA2" / "NCA3".
+ u8 distribution_type; ///< NcaDistributionType.
+ u8 content_type; ///< NcaContentType.
+ u8 key_generation_old; ///< NcaKeyGenerationOld.
+ u8 kaek_index; ///< NcaKeyAreaEncryptionKeyIndex.
u64 content_size;
u64 program_id;
u32 content_index;
@@ -218,7 +211,7 @@ typedef struct {
u8 sdk_addon_major;
};
};
- u8 key_generation; ///< NcaKeyGeneration
+ u8 key_generation; ///< NcaKeyGeneration.
u8 main_signature_key_generation;
u8 reserved_1[0xE];
FsRightsId rights_id; ///< Used for titlekey crypto.
@@ -252,20 +245,21 @@ typedef struct {
} NcaFsContext;
typedef struct {
- u8 storage_id; ///< NcmStorageId
+ u8 storage_id; ///< NcmStorageId.
NcmContentStorage *ncm_storage; ///< Pointer to a NcmContentStorage instance. Used to read NCA data.
- u64 gamecard_base_offset; ///< Used to read NCA data from a gamecard using a FsStorage instance when storage_id == NcmStorageId_GameCard.
+ u64 gc_secure_area_base_offset; ///< Used to read NCA data from a gamecard using a FsStorage instance when storage_id == NcmStorageId_GameCard.
NcmContentId id; ///< Also used to read NCA data.
-
char id_str[0x21];
u8 hash[0x20];
char hash_str[0x41];
+ u8 format_version; ///< NcaVersion.
u8 type; ///< NcmContentType. Retrieved from NcmContentInfo.
u64 size; ///< Retrieved from NcmContentInfo.
u8 key_generation; ///< NcaKeyGenerationOld / NcaKeyGeneration. Retrieved from the decrypted header.
u8 id_offset; ///< Retrieved from NcmContentInfo.
bool rights_id_available;
NcaHeader header;
+ bool dirty_header;
NcaEncryptedKey decrypted_keys[4];
NcaFsContext fs_contexts[4];
} NcaContext;
@@ -293,8 +287,9 @@ static inline u8 ncaGetKeyGenerationValue(NcaContext *ctx)
static inline void ncaSetDownloadDistributionType(NcaContext *ctx)
{
- if (!ctx) return;
+ if (!ctx || ctx->header.distribution_type == NcaDistributionType_Download) return;
ctx->header.distribution_type = NcaDistributionType_Download;
+ ctx->dirty_header = true;
}
static inline bool ncaCheckRightsIdAvailability(NcaContext *ctx)
@@ -319,6 +314,7 @@ static inline void ncaWipeRightsId(NcaContext *ctx)
{
if (!ctx) return;
memset(ctx->header.rights_id, 0, sizeof(FsRightsId));
+ ctx->dirty_header = true;
}
@@ -327,6 +323,9 @@ static inline void ncaWipeRightsId(NcaContext *ctx)
bool ncaDecryptKeyArea(NcaContext *nca_ctx);
bool ncaEncryptKeyArea(NcaContext *nca_ctx);
+bool ncaDecryptHeader(NcaContext *ctx);
+bool ncaEncryptHeader(NcaContext *ctx);
+
diff --git a/source/new/service_guard.h b/source/new/service_guard.h
index da077cc..1428a63 100644
--- a/source/new/service_guard.h
+++ b/source/new/service_guard.h
@@ -32,7 +32,7 @@ NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void))
#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \
\
-static ServiceGuard g_##name##Guard; \
+static ServiceGuard g_##name##Guard = {0}; \
NX_INLINE Result _##name##Initialize _paramdecl; \
static void _##name##Cleanup(void); \
\
diff --git a/source/new/tik.c b/source/new/tik.c
index de036cb..d0e66c1 100644
--- a/source/new/tik.c
+++ b/source/new/tik.c
@@ -22,7 +22,7 @@ typedef struct {
u8 modulus[0x100];
u32 public_exponent; ///< Must match ETICKET_DEVKEY_PUBLIC_EXPONENT. Stored using big endian byte order.
u8 padding[0x14];
- u8 device_id[0x8];
+ u64 device_id;
u8 ghash[0x10];
} tikEticketDeviceKeyData;
@@ -272,7 +272,7 @@ bool tikGetTitleKekDecryptedTitleKeyFromTicket(void *dst, Ticket *tik)
return false;
}
- /* Even though tickets do have a proper key_generation field, we'll default to retrieve it from the rights_id field */
+ /* Even though tickets do have a proper key_generation field, we'll just retrieve it from the rights_id field */
/* Old custom tools used to wipe the key_generation field or save it to a different offset */
if (!tikGetTitleKekDecryptedTitleKey(dst, titlekey, tik_common_blk->rights_id.c[0xF]))
{
diff --git a/source/util.h b/source/util.h
index 7d90e59..08d9122 100644
--- a/source/util.h
+++ b/source/util.h
@@ -3,7 +3,6 @@
#ifndef __UTIL_H__
#define __UTIL_H__
-#include
#include
#include "nca.h"
@@ -24,13 +23,20 @@
+#define LOGFILE(fmt, ...) utilsWriteLogMessage(__func__, fmt, ##__VA_ARGS__)
+
+#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member)
+
+#define SLEEP(x) svcSleepThread((x) * (u64)1000000000)
+
+static Mutex g_logfileMutex = 0;
+
void utilsWriteLogMessage(const char *func_name, const char *fmt, ...)
{
- if (!func_name || !strlen(func_name) || !fmt || !strlen(fmt)) return;
+ mutexLock(&g_logfileMutex);
va_list args;
FILE *logfile = NULL;
- char str[FS_MAX_PATH] = {0};
logfile = fopen(APP_BASE_PATH "log.txt", "a+");
if (!logfile) return;
@@ -47,11 +53,25 @@ void utilsWriteLogMessage(const char *func_name, const char *fmt, ...)
fprintf(logfile, "\r\n");
fclose(logfile);
+
+ mutexUnlock(&g_logfileMutex);
}
-#define LOGFILE(fmt, ...) utilsWriteLogMessage(__func__, fmt, ##__VA_ARGS__)
+typedef enum {
+ UtilsCustomFirmwareType_Atmosphere = 0,
+ UtilsCustomFirmwareType_SXOS = 1,
+ UtilsCustomFirmwareType_ReiNX = 2
+} UtilsCustomFirmwareType;
+
+
+typedef struct {
+ u16 major : 6;
+ u16 minor : 6;
+ u16 micro : 4;
+ u16 bugfix;
+} TitleVersion;
+
-#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member)
@@ -128,7 +148,6 @@ void utilsWriteLogMessage(const char *func_name, const char *fmt, ...)
#define GAMECARD_TYPE1_PARTITION_CNT 3 // "update" (0), "normal" (1), "secure" (2)
#define GAMECARD_TYPE2_PARTITION_CNT 4 // "update" (0), "logo" (1), "normal" (2), "secure" (3)
-#define GAMECARD_TYPE(x) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? "Type 0x01" : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? "Type 0x02" : "Unknown"))
#define GAMECARD_TYPE1_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Normal" : ((x) == 2 ? "Secure" : "Unknown")))
#define GAMECARD_TYPE2_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Logo" : ((x) == 2 ? "Normal" : ((x) == 3 ? "Secure" : "Unknown"))))
#define GAMECARD_PARTITION_NAME(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? GAMECARD_TYPE1_PART_NAMES(y) : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? GAMECARD_TYPE2_PART_NAMES(y) : "Unknown"))