diff --git a/README.md b/README.md index 8c94bdd..0e6d339 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,12 @@ Usage: -------------- ``` -wad2bin +%s [parent title ID] Paths must not exceed 1023 characters. Relative paths are supported. The required directory tree for the *.bin file(s) will be created at the output directory. You can set your SD card root directory as the output directory. +Parent title ID is only required if the input WAD is a DLC. A 16 character long hex string is expected. ``` Differences between `content.bin` files and `.bin` files: @@ -51,6 +52,10 @@ wad2bin is licensed under GPLv3 or (at your option) any later version. Changelog: -------------- +**v0.3:** + +Force the user to provide the full parent title ID for DLC WADs. + **v0.2:** Added proper support for DLC WADs, even if they're incomplete (e.g. full TMD with missing content files). diff --git a/source/bin.c b/source/bin.c index 51808d4..179cd8f 100644 --- a/source/bin.c +++ b/source/bin.c @@ -423,7 +423,7 @@ bool binGenerateContentBinFromUnpackedInstallableWadPackage(os_char_t *unpacked_ return success; } -bool binGenerateIndexedPackagesFromUnpackedInstallableWadPackage(os_char_t *unpacked_wad_path, os_char_t *out_path, u8 *tmd, size_t tmd_size) +bool binGenerateIndexedPackagesFromUnpackedInstallableWadPackage(os_char_t *unpacked_wad_path, os_char_t *out_path, u8 *tmd, size_t tmd_size, u64 parent_tid) { size_t unpacked_wad_path_len = 0; size_t out_path_len = 0, new_out_path_len = 0; @@ -447,9 +447,6 @@ bool binGenerateIndexedPackagesFromUnpackedInstallableWadPackage(os_char_t *unpa u64 title_id = 0; char tid_lower_ascii[5] = {0}; - u64 parent_tid = 0; - u32 parent_tid_lower = 0; - WadBackupPackageHeader bk_header = {0}; size_t res = 0; @@ -468,10 +465,6 @@ bool binGenerateIndexedPackagesFromUnpackedInstallableWadPackage(os_char_t *unpa title_id = bswap_64(tmd_common_block->title_id); utilsGenerateAsciiStringFromTitleIdLower(title_id, tid_lower_ascii); - /* Generate parent Title ID. */ - parent_tid_lower = ((TITLE_LOWER(title_id) & 0xFFFFFF) | ((u32)(tid_lower_ascii[0] - 0x20) << 24)); - parent_tid = TITLE_ID((tid_lower_ascii[0] == 'r' || tid_lower_ascii[0] == 's') ? TITLE_TYPE_DISC_GAME : TITLE_TYPE_DOWNLOADABLE_CHANNEL, parent_tid_lower); - /* Create directory tree. */ os_snprintf(out_path + out_path_len, MAX_PATH - out_path_len, PRIVATE_PATH("data"), tid_lower_ascii); utilsCreateDirectoryTree(out_path); @@ -535,7 +528,7 @@ bool binGenerateIndexedPackagesFromUnpackedInstallableWadPackage(os_char_t *unpa printf(" TMD size: 0x%" PRIx32 ".\n", bk_header.content_tmd_size); printf(" Content data size: 0x%" PRIx32 ".\n", bk_header.content_data_size); printf(" Backup area size: 0x%" PRIx32 ".\n", bk_header.backup_area_size); - printf(" Title ID: 0x%" PRIx64 ".\n\n", bk_header.title_id); + printf(" Title ID: %016" PRIx64 ".\n\n", bk_header.title_id); /* Byteswap backup WAD header fields. */ wadByteswapBackupPackageHeaderFields(&bk_header); diff --git a/source/bin.h b/source/bin.h index 6e2e603..8aecd9a 100644 --- a/source/bin.h +++ b/source/bin.h @@ -86,7 +86,7 @@ typedef struct { /// Generates a content.bin file using an unpacked WAD data directory and TMD data loaded into memory. bool binGenerateContentBinFromUnpackedInstallableWadPackage(os_char_t *unpacked_wad_path, os_char_t *out_path, u8 *tmd, size_t tmd_size); -/// Generates .bin file(s) using an unpacked WAD data directory and TMD data loaded into memory. -bool binGenerateIndexedPackagesFromUnpackedInstallableWadPackage(os_char_t *unpacked_wad_path, os_char_t *out_path, u8 *tmd, size_t tmd_size); +/// Generates .bin file(s) using an unpacked WAD data directory, TMD data loaded into memory and a parent title ID. +bool binGenerateIndexedPackagesFromUnpackedInstallableWadPackage(os_char_t *unpacked_wad_path, os_char_t *out_path, u8 *tmd, size_t tmd_size, u64 parent_tid); #endif /* __BIN_H__ */ diff --git a/source/keys.c b/source/keys.c index 3109a13..1be7058 100644 --- a/source/keys.c +++ b/source/keys.c @@ -138,7 +138,6 @@ static bool g_keyDataLoaded = false, g_deviceCertRetrieved = false; static int keysGetKeyAndValueFromFile(FILE *f, char **key, char **value); static char keysConvertHexCharToBinary(char c); -static bool keysParseHexKey(u8 *out, const char *key, const char *value, u32 size); static bool keysReadKeysFromFile(const os_char_t *keys_file_path); static bool keysReadDeviceCertificateFromFile(const os_char_t *device_cert_path); @@ -227,6 +226,41 @@ CertSigEcc480PubKeyEcc480 *keysGetDeviceCertificate(void) return (g_deviceCertRetrieved ? &g_deviceCert : NULL); } +bool keysParseHexKey(u8 *out, const char *key, const char *value, u32 size, bool verbose) +{ + u32 hex_str_len = (2 * size); + size_t value_len = 0; + + if (!out || (verbose && (!key || !strlen(key))) || !value || !(value_len = strlen(value)) || !size) + { + ERROR_MSG("Invalid parameters!"); + return false; + } + + if (value_len != hex_str_len) + { + if (verbose) ERROR_MSG("Key \"%s\" must be %u hex digits long!", key, hex_str_len); + return false; + } + + memset(out, 0, size); + + for(u32 i = 0; i < hex_str_len; i++) + { + char val = keysConvertHexCharToBinary(value[i]); + if (val == 'z') + { + if (verbose) ERROR_MSG("Invalid hex character in key \"%s\" at position %u!", key, i); + return false; + } + + if ((i & 1) == 0) val <<= 4; + out[i >> 1] |= val; + } + + return true; +} + /** * 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_] *[,=] *.+$/. @@ -373,40 +407,6 @@ static char keysConvertHexCharToBinary(char c) return 'z'; } -static bool keysParseHexKey(u8 *out, const char *key, const char *value, u32 size) -{ - if (!out || !key || !strlen(key) || !value || !strlen(value) || !size) - { - ERROR_MSG("Invalid parameters!"); - return false; - } - - u32 hex_str_len = (2 * size); - - if (strlen(value) != hex_str_len) - { - ERROR_MSG("Key \"%s\" must be %u hex digits long!", key, hex_str_len); - return false; - } - - memset(out, 0, size); - - for(u32 i = 0; i < hex_str_len; i++) - { - char val = keysConvertHexCharToBinary(value[i]); - if (val == 'z') - { - ERROR_MSG("Invalid hex character in key \"%s\" at position %u!", key, i); - return false; - } - - if ((i & 1) == 0) val <<= 4; - out[i >> 1] |= val; - } - - return true; -} - static bool keysReadKeysFromFile(const os_char_t *keys_file_path) { if (!keys_file_path || !os_strlen(keys_file_path)) @@ -442,7 +442,7 @@ static bool keysReadKeysFromFile(const os_char_t *keys_file_path) if (g_keyData[i].retrieved || strlen(key) != strlen(g_keyData[i].name) || strcmp(key, g_keyData[i].name) != 0) continue; /* Parse current key. */ - if ((parse_fail = !keysParseHexKey(g_keyData[i].key, key, value, g_keyData[i].key_size))) + if ((parse_fail = !keysParseHexKey(g_keyData[i].key, key, value, g_keyData[i].key_size, true))) { /* Reset flag if we're not dealing with a mandatory key. */ if (!g_keyData[i].mandatory) parse_fail = false; diff --git a/source/keys.h b/source/keys.h index aeead19..11851e3 100644 --- a/source/keys.h +++ b/source/keys.h @@ -42,4 +42,7 @@ u8 *keysGetPrngKey(void); u8 *keysGetEccPrivateKey(void); CertSigEcc480PubKeyEcc480 *keysGetDeviceCertificate(void); +/// Parses binary data from a hex string. +bool keysParseHexKey(u8 *out, const char *key, const char *value, u32 size, bool verbose); + #endif /* __KEYS_H__ */ diff --git a/source/main.c b/source/main.c index b1caef6..bc4c8e6 100644 --- a/source/main.c +++ b/source/main.c @@ -31,6 +31,7 @@ int main(int argc, char **argv) /* Reserve memory for an extra temporary path. */ os_char_t *paths[ARG_COUNT + 1] = {0}; + u64 parent_tid = 0; u8 *cert_chain = NULL; size_t cert_chain_size = 0; @@ -48,12 +49,14 @@ int main(int argc, char **argv) printf("\nwad2bin v%s (c) DarkMatterCore.\n", VERSION); printf("Built: %s %s.\n\n", __TIME__, __DATE__); - if (argc != (ARG_COUNT + 1) || strlen(argv[1]) >= MAX_PATH || strlen(argv[2]) >= MAX_PATH || strlen(argv[3]) >= MAX_PATH || (strlen(argv[4]) + SD_CONTENT_PATH_MAX_LENGTH) >= MAX_PATH) + if (argc < (ARG_COUNT + 1) || argc > (ARG_COUNT + 2) || strlen(argv[1]) >= MAX_PATH || strlen(argv[2]) >= MAX_PATH || strlen(argv[3]) >= MAX_PATH || \ + (strlen(argv[4]) + SD_CONTENT_PATH_MAX_LENGTH) >= MAX_PATH || (argc == (ARG_COUNT + 2) && strlen(argv[5]) != 16)) { - printf("Usage: %s \n\n", argv[0]); + printf("Usage: %s [parent title ID]\n\n", argv[0]); printf("Paths must not exceed %u characters. Relative paths are supported.\n", MAX_PATH - 1); printf("The required directory tree for the *.bin file(s) will be created at the output directory.\n"); printf("You can set your SD card root directory as the output directory.\n"); + printf("Parent title ID is only required if the input WAD is a DLC. A 16 character long hex string is expected.\n"); ret = -1; goto out; } @@ -97,10 +100,34 @@ int main(int argc, char **argv) } } + /* Check if the user provided a parent title ID. */ + if (argc == (ARG_COUNT + 2)) + { + /* Parse parent title ID. */ + if (!keysParseHexKey((u8*)&parent_tid, NULL, argv[5], 8, false)) + { + printf("Failed to parse parent title ID!\n"); + ret = -4; + goto out; + } + + /* Byteswap parent title ID. */ + parent_tid = bswap_64(parent_tid); + + /* Check if the TID upper u32 is valid. */ + u32 parent_tid_upper = TITLE_UPPER(parent_tid); + if (parent_tid_upper != TITLE_TYPE_DISC_GAME && parent_tid_upper != TITLE_TYPE_DOWNLOADABLE_CHANNEL && parent_tid_upper != TITLE_TYPE_DISC_BASED_CHANNEL) + { + printf("Invalid parent title ID category!\nOnly disc-based game IDs, downloadable channel IDs and disc-based channel IDs are supported.\n"); + ret = -5; + goto out; + } + } + /* Load keydata and device certificate. */ if (!keysLoadKeyDataAndDeviceCert(paths[0], paths[1])) { - ret = -4; + ret = -6; goto out; } @@ -109,7 +136,7 @@ int main(int argc, char **argv) /* Unpack input WAD package. */ if (!wadUnpackInstallablePackage(paths[2], paths[4], &cert_chain, &cert_chain_size, &ticket, &ticket_size, &tmd, &tmd_size, dec_titlekey, &tid_upper)) { - ret = -5; + ret = -7; goto out; } @@ -121,23 +148,31 @@ int main(int argc, char **argv) if (!utilsAlignBuffer((void**)&tmd, &aligned_tmd_size, WAD_BLOCK_SIZE)) { printf("Failed to align TMD buffer to WAD block size!\n"); - ret = -6; + ret = -8; goto out; } if (tid_upper == TITLE_TYPE_DLC) { + /* Check if a parent title ID was provided. */ + if (argc != (ARG_COUNT + 2)) + { + printf("Error: parent title ID not provided! This is required for DLC titles.\n"); + ret = -9; + goto out; + } + /* Generate .bin file(s). */ - if (!binGenerateIndexedPackagesFromUnpackedInstallableWadPackage(paths[4], paths[3], tmd, tmd_size)) + if (!binGenerateIndexedPackagesFromUnpackedInstallableWadPackage(paths[4], paths[3], tmd, tmd_size, parent_tid)) { - ret = -7; + ret = -10; goto out; } } else { /* Generate content.bin file. */ if (!binGenerateContentBinFromUnpackedInstallableWadPackage(paths[4], paths[3], tmd, tmd_size)) { - ret = -8; + ret = -11; goto out; } } @@ -158,7 +193,7 @@ int main(int argc, char **argv) if (cert_chain) free(cert_chain); /* Remove unpacked WAD directory. */ - utilsRemoveDirectoryRecursively(paths[4]); + if (paths[4]) utilsRemoveDirectoryRecursively(paths[4]); for(u32 i = 0; i <= ARG_COUNT; i++) { diff --git a/source/utils.h b/source/utils.h index d0841ac..9c62f38 100644 --- a/source/utils.h +++ b/source/utils.h @@ -27,7 +27,7 @@ #include "types.h" #include "os.h" -#define VERSION "0.2" +#define VERSION "0.3" /* Supress clang warnings about variadic macro arguments. */ #ifdef __clang__