diff --git a/doc/sdk_header.md b/doc/sdk_header.md new file mode 100644 index 0000000..722bf64 --- /dev/null +++ b/doc/sdk_header.md @@ -0,0 +1,39 @@ +# GCM SDK Headers + +This document is Copyright (C) 2018 by David Korth.
+Licensed under the GNU Free Documentation License v1.3. + +GCM images produced by the official GameCube and Wii SDKs have a 32 KB header. +This header isn't present in images ripped using e.g. CleanRip, and it isn't +recognized by the Dolphin emulator. It's also not present on RVT-H Reader HDDs. +However, it **is** needed when using the official SDK tools, e.g. rvtwriter and +the NDEV optical drive emulator. + +## Example Header + +The following header is from an swupdate image from one of the Wii SDKs: + +``` +00000000 ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +* +00000820 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e0 06 |................| +00000830 00 00 6b 4f 00 00 00 00 00 00 00 00 00 00 00 00 |..kO............| +00000840 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 |................| +00000850 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +* +00008000 52 41 42 41 30 31 00 00 00 00 00 00 00 00 00 00 |RABA01..........| +``` + +The header is mostly zeroes, except for these important DWORD values: +* 0x0000: `FF FF 00 00` +* 0x082C: `00 00 E0 06` +* 0x0830: `00 00 6B 4F` + +The value at 0x0830 appears to be a checksum of some sort. If this header is +prepended as-is to any generic Wii image and loaded onto an NDEV using ODEM, +the NDEV will show a "Wrong disk" error. However, zeroing out this DWORD gets +it working for all discs. + +Images built using `makeGCM` from the GameCube SDK always have a checksum of +`0xAB0B`; however, the NDEV system locks up after displaying the disc info. diff --git a/src/librvth/nhcd_structs.h b/src/librvth/nhcd_structs.h index b6b33db..ae161a5 100644 --- a/src/librvth/nhcd_structs.h +++ b/src/librvth/nhcd_structs.h @@ -140,6 +140,10 @@ ASSERT_STRUCT(NHCD_BankTable, 512*9); // Block size. #define NHCD_BLOCK_SIZE 512 +// SDK header size. +#define SDK_HEADER_SIZE_BYTES 32768 +#define SDK_HEADER_SIZE_LBA (SDK_HEADER_SIZE_BYTES / NHCD_BLOCK_SIZE) + #pragma pack() #ifdef __cplusplus diff --git a/src/librvth/ref_file.c b/src/librvth/ref_file.c index 96ace1a..e4c8c7f 100644 --- a/src/librvth/ref_file.c +++ b/src/librvth/ref_file.c @@ -81,9 +81,9 @@ static RefFile *ref_open_int(const TCHAR *filename, const TCHAR *mode) // Initialize the reference count. f->ref_count = 1; - // File writability depends on the mode. + // File writability depends on the mode. // NOTE: Only checking the first character. - f->is_writable = (mode[0] == _T('w')); + f->is_writable = (mode[0] == _T('w')); return f; fail: diff --git a/src/librvth/rvth.c b/src/librvth/rvth.c index fa8c4dd..8f20766 100644 --- a/src/librvth/rvth.c +++ b/src/librvth/rvth.c @@ -434,7 +434,7 @@ const char *rvth_error(int err) // for system-level issues. For anything weird encountered // within an RVT-H HDD or GCN/Wii disc image, an // RvtH_Errors code should be retured instead. - static const char *const errtbl[RVTH_ERROR_MAX] = { + static const char *const errtbl[] = { // tr: RVTH_ERROR_SUCCESS "Success", // tr: RVTH_ERROR_UNRECOGNIZED_FILE @@ -492,8 +492,13 @@ const char *rvth_error(int err) "The second bank for the Dual-Layer image is not empty or deleted", // tr: RVTH_ERROR_IMPORT_DL_NOT_CONTIGUOUS "The two banks are not contiguous", + + // NDEV option. + + // tr: RVTH_ERROR_NDEV_GCN_NOT_SUPPORTED + "NDEV headers for GCN are currently unsupported.", }; - // TODO: static_assert() + static_assert(ARRAY_SIZE(errtbl) == RVTH_ERROR_MAX, "Missing error descriptions!"); if (err < 0) { return strerror(-err); diff --git a/src/librvth/rvth.h b/src/librvth/rvth.h index fdf93e0..ab042a4 100644 --- a/src/librvth/rvth.h +++ b/src/librvth/rvth.h @@ -69,6 +69,9 @@ typedef enum { RVTH_ERROR_BANK2DL_NOT_EMPTY_OR_DELETED = 25, // The second bank for the Dual-Layer image is not empty or deleted. RVTH_ERROR_IMPORT_DL_NOT_CONTIGUOUS = 26, // The two banks are not contiguous. + // NDEV option. + RVTH_ERROR_NDEV_GCN_NOT_SUPPORTED = 27, // NDEV headers for GCN are currently unsupported. + RVTH_ERROR_MAX } RvtH_Errors; @@ -274,6 +277,15 @@ RvtH *rvth_create_gcm(const TCHAR *filename, uint32_t lba_len, int *pErr); */ int rvth_copy_to_gcm(RvtH *rvth_dest, const RvtH *rvth_src, unsigned int bank_src, RvtH_Progress_Callback callback); +/** + * RVT-H extraction flags. + */ +typedef enum { + // Prepend a 32k SDK header. + // Required for rvtwriter, NDEV ODEM, etc. + RVTH_EXTRACT_PREPEND_SDK_HEADER = (1 << 0), +} RvtH_Extract_Flags; + /** * Extract a disc image from the RVT-H disk image. * Compatibility wrapper; this function calls rvth_create_gcm() and rvth_copy(). @@ -281,10 +293,12 @@ int rvth_copy_to_gcm(RvtH *rvth_dest, const RvtH *rvth_src, unsigned int bank_sr * @param bank [in] Bank number. (0-7) * @param filename [in] Destination filename. * @param recrypt_key [in] Key for recryption. (-1 for default; otherwise, see RvtH_CryptoType_e) + * @param flags [in] Flags. (See RvtH_Extract_Flags.) * @param callback [in,opt] Progress callback. * @return Error code. (If negative, POSIX error; otherwise, see RvtH_Errors.) */ -int rvth_extract(const RvtH *rvth, unsigned int bank, const TCHAR *filename, int recrypt_key, RvtH_Progress_Callback callback); +int rvth_extract(const RvtH *rvth, unsigned int bank, const TCHAR *filename, + int recrypt_key, uint32_t flags, RvtH_Progress_Callback callback); /** * Copy a bank from an RVT-H HDD or standalone disc image to an RVT-H system. diff --git a/src/librvth/rvth_extract.c b/src/librvth/rvth_extract.c index a973d47..6e2b78a 100644 --- a/src/librvth/rvth_extract.c +++ b/src/librvth/rvth_extract.c @@ -276,12 +276,14 @@ end: * @param bank [in] Bank number. (0-7) * @param filename [in] Destination filename. * @param recrypt_key [in] Key for recryption. (-1 for default; otherwise, see RvtH_CryptoType_e) + * @param flags [in] Flags. (See RvtH_Extract_Flags.) * @param callback [in,opt] Progress callback. * @return Error code. (If negative, POSIX error; otherwise, see RvtH_Errors.) */ -int rvth_extract(const RvtH *rvth, unsigned int bank, const TCHAR *filename, int recrypt_key, RvtH_Progress_Callback callback) +int rvth_extract(const RvtH *rvth, unsigned int bank, const TCHAR *filename, + int recrypt_key, uint32_t flags, RvtH_Progress_Callback callback) { - RvtH *rvth_dest; + RvtH *rvth_dest = NULL; RvtH_BankEntry *entry; uint32_t gcm_lba_len; bool unenc_to_enc; @@ -313,7 +315,8 @@ int rvth_extract(const RvtH *rvth, unsigned int bank, const TCHAR *filename, int if (!game_pte) { // No game partition... errno = EIO; - return RVTH_ERROR_NO_GAME_PARTITION; + ret = RVTH_ERROR_NO_GAME_PARTITION; + goto end; } // TODO: Read the partition header to determine the data offset. @@ -330,6 +333,15 @@ int rvth_extract(const RvtH *rvth, unsigned int bank, const TCHAR *filename, int gcm_lba_len = entry->lba_len; } + if (flags & RVTH_EXTRACT_PREPEND_SDK_HEADER) { + if (entry->type == RVTH_BankType_GCN) { + // FIXME: Not supported. + return RVTH_ERROR_NDEV_GCN_NOT_SUPPORTED; + } + // Prepend 32k to the GCM. + gcm_lba_len += BYTES_TO_LBA(32768); + } + ret = 0; rvth_dest = rvth_create_gcm(filename, gcm_lba_len, &ret); if (!rvth_dest) { @@ -337,7 +349,65 @@ int rvth_extract(const RvtH *rvth, unsigned int bank, const TCHAR *filename, int if (ret == 0) { ret = -EIO; } - return ret; + goto end; + } + + if (flags & RVTH_EXTRACT_PREPEND_SDK_HEADER) { + // Prepend 32k to the GCM. + size_t size; + Reader *const reader = rvth_dest->entries[0].reader; + uint8_t *sdk_header = calloc(1, SDK_HEADER_SIZE_BYTES); + if (!sdk_header) { + ret = -errno; + if (ret == 0) { + ret = -ENOMEM; + } + goto end; + } + + // TODO: Get headers for GC1L and NN2L. + // TODO: Optimize by using 32-bit writes? + switch (entry->type) { + case RVTH_BankType_GCN: + // FIXME; GameCube GCM seems to use the same values, + // but it doesn't load with NDEV. + // Checksum field is always 0xAB0B. + // TODO: Delete the file? + rvth_close(rvth_dest); + return RVTH_ERROR_NDEV_GCN_NOT_SUPPORTED; + case RVTH_BankType_Wii_SL: + case RVTH_BankType_Wii_DL: + // 0x0000: FF FF 00 00 + sdk_header[0x0000] = 0xFF; + sdk_header[0x0001] = 0xFF; + // 0x082C: 00 00 E0 06 + sdk_header[0x082E] = 0xE0; + sdk_header[0x082F] = 0x06; + // TODO: Checksum at 0x0830? (If 00 00, seems to work for all discs.) + // 0x0844: 01 00 00 00 + sdk_header[0x0844] = 0x01; + break; + default: + // Should not get here... + assert(!"Incorrect bank type."); + free(sdk_header); + goto end; + } + + size = reader_write(reader, sdk_header, 0, SDK_HEADER_SIZE_LBA); + if (size != SDK_HEADER_SIZE_LBA) { + // Write error. + ret = -errno; + if (ret == 0) { + ret = -EIO; + } + goto end; + } + free(sdk_header); + + // Remove the SDK header from the reader's offsets. + reader->lba_start += SDK_HEADER_SIZE_LBA; + reader->lba_len -= SDK_HEADER_SIZE_LBA; } // Copy the bank from the source image to the destination GCM. @@ -354,7 +424,11 @@ int rvth_extract(const RvtH *rvth, unsigned int bank, const TCHAR *filename, int } } - rvth_close(rvth_dest); +end: + // TODO: Delete the file on error? + if (rvth_dest) { + rvth_close(rvth_dest); + } return ret; } diff --git a/src/rvthtool/extract.c b/src/rvthtool/extract.c index 2f11e93..4835855 100644 --- a/src/rvthtool/extract.c +++ b/src/rvthtool/extract.c @@ -77,9 +77,10 @@ static bool progress_callback(const RvtH_Progress_State *state) * @param s_bank [in] Bank number (as a string). (If NULL, assumes bank 1.) * @param gcm_filename [in] Filename for the extracted GCM image. * @param recrypt_key [in] Key for recryption. (-1 for default) + * @param flags [in] Flags. (See RvtH_Extract_Flags.) * @return 0 on success; non-zero on error. */ -int extract(const TCHAR *rvth_filename, const TCHAR *s_bank, const TCHAR *gcm_filename, int recrypt_key) +int extract(const TCHAR *rvth_filename, const TCHAR *s_bank, const TCHAR *gcm_filename, int recrypt_key, uint32_t flags) { RvtH *rvth; TCHAR *endptr; @@ -126,7 +127,7 @@ int extract(const TCHAR *rvth_filename, const TCHAR *s_bank, const TCHAR *gcm_fi printf("Extracting Bank %u into '", bank+1); _fputts(gcm_filename, stdout); fputs("'...\n", stdout); - ret = rvth_extract(rvth, bank, gcm_filename, recrypt_key, progress_callback); + ret = rvth_extract(rvth, bank, gcm_filename, recrypt_key, flags, progress_callback); if (ret == 0) { printf("Bank %u extracted to '", bank+1); _fputts(gcm_filename, stdout); diff --git a/src/rvthtool/extract.h b/src/rvthtool/extract.h index f900dee..aa3fd4f 100644 --- a/src/rvthtool/extract.h +++ b/src/rvthtool/extract.h @@ -22,6 +22,7 @@ #define __RVTHTOOL_RVTHTOOL_EXTRACT_H__ #include "librvth/tcharx.h" +#include #ifdef __cplusplus extern "C" { @@ -33,9 +34,10 @@ extern "C" { * @param s_bank Bank number (as a string). (If NULL, assumes bank 1.) * @param gcm_filename Filename for the extracted GCM image. * @param recrypt_key [in] Key for recryption. (-1 for default) + * @param flags [in] Flags. (See RvtH_Extract_Flags.) * @return 0 on success; non-zero on error. */ -int extract(const TCHAR *rvth_filename, const TCHAR *s_bank, const TCHAR *gcm_filename, int recrypt_key); +int extract(const TCHAR *rvth_filename, const TCHAR *s_bank, const TCHAR *gcm_filename, int recrypt_key, uint32_t flags); /** * 'import' command. diff --git a/src/rvthtool/main.c b/src/rvthtool/main.c index 0f6d113..47781b5 100644 --- a/src/rvthtool/main.c +++ b/src/rvthtool/main.c @@ -125,6 +125,8 @@ static void print_help(const TCHAR *argv0) " default, retail, korean, debug\n" " Recrypting to retail will use fakesigning.\n" " Importing to RVT-H will always use debug keys.\n" + " -N, --ndev Prepend extracted images with a 32 KB header\n" + " required by official SDK tools.\n" " -h, --help Display this help and exit.\n" "\n" , stdout); @@ -133,6 +135,7 @@ static void print_help(const TCHAR *argv0) int RVTH_CDECL _tmain(int argc, TCHAR *argv[]) { int ret; + uint32_t flags = 0; // Key to use for recryption. // -1 == default; no recryption, except when importing retail to RVT-H. @@ -159,12 +162,13 @@ int RVTH_CDECL _tmain(int argc, TCHAR *argv[]) while (true) { static const struct option long_options[] = { {_T("recrypt"), required_argument, 0, _T('k')}, + {_T("ndev"), no_argument, 0, _T('N')}, {_T("help"), no_argument, 0, _T('h')}, {NULL, 0, 0, 0} }; - int c = getopt_long(argc, argv, _T("k:h"), long_options, NULL); + int c = getopt_long(argc, argv, _T("k:Nh"), long_options, NULL); if (c == -1) break; @@ -190,6 +194,12 @@ int RVTH_CDECL _tmain(int argc, TCHAR *argv[]) } break; + case 'N': + // Prepend the SDK header. + // TODO: Show error if not using 'extract'? + flags |= RVTH_EXTRACT_PREPEND_SDK_HEADER; + break; + case 'h': print_help(argv[0]); return EXIT_SUCCESS; @@ -230,10 +240,10 @@ int RVTH_CDECL _tmain(int argc, TCHAR *argv[]) // Pass NULL as the bank number, which will be // interpreted as bank 1 for single-disc images // and an error for HDD images. - ret = extract(argv[optind+1], NULL, argv[optind+2], recrypt_key); + ret = extract(argv[optind+1], NULL, argv[optind+2], recrypt_key, flags); } else { // Three or more parameters specified. - ret = extract(argv[optind+1], argv[optind+2], argv[optind+3], recrypt_key); + ret = extract(argv[optind+1], argv[optind+2], argv[optind+3], recrypt_key, flags); } } else if (!_tcscmp(argv[optind], _T("import"))) { // Import a bank.