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.