From c3e2ded1fce074e1f1f63583724a2d03880bf29b Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sat, 17 May 2025 11:04:18 +0100 Subject: [PATCH 1/4] [wii] load encrypted SD DLC as if they were on NAND --- include/aes.h | 95 +++++++ include/ports_wii.h | 6 + include/ports_wii_bank8.h | 6 + include/rvl/cnt.h | 57 +++- include/rvl/mem.h | 20 ++ include/wii_cnt_crypt.h | 26 ++ include/wii_cnt_hooks.h | 9 + source/_functions.c | 6 + source/aes.c | 575 ++++++++++++++++++++++++++++++++++++++ source/rb3enhanced.c | 8 + source/wii.c | 1 + source/wii_cnt_crypt.c | 308 ++++++++++++++++++++ source/wii_cnt_hooks.c | 174 ++++++++++++ 13 files changed, 1285 insertions(+), 6 deletions(-) create mode 100644 include/aes.h create mode 100644 include/rvl/mem.h create mode 100644 include/wii_cnt_crypt.h create mode 100644 include/wii_cnt_hooks.h create mode 100644 source/aes.c create mode 100644 source/wii_cnt_crypt.c create mode 100644 source/wii_cnt_hooks.c diff --git a/include/aes.h b/include/aes.h new file mode 100644 index 0000000..4867d1a --- /dev/null +++ b/include/aes.h @@ -0,0 +1,95 @@ +#ifdef RB3E_WII + +#ifndef _AES_H_ +#define _AES_H_ + +#include +#include + +// #define the macros below to 1/0 to enable/disable the mode of operation. +// +// CBC enables AES encryption in CBC-mode of operation. +// CTR enables encryption in counter-mode. +// ECB enables the basic ECB 16-byte block algorithm. All can be enabled simultaneously. + +// The #ifndef-guard allows it to be configured before #include'ing or at compile time. +#ifndef CBC + #define CBC 1 +#endif + +#ifndef ECB + #define ECB 1 +#endif + +#ifndef CTR + #define CTR 0 +#endif + + +#define AES128 1 +//#define AES192 1 +//#define AES256 1 + +#define AES_BLOCKLEN 16 // Block length in bytes - AES is 128b block only + +#if defined(AES256) && (AES256 == 1) + #define AES_KEYLEN 32 + #define AES_keyExpSize 240 +#elif defined(AES192) && (AES192 == 1) + #define AES_KEYLEN 24 + #define AES_keyExpSize 208 +#else + #define AES_KEYLEN 16 // Key length in bytes + #define AES_keyExpSize 176 +#endif + +struct AES_ctx +{ + uint8_t RoundKey[AES_keyExpSize]; +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) + uint8_t Iv[AES_BLOCKLEN]; +#endif +}; + +void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key); +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); +void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv); +#endif + +#if defined(ECB) && (ECB == 1) +// buffer size is exactly AES_BLOCKLEN bytes; +// you need only AES_init_ctx as IV is not used in ECB +// NB: ECB is considered insecure for most uses +void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf); +void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf); + +#endif // #if defined(ECB) && (ECB == !) + + +#if defined(CBC) && (CBC == 1) +// buffer size MUST be mutile of AES_BLOCKLEN; +// Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme +// NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv() +// no IV should ever be reused with the same key +void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + +#endif // #if defined(CBC) && (CBC == 1) + + +#if defined(CTR) && (CTR == 1) + +// Same function for encrypting as for decrypting. +// IV is incremented for every block, and used after encryption as XOR-compliment for output +// Suggesting https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme +// NOTES: you need to set IV in ctx with AES_init_ctx_iv() or AES_ctx_set_iv() +// no IV should ever be reused with the same key +void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + +#endif // #if defined(CTR) && (CTR == 1) + + +#endif // _AES_H_ + +#endif // RB3E_WII diff --git a/include/ports_wii.h b/include/ports_wii.h index 842265b..46dba05 100644 --- a/include/ports_wii.h +++ b/include/ports_wii.h @@ -102,6 +102,7 @@ #define PORT_DATAREGISTERFUNC 0x8031b2b8 // DataRegisterFunc #define PORT_FILEISLOCAL 0x802fb548 // FileIsLocal #define PORT_FILEISDLC 0x802fb54c // FileIsDLC +#define PORT_SDMODECHECK 0x802F5638 // WiiContentMgr::SDModeCheck // instance addresses #define PORT_MODIFIERMGR_POINTER 0x808fda68 // pointer to ModifierManager #define PORT_ROCKCENTRALGATEWAY 0x80900870 // address of RockCentralGateway @@ -141,6 +142,11 @@ #define PORT_PPCHALT 0x80706390 // PPCHalt #define PORT_OSRETURNTOMENU 0x8076a610 // OSReturnToMenu #define PORT_OSREADROM 0x8076ae20 // OSReadROM +#define PORT_ARCINITHANDLE 0x806ffdb0 // ARCInitHandle +#define PORT_CONTENTINITHANDLETITLENAND 0x80733d60 // contentInitHandleTitleNAND +#define PORT_CNTRELEASEHANDLE 0x80734620 // CNTReleaseHandle +#define PORT_CNTREAD 0x807349d0 // CNTRead +#define PORT_ECGETCONTENTINFOS 0x807a9680 // EC_GetContentInfos // define logging functions #define RB3E_PRINT printf diff --git a/include/ports_wii_bank8.h b/include/ports_wii_bank8.h index faccd3b..5f01c1e 100644 --- a/include/ports_wii_bank8.h +++ b/include/ports_wii_bank8.h @@ -104,6 +104,7 @@ #define PORT_DATAREGISTERFUNC 0x804545e0 // DataRegisterFunc #define PORT_FILEISLOCAL 0x80422560 // FileIsLocal #define PORT_FILEISDLC 0x80422570 // FileIsDLC +#define PORT_SDMODECHECK 0x8041a790 // WiiContentMgr::SDModeCheck // instance addresses #define PORT_MODIFIERMGR_POINTER 0x80c904a8 // pointer to ModifierManager #define PORT_ROCKCENTRALGATEWAY 0x80c91818 // address of RockCentralGateway @@ -143,6 +144,11 @@ #define PORT_PPCHALT 0x80a49600 // PPCHalt #define PORT_OSRETURNTOMENU 0x80aad970 // OSReturnToMenu #define PORT_OSREADROM 0x80aae180 // OSReadROM +#define PORT_ARCINITHANDLE 0x80a43020 // ARCInitHandle +#define PORT_CONTENTINITHANDLETITLENAND 0x80a76fd0 // contentInitHandleTitleNAND +#define PORT_CNTRELEASEHANDLE 0x80a77890 // CNTReleaseHandle +#define PORT_CNTREAD 0x80a77c40 // CNTRead +#define PORT_ECGETCONTENTINFOS 0x80aed870 // EC_GetContentInfos // bank8 specific stuff #define PORT_BANK8_MEM2_RSO_ASSERT1 0x804428e8 #define PORT_BANK8_MEM2_RSO_ASSERT2 0x80442940 diff --git a/include/rvl/cnt.h b/include/rvl/cnt.h index 635964e..545756f 100644 --- a/include/rvl/cnt.h +++ b/include/rvl/cnt.h @@ -1,6 +1,10 @@ #ifndef _RVL_CNT_H #define _RVL_CNT_H +#include +#include +#include "emvolution/dvd.h" + // -- ARC/U8, on-disk -- // 'U.8-' @@ -13,7 +17,7 @@ typedef struct _u8_header_t { int first_node; int nodes_size; int data_offset; - int reserved[4]; + unsigned int reserved[4]; } u8_header_t; // 0x20 // the types of nodes within an ARC @@ -46,9 +50,21 @@ typedef struct _u8_node_t { // -- ARC, internally -- typedef struct _arc_handle_t { - unsigned char backing[0x1C]; + u8_header_t *header; + u8_node_t *nodes; + uint8_t *file; + unsigned int count; + const char *strings; + unsigned int fstSize; + int entrynum; } arc_handle_t; +typedef struct _arc_file_info_t { + arc_handle_t *handle; + unsigned int offset; + unsigned int size; +} arc_file_info_t; + typedef struct _arc_entry_t { arc_handle_t *handle; unsigned int path; @@ -63,8 +79,21 @@ typedef enum _cnt_handle_type { cnthandle_dvd = 2 } cnt_handle_type; +typedef struct _cnt_handle_nand { + arc_handle_t ArcHandle; + long FileDescriptor; + void *allocator; +} cnt_handle_nand; + +typedef struct _cnt_handle_dvd { + unsigned char backing[0x24]; +} cnt_handle_dvd; + typedef struct _cnt_handle { - unsigned char backing[0x28]; // TODO: split between ARC/NAND and DVD + union { + cnt_handle_nand nand; + cnt_handle_dvd dvd; + }; unsigned char type; } cnt_handle; @@ -79,14 +108,28 @@ typedef struct _cnt_dir_entry { }; unsigned char type; } cnt_dir_entry; -int a = sizeof(cnt_dir_entry); + +typedef struct _cnt_file_info_nand { + cnt_handle_nand *CntHandle; + unsigned int startoffset; + unsigned int length; + long readOffset; +} cnt_file_info_nand; + +typedef struct _cnt_file_info_dvd { + DVDFileInfo fileInfo; + long readOffset; +} cnt_file_info_dvd; typedef struct _cnt_file_info { - unsigned char backing[0x40]; + union { + cnt_file_info_nand nand; + cnt_file_info_dvd dvd; + }; unsigned char type; } cnt_file_info; -cnt_handle *contentInitHandleTitleNAND(unsigned long long title_id, unsigned int content_index, cnt_handle *handle, void *allocator); +int contentInitHandleTitleNAND(unsigned long long title_id, unsigned int content_index, cnt_handle *handle, MEMAllocator *allocator); int CNTReleaseHandle(cnt_handle *handle); int CNTOpen(cnt_handle *handle, const char *path, cnt_file_info *file_info); @@ -99,4 +142,6 @@ int CNTOpenDir(cnt_handle *handle, const char *path, cnt_dir *dir); int CNTReadDir(cnt_dir *dir, cnt_dir_entry *entry); int CNTCloseDir(cnt_dir *dir); +int ARCInitHandle(void *bin, arc_handle_t *handle); + #endif // _RVL_CNT_H diff --git a/include/rvl/mem.h b/include/rvl/mem.h new file mode 100644 index 0000000..2cbf51f --- /dev/null +++ b/include/rvl/mem.h @@ -0,0 +1,20 @@ +#ifndef _RVL_MEM_H + +typedef struct MEMAllocator; + +typedef void* (*MEMAllocatorAllocFunc)(struct MEMAllocator* allocator, unsigned int size); +typedef void (*MEMAllocatorFreeFunc)(struct MEMAllocator* allocator, void* block); + +typedef struct _MEMAllocatorFuncs { + MEMAllocatorAllocFunc allocFunc; + MEMAllocatorFreeFunc freeFunc; +} MEMAllocatorFuncs; + +typedef struct _MEMAllocator { + MEMAllocatorFuncs *funcs; + void *heap; + unsigned int heapParam1; + unsigned int heapParam2; +} MEMAllocator; + +#endif // _RVL_MEM_H diff --git a/include/wii_cnt_crypt.h b/include/wii_cnt_crypt.h new file mode 100644 index 0000000..ebabe67 --- /dev/null +++ b/include/wii_cnt_crypt.h @@ -0,0 +1,26 @@ +/* + RB3Enhanced - wii_cnt_crypt.h +*/ + +#ifdef RB3E_WII + +#include + +typedef struct _RB3E_CNTFileSD { + int fd; + uint32_t titleId; + int contentLength; + int startOffset; + uint16_t contentIndex; + struct AES_ctx *aesCtx; + uint8_t *arcHeader; + int lastBlockIndex; + uint8_t lastBlock[0x10]; + uint8_t lastBlockEnc[0x10]; +} RB3E_CNTFileSD; + +RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath); +void RB3E_CloseCNTFileSD(RB3E_CNTFileSD *file); +void RB3E_CNTFileRead(RB3E_CNTFileSD *file, int offset, uint8_t *buffer, int length); + +#endif // RB3E_WII diff --git a/include/wii_cnt_hooks.h b/include/wii_cnt_hooks.h new file mode 100644 index 0000000..1f84958 --- /dev/null +++ b/include/wii_cnt_hooks.h @@ -0,0 +1,9 @@ +/* + RB3Enhanced - wii_cnt_hooks.h +*/ + +#ifdef RB3E_WII + +void InitCNTHooks(); + +#endif diff --git a/source/_functions.c b/source/_functions.c index 898db47..c219990 100644 --- a/source/_functions.c +++ b/source/_functions.c @@ -117,6 +117,12 @@ RB3E_STUB(OSFatal) RB3E_STUB(OSSetErrorHandler) RB3E_STUB(PPCHalt) RB3E_STUB(OSReturnToMenu) +RB3E_STUB(ARCInitHandle) +RB3E_STUB(contentInitHandleTitleNAND) +RB3E_STUB(CNTReleaseHandle) +RB3E_STUB(CNTRead) +RB3E_STUB(EC_GetContentInfos) +RB3E_STUB(CNTOpen) #endif RB3E_STUB(RB3EStubEnd); diff --git a/source/aes.c b/source/aes.c new file mode 100644 index 0000000..9ab5131 --- /dev/null +++ b/source/aes.c @@ -0,0 +1,575 @@ +#ifdef RB3E_WII + +/* + +This is an implementation of the AES algorithm, specifically ECB, CTR and CBC mode. +Block size can be chosen in aes.h - available choices are AES128, AES192, AES256. + +The implementation is verified against the test vectors in: + National Institute of Standards and Technology Special Publication 800-38A 2001 ED + +ECB-AES128 +---------- + + plain-text: + 6bc1bee22e409f96e93d7e117393172a + ae2d8a571e03ac9c9eb76fac45af8e51 + 30c81c46a35ce411e5fbc1191a0a52ef + f69f2445df4f9b17ad2b417be66c3710 + + key: + 2b7e151628aed2a6abf7158809cf4f3c + + resulting cipher + 3ad77bb40d7a3660a89ecaf32466ef97 + f5d3d58503b9699de785895a96fdbaaf + 43b1cd7f598ece23881b00e3ed030688 + 7b0c785e27e8ad3f8223207104725dd4 + + +NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0) + You should pad the end of the string with zeros if this is not the case. + For AES192/256 the key size is proportionally larger. + +*/ + + +/*****************************************************************************/ +/* Includes: */ +/*****************************************************************************/ +#include // CBC mode, for memset +#include "aes.h" + +/*****************************************************************************/ +/* Defines: */ +/*****************************************************************************/ +// The number of columns comprising a state in AES. This is a constant in AES. Value=4 +#define Nb 4 + +#if defined(AES256) && (AES256 == 1) + #define Nk 8 + #define Nr 14 +#elif defined(AES192) && (AES192 == 1) + #define Nk 6 + #define Nr 12 +#else + #define Nk 4 // The number of 32 bit words in a key. + #define Nr 10 // The number of rounds in AES Cipher. +#endif + +// jcallan@github points out that declaring Multiply as a function +// reduces code size considerably with the Keil ARM compiler. +// See this link for more information: https://github.com/kokke/tiny-AES-C/pull/3 +#ifndef MULTIPLY_AS_A_FUNCTION + #define MULTIPLY_AS_A_FUNCTION 0 +#endif + + + + +/*****************************************************************************/ +/* Private variables: */ +/*****************************************************************************/ +// state - array holding the intermediate results during decryption. +typedef uint8_t state_t[4][4]; + + + +// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM +// The numbers below can be computed dynamically trading ROM for RAM - +// This can be useful in (embedded) bootloader applications, where ROM is often limited. +static const uint8_t sbox[256] = { + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; + +#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +static const uint8_t rsbox[256] = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }; +#endif + +// The round constant word array, Rcon[i], contains the values given by +// x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) +static const uint8_t Rcon[11] = { + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; + +/* + * Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES-C/pull/12), + * that you can remove most of the elements in the Rcon array, because they are unused. + * + * From Wikipedia's article on the Rijndael key schedule @ https://en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon + * + * "Only the first some of these constants are actually used – up to rcon[10] for AES-128 (as 11 round keys are needed), + * up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm." + */ + + +/*****************************************************************************/ +/* Private functions: */ +/*****************************************************************************/ +/* +static uint8_t getSBoxValue(uint8_t num) +{ + return sbox[num]; +} +*/ +#define getSBoxValue(num) (sbox[(num)]) + +// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. +static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) +{ + unsigned i, j, k; + uint8_t tempa[4]; // Used for the column/row operations + + // The first round key is the key itself. + for (i = 0; i < Nk; ++i) + { + RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; + RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; + RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; + RoundKey[(i * 4) + 3] = Key[(i * 4) + 3]; + } + + // All other round keys are found from the previous round keys. + for (i = Nk; i < Nb * (Nr + 1); ++i) + { + { + k = (i - 1) * 4; + tempa[0]=RoundKey[k + 0]; + tempa[1]=RoundKey[k + 1]; + tempa[2]=RoundKey[k + 2]; + tempa[3]=RoundKey[k + 3]; + + } + + if (i % Nk == 0) + { + // This function shifts the 4 bytes in a word to the left once. + // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] + + // Function RotWord() + { + const uint8_t u8tmp = tempa[0]; + tempa[0] = tempa[1]; + tempa[1] = tempa[2]; + tempa[2] = tempa[3]; + tempa[3] = u8tmp; + } + + // SubWord() is a function that takes a four-byte input word and + // applies the S-box to each of the four bytes to produce an output word. + + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + + tempa[0] = tempa[0] ^ Rcon[i/Nk]; + } +#if defined(AES256) && (AES256 == 1) + if (i % Nk == 4) + { + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + } +#endif + j = i * 4; k=(i - Nk) * 4; + RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0]; + RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1]; + RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2]; + RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3]; + } +} + +void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key) +{ + KeyExpansion(ctx->RoundKey, key); +} +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) +{ + KeyExpansion(ctx->RoundKey, key); + memcpy (ctx->Iv, iv, AES_BLOCKLEN); +} +void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv) +{ + memcpy (ctx->Iv, iv, AES_BLOCKLEN); +} +#endif + +// This function adds the round key to state. +// The round key is added to the state by an XOR function. +static void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey) +{ + uint8_t i,j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j]; + } + } +} + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void SubBytes(state_t* state) +{ + uint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxValue((*state)[j][i]); + } + } +} + +// The ShiftRows() function shifts the rows in the state to the left. +// Each row is shifted with different offset. +// Offset = Row number. So the first row is not shifted. +static void ShiftRows(state_t* state) +{ + uint8_t temp; + + // Rotate first row 1 columns to left + temp = (*state)[0][1]; + (*state)[0][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[3][1]; + (*state)[3][1] = temp; + + // Rotate second row 2 columns to left + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to left + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[3][3]; + (*state)[3][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[1][3]; + (*state)[1][3] = temp; +} + +static uint8_t xtime(uint8_t x) +{ + return ((x<<1) ^ (((x>>7) & 1) * 0x1b)); +} + +// MixColumns function mixes the columns of the state matrix +static void MixColumns(state_t* state) +{ + uint8_t i; + uint8_t Tmp, Tm, t; + for (i = 0; i < 4; ++i) + { + t = (*state)[i][0]; + Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ; + Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ; + Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ; + Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ; + Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ; + } +} + +// Multiply is used to multiply numbers in the field GF(2^8) +// Note: The last call to xtime() is unneeded, but often ends up generating a smaller binary +// The compiler seems to be able to vectorize the operation better this way. +// See https://github.com/kokke/tiny-AES-c/pull/34 +#if MULTIPLY_AS_A_FUNCTION +static uint8_t Multiply(uint8_t x, uint8_t y) +{ + return (((y & 1) * x) ^ + ((y>>1 & 1) * xtime(x)) ^ + ((y>>2 & 1) * xtime(xtime(x))) ^ + ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ + ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))); /* this last call to xtime() can be omitted */ + } +#else +#define Multiply(x, y) \ + ( ((y & 1) * x) ^ \ + ((y>>1 & 1) * xtime(x)) ^ \ + ((y>>2 & 1) * xtime(xtime(x))) ^ \ + ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \ + ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \ + +#endif + +#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +/* +static uint8_t getSBoxInvert(uint8_t num) +{ + return rsbox[num]; +} +*/ +#define getSBoxInvert(num) (rsbox[(num)]) + +// MixColumns function mixes the columns of the state matrix. +// The method used to multiply may be difficult to understand for the inexperienced. +// Please use the references to gain more information. +static void InvMixColumns(state_t* state) +{ + int i; + uint8_t a, b, c, d; + for (i = 0; i < 4; ++i) + { + a = (*state)[i][0]; + b = (*state)[i][1]; + c = (*state)[i][2]; + d = (*state)[i][3]; + + (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); + (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); + (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); + (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); + } +} + + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void InvSubBytes(state_t* state) +{ + uint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxInvert((*state)[j][i]); + } + } +} + +static void InvShiftRows(state_t* state) +{ + uint8_t temp; + + // Rotate first row 1 columns to right + temp = (*state)[3][1]; + (*state)[3][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[0][1]; + (*state)[0][1] = temp; + + // Rotate second row 2 columns to right + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to right + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[1][3]; + (*state)[1][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[3][3]; + (*state)[3][3] = temp; +} +#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) + +// Cipher is the main function that encrypts the PlainText. +static void Cipher(state_t* state, const uint8_t* RoundKey) +{ + uint8_t round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(0, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without MixColumns() + for (round = 1; ; ++round) + { + SubBytes(state); + ShiftRows(state); + if (round == Nr) { + break; + } + MixColumns(state); + AddRoundKey(round, state, RoundKey); + } + // Add round key to last round + AddRoundKey(Nr, state, RoundKey); +} + +#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +static void InvCipher(state_t* state, const uint8_t* RoundKey) +{ + uint8_t round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(Nr, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without InvMixColumn() + for (round = (Nr - 1); ; --round) + { + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(round, state, RoundKey); + if (round == 0) { + break; + } + InvMixColumns(state); + } + +} +#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) + +/*****************************************************************************/ +/* Public functions: */ +/*****************************************************************************/ +#if defined(ECB) && (ECB == 1) + + +void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf) +{ + // The next function call encrypts the PlainText with the Key using AES algorithm. + Cipher((state_t*)buf, ctx->RoundKey); +} + +void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf) +{ + // The next function call decrypts the PlainText with the Key using AES algorithm. + InvCipher((state_t*)buf, ctx->RoundKey); +} + + +#endif // #if defined(ECB) && (ECB == 1) + + + + + +#if defined(CBC) && (CBC == 1) + + +static void XorWithIv(uint8_t* buf, const uint8_t* Iv) +{ + uint8_t i; + for (i = 0; i < AES_BLOCKLEN; ++i) // The block in AES is always 128bit no matter the key size + { + buf[i] ^= Iv[i]; + } +} + +void AES_CBC_encrypt_buffer(struct AES_ctx *ctx, uint8_t* buf, size_t length) +{ + size_t i; + uint8_t *Iv = ctx->Iv; + for (i = 0; i < length; i += AES_BLOCKLEN) + { + XorWithIv(buf, Iv); + Cipher((state_t*)buf, ctx->RoundKey); + Iv = buf; + buf += AES_BLOCKLEN; + } + /* store Iv in ctx for next call */ + memcpy(ctx->Iv, Iv, AES_BLOCKLEN); +} + +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) +{ + size_t i; + uint8_t storeNextIv[AES_BLOCKLEN]; + for (i = 0; i < length; i += AES_BLOCKLEN) + { + memcpy(storeNextIv, buf, AES_BLOCKLEN); + InvCipher((state_t*)buf, ctx->RoundKey); + XorWithIv(buf, ctx->Iv); + memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN); + buf += AES_BLOCKLEN; + } + +} + +#endif // #if defined(CBC) && (CBC == 1) + + + +#if defined(CTR) && (CTR == 1) + +/* Symmetrical operation: same function for encrypting as for decrypting. Note any IV/nonce should never be reused with the same key */ +void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) +{ + uint8_t buffer[AES_BLOCKLEN]; + + size_t i; + int bi; + for (i = 0, bi = AES_BLOCKLEN; i < length; ++i, ++bi) + { + if (bi == AES_BLOCKLEN) /* we need to regen xor compliment in buffer */ + { + + memcpy(buffer, ctx->Iv, AES_BLOCKLEN); + Cipher((state_t*)buffer,ctx->RoundKey); + + /* Increment Iv and handle overflow */ + for (bi = (AES_BLOCKLEN - 1); bi >= 0; --bi) + { + /* inc will overflow */ + if (ctx->Iv[bi] == 255) + { + ctx->Iv[bi] = 0; + continue; + } + ctx->Iv[bi] += 1; + break; + } + bi = 0; + } + + buf[i] = (buf[i] ^ buffer[bi]); + } +} + +#endif // #if defined(CTR) && (CTR == 1) + +#endif // RB3E_WII diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index a5052eb..a941f7f 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -78,6 +78,7 @@ void *NewFileHook(char *fileName, int flags) return FileSD_New(fileName, flags); } #ifdef RB3EDEBUG + // only on debug builds and only on Dolphin, we can read from NAND if (RB3E_IsEmulator() && memcmp(fileName, "nand/", 5) == 0) { return FileWiiNAND_New(fileName + 4); } @@ -433,6 +434,8 @@ void ApplyHooks() RB3E_MSG("Hooks applied!", NULL); } +void InitCNTHooks(); + void StartupHook(void *ThisApp, int argc, char **argv) { RB3E_MSG("Loaded! Version " RB3E_BUILDTAG " (" RB3E_BUILDCOMMIT ")", NULL); @@ -451,6 +454,11 @@ void StartupHook(void *ThisApp, int argc, char **argv) // apply any patches that are only added after config loads ApplyConfigurablePatches(); +#ifdef RB3E_WII + if (RB3E_Mounted && config.LegacySDMode == false) + InitCNTHooks(); +#endif + // start the game by calling the proper app constructor RB3E_MSG("Starting Rock Band 3...", NULL); AppConstructor(ThisApp, argc, argv); diff --git a/source/wii.c b/source/wii.c index bf6129c..9f8d0dd 100644 --- a/source/wii.c +++ b/source/wii.c @@ -63,6 +63,7 @@ int RB3E_IsEmulator() if (HasRunDetection) return DetectionResult; DetectionResult = DetectDolphin(); + HasRunDetection = 1; return DetectionResult; } diff --git a/source/wii_cnt_crypt.c b/source/wii_cnt_crypt.c new file mode 100644 index 0000000..fb79273 --- /dev/null +++ b/source/wii_cnt_crypt.c @@ -0,0 +1,308 @@ +/* + RB3Enhanced - wii_cnt_crypt.c + Handling for encrypted and unencrypted Wii CNT(, CNTSD, ARC) files +*/ + +#ifdef RB3E_WII + +#include +#include +#include +#include +#include +#include "rvl/cnt.h" +#include "rvl/wad.h" +#include "rvl/tmd.h" +#include "rb3/Mem.h" +#include "aes.h" +#include "ports.h" +#include "rb3enhanced.h" +#include "wii_cnt_crypt.h" + +#define PRINT_BLOCK(name, x) RB3E_DEBUG(name ": %08x%08x%08x%08x", *(uint32_t *)&x[0], *(uint32_t *)&x[4], *(uint32_t *)&x[8], *(uint32_t *)&x[12]) + +// to be filled in by the brainslug launcher +uint8_t RB3E_ConsolePRNGKey[0x10]; + +uint64_t OSGetTime(); +uint64_t timeSpentInAES = 0; +uint64_t timeSpentInSD = 0; +uint64_t timeSpentInSeek = 0; +void RB3E_CNTFileReadBlock(RB3E_CNTFileSD *file, int blockIndex) +{ + // if we're trying to read the same block we're on, don't do anything + if (blockIndex == file->lastBlockIndex) + return; + + uint64_t time = 0; + if (file->aesCtx == NULL) // unencrypted reads + { + if (blockIndex == 0) + { + SD_seek(file->fd, file->startOffset, SEEK_SET); + } + else if (blockIndex != file->lastBlockIndex + 1) + { + RB3E_DEBUG("WARNING! CNTFileReadBlock had to seek back, this is gonna hurt", NULL); + SD_seek(file->fd, file->startOffset + (blockIndex * 0x10), SEEK_SET); + } + SD_read(file->fd, file->lastBlock, 0x10); + file->lastBlockIndex = blockIndex; + } + else // encrypted reads + { + if (blockIndex == 0) + { + time = OSGetTime(); + // block index 0 uses the IV derived from the content index + memset(file->aesCtx->Iv, 0, 0x10); + *(uint16_t *)file->aesCtx->Iv = file->contentIndex; + SD_seek(file->fd, file->startOffset, SEEK_SET); + timeSpentInSeek += OSGetTime() - time; + } + else if (blockIndex == file->lastBlockIndex + 1) + { + time = OSGetTime(); + // if we're reading in the next block, we don't need to seek backwards + memcpy(file->aesCtx->Iv, file->lastBlockEnc, 0x10); + timeSpentInSeek += OSGetTime() - time; + } + else + { + time = OSGetTime(); + RB3E_DEBUG("WARNING! CNTFileReadBlock had to seek back, this is gonna hurt", NULL); + // all other blocks use the IV with the previous block's ciphertext + SD_seek(file->fd, file->startOffset + ((blockIndex - 1) * 0x10), SEEK_SET); + SD_read(file->fd, file->aesCtx->Iv, 0x10); + timeSpentInSeek += OSGetTime() - time; + } + time = OSGetTime(); + // read and decrypt the current block + SD_read(file->fd, file->lastBlock, 0x10); + timeSpentInSD += OSGetTime() - time; + + time = OSGetTime(); + memcpy(file->lastBlockEnc, file->lastBlock, 0x10); // keep a copy of the encrypted last block to use as the next IV + // TODO(Emma): can we use the hardware AES engine to make this faster? + // TODO(Emma): can we + AES_CBC_decrypt_buffer(file->aesCtx, file->lastBlock, 0x10); + timeSpentInAES += OSGetTime() - time; + file->lastBlockIndex = blockIndex; + } +} + +void RB3E_CNTFileRead(RB3E_CNTFileSD *file, int offset, uint8_t *buffer, int length) +{ + int blockOffset = offset & 0xF; + int curBlock = (offset & ~0xF) >> 4; + int readBytes = 0; + uint64_t timeStart = OSGetTime(); + timeSpentInAES = 0; + timeSpentInSD = 0; + timeSpentInSeek = 0; + while (readBytes < length) + { + int toRead = 0x10 - blockOffset; + if (length - readBytes < 0x10) + toRead -= (toRead - (length - readBytes)); + if (toRead < 1 || toRead > 0x10) + { + RB3E_MSG("TRIED TO READ 0x%x BYTE BLOCK!", toRead); + break; + } + RB3E_CNTFileReadBlock(file, curBlock); + memcpy(buffer + readBytes, file->lastBlock + blockOffset, toRead); + //RB3E_DEBUG("Wrote 0x%x to %p", toRead, buffer + readBytes); + curBlock++; + blockOffset = 0; + readBytes += toRead; + } + RB3E_DEBUG("%p:0x%x:0x%x - %lli tb", buffer, offset, length, OSGetTime() - timeStart); + RB3E_DEBUG("seek %lli sd %lli aes %lli", timeSpentInSeek, timeSpentInSD, timeSpentInAES); +} + +void RB3E_CloseCNTFileSD(RB3E_CNTFileSD *file) +{ + if (file->fd != -1) + { + RB3E_CloseFile(file->fd); + file->fd = -1; + } + if (file->aesCtx != NULL) + { + MemFree(file->aesCtx); + file->aesCtx = NULL; + } + if (file->arcHeader != NULL) + { + MemFree(file->arcHeader); + file->arcHeader = NULL; + } + MemFree(file); +} + +static inline int alignoffset(int off, int align) +{ + return ((off + align - 1) & ~(align - 1)); +} + +static inline int getFlippedContent(uint8_t *contentFlags) +{ + int index = 0, i = 0, j = 0; + for (i = 0; i < 0x40; i++) + { + for (j = 0; j < 8; j++) + { + unsigned char mask = (1 << j); + if (contentFlags[i] == mask) goto found; + index++; + } + } +found: + return index; +} + +RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath) +{ + // open a handle to the file + int fd = RB3E_OpenFile(filepath, 0); + if (fd == -1) + { + RB3E_MSG("Failed to open CNT archive '%s'", filepath); + return NULL; + } + // allocate a buffer for our file metadata + RB3E_CNTFileSD *file = (RB3E_CNTFileSD *)MemAlloc(sizeof(RB3E_CNTFileSD), 0); + if (file == NULL) + { + RB3E_MSG("Could not allocate enough memory for CNTSD file", NULL); + RB3E_CloseFile(fd); + return NULL; + } + memset(file, 0, sizeof(RB3E_CNTFileSD)); + file->fd = fd; + file->lastBlockIndex = -1; + // read the header of the file + uint32_t magic[2]; + RB3E_ReadFile(fd, 0, magic, 8); + if (magic[1] == BACKUP_WAD_MAGIC) + { + // we're an encrypted file, allocate an AES context + file->aesCtx = MemAlloc(sizeof(struct AES_ctx), 0); + if (file->aesCtx == NULL) + { + RB3E_MSG("Failed to allocate space for the AES context", NULL); + RB3E_CloseCNTFileSD(file); + return NULL; + } + // read the WAD header and get the current content index from it + backup_wad_header_t wad; + RB3E_ReadFile(fd, 0, &wad, sizeof(backup_wad_header_t)); + int index = getFlippedContent(wad.included_contents); + RB3E_MSG("Host %.4s, TMD size 0x%x, Content size 0x%x", wad.title_id.name.code, wad.content_tmd_size, wad.content_data_size); + if (index == 0 || index > 511) + { + RB3E_MSG("! Invalid index %i!", index); + RB3E_CloseCNTFileSD(file); + return NULL; + } + RB3E_DEBUG("Content index %i", index); + file->contentIndex = index; + // read the TMD to get the true content length + tmd_header_t tmd; + RB3E_ReadFile(fd, sizeof(backup_wad_header_t), &tmd, sizeof(tmd_header_t)); + if (tmd.num_contents < index) + { + RB3E_MSG("Invalid number of contents!", NULL); + RB3E_CloseCNTFileSD(file); + return NULL; + } + // i don't know if this is *always* true but i'm assuming it is true that content indices are always sequential + tmd_content_t tmd_cnt; + RB3E_ReadFile(fd, sizeof(backup_wad_header_t) + sizeof(tmd_header_t) + (index * sizeof(tmd_content_t)), &tmd_cnt, sizeof(tmd_content_t)); + if (tmd_cnt.index != index) + { + RB3E_MSG("Wrong TMD content index (tmd:%i != flipped:%i)", tmd_cnt.index, index); + RB3E_CloseCNTFileSD(file); + return NULL; + } + RB3E_MSG("%.4s/%03i - content: %08x, size: 0x%llx", tmd.title_id.name.code, index, tmd_cnt.id, tmd_cnt.size); + file->titleId = *(uint32_t *)tmd.title_id.name.code; + file->contentLength = tmd_cnt.size; + // get the start of the encrypted data blob + int startOfData = alignoffset(sizeof(backup_wad_header_t) + wad.content_tmd_size, 0x40); + RB3E_DEBUG("Content starts at 0x%x", startOfData); + file->startOffset = startOfData; + // try to decrypt it with the NULL PRNG key (sZA-sZG) + RB3E_DEBUG("Attempting to read with NULLed key", NULL); + static uint8_t nullKey[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + PRINT_BLOCK("Key", nullKey); + AES_init_ctx(file->aesCtx, nullKey); + RB3E_CNTFileReadBlock(file, 0); + if (*(uint32_t *)file->lastBlock != ARC_MAGIC) + { + // that failed - try to decrypt it with the console-specific PRNG key (sZH+) + RB3E_DEBUG("Trying console specific PRNG key", NULL); + PRINT_BLOCK("Key", RB3E_ConsolePRNGKey); + AES_init_ctx(file->aesCtx, RB3E_ConsolePRNGKey); + file->lastBlockIndex = -1; + RB3E_CNTFileReadBlock(file, 0); + if (*(uint32_t *)file->lastBlock != ARC_MAGIC) + { + // couldn't decrypt + RB3E_MSG("Failed to decrypt the BIN file", NULL); + PRINT_BLOCK("Last Block", file->lastBlock); + RB3E_CloseCNTFileSD(file); + return NULL; + } + } + RB3E_MSG("Opened BIN successfully!", NULL); + // read the ARC file header into RAM + u8_header_t archeader; + RB3E_CNTFileRead(file, 0, (uint8_t *)&archeader, sizeof(u8_header_t)); + // allocate the size of the header - data_offset doubles as the length of the full "header" + void *fullarcheader = MemAlloc(archeader.data_offset, 0); + if (fullarcheader == NULL) + { + RB3E_MSG("Failed to allocate space for the header", NULL); + RB3E_CloseCNTFileSD(file); + return NULL; + } + // read the header, including node and string tables + RB3E_CNTFileRead(file, 0, fullarcheader, archeader.data_offset); + file->arcHeader = fullarcheader; + RB3E_DEBUG("Created file %p", file); + return file; + } + else if (magic[0] == ARC_MAGIC) + { + // untested, just try to handle the ARC file directly + file->aesCtx = NULL; + file->contentIndex = 0xFFFF; // TODO(Emma): get this from the filename and a TMD file + file->contentLength = RB3E_FileSize(file->fd); + file->startOffset = 0; + file->titleId = 0x72423345; // 'rB3E' + // read the ARC file header into RAM + u8_header_t archeader; + RB3E_CNTFileRead(file, 0, (uint8_t *)&archeader, sizeof(u8_header_t)); + // allocate the size of the header - data_offset doubles as the length of the full "header" + void *fullarcheader = MemAlloc(archeader.data_offset, 0); + if (fullarcheader == NULL) { + RB3E_MSG("Failed to allocate space for the header", NULL); + RB3E_CloseCNTFileSD(file); + return NULL; + } + // read the header, including node and string tables + RB3E_CNTFileRead(file, 0, fullarcheader, archeader.data_offset); + file->arcHeader = fullarcheader; + return file; + } + else + { + RB3E_MSG("'%s' was not valid CNT file.", filepath); + RB3E_CloseCNTFileSD(file); + return NULL; + } +} + +#endif // RB3E_WII diff --git a/source/wii_cnt_hooks.c b/source/wii_cnt_hooks.c new file mode 100644 index 0000000..b550b0a --- /dev/null +++ b/source/wii_cnt_hooks.c @@ -0,0 +1,174 @@ +/* + RB3Enhanced - wii_cnt_hooks.c + Hooks to CNT and EC functions to allow for custom content loading. +*/ + +#ifdef RB3E_WII + +#include +#include +#include +#include +#include "rvl/cnt.h" +#include "rvl/ec.h" +#include "rb3enhanced.h" +#include "utilities.h" +#include "ports.h" +#include "ppcasm.h" +#include "wii_cnt_crypt.h" + +int contentInitHandleTitleNAND_hook(unsigned long long title_id, unsigned int content_index, cnt_handle *handle, MEMAllocator *allocator) +{ + // make a file path of the SD DLC file + char titleid[8]; + char sdfilepath[256]; + memcpy(titleid, &title_id, 8); + sprintf(sdfilepath, "sd:/private/wii/data/%.4s/%03i.bin", &titleid[4], content_index); + // if the file exists then we're going to be opening that + if (RB3E_FileExists(sdfilepath)) + { + // open the bin file on the SD + RB3E_CNTFileSD *cntfilesd = RB3E_OpenCNTFileSD(sdfilepath); + if (cntfilesd == NULL) { + return -1; // TODO: what's the proper CNT error for "file not found"? + } + // open an ARC handle + arc_handle_t archandle; + ARCInitHandle(cntfilesd->arcHeader, &archandle); + // fill our output struct + memcpy(&handle->nand.ArcHandle, &archandle, sizeof(arc_handle_t)); + handle->nand.FileDescriptor = -0xBEEF; // dude is hungry + handle->nand.allocator = allocator; + handle->type = cnthandle_nand; + // sneak the handle to our RB3E_CNTFileSD in there + handle->nand.ArcHandle.header->reserved[0] = (unsigned int)cntfilesd; + // return success + return 0; + } + // fall back to reading from NAND + return contentInitHandleTitleNAND(title_id, content_index, handle, allocator); +} + +int CNTReleaseHandle_hook(cnt_handle *handle) +{ + // check if the file is reading from NAND and has our fake beef fd + if (handle->type == cnthandle_nand && handle->nand.FileDescriptor == -0xBEEF) + { + // get out the handle to our RB3E_CNTFileSD + RB3E_CNTFileSD *cntfilesd = (RB3E_CNTFileSD *)handle->nand.ArcHandle.header->reserved[0]; + RB3E_DEBUG("Freeing %p", cntfilesd); + RB3E_CloseCNTFileSD(cntfilesd); + // we don't call the original release function as we manage our own memory + return 0; + } + // fall back to the original release function + return CNTReleaseHandle(handle); +} + +int CNTRead_hook(cnt_file_info *file_info, void *buffer, int size) +{ + // check if the file is reading from NAND and has our fake beef fd + if (file_info->type == cnthandle_nand && file_info->nand.CntHandle->FileDescriptor == -0xBEEF) + { + //RB3E_DEBUG("File read %p 0x%x", buffer, size); + RB3E_CNTFileSD *cntfilesd = (RB3E_CNTFileSD *)file_info->nand.CntHandle->ArcHandle.header->reserved[0]; + RB3E_CNTFileRead(cntfilesd, file_info->nand.startoffset + file_info->nand.readOffset, buffer, size); + file_info->nand.readOffset += size; + return size; + } + // fall back to the original read function + return CNTRead(file_info, buffer, size); +} + +int EC_GetContentInfos_hook(unsigned long long titleId, ec_content_info_t *contentInfos, int *numContentInfos) +{ + // make a file path of the SD DLC folder + char titleid[8]; + char sddirpath[256]; + memcpy(titleid, &titleId, 8); + sprintf(sddirpath, "sd:/private/wii/data/%.4s/", &titleid[4]); + if (numContentInfos != NULL) + { + int originalNumContentInfos = *numContentInfos; + int r = 0; + DIR_STATE_STRUCT ds; + if (SD_diropen(&ds, sddirpath) != NULL) + { + RB3E_DEBUG("Enumerating %s", sddirpath); + // call the original so information about files on the NAND are populated + EC_GetContentInfos(titleId, contentInfos, numContentInfos); + *numContentInfos = originalNumContentInfos; + // we *do* have a directory, check if we need to fill the contentInfos buffer + if (contentInfos != NULL) + { + char filename[MAX_FILENAME_LENGTH]; + struct stat fs; + while (true) + { + if (SD_dirnext(&ds, filename, &fs) == 0) + { + // hacky check to see if the filename is some form of "000.BIN" + if ((filename[0] >= '0' && filename[0] <= '9') && + (filename[1] >= '0' && filename[1] <= '9') && + (filename[2] >= '0' && filename[2] <= '9') && + (filename[3] == '.') && + (filename[4] & ~0x20) == 'B' && + (filename[5] & ~0x20) == 'I' && + (filename[6] & ~0x20) == 'N') + { + // make a content index out of the filename + int contentIndex = + ((filename[0] - '0') * 100) + ((filename[1] - '0') * 10) + (filename[2] - '0'); + if (contentIndex < originalNumContentInfos) + { + // mark that DLC as installed + contentInfos[contentIndex].flags = 3; + contentInfos[contentIndex].index = contentIndex; + contentInfos[contentIndex].type = 0x4001; // DLC + // ROCK BAND HACK - the filesize of the DLC is checked to determine if something is a song or not + contentInfos[contentIndex].size = (contentIndex & 1) == 0 ? 0x414141 : 1; + } + else + { + RB3E_DEBUG("%i is out of range of %i", contentIndex, originalNumContentInfos); + } + } + } + else break; + } + return r; + } + else + { + *numContentInfos = 512; // act like we have every possible content index + RB3E_DEBUG("Returning %i", *numContentInfos); + SD_dirclose(&ds); + } + return r; + } + else + { + RB3E_DEBUG("No SD DLC for %.4s", &titleid[4]); + // call the original function, we don't have our own directory + return EC_GetContentInfos(titleId, contentInfos, numContentInfos); + } + return r; + } + else + { + return EC_GetContentInfos(titleId, contentInfos, numContentInfos); + } +} + +void InitCNTHooks() +{ + POKE_32(PORT_SDMODECHECK, LI(3, 3)); + POKE_32(PORT_SDMODECHECK + 4, BLR); + POKE_B(&ARCInitHandle, PORT_ARCINITHANDLE); + HookFunction(PORT_CONTENTINITHANDLETITLENAND, contentInitHandleTitleNAND, contentInitHandleTitleNAND_hook); + HookFunction(PORT_CNTRELEASEHANDLE, CNTReleaseHandle, CNTReleaseHandle_hook); + HookFunction(PORT_CNTREAD, CNTRead, CNTRead_hook); + HookFunction(PORT_ECGETCONTENTINFOS, EC_GetContentInfos, EC_GetContentInfos_hook); +} + +#endif // RB3E_WII From 8e2e748b932592c981ff5fd6b3e0d7a6063f5eb0 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sun, 18 May 2025 02:40:50 +0100 Subject: [PATCH 2/4] [wii] correct filesize when enumerating CNTSD DLC, read key from SD --- include/wii_cnt_crypt.h | 25 ++++---- source/rb3enhanced.c | 4 ++ source/wii_cnt_crypt.c | 138 +++++++++++++++++++++++++++++++++++++--- source/wii_cnt_hooks.c | 6 +- 4 files changed, 150 insertions(+), 23 deletions(-) diff --git a/include/wii_cnt_crypt.h b/include/wii_cnt_crypt.h index ebabe67..6812683 100644 --- a/include/wii_cnt_crypt.h +++ b/include/wii_cnt_crypt.h @@ -7,20 +7,23 @@ #include typedef struct _RB3E_CNTFileSD { - int fd; - uint32_t titleId; - int contentLength; - int startOffset; - uint16_t contentIndex; - struct AES_ctx *aesCtx; - uint8_t *arcHeader; - int lastBlockIndex; - uint8_t lastBlock[0x10]; - uint8_t lastBlockEnc[0x10]; + int fd; // 0x0 + uint32_t titleId; // 0x4 + int contentLength; // 0x8 + int startOffset; // 0xC + uint16_t contentIndex; // 0x10 + uint16_t padding; // 0x12 - probably not needed + struct AES_ctx *aesCtx; // 0x14 + uint8_t *arcHeader; // 0x18 + int lastBlockIndex; // 0x1c + uint8_t aesKey[0x10]; // 0x20 + uint8_t lastBlock[0x10]; // 0x30 + uint8_t lastBlockEnc[0x10]; // 0x40 } RB3E_CNTFileSD; -RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath); +RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath, unsigned long long titleid, unsigned int index); void RB3E_CloseCNTFileSD(RB3E_CNTFileSD *file); void RB3E_CNTFileRead(RB3E_CNTFileSD *file, int offset, uint8_t *buffer, int length); +void TryToLoadPRNGKeyFromFile(); #endif // RB3E_WII diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index a941f7f..e9b3cb3 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -435,6 +435,7 @@ void ApplyHooks() } void InitCNTHooks(); +void TryToLoadPRNGKeyFromFile(); void StartupHook(void *ThisApp, int argc, char **argv) { @@ -456,7 +457,10 @@ void StartupHook(void *ThisApp, int argc, char **argv) #ifdef RB3E_WII if (RB3E_Mounted && config.LegacySDMode == false) + { + TryToLoadPRNGKeyFromFile(); InitCNTHooks(); + } #endif // start the game by calling the proper app constructor diff --git a/source/wii_cnt_crypt.c b/source/wii_cnt_crypt.c index fb79273..45cbf3d 100644 --- a/source/wii_cnt_crypt.c +++ b/source/wii_cnt_crypt.c @@ -22,7 +22,31 @@ #define PRINT_BLOCK(name, x) RB3E_DEBUG(name ": %08x%08x%08x%08x", *(uint32_t *)&x[0], *(uint32_t *)&x[4], *(uint32_t *)&x[8], *(uint32_t *)&x[12]) // to be filled in by the brainslug launcher -uint8_t RB3E_ConsolePRNGKey[0x10]; +uint8_t RB3E_ConsolePRNGKey[0x10] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +static uint8_t nullKey[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +// This is really slow apparently +void IOS_AES_Decrypt(uint8_t *input, uint8_t *key, uint8_t *iv, uint8_t *output) +{ + static ios_fd_t aesFd = -1; + if (aesFd == -1) + aesFd = IOS_Open("/dev/aes", IPC_OPEN_NONE); + + ioctlv vec[4] __attribute__((align(32))); + // input vector + vec[0].data = input; + vec[0].len = 0x10; + vec[1].data = key; + vec[1].len = 0x10; + // output vector + vec[2].data = output; + vec[2].len = 0x10; + vec[3].data = iv; + vec[3].len = 0x10; + // decrypt + IOS_Ioctlv(aesFd, 0x3, 2, 2, vec); +} uint64_t OSGetTime(); uint64_t timeSpentInAES = 0; @@ -78,13 +102,12 @@ void RB3E_CNTFileReadBlock(RB3E_CNTFileSD *file, int blockIndex) } time = OSGetTime(); // read and decrypt the current block - SD_read(file->fd, file->lastBlock, 0x10); + SD_read(file->fd, file->lastBlockEnc, 0x10); timeSpentInSD += OSGetTime() - time; time = OSGetTime(); - memcpy(file->lastBlockEnc, file->lastBlock, 0x10); // keep a copy of the encrypted last block to use as the next IV - // TODO(Emma): can we use the hardware AES engine to make this faster? - // TODO(Emma): can we + //memcpy(file->lastBlockEnc, file->lastBlock, 0x10); // keep a copy of the encrypted last block to use as the next IV + memcpy(file->lastBlock, file->lastBlockEnc, 0x10); AES_CBC_decrypt_buffer(file->aesCtx, file->lastBlock, 0x10); timeSpentInAES += OSGetTime() - time; file->lastBlockIndex = blockIndex; @@ -162,7 +185,7 @@ static inline int getFlippedContent(uint8_t *contentFlags) return index; } -RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath) +RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath, unsigned long long titleid, unsigned int index) { // open a handle to the file int fd = RB3E_OpenFile(filepath, 0); @@ -235,9 +258,9 @@ RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath) file->startOffset = startOfData; // try to decrypt it with the NULL PRNG key (sZA-sZG) RB3E_DEBUG("Attempting to read with NULLed key", NULL); - static uint8_t nullKey[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; PRINT_BLOCK("Key", nullKey); AES_init_ctx(file->aesCtx, nullKey); + memcpy(file->aesKey, nullKey, 0x10); RB3E_CNTFileReadBlock(file, 0); if (*(uint32_t *)file->lastBlock != ARC_MAGIC) { @@ -245,6 +268,7 @@ RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath) RB3E_DEBUG("Trying console specific PRNG key", NULL); PRINT_BLOCK("Key", RB3E_ConsolePRNGKey); AES_init_ctx(file->aesCtx, RB3E_ConsolePRNGKey); + memcpy(file->aesKey, RB3E_ConsolePRNGKey, 0x10); file->lastBlockIndex = -1; RB3E_CNTFileReadBlock(file, 0); if (*(uint32_t *)file->lastBlock != ARC_MAGIC) @@ -278,10 +302,10 @@ RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath) { // untested, just try to handle the ARC file directly file->aesCtx = NULL; - file->contentIndex = 0xFFFF; // TODO(Emma): get this from the filename and a TMD file + file->contentIndex = index; file->contentLength = RB3E_FileSize(file->fd); file->startOffset = 0; - file->titleId = 0x72423345; // 'rB3E' + file->titleId = (uint32_t)titleid; // read the ARC file header into RAM u8_header_t archeader; RB3E_CNTFileRead(file, 0, (uint8_t *)&archeader, sizeof(u8_header_t)); @@ -305,4 +329,100 @@ RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath) } } +void ParseKeysFile(uint8_t *keys_data, size_t keys_len) +{ + RB3E_DEBUG("0x%x bytes key file", keys_len); + // check if its a BootMii-style keys.bin + if (keys_len >= 0x400 && *(uint32_t *)&keys_data[0x114] == 0xEBE42A22) + { + memcpy(RB3E_ConsolePRNGKey, keys_data + 0x168, 0x10); + } + // check if its an OTP file dump + else if (keys_len >= 0x100 && *(uint32_t *)&keys_data[0x14] == 0xEBE42A22) + { + memcpy(RB3E_ConsolePRNGKey, keys_data + 0x68, 0x10); + } + // check if its a straight up key + else if (keys_len == 0x10) + { + memcpy(RB3E_ConsolePRNGKey, keys_data, 0x10); + } + else + { + RB3E_DEBUG("FAILED TO LOAD KEY!", NULL); + return; + } + PRINT_BLOCK("Loaded Key", RB3E_ConsolePRNGKey); +} + +void TryToLoadPRNGKeyFromFile() +{ + uint8_t keys_buffer[0x400]; // size of keys.bin file + + // check if the loader already loaded a prng key + if (memcmp(RB3E_ConsolePRNGKey, nullKey, 0x10) != 0) + { + RB3E_DEBUG("PRNG key loaded, not scanning for file", NULL); + return; + } + + // DOLPHIN ONLY: try to load keys.bin from the NAND + if (RB3E_IsEmulator()) + { + ios_fd_t nandfd = IOS_Open("/keys.bin", IPC_OPEN_READ); + if (nandfd >= 0) + { + RB3E_DEBUG("Loading keys from NAND", NULL); + ios_ret_t readret = IOS_Read(nandfd, keys_buffer, sizeof(keys_buffer)); + IOS_Close(nandfd); + if (readret >= IPC_OK) + { + ParseKeysFile(keys_buffer, readret); + } + return; + } + } + + // try a few files on SD + // PRNG key as binary + if (RB3E_FileExists("sd:/prng_key.bin")) + { + int keysfd = RB3E_OpenFile("sd:/prng_key.bin", 0); + if (keysfd != -1) + { + RB3E_DEBUG("Loading keys from prng_key.bin", NULL); + int r = RB3E_ReadFile(keysfd, 0, keys_buffer, 0x10); + RB3E_CloseFile(keysfd); + ParseKeysFile(keys_buffer, r == 0x10 ? 0x10 : 0); + return; + } + } + // vWii/xyzzy OTP backup + if (RB3E_FileExists("sd:/otp.bin")) + { + int otpfd = RB3E_OpenFile("sd:/otp.bin", 0); + if (otpfd != -1) + { + RB3E_DEBUG("Loading keys from otp.bin", NULL); + int r = RB3E_ReadFile(otpfd, 0, keys_buffer, sizeof(keys_buffer)); + RB3E_CloseFile(otpfd); + ParseKeysFile(keys_buffer, r); + return; + } + } + // BootMii/xyzzy keys.bin backup + if (RB3E_FileExists("sd:/keys.bin")) + { + int keysfd = RB3E_OpenFile("sd:/keys.bin", 0); + if (keysfd != -1) + { + RB3E_DEBUG("Loading keys from keys.bin", NULL); + int r = RB3E_ReadFile(keysfd, 0, keys_buffer, sizeof(keys_buffer)); + RB3E_CloseFile(keysfd); + ParseKeysFile(keys_buffer, r); + return; + } + } +} + #endif // RB3E_WII diff --git a/source/wii_cnt_hooks.c b/source/wii_cnt_hooks.c index b550b0a..62d4bca 100644 --- a/source/wii_cnt_hooks.c +++ b/source/wii_cnt_hooks.c @@ -28,7 +28,7 @@ int contentInitHandleTitleNAND_hook(unsigned long long title_id, unsigned int co if (RB3E_FileExists(sdfilepath)) { // open the bin file on the SD - RB3E_CNTFileSD *cntfilesd = RB3E_OpenCNTFileSD(sdfilepath); + RB3E_CNTFileSD *cntfilesd = RB3E_OpenCNTFileSD(sdfilepath, title_id, content_index); if (cntfilesd == NULL) { return -1; // TODO: what's the proper CNT error for "file not found"? } @@ -125,8 +125,8 @@ int EC_GetContentInfos_hook(unsigned long long titleId, ec_content_info_t *conte contentInfos[contentIndex].flags = 3; contentInfos[contentIndex].index = contentIndex; contentInfos[contentIndex].type = 0x4001; // DLC - // ROCK BAND HACK - the filesize of the DLC is checked to determine if something is a song or not - contentInfos[contentIndex].size = (contentIndex & 1) == 0 ? 0x414141 : 1; + // the filesize of the DLC is checked to determine if something is a song or not + contentInfos[contentIndex].size = fs.st_size; } else { From 3b29b87edd3bb7c84db5cff4afb04913e9b08215 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Tue, 20 May 2025 11:06:14 +0100 Subject: [PATCH 3/4] [wii] allow unencrypted .app DLC from SD, very small speed improvement --- source/wii_cnt_crypt.c | 6 +++--- source/wii_cnt_hooks.c | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/source/wii_cnt_crypt.c b/source/wii_cnt_crypt.c index 45cbf3d..fa1d73e 100644 --- a/source/wii_cnt_crypt.c +++ b/source/wii_cnt_crypt.c @@ -88,7 +88,7 @@ void RB3E_CNTFileReadBlock(RB3E_CNTFileSD *file, int blockIndex) { time = OSGetTime(); // if we're reading in the next block, we don't need to seek backwards - memcpy(file->aesCtx->Iv, file->lastBlockEnc, 0x10); + //memcpy(file->aesCtx->Iv, file->lastBlockEnc, 0x10); timeSpentInSeek += OSGetTime() - time; } else @@ -102,12 +102,12 @@ void RB3E_CNTFileReadBlock(RB3E_CNTFileSD *file, int blockIndex) } time = OSGetTime(); // read and decrypt the current block - SD_read(file->fd, file->lastBlockEnc, 0x10); + SD_read(file->fd, file->lastBlock, 0x10); timeSpentInSD += OSGetTime() - time; time = OSGetTime(); //memcpy(file->lastBlockEnc, file->lastBlock, 0x10); // keep a copy of the encrypted last block to use as the next IV - memcpy(file->lastBlock, file->lastBlockEnc, 0x10); + //memcpy(file->lastBlock, file->lastBlockEnc, 0x10); AES_CBC_decrypt_buffer(file->aesCtx, file->lastBlock, 0x10); timeSpentInAES += OSGetTime() - time; file->lastBlockIndex = blockIndex; diff --git a/source/wii_cnt_hooks.c b/source/wii_cnt_hooks.c index 62d4bca..cf39ecf 100644 --- a/source/wii_cnt_hooks.c +++ b/source/wii_cnt_hooks.c @@ -107,14 +107,13 @@ int EC_GetContentInfos_hook(unsigned long long titleId, ec_content_info_t *conte { if (SD_dirnext(&ds, filename, &fs) == 0) { - // hacky check to see if the filename is some form of "000.BIN" + // hacky check to see if the filename is some form of "000.BIN" or "000.APP" if ((filename[0] >= '0' && filename[0] <= '9') && (filename[1] >= '0' && filename[1] <= '9') && (filename[2] >= '0' && filename[2] <= '9') && (filename[3] == '.') && - (filename[4] & ~0x20) == 'B' && - (filename[5] & ~0x20) == 'I' && - (filename[6] & ~0x20) == 'N') + (((filename[4] & ~0x20) == 'B' && (filename[5] & ~0x20) == 'I' && (filename[6] & ~0x20) == 'N') || + ((filename[4] & ~0x20) == 'A' && (filename[5] & ~0x20) == 'P' && (filename[6] & ~0x20) == 'P'))) { // make a content index out of the filename int contentIndex = From fd8813edf33cadaf41f160fe7343b14229a9a9c3 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Tue, 27 May 2025 00:20:58 +0100 Subject: [PATCH 4/4] [wii] add ModernSDMode toggle for emulated CNTSD --- include/config.h | 1 + source/config.c | 6 ++++-- source/rb3enhanced.c | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/include/config.h b/include/config.h index 82f121d..967e453 100644 --- a/include/config.h +++ b/include/config.h @@ -57,6 +57,7 @@ typedef struct _RB3E_Config // [Wii] char NASServer[RB3E_MAX_DOMAIN]; char LegacySDMode; + char ModernSDMode; #endif #ifdef RB3EDEBUG // [Debug] diff --git a/source/config.c b/source/config.c index 166e6b2..a6f6a64 100644 --- a/source/config.c +++ b/source/config.c @@ -27,8 +27,8 @@ void InitDefaultConfig() memset(&config, 0, sizeof(config)); strcpy(config.RawfilesDir, "rb3"); #ifdef RB3E_WII - // uncomment when GoCentral has a default instance that uses naswii auth - // strcpy(config.NASServer, "naswii.rbenhanced.rocks"); + strcpy(config.NASServer, "naswii.rbenhanced.rocks"); + config.ModernSDMode = 1; #endif config.SongSpeedMultiplier = 1.0; config.TrackSpeedMultiplier = 1.0; @@ -105,6 +105,8 @@ static int INIHandler(void *user, const char *section, const char *name, const c strncpy(config.NASServer, value, RB3E_MAX_DOMAIN); if (strcmp(name, "LegacySDMode") == 0) config.LegacySDMode = RB3E_CONFIG_BOOL(value); + if (strcmp(name, "ModernSDMode") == 0) + config.ModernSDMode = RB3E_CONFIG_BOOL(value); } #elif RB3E_XBOX if (strcmp(section, "Xbox360") == 0) diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index e9b3cb3..4da721a 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -456,7 +456,7 @@ void StartupHook(void *ThisApp, int argc, char **argv) ApplyConfigurablePatches(); #ifdef RB3E_WII - if (RB3E_Mounted && config.LegacySDMode == false) + if (RB3E_Mounted && config.LegacySDMode == false && config.ModernSDMode == true) { TryToLoadPRNGKeyFromFile(); InitCNTHooks();