Added an option to prepend extracted images with a 32 KB SDK header.

Option is -N or --ndev.

NOTE: Only seems to work with Wii images at the moment. NDEV displays
the disc information from GameCube images, but then hangs.

Tested with SL and DL discs on the apploader.

doc/sdk_header.md: Initial SDK header documentation.
This commit is contained in:
David Korth 2018-04-25 22:55:56 -04:00
parent b7728386c6
commit 068596d4b6
9 changed files with 165 additions and 16 deletions

39
doc/sdk_header.md Normal file
View File

@ -0,0 +1,39 @@
# GCM SDK Headers
This document is Copyright (C) 2018 by David Korth.<br>
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.

View File

@ -140,6 +140,10 @@ ASSERT_STRUCT(NHCD_BankTable, 512*9);
// Block size. // Block size.
#define NHCD_BLOCK_SIZE 512 #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() #pragma pack()
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -81,9 +81,9 @@ static RefFile *ref_open_int(const TCHAR *filename, const TCHAR *mode)
// Initialize the reference count. // Initialize the reference count.
f->ref_count = 1; f->ref_count = 1;
// File writability depends on the mode. // File writability depends on the mode.
// NOTE: Only checking the first character. // NOTE: Only checking the first character.
f->is_writable = (mode[0] == _T('w')); f->is_writable = (mode[0] == _T('w'));
return f; return f;
fail: fail:

View File

@ -434,7 +434,7 @@ const char *rvth_error(int err)
// for system-level issues. For anything weird encountered // for system-level issues. For anything weird encountered
// within an RVT-H HDD or GCN/Wii disc image, an // within an RVT-H HDD or GCN/Wii disc image, an
// RvtH_Errors code should be retured instead. // RvtH_Errors code should be retured instead.
static const char *const errtbl[RVTH_ERROR_MAX] = { static const char *const errtbl[] = {
// tr: RVTH_ERROR_SUCCESS // tr: RVTH_ERROR_SUCCESS
"Success", "Success",
// tr: RVTH_ERROR_UNRECOGNIZED_FILE // 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", "The second bank for the Dual-Layer image is not empty or deleted",
// tr: RVTH_ERROR_IMPORT_DL_NOT_CONTIGUOUS // tr: RVTH_ERROR_IMPORT_DL_NOT_CONTIGUOUS
"The two banks are 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) { if (err < 0) {
return strerror(-err); return strerror(-err);

View File

@ -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_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. 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_ERROR_MAX
} RvtH_Errors; } 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); 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. * Extract a disc image from the RVT-H disk image.
* Compatibility wrapper; this function calls rvth_create_gcm() and rvth_copy(). * 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 bank [in] Bank number. (0-7)
* @param filename [in] Destination filename. * @param filename [in] Destination filename.
* @param recrypt_key [in] Key for recryption. (-1 for default; otherwise, see RvtH_CryptoType_e) * @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. * @param callback [in,opt] Progress callback.
* @return Error code. (If negative, POSIX error; otherwise, see RvtH_Errors.) * @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. * Copy a bank from an RVT-H HDD or standalone disc image to an RVT-H system.

View File

@ -276,12 +276,14 @@ end:
* @param bank [in] Bank number. (0-7) * @param bank [in] Bank number. (0-7)
* @param filename [in] Destination filename. * @param filename [in] Destination filename.
* @param recrypt_key [in] Key for recryption. (-1 for default; otherwise, see RvtH_CryptoType_e) * @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. * @param callback [in,opt] Progress callback.
* @return Error code. (If negative, POSIX error; otherwise, see RvtH_Errors.) * @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; RvtH_BankEntry *entry;
uint32_t gcm_lba_len; uint32_t gcm_lba_len;
bool unenc_to_enc; bool unenc_to_enc;
@ -313,7 +315,8 @@ int rvth_extract(const RvtH *rvth, unsigned int bank, const TCHAR *filename, int
if (!game_pte) { if (!game_pte) {
// No game partition... // No game partition...
errno = EIO; 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. // 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; 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; ret = 0;
rvth_dest = rvth_create_gcm(filename, gcm_lba_len, &ret); rvth_dest = rvth_create_gcm(filename, gcm_lba_len, &ret);
if (!rvth_dest) { if (!rvth_dest) {
@ -337,7 +349,65 @@ int rvth_extract(const RvtH *rvth, unsigned int bank, const TCHAR *filename, int
if (ret == 0) { if (ret == 0) {
ret = -EIO; 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. // 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; return ret;
} }

View File

@ -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 s_bank [in] Bank number (as a string). (If NULL, assumes bank 1.)
* @param gcm_filename [in] Filename for the extracted GCM image. * @param gcm_filename [in] Filename for the extracted GCM image.
* @param recrypt_key [in] Key for recryption. (-1 for default) * @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. * @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; RvtH *rvth;
TCHAR *endptr; 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); printf("Extracting Bank %u into '", bank+1);
_fputts(gcm_filename, stdout); _fputts(gcm_filename, stdout);
fputs("'...\n", 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) { if (ret == 0) {
printf("Bank %u extracted to '", bank+1); printf("Bank %u extracted to '", bank+1);
_fputts(gcm_filename, stdout); _fputts(gcm_filename, stdout);

View File

@ -22,6 +22,7 @@
#define __RVTHTOOL_RVTHTOOL_EXTRACT_H__ #define __RVTHTOOL_RVTHTOOL_EXTRACT_H__
#include "librvth/tcharx.h" #include "librvth/tcharx.h"
#include <stdint.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -33,9 +34,10 @@ extern "C" {
* @param s_bank Bank number (as a string). (If NULL, assumes bank 1.) * @param s_bank Bank number (as a string). (If NULL, assumes bank 1.)
* @param gcm_filename Filename for the extracted GCM image. * @param gcm_filename Filename for the extracted GCM image.
* @param recrypt_key [in] Key for recryption. (-1 for default) * @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. * @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. * 'import' command.

View File

@ -125,6 +125,8 @@ static void print_help(const TCHAR *argv0)
" default, retail, korean, debug\n" " default, retail, korean, debug\n"
" Recrypting to retail will use fakesigning.\n" " Recrypting to retail will use fakesigning.\n"
" Importing to RVT-H will always use debug keys.\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" " -h, --help Display this help and exit.\n"
"\n" "\n"
, stdout); , stdout);
@ -133,6 +135,7 @@ static void print_help(const TCHAR *argv0)
int RVTH_CDECL _tmain(int argc, TCHAR *argv[]) int RVTH_CDECL _tmain(int argc, TCHAR *argv[])
{ {
int ret; int ret;
uint32_t flags = 0;
// Key to use for recryption. // Key to use for recryption.
// -1 == default; no recryption, except when importing retail to RVT-H. // -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) { while (true) {
static const struct option long_options[] = { static const struct option long_options[] = {
{_T("recrypt"), required_argument, 0, _T('k')}, {_T("recrypt"), required_argument, 0, _T('k')},
{_T("ndev"), no_argument, 0, _T('N')},
{_T("help"), no_argument, 0, _T('h')}, {_T("help"), no_argument, 0, _T('h')},
{NULL, 0, 0, 0} {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) if (c == -1)
break; break;
@ -190,6 +194,12 @@ int RVTH_CDECL _tmain(int argc, TCHAR *argv[])
} }
break; break;
case 'N':
// Prepend the SDK header.
// TODO: Show error if not using 'extract'?
flags |= RVTH_EXTRACT_PREPEND_SDK_HEADER;
break;
case 'h': case 'h':
print_help(argv[0]); print_help(argv[0]);
return EXIT_SUCCESS; return EXIT_SUCCESS;
@ -230,10 +240,10 @@ int RVTH_CDECL _tmain(int argc, TCHAR *argv[])
// Pass NULL as the bank number, which will be // Pass NULL as the bank number, which will be
// interpreted as bank 1 for single-disc images // interpreted as bank 1 for single-disc images
// and an error for HDD 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 { } else {
// Three or more parameters specified. // 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"))) { } else if (!_tcscmp(argv[optind], _T("import"))) {
// Import a bank. // Import a bank.