diff --git a/.gitignore b/.gitignore index 85a49d5..b48f5c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ build -/*.elf -/*.dol - -# Clion files -.idea -cmake-build-debug -CMakeLists.txt +output +*.elf +*.dol +*.bin +*.cert +*.txt diff --git a/HBC/boot.dol b/HBC/boot.dol deleted file mode 100644 index d837663..0000000 Binary files a/HBC/boot.dol and /dev/null differ diff --git a/HBC/boot.elf b/HBC/boot.elf deleted file mode 100644 index 98369e3..0000000 Binary files a/HBC/boot.elf and /dev/null differ diff --git a/HBC/meta.xml b/HBC/meta.xml index 7bd821b..4aa109d 100644 --- a/HBC/meta.xml +++ b/HBC/meta.xml @@ -1,8 +1,8 @@ Xyzzy - 1.3.1 - 20220713022030 + 1.3.2 + 20220718184500 Bushing, DarkMatterCore Extract your Wii console keys! Xyzzy is a homebrew application that allows the extraction of the OTP and SEEPROM Encryption Keys. @@ -15,9 +15,10 @@ Other changes include: * Support for GCN controllers and newer WiiMotes. * Retrieves SD IV, MD5 Blanker and MAC address. * Besides generating a "keys.txt" file with a hexdump of every dumped key, which follows the format required by wad2bin (https://github.com/DarkMatterCore/wad2bin), these files are also created: - * "bootmii_keys.bin" (follows the BootMii keys.bin format). * "device.cert" (raw device certificate dump). * "otp.bin" (raw OTP memory dump). - * "seeprom.bin" (raw SEEPROM memory dump) (Wii only). + * "seeprom.bin" (raw SEEPROM memory dump) (Wii only). + * "bootmii_keys.bin" (follows the BootMii keys.bin format) (Wii only). + * "boot0.bin" (raw boot0 Mask ROM dump) (Wii only). diff --git a/README.md b/README.md index 1f39203..a7105f7 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ Other changes include: * Support for GCN controllers and newer WiiMotes. * Retrieves SD IV, MD5 Blanker and MAC address. * Besides generating a "keys.txt" file with a hexdump of every dumped key, which follows the format required by [wad2bin](https://github.com/DarkMatterCore/wad2bin), these files are also created: - * "bootmii_keys.bin" (follows the BootMii keys.bin format). * "device.cert" (raw device certificate dump). * "otp.bin" (raw OTP memory dump). * "seeprom.bin" (raw SEEPROM memory dump) (Wii only). + * "bootmii_keys.bin" (follows the BootMii keys.bin format) (Wii only). + * "boot0.bin" (raw boot0 Mask ROM dump) (Wii only). diff --git a/source/aes.c b/source/aes.c index 99ec0f0..b82a542 100644 --- a/source/aes.c +++ b/source/aes.c @@ -19,7 +19,6 @@ */ #include -#include #include #include @@ -748,7 +747,7 @@ void rijndaelKeySetupEnc(u32 rk[/*44*/], const u8 cipherKey[]) rk[1] = GETU32(cipherKey + 4); rk[2] = GETU32(cipherKey + 8); rk[3] = GETU32(cipherKey + 12); - + for (i = 0; i < 10; i++) { temp = rk[3]; @@ -770,7 +769,7 @@ void rijndaelKeySetupDec(u32 rk[/*44*/], const u8 cipherKey[]) /* expand the cipher key: */ rijndaelKeySetupEnc(rk, cipherKey); - + /* invert the order of the round keys: */ for (i = 0, j = 4*Nr; i < j; i += 4, j -= 4) { @@ -779,7 +778,7 @@ void rijndaelKeySetupDec(u32 rk[/*44*/], const u8 cipherKey[]) temp = rk[i + 2]; rk[i + 2] = rk[j + 2]; rk[j + 2] = temp; temp = rk[i + 3]; rk[i + 3] = rk[j + 3]; rk[j + 3] = temp; } - + /* apply the inverse MixColumn transform to all round keys but the first and the last: */ for (i = 1; i < Nr; i++) { @@ -808,15 +807,15 @@ void rijndaelEncrypt(const u32 rk[/*44*/], const u8 pt[16], u8 ct[16]) s1 = GETU32(pt + 4) ^ rk[1]; s2 = GETU32(pt + 8) ^ rk[2]; s3 = GETU32(pt + 12) ^ rk[3]; - + #define ROUND(i,d,s) \ d##0 = TE0(s##0) ^ TE1(s##1) ^ TE2(s##2) ^ TE3(s##3) ^ rk[4 * i]; \ d##1 = TE0(s##1) ^ TE1(s##2) ^ TE2(s##3) ^ TE3(s##0) ^ rk[4 * i + 1]; \ d##2 = TE0(s##2) ^ TE1(s##3) ^ TE2(s##0) ^ TE3(s##1) ^ rk[4 * i + 2]; \ d##3 = TE0(s##3) ^ TE1(s##0) ^ TE2(s##1) ^ TE3(s##2) ^ rk[4 * i + 3] - + #ifdef FULL_UNROLL - + ROUND(1,t,s); ROUND(2,s,t); ROUND(3,t,s); @@ -826,11 +825,11 @@ d##3 = TE0(s##3) ^ TE1(s##0) ^ TE2(s##1) ^ TE3(s##2) ^ rk[4 * i + 3] ROUND(7,t,s); ROUND(8,s,t); ROUND(9,t,s); - + rk += Nr << 2; - + #else /* !FULL_UNROLL */ - + /* Nr - 1 full rounds: */ r = Nr >> 1; for (;;) @@ -840,11 +839,11 @@ d##3 = TE0(s##3) ^ TE1(s##0) ^ TE2(s##1) ^ TE3(s##2) ^ rk[4 * i + 3] if (--r == 0) break; ROUND(0,s,t); } - + #endif /* ?FULL_UNROLL */ - + #undef ROUND - + /* * apply last round and * map cipher state to byte array block: @@ -875,15 +874,15 @@ void rijndaelDecrypt(const u32 rk[/*44*/], const u8 ct[16], u8 pt[16]) s1 = GETU32(ct + 4) ^ rk[1]; s2 = GETU32(ct + 8) ^ rk[2]; s3 = GETU32(ct + 12) ^ rk[3]; - + #define ROUND(i,d,s) \ d##0 = TD0(s##0) ^ TD1(s##3) ^ TD2(s##2) ^ TD3(s##1) ^ rk[4 * i]; \ d##1 = TD0(s##1) ^ TD1(s##0) ^ TD2(s##3) ^ TD3(s##2) ^ rk[4 * i + 1]; \ d##2 = TD0(s##2) ^ TD1(s##1) ^ TD2(s##0) ^ TD3(s##3) ^ rk[4 * i + 2]; \ d##3 = TD0(s##3) ^ TD1(s##2) ^ TD2(s##1) ^ TD3(s##0) ^ rk[4 * i + 3] - + #ifdef FULL_UNROLL - + ROUND(1,t,s); ROUND(2,s,t); ROUND(3,t,s); @@ -895,9 +894,9 @@ d##3 = TD0(s##3) ^ TD1(s##2) ^ TD2(s##1) ^ TD3(s##0) ^ rk[4 * i + 3] ROUND(9,t,s); rk += Nr << 2; - + #else /* !FULL_UNROLL */ - + /* Nr - 1 full rounds: */ r = Nr >> 1; for (;;) @@ -907,11 +906,11 @@ d##3 = TD0(s##3) ^ TD1(s##2) ^ TD2(s##1) ^ TD3(s##0) ^ rk[4 * i + 3] if (--r == 0) break; ROUND(0,s,t); } - + #endif /* ?FULL_UNROLL */ #undef ROUND - + /* * apply last round and * map cipher state to byte array block: @@ -930,14 +929,14 @@ void *aes_init(const u8 *key, bool Enc) { u32 *rk = malloc(AES_PRIV_SIZE); if (rk == NULL) return NULL; - + if (Enc) { rijndaelKeySetupEnc(rk, key); } else { rijndaelKeySetupDec(rk, key); } - + return rk; } @@ -961,12 +960,12 @@ int aes_128_cbc_encrypt(const u8 *key, const u8 *iv, u8 *data, size_t data_len) u8 cbc[AES_BLOCK_SIZE]; u8 *pos = data; int i, j, blocks; - + ctx = aes_init(key, true); if (ctx == NULL) return -1; - + memcpy(cbc, iv, AES_BLOCK_SIZE); - + blocks = data_len / AES_BLOCK_SIZE; for (i = 0; i < blocks; i++) { @@ -975,7 +974,7 @@ int aes_128_cbc_encrypt(const u8 *key, const u8 *iv, u8 *data, size_t data_len) memcpy(pos, cbc, AES_BLOCK_SIZE); pos += AES_BLOCK_SIZE; } - + aes_deinit(ctx); return 0; } @@ -994,12 +993,12 @@ int aes_128_cbc_decrypt(const u8 *key, const u8 *iv, u8 *data, size_t data_len) u8 cbc[AES_BLOCK_SIZE], tmp[AES_BLOCK_SIZE]; u8 *pos = data; int i, j, blocks; - + ctx = aes_init(key, false); if (ctx == NULL) return -1; - + memcpy(cbc, iv, AES_BLOCK_SIZE); - + blocks = data_len / AES_BLOCK_SIZE; for (i = 0; i < blocks; i++) { @@ -1009,7 +1008,7 @@ int aes_128_cbc_decrypt(const u8 *key, const u8 *iv, u8 *data, size_t data_len) memcpy(cbc, tmp, AES_BLOCK_SIZE); pos += AES_BLOCK_SIZE; } - + aes_deinit(ctx); return 0; } diff --git a/source/boot0.c b/source/boot0.c new file mode 100644 index 0000000..d7e7597 --- /dev/null +++ b/source/boot0.c @@ -0,0 +1,78 @@ +#include +#include +#include + +#include "boot0.h" +#include "tools.h" + +#define SRAM_MIRROR 0xD400000 + +#define HW_SRNPROT 0xD800060 +#define SRAM_MASK 0x20 + +#define HW_BOOT0 0xD80018C +#define BOOT0_MASK 0x1000 + +#define BOOT0_BLK_SIZE 4 + +u16 boot0_read(void *dst, u16 offset, u16 size) +{ + if (!dst || offset >= BOOT0_SIZE || !size || (offset + size) > BOOT0_SIZE) return 0; + + u8 *ptr = (u8*)dst; + u8 val[BOOT0_BLK_SIZE] = {0}; + u16 cur_offset = 0; + bool disable_sram_mirror = false; + + // Calculate block offsets and sizes + u32 start_addr = (SRAM_MIRROR + ALIGN_DOWN(offset, BOOT0_BLK_SIZE)); + u8 start_addr_offset = (offset % BOOT0_BLK_SIZE); + + u32 end_addr = (SRAM_MIRROR + ALIGN_UP(offset + size, BOOT0_BLK_SIZE)); + u8 end_addr_size = ((offset + size) % BOOT0_BLK_SIZE); + + // Make sure the SRAM mirror is enabled (unlikely to be disabled, but let's play it safe) + if (!(read32(HW_SRNPROT) & SRAM_MASK)) + { + // Enable SRAM mirror + mask32(HW_SRNPROT, 0, SRAM_MASK); + disable_sram_mirror = true; + } + + // Enable boot0 + mask32(HW_BOOT0, BOOT0_MASK, 0); + + for(u32 addr = start_addr; addr < end_addr; addr += BOOT0_BLK_SIZE) + { + if (cur_offset >= size) break; + + // Read SRAM mirror (actually holds boot0 data) + *((u32*)val) = read32(addr); + + // Copy data to destination buffer + if (addr == start_addr && start_addr_offset != 0) + { + // Handle unaligned read at start address + memcpy(ptr + cur_offset, val + start_addr_offset, BOOT0_BLK_SIZE - start_addr_offset); + cur_offset += (BOOT0_BLK_SIZE - start_addr_offset); + } else + if (addr >= (end_addr - BOOT0_BLK_SIZE) && end_addr_size != 0) + { + // Handle unaligned read at end address + memcpy(ptr + cur_offset, val, end_addr_size); + cur_offset += end_addr_size; + } else { + // Normal read + memcpy(ptr + cur_offset, val, BOOT0_BLK_SIZE); + cur_offset += BOOT0_BLK_SIZE; + } + } + + // Disable boot0 + mask32(HW_BOOT0, 0, BOOT0_MASK); + + // Disable SRAM mirror, if needed + if (disable_sram_mirror) mask32(HW_SRNPROT, SRAM_MASK, 0); + + return cur_offset; +} diff --git a/source/boot0.h b/source/boot0.h new file mode 100644 index 0000000..49ec830 --- /dev/null +++ b/source/boot0.h @@ -0,0 +1,8 @@ +#ifndef __BOOT0_H__ +#define __BOOT0_H__ + +#define BOOT0_SIZE 0x1000 + +u16 boot0_read(void *dst, u16 offset, u16 size); + +#endif /* __BOOT0_H__ */ diff --git a/source/main.c b/source/main.c index 298153b..9052d32 100644 --- a/source/main.c +++ b/source/main.c @@ -1,4 +1,3 @@ -#include #include #include #include @@ -13,16 +12,16 @@ int XyzzyGetKeys(bool vWii); int main(int argc, char **argv) { __exception_setreload(10); - + int ret = 0; - + InitConsole(); InitPads(); - + bool vWii = IsWiiU(); - + PrintHeadline(); - + /* HW_AHBPROT check */ if (AHBPROT_DISABLED) { @@ -48,10 +47,10 @@ int main(int argc, char **argv) printf("without full hardware access rights.\n"); printf("\nProcess cannot continue. Press any button to exit."); } - + if (ret != -2) WaitForButtonPress(NULL, NULL); - + Reboot(); - + return 0; } diff --git a/source/mini_seeprom.c b/source/mini_seeprom.c index c9888c1..e91aec8 100644 --- a/source/mini_seeprom.c +++ b/source/mini_seeprom.c @@ -37,7 +37,7 @@ enum { static void seeprom_send_bits(u16 value, u8 bits) { if (!bits || bits > 16) return; - + while(bits--) { if (value & (1 << bits)) @@ -46,12 +46,12 @@ static void seeprom_send_bits(u16 value, u8 bits) } else { mask32(HW_GPIO1OUT, GP_EEP_MOSI, 0); } - + eeprom_delay(); - + mask32(HW_GPIO1OUT, 0, GP_EEP_CLK); eeprom_delay(); - + mask32(HW_GPIO1OUT, GP_EEP_CLK, 0); eeprom_delay(); } @@ -60,70 +60,70 @@ static void seeprom_send_bits(u16 value, u8 bits) static u16 seeprom_recv_bits(u8 bits) { if (!bits || bits > 16) return 0; - + int res = 0; - + while(bits--) { res <<= 1; - + mask32(HW_GPIO1OUT, 0, GP_EEP_CLK); eeprom_delay(); - + mask32(HW_GPIO1OUT, GP_EEP_CLK, 0); eeprom_delay(); - + res |= !!(read32(HW_GPIO1IN) & GP_EEP_MISO); } - + return (u16)res; } u16 seeprom_read(void *dst, u16 offset, u16 size) { if (!dst || offset >= SEEPROM_SIZE || !size || (offset + size) > SEEPROM_SIZE) return 0; - + u16 cur_offset = 0; - + u8 *ptr = (u8*)dst; u8 val[HW_SEEPROM_BLK_SIZE] = {0}; - + // Calculate block offsets and sizes u8 start_addr = (u8)(offset / HW_SEEPROM_BLK_SIZE); u8 start_addr_offset = (u8)(offset % HW_SEEPROM_BLK_SIZE); - + u8 end_addr = (u8)((offset + size) / HW_SEEPROM_BLK_SIZE); u8 end_addr_size = (u8)((offset + size) % HW_SEEPROM_BLK_SIZE); - + if (!end_addr_size) { end_addr--; end_addr_size = HW_SEEPROM_BLK_SIZE; } - + if (end_addr == start_addr) end_addr_size -= start_addr_offset; - + mask32(HW_GPIO1OUT, GP_EEP_CLK, 0); mask32(HW_GPIO1OUT, GP_EEP_CS, 0); eeprom_delay(); - + for(u16 i = start_addr; i <= end_addr; i++) { if (cur_offset >= size) break; - + // Start command cycle mask32(HW_GPIO1OUT, 0, GP_EEP_CS); - + // Send read command + address seeprom_send_bits(0x600 | i, 11); - + // Receive data *((u16*)val) = seeprom_recv_bits(16); - + // End of command cycle mask32(HW_GPIO1OUT, GP_EEP_CS, 0); eeprom_delay(); - + // Copy read data to destination buffer if (i == start_addr && start_addr_offset != 0) { @@ -142,70 +142,70 @@ u16 seeprom_read(void *dst, u16 offset, u16 size) cur_offset += HW_SEEPROM_BLK_SIZE; } } - + return cur_offset; } u16 seeprom_write(const void *src, u16 offset, u16 size) { if (!src || offset >= SEEPROM_SIZE || !size || (offset + size) > SEEPROM_SIZE) return 0; - + u32 level = 0; u16 cur_offset = 0; - + const u8 *ptr = (const u8*)src; u8 val[HW_SEEPROM_BLK_SIZE] = {0}; - + // Calculate block offsets and sizes u8 start_addr = (u8)(offset / HW_SEEPROM_BLK_SIZE); u8 start_addr_offset = (u8)(offset % HW_SEEPROM_BLK_SIZE); - + u8 end_addr = (u8)((offset + size) / HW_SEEPROM_BLK_SIZE); u8 end_addr_size = (u8)((offset + size) % HW_SEEPROM_BLK_SIZE); - + if (!end_addr_size) { end_addr--; end_addr_size = HW_SEEPROM_BLK_SIZE; } - + if (end_addr == start_addr) end_addr_size -= start_addr_offset; - + // Disable CPU interruptions _CPU_ISR_Disable(level); - + mask32(HW_GPIO1OUT, GP_EEP_CLK, 0); mask32(HW_GPIO1OUT, GP_EEP_CS, 0); eeprom_delay(); - + // EWEN - Enable programming commands mask32(HW_GPIO1OUT, 0, GP_EEP_CS); seeprom_send_bits(0x4FF, 11); mask32(HW_GPIO1OUT, GP_EEP_CS, 0); eeprom_delay(); - + for(u16 i = start_addr; i <= end_addr; i++) { if (cur_offset >= size) break; - + // Copy data to write from source buffer if ((i == start_addr && start_addr_offset != 0) || (i == end_addr && end_addr_size != HW_SEEPROM_BLK_SIZE)) { // Read data from SEEPROM to handle unaligned writes - + // Start command cycle mask32(HW_GPIO1OUT, 0, GP_EEP_CS); - + // Send read command + address seeprom_send_bits(0x600 | i, 11); - + // Receive data *((u16*)val) = seeprom_recv_bits(16); - + // End of command cycle mask32(HW_GPIO1OUT, GP_EEP_CS, 0); eeprom_delay(); - + if (i == start_addr && start_addr_offset != 0) { // Handle unaligned write at start address @@ -221,39 +221,39 @@ u16 seeprom_write(const void *src, u16 offset, u16 size) memcpy(val, ptr + cur_offset, HW_SEEPROM_BLK_SIZE); cur_offset += HW_SEEPROM_BLK_SIZE; } - + // Start command cycle mask32(HW_GPIO1OUT, 0, GP_EEP_CS); - + // Send write command + address seeprom_send_bits(0x500 | i, 11); - + // Send data seeprom_send_bits(*((u16*)val), 16); - + // End of command cycle mask32(HW_GPIO1OUT, GP_EEP_CS, 0); eeprom_delay(); - + // Wait until SEEPROM is ready (write cycle is self-timed so no clocking needed) mask32(HW_GPIO1OUT, 0, GP_EEP_CS); - + do { eeprom_delay(); } while(!(read32(HW_GPIO1IN) & GP_EEP_MISO)); - + mask32(HW_GPIO1OUT, GP_EEP_CS, 0); eeprom_delay(); } - + // EWDS - Disable programming commands mask32(HW_GPIO1OUT, 0, GP_EEP_CS); seeprom_send_bits(0x400, 11); mask32(HW_GPIO1OUT, GP_EEP_CS, 0); eeprom_delay(); - + // Enable CPU interruptions _CPU_ISR_Restore(level); - + return cur_offset; } diff --git a/source/otp.c b/source/otp.c index 0028863..c52d708 100644 --- a/source/otp.c +++ b/source/otp.c @@ -13,37 +13,37 @@ u8 otp_read(void *dst, u8 offset, u8 size) { if (!dst || offset >= OTP_SIZE || !size || (offset + size) > OTP_SIZE) return 0; - + u8 cur_offset = 0; - + u8 *ptr = (u8*)dst; u8 val[HW_OTP_BLK_SIZE] = {0}; - + // Calculate block offsets and sizes u8 start_addr = (offset / HW_OTP_BLK_SIZE); u8 start_addr_offset = (offset % HW_OTP_BLK_SIZE); - + u8 end_addr = ((offset + size) / HW_OTP_BLK_SIZE); u8 end_addr_size = ((offset + size) % HW_OTP_BLK_SIZE); - + if (!end_addr_size) { end_addr--; end_addr_size = HW_OTP_BLK_SIZE; } - + if (end_addr == start_addr) end_addr_size -= start_addr_offset; - + for(u8 i = start_addr; i <= end_addr; i++) { if (cur_offset >= size) break; - + // Send command + address HW_OTP_COMMAND = (0x80000000 | i); - + // Receive data *((u32*)val) = HW_OTP_DATA; - + // Copy read data to destination buffer if (i == start_addr && start_addr_offset != 0) { @@ -62,6 +62,6 @@ u8 otp_read(void *dst, u8 offset, u8 size) cur_offset += HW_OTP_BLK_SIZE; } } - + return cur_offset; } diff --git a/source/tools.c b/source/tools.c index 59ed0fb..abf4d4f 100644 --- a/source/tools.c +++ b/source/tools.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include @@ -40,13 +39,13 @@ bool IsWiiU(void) { s32 ret = 0; u32 x = 0; - + ret = ES_GetTitleContentsCount(TITLEID_200, &x); - + if (ret < 0) return false; // Title was never installed - + if (x == 0) return false; // Title was installed but deleted via Channel Management - + return true; } @@ -72,18 +71,18 @@ void WaitForButtonPress(u32 *out, u32 *outGC) { WPAD_ScanPads(); pressed = (WPAD_ButtonsDown(0) | WPAD_ButtonsDown(1) | WPAD_ButtonsDown(2) | WPAD_ButtonsDown(3)); - + PAD_ScanPads(); pressedGC = (PAD_ButtonsDown(0) | PAD_ButtonsDown(1) | PAD_ButtonsDown(2) | PAD_ButtonsDown(3)); - - if (pressed || pressedGC) + + if (pressed || pressedGC) { // Without waiting you can't select anything if (pressedGC) usleep(20000); - + if (out) *out = pressed; if (outGC) *outGC = pressedGC; - + break; } } @@ -93,59 +92,59 @@ void InitConsole(void) { // Initialise the video system VIDEO_Init(); - + // Obtain the preferred video mode from the system // This will correspond to the settings in the Wii menu rmode = VIDEO_GetPreferredMode(NULL); - + // Allocate memory for the display in the uncached region xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode)); - + // Set up the video registers with the chosen mode VIDEO_Configure(rmode); - + // Tell the video hardware where our display memory is VIDEO_SetNextFramebuffer(xfb); - + // Make the display visible VIDEO_SetBlack(FALSE); - + // Flush the video register changes to the hardware VIDEO_Flush(); - + // Wait for Video setup to complete VIDEO_WaitVSync(); if (rmode->viTVMode & VI_NON_INTERLACE) VIDEO_WaitVSync(); - + // Set console parameters int x = 24; int y = 32; int w = (rmode->fbWidth - 32); int h = (rmode->xfbHeight - 48); - + // Initialize the console - CON_InitEx works after VIDEO_ calls CON_InitEx(rmode, x, y, w, h); - + // Clear the garbage around the edges of the console VIDEO_ClearFrameBuffer(rmode, xfb, COLOR_BLACK); - + printf("\x1b[%u;%um", 37, false); } void PrintHeadline(void) { ResetScreen(); - + int rows, cols; CON_GetMetrics(&cols, &rows); - + printf("Xyzzy v%s (unofficial).", VERSION); - + char buf[64]; sprintf(buf, "IOS%d (v%d)", IOS_GetVersion(), IOS_GetRevision()); printf("\x1B[%d;%dH", 0, cols - strlen(buf) - 1); printf(buf); - + printf("\nOriginal code by bushing (RIP). Maintained by DarkMatterCore.\n\n"); } @@ -204,13 +203,13 @@ static void UnmountUSB(void) static int MountUSB(void) { UnmountUSB(); - + if (!USB_PORT_CONNECTED) { return -1; } else { bool started = false; - + time_t tStart = time(0); while((time(0) - tStart) < 10) // 10 seconds timeout { @@ -218,14 +217,14 @@ static int MountUSB(void) if (started) break; usleep(50000); } - + if (started) { if (!fatMountSimple("usb", &__io_usbstorage)) return -1; return 0; } } - + return -1; } @@ -248,24 +247,24 @@ int SelectStorageDevice(void) { u32 pressed, pressedGC; int ret = 0, selection = 0; - + const char *options[] = { "SD card", "USB device" }; const int options_cnt = (sizeof(options) / sizeof(options[0])); - + printf("Press HOME or Start to exit.\n\n"); - + while(true) { Con_ClearLine(); - + printf("Select device: "); - + SetHighlight(true); printf("< %s >", options[selection]); SetHighlight(false); - + WaitForButtonPress(&pressed, &pressedGC); - + if (pressed == WPAD_BUTTON_LEFT || pressedGC == PAD_BUTTON_LEFT) { if (selection > 0) @@ -275,7 +274,7 @@ int SelectStorageDevice(void) selection = (options_cnt - 1); } } - + if (pressed == WPAD_BUTTON_RIGHT || pressedGC == PAD_BUTTON_RIGHT) { if (selection < (options_cnt - 1)) @@ -285,20 +284,20 @@ int SelectStorageDevice(void) selection = 0; } } - + if (pressed == WPAD_BUTTON_HOME || pressedGC == PAD_BUTTON_START) { ret = -2; break; } - + if (pressed == WPAD_BUTTON_A || pressedGC == PAD_BUTTON_A) break; } - + if (ret == -2) return ret; - + printf("\n\nMounting %s...", options[selection]); - + switch(selection) { case 0: @@ -310,7 +309,7 @@ int SelectStorageDevice(void) default: break; } - + if (ret >= 0) { printf(" OK!"); @@ -319,16 +318,16 @@ int SelectStorageDevice(void) printf("\n\t- fatMountSimple failed."); printf("\n\t- Sorry, not writing keys to %s.", options[selection]); } - + sleep(2); - + return ret; } char *StorageDeviceString(void) { char *str = NULL; - + switch(device_type) { case STORAGE_DEVICE_TYPE_SD: @@ -340,14 +339,14 @@ char *StorageDeviceString(void) default: break; } - + return str; } char *StorageDeviceMountName(void) { char *str = NULL; - + switch(device_type) { case STORAGE_DEVICE_TYPE_SD: @@ -359,21 +358,21 @@ char *StorageDeviceMountName(void) default: break; } - + return str; } void HexKeyDump(FILE *fp, void *d, size_t len, bool add_spaces) { if (!fp || !d || !len) return; - + size_t i; u8 *data = (u8*)d; - + for(i = 0; i < len; i++) { fprintf(fp, "%02X", data[i]); - + if (add_spaces && (i + 1) < len) { if (((i + 1) % 16) > 0) @@ -389,124 +388,124 @@ void HexKeyDump(FILE *fp, void *d, size_t len, bool add_spaces) signed_blob *GetSignedTMDFromTitle(u64 title_id, u32 *out_size) { if (!out_size) return NULL; - + s32 ret = 0; signed_blob *stmd = NULL; bool success = false; - + tmd_tid = title_id; - + ret = ES_GetStoredTMDSize(tmd_tid, &tmd_size); if (ret < 0) { printf("ES_GetStoredTMDSize failed! (%d) (TID %X-%X)\n", ret, TITLE_UPPER(tmd_tid), TITLE_LOWER(tmd_tid)); return NULL; } - + stmd = (signed_blob*)memalign(32, ALIGN_UP(tmd_size, 32)); if (!stmd) { printf("Failed to allocate memory for TMD! (TID %X-%X)\n", TITLE_UPPER(tmd_tid), TITLE_LOWER(tmd_tid)); return NULL; } - + ret = ES_GetStoredTMD(tmd_tid, stmd, tmd_size); if (ret < 0) { printf("ES_GetStoredTMD failed! (%d) (TID %X-%X)\n", ret, TITLE_UPPER(tmd_tid), TITLE_LOWER(tmd_tid)); goto out; } - + if (!IS_VALID_SIGNATURE(stmd)) { printf("Invalid TMD signature! (TID %X-%X)\n", TITLE_UPPER(tmd_tid), TITLE_LOWER(tmd_tid)); goto out; } - + *out_size = tmd_size; success = true; - + out: if (!success && stmd) { free(stmd); stmd = NULL; } - + return stmd; } void *ReadFileFromFlashFileSystem(const char *path, u32 *out_size) { if (!path || !strlen(path) || !out_size) return NULL; - + s32 ret = 0; u8 *buf = NULL; bool success = false; - + snprintf(isfs_file_path, ISFS_MAXPATH, path); - + isfs_fd = ISFS_Open(isfs_file_path, ISFS_OPEN_READ); if (isfs_fd < 0) { printf("ISFS_Open(\"%s\") failed! (%d)\n", isfs_file_path, isfs_fd); return NULL; } - + ret = ISFS_GetFileStats(isfs_fd, &isfs_file_stats); if (ret < 0) { printf("ISFS_GetFileStats(\"%s\") failed! (%d)\n", isfs_file_path, ret); goto out; } - + if (!isfs_file_stats.file_length) { printf("\"%s\" is empty!\n", isfs_file_path); goto out; } - + buf = (u8*)memalign(32, ALIGN_UP(isfs_file_stats.file_length, 32)); if (!buf) { printf("Failed to allocate memory for \"%s\"!\n", isfs_file_path); goto out; } - + ret = ISFS_Read(isfs_fd, buf, isfs_file_stats.file_length); if (ret < 0) { printf("ISFS_Read(\"%s\") failed! (%d)\n", isfs_file_path, ret); goto out; } - + *out_size = isfs_file_stats.file_length; success = true; - + out: if (!success && buf) { free(buf); buf = NULL; } - + ISFS_Close(isfs_fd); isfs_fd = 0; - + return (void*)buf; } bool CheckIfFlashFileSystemFileExists(const char *path) { if (!path || !strlen(path)) return NULL; - + snprintf(isfs_file_path, ISFS_MAXPATH, path); - + isfs_fd = ISFS_Open(isfs_file_path, ISFS_OPEN_READ); if (isfs_fd < 0) return false; - + ISFS_Close(isfs_fd); isfs_fd = 0; - + return true; } diff --git a/source/tools.h b/source/tools.h index 9fd0208..4e3d6d8 100644 --- a/source/tools.h +++ b/source/tools.h @@ -4,8 +4,9 @@ #include #include #include +#include -#define VERSION "1.3.1" +#define VERSION "1.3.2" //#define IsWiiU() (((*(vu32*)0xCD8005A0) >> 16) == 0xCAFE) #define ResetScreen() printf("\x1b[2J") @@ -14,7 +15,8 @@ #define TITLE_LOWER(x) ((u32)(x)) #define TITLE_ID(x, y) (((u64)(x) << 32) | (y)) -#define ALIGN_UP(x, y) ((((y) - 1) + (x)) & ~((y) - 1)) +#define ALIGN_UP(x, y) (((x) + ((y) - 1)) & ~((y) - 1)) +#define ALIGN_DOWN(x, y) ((x) & ~((y) - 1)) #define MAX_ELEMENTS(x) (sizeof((x)) / sizeof((x)[0])) diff --git a/source/xyzzy.c b/source/xyzzy.c index acfc901..0d90beb 100644 --- a/source/xyzzy.c +++ b/source/xyzzy.c @@ -6,9 +6,8 @@ /* Kudos to WiiPower and Arikado for additional init code */ -/* DarkMatterCore - 2020 */ +/* DarkMatterCore - 2020-2022 */ -#include #include #include #include @@ -19,6 +18,7 @@ #include "mini_seeprom.h" #include "sha1.h" #include "aes.h" +#include "boot0.h" #define SYSTEM_MENU_TID (u64)0x0000000100000002 @@ -161,9 +161,9 @@ static void OTP_ClearData(void) static bool OTP_ReadData(void) { OTP_ClearData(); - + u8 ret = otp_read(otp_ptr, 0, OTP_SIZE); - + return (ret == OTP_SIZE); } @@ -175,9 +175,9 @@ static void SEEPROM_ClearData(void) static bool SEEPROM_ReadData(void) { SEEPROM_ClearData(); - + u16 ret = seeprom_read(seeprom_ptr, 0, SEEPROM_SIZE); - + return (ret == SEEPROM_SIZE && *(((u32*)seeprom_ptr) + 2) != 0); } @@ -188,14 +188,14 @@ static bool FillOTPStruct(otp_t **out) printf("Fatal error: invalid output OTP struct pointer.\n\n"); return false; } - + otp_t *otp_data = memalign(32, sizeof(otp_t)); if (!otp_data) { printf("Fatal error: unable to allocate memory for OTP struct.\n\n"); return false; } - + /* Read OTP data into otp_ptr pointer */ if (!OTP_ReadData()) { @@ -203,14 +203,14 @@ static bool FillOTPStruct(otp_t **out) OTP_ClearData(); return false; } - + /* Copy OTP data into our allocated struct */ memcpy(otp_data, otp_ptr, sizeof(otp_t)); OTP_ClearData(); - + /* Save OTP struct pointer */ *out = otp_data; - + return true; } @@ -221,14 +221,14 @@ static bool FillSEEPROMStruct(seeprom_t **out) printf("Fatal error: invalid output SEEPROM struct pointer.\n\n"); return false; } - + seeprom_t *seeprom_data = memalign(32, sizeof(seeprom_t)); if (!seeprom_data) { printf("Fatal error: unable to allocate memory for SEEPROM struct.\n\n"); return false; } - + /* Read SEEPROM data into seeprom_ptr pointer */ if (!SEEPROM_ReadData()) { @@ -236,14 +236,14 @@ static bool FillSEEPROMStruct(seeprom_t **out) SEEPROM_ClearData(); return false; } - + /* Copy SEEPROM data into our allocated struct */ memcpy(seeprom_data, seeprom_ptr, sizeof(seeprom_t)); SEEPROM_ClearData(); - + /* Save SEEPROM struct pointer */ *out = seeprom_data; - + return true; } @@ -254,29 +254,29 @@ static bool FillBootMiiKeysStruct(otp_t *otp_data, seeprom_t *seeprom_data, boot printf("Fatal error: invalid OTP/SEEPROM/BootMiiKeys struct pointer(s).\n\n"); return false; } - + bootmii_keys_bin_t *bootmii_keys = memalign(32, sizeof(bootmii_keys_bin_t)); if (!bootmii_keys) { printf("Fatal error: unable to allocate memory for BootMiiKeys struct.\n\n"); return false; } - + /* Fill structure with zeroes */ memset(bootmii_keys, 0, sizeof(bootmii_keys_bin_t)); - + /* Fill human info text block */ sprintf(bootmii_keys->human_info, "BackupMii v1, ConsoleID: %08x\n", *((u32*)otp_data->ng_id)); - + /* Fill OTP block */ memcpy(&(bootmii_keys->otp_data), otp_data, sizeof(otp_t)); - + /* Fill SEEPROM block */ memcpy(&(bootmii_keys->seeprom_data), seeprom_data, sizeof(seeprom_t)); - + /* Save BootMiiKeys struct pointer */ *out = bootmii_keys; - + return true; } @@ -284,22 +284,22 @@ static void RetrieveSDKey(void) { content_map_entry_t *content_map = NULL; u32 content_map_size = 0; - + u32 ios_version = (u32)IOS_GetVersion(); u64 ios_tid = TITLE_ID(1, ios_version); - + signed_blob *ios_stmd = NULL; u32 ios_stmd_size = 0; - + tmd *ios_tmd = NULL; tmd_content *ios_content = NULL; - + char content_path[ISFS_MAXPATH] = {0}; u8 *ios_content_data = NULL; u32 ios_content_size = 0; - + sha1 hash = {0}; - + /* Retrieve shared.map contents */ content_map = (content_map_entry_t*)ReadFileFromFlashFileSystem("/shared1/content.map", &content_map_size); if (!content_map) @@ -307,16 +307,16 @@ static void RetrieveSDKey(void) printf("Failed to retrieve \"%s\" contents!\n\n", content_path); return; } - + if ((content_map_size % sizeof(content_map_entry_t)) != 0) { printf("Invalid \"%s\" size!\n\n", content_path); goto out; } - + /* Calculate content.map entry count */ content_map_size /= sizeof(content_map_entry_t); - + /* Get IOS TMD */ ios_stmd = GetSignedTMDFromTitle(ios_tid, &ios_stmd_size); if (!ios_stmd) @@ -324,40 +324,40 @@ static void RetrieveSDKey(void) printf("Error retrieving IOS%u TMD!\n\n", ios_version); goto out; } - + ios_tmd = GetTMDFromSignedBlob(ios_stmd); - + /* Loop through all contents */ for(u16 i = 0; i < ios_tmd->num_contents; i++) { u32 j = 0; ios_content = &(ios_tmd->contents[i]); - + /* Only process shared contents */ if (ios_content->type != 0x8001) continue; - + /* Look for this content's hash in the content.map data */ for(j = 0; j < content_map_size; j++) { if (!memcmp(ios_content->hash, content_map[j].content_hash, 20)) break; } - + /* Continue if we couldn't find this hash */ if (j >= content_map_size) continue; - + /* Generate shared content path and read it */ sprintf(content_path, "/shared1/%.*s.app", sizeof(content_map[j].content_name), content_map[j].content_name); - + ios_content_data = (u8*)ReadFileFromFlashFileSystem(content_path, &ios_content_size); if (!ios_content_data) continue; - + /* Look for our key */ for(j = 0; j < ios_content_size; j++) { if (additional_keys[0].key_size > (ios_content_size - j)) break; - + if (SHA1(ios_content_data + j, additional_keys[0].key_size, hash) != shaSuccess) continue; - + if (!memcmp(hash, additional_keys[0].hash, 20)) { memcpy(additional_keys[0].key, ios_content_data + j, additional_keys[0].key_size); @@ -365,14 +365,14 @@ static void RetrieveSDKey(void) break; } } - + free(ios_content_data); ios_content_data = NULL; ios_content_size = 0; - + if (additional_keys[0].retrieved) break; } - + out: if (ios_stmd) free(ios_stmd); if (content_map) free(content_map); @@ -382,20 +382,20 @@ static void RetrieveSystemMenuKeys(bool vWii) { signed_blob *sysmenu_stmd = NULL; u32 sysmenu_stmd_size = 0; - + tmd *sysmenu_tmd = NULL; tmd_content *sysmenu_boot_content = NULL; - + char content_path[ISFS_MAXPATH] = {0}; u8 *sysmenu_boot_content_data = NULL; u32 sysmenu_boot_content_size = 0; - + u8 *binary_body = NULL; - + bool priiloader = false; - + sha1 hash = {0}; - + /* Get System Menu TMD */ sysmenu_stmd = GetSignedTMDFromTitle(SYSTEM_MENU_TID, &sysmenu_stmd_size); if (!sysmenu_stmd) @@ -403,12 +403,12 @@ static void RetrieveSystemMenuKeys(bool vWii) printf("Error retrieving System Menu TMD!\n\n"); return; } - + sysmenu_tmd = GetTMDFromSignedBlob(sysmenu_stmd); - + /* Get System Menu TMD boot content entry */ sysmenu_boot_content = &(sysmenu_tmd->contents[sysmenu_tmd->boot_index]); - + /* Check for Priiloader */ for(u32 i = 0; i < priiloader_files_count; i++) { @@ -419,7 +419,7 @@ static void RetrieveSystemMenuKeys(bool vWii) break; } } - + /* Generate boot content path and read it */ sprintf(content_path, "/title/%08x/%08x/content/%08x.app", TITLE_UPPER(SYSTEM_MENU_TID), TITLE_LOWER(SYSTEM_MENU_TID), \ priiloader ? (0x10000000 | sysmenu_boot_content->cid) : sysmenu_boot_content->cid); @@ -429,13 +429,13 @@ static void RetrieveSystemMenuKeys(bool vWii) sprintf(content_path, "/title/%08x/%08x/content/%08x.app", TITLE_UPPER(SYSTEM_MENU_TID), TITLE_LOWER(SYSTEM_MENU_TID), sysmenu_boot_content->cid); sysmenu_boot_content_data = (u8*)ReadFileFromFlashFileSystem(content_path, &sysmenu_boot_content_size); } - + if (!sysmenu_boot_content_data) { printf("Failed to read System Menu boot content data!\n\n"); goto out; } - + if (vWii) { /* Retrieve a pointer to the PPC Ancast Image header */ @@ -445,25 +445,25 @@ static void RetrieveSystemMenuKeys(bool vWii) printf("Invalid vWii System Menu ancast image header magic word!\n\n"); goto out; } - + /* Set the binary body pointer to the end of the PPC Ancast Image header and update size */ binary_body = (sysmenu_boot_content_data + 0x500 + sizeof(ppc_ancast_image_header_t)); sysmenu_boot_content_size = ancast_image_header->body_size; - + /* Calculate hash */ if (SHA1(binary_body, sysmenu_boot_content_size, hash) != shaSuccess) { printf("Failed to calculate encrypted vWii System Menu ancast image body SHA-1 hash!\n\n"); goto out; } - + /* Compare hashes */ if (memcmp(hash, ancast_image_header->body_hash, 20) != 0) { printf("Encrypted vWii System Menu ancast image body SHA-1 hash mismatch!\n\n"); goto out; } - + /* Decrypt System Menu binary using baked in vWii Ancast Key and IV (unavoidable...) */ if (aes_128_cbc_decrypt(vwii_ancast_key, vwii_ancast_iv, binary_body, sysmenu_boot_content_size) != 0) { @@ -474,16 +474,16 @@ static void RetrieveSystemMenuKeys(bool vWii) /* Set the binary body pointer to our allocated buffer */ binary_body = sysmenu_boot_content_data; } - + /* Retrieve keys starting from the right index */ for(u32 i = 1; i < 3; i++) { for(u32 offset = 0; offset < sysmenu_boot_content_size; offset++) { if (additional_keys[i].key_size > (sysmenu_boot_content_size - offset)) break; - + if (SHA1(binary_body + offset, additional_keys[i].key_size, hash) != shaSuccess) continue; - + if (!memcmp(hash, additional_keys[i].hash, 20)) { memcpy(additional_keys[i].key, binary_body + offset, additional_keys[i].key_size); @@ -492,7 +492,7 @@ static void RetrieveSystemMenuKeys(bool vWii) } } } - + out: if (sysmenu_boot_content_data) free(sysmenu_boot_content_data); if (sysmenu_stmd) free(sysmenu_stmd); @@ -512,32 +512,32 @@ static void GetMACAddress(void) static void PrintAllKeys(otp_t *otp_data, seeprom_t *seeprom_data, FILE *fp, bool is_txt) { if (!otp_data || !fp) return; - + u8 key_idx = 1; const char **key_names = (is_txt ? key_names_txt : key_names_stdout); - + /* We'll use this for the Korean common key check */ u8 null_key[16] = {0}; - + for(u8 i = 0; key_names[i]; i++) { /* Do not print SEEPROM keys if its access is disabled */ if (!seeprom_data && (i == 7 || i == 8 || i == 9)) continue; - + /* Only display the Korean common key if it's really available in the SEEPROM data */ /* Otherwise, we'll just skip it */ if (seeprom_data && i == 9 && !memcmp(seeprom_data->korean_key, null_key, sizeof(seeprom_data->korean_key))) continue; - + /* Only display the current additional key if we retrieved it */ if (i >= 10 && !additional_keys[i - 10].retrieved) continue; - + if (is_txt) { fprintf(fp, "%s= ", key_names[i]); } else { fprintf(fp, "[%u] %s: ", key_idx, key_names[i]); } - + switch(i) { case 0: // boot1 Hash @@ -574,9 +574,9 @@ static void PrintAllKeys(otp_t *otp_data, seeprom_t *seeprom_data, FILE *fp, boo HexKeyDump(fp, additional_keys[i - 10].key, additional_keys[i - 10].key_size, !is_txt); break; } - + fprintf(fp, "\r\n"); - + key_idx++; } } @@ -586,18 +586,18 @@ int XyzzyGetKeys(bool vWii) int ret = 0; FILE *fp = NULL; char path[128] = {0}; - + otp_t *otp_data = NULL; seeprom_t *seeprom_data = NULL; bootmii_keys_bin_t *bootmii_keys = NULL; - u8 *devcert = NULL; - + u8 *devcert = NULL, *boot0 = NULL; + ret = SelectStorageDevice(); - + if (ret >= 0) { sprintf(path, "%s:/keys.txt", StorageDeviceMountName()); - + fp = fopen(path, "w"); if (!fp) { @@ -610,19 +610,19 @@ int XyzzyGetKeys(bool vWii) { return ret; } - + ret = 0; - + PrintHeadline(); printf("Getting keys, please wait...\n\n"); - + if (!FillOTPStruct(&otp_data)) { ret = -1; sleep(2); goto out; } - + if (!vWii) { /* Access to the SEEPROM will be disabled in we're running under vWii */ @@ -632,40 +632,55 @@ int XyzzyGetKeys(bool vWii) sleep(2); goto out; } - + if (!FillBootMiiKeysStruct(otp_data, seeprom_data, &bootmii_keys)) { ret = -1; sleep(2); goto out; } + + /* Get boot0 dump */ + boot0 = memalign(32, BOOT0_SIZE); + if (boot0) + { + u16 rd = boot0_read(boot0, 0, BOOT0_SIZE); + if (rd != BOOT0_SIZE) + { + free(boot0); + boot0 = NULL; + printf("boot0_read failed! (%u).\n\n", rd); + } + } else { + printf("Error allocating memory for boot0 buffer.\n\n"); + } } - + /* Initialize filesystem driver */ ret = ISFS_Initialize(); if (ret >= 0) { /* Retrieve SD key from IOS */ RetrieveSDKey(); - + /* Retrieve keys from System Menu binary */ RetrieveSystemMenuKeys(vWii); - + /* Deinitialize filesystem driver */ ISFS_Deinitialize(); } else { printf("ISFS_Initialize failed! (%d)\n\n", ret); } - + /* Get MAC address */ GetMACAddress(); - + /* Get device certificate */ devcert = memalign(32, DEVCERT_BUF_SIZE); if (devcert) { memset(devcert, 42, DEVCERT_BUF_SIZE); // Why... ? - + ret = ES_GetDeviceCert(devcert); if (ret < 0) { @@ -676,19 +691,19 @@ int XyzzyGetKeys(bool vWii) } else { printf("Error allocating memory for device certificate buffer.\n\n"); } - + /* Print all keys to stdout */ PrintAllKeys(otp_data, seeprom_data, stdout, false); - + if (fp) { /* Print all keys to output txt */ PrintAllKeys(otp_data, seeprom_data, fp, true); - + fclose(fp); fp = NULL; } - + if (devcert) { /* Save raw device.cert */ @@ -705,7 +720,7 @@ int XyzzyGetKeys(bool vWii) sleep(2); } } - + /* Save raw OTP data */ sprintf(path, "%s:/otp.bin", StorageDeviceMountName()); fp = fopen(path, "wb"); @@ -719,7 +734,7 @@ int XyzzyGetKeys(bool vWii) printf("\n\t- Sorry, not writing raw OTP data to %s.\n", StorageDeviceString()); sleep(2); } - + /* Save raw SEEPROM data */ if (!vWii) { @@ -735,7 +750,7 @@ int XyzzyGetKeys(bool vWii) printf("\n\t- Sorry, not writing raw SEEPROM data to %s.\n", StorageDeviceString()); sleep(2); } - + sprintf(path, "%s:/bootmii_keys.bin", StorageDeviceMountName()); fp = fopen(path, "wb"); if (fp) @@ -748,20 +763,39 @@ int XyzzyGetKeys(bool vWii) printf("\n\t- Sorry, not writing BootMii keys.bin data to %s.\n", StorageDeviceString()); sleep(2); } + + if (boot0) + { + /* Save raw boot0.bin */ + sprintf(path, "%s:/boot0.bin", StorageDeviceMountName()); + fp = fopen(path, "wb"); + if (fp) + { + fwrite(boot0, 1, BOOT0_SIZE, fp); + fclose(fp); + fp = NULL; + } else { + printf("\n\t- Unable to open device.cert for writing."); + printf("\n\t- Sorry, not writing raw boot0.bin to %s.\n", StorageDeviceString()); + sleep(2); + } + } } - + out: + if (boot0) free(boot0); + if (devcert) free(devcert); - + if (bootmii_keys) free(bootmii_keys); - + if (seeprom_data) free(seeprom_data); - + if (otp_data) free(otp_data); - + if (fp) fclose(fp); - + UnmountStorageDevice(); - + return ret; }