2015-05-04 15:32:23 -07:00
|
|
|
#include "draw.h"
|
|
|
|
#include "hid.h"
|
2015-12-16 15:47:09 +01:00
|
|
|
#include "i2c.h"
|
2015-05-04 15:32:23 -07:00
|
|
|
#include "fatfs/ff.h"
|
2015-05-10 00:53:09 -07:00
|
|
|
#include "gamecart/protocol.h"
|
2015-05-10 12:51:46 -07:00
|
|
|
#include "gamecart/command_ctr.h"
|
2016-06-07 02:30:30 -04:00
|
|
|
#include "headers.h"
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
2016-06-07 02:34:07 -04:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <malloc.h> //For memalign
|
2015-05-04 15:32:23 -07:00
|
|
|
|
|
|
|
extern s32 CartID;
|
|
|
|
extern s32 CartID2;
|
|
|
|
|
|
|
|
// File IO utility functions
|
|
|
|
static FATFS fs;
|
|
|
|
static FIL file;
|
|
|
|
|
2015-05-14 21:30:39 -04:00
|
|
|
static void ClearTop(void) {
|
2016-06-07 23:15:14 -04:00
|
|
|
ClearScreen(TOP_SCREEN0, RGB(255, 255, 255));
|
2015-05-04 15:32:23 -07:00
|
|
|
ClearScreen(TOP_SCREEN1, RGB(255, 255, 255));
|
|
|
|
current_y = 0;
|
|
|
|
}
|
|
|
|
|
2015-05-14 21:30:39 -04:00
|
|
|
static void wait_key(void) {
|
2015-05-24 23:29:37 -03:00
|
|
|
Debug("Press key to continue...");
|
2015-05-04 15:32:23 -07:00
|
|
|
InputWait();
|
|
|
|
}
|
|
|
|
|
2016-06-07 02:30:30 -04:00
|
|
|
static void Reboot()
|
2015-12-16 15:47:09 +01:00
|
|
|
{
|
|
|
|
i2cWriteRegister(I2C_DEV_MCU, 0x20, 1 << 2);
|
|
|
|
while(true);
|
|
|
|
}
|
|
|
|
|
2015-05-28 16:51:39 -03:00
|
|
|
struct Context {
|
|
|
|
u8* buffer;
|
|
|
|
size_t buffer_size;
|
|
|
|
|
|
|
|
u32 cart_size;
|
|
|
|
u32 media_unit;
|
|
|
|
};
|
|
|
|
|
2016-06-07 02:30:30 -04:00
|
|
|
static int dump_cart_region(u32 start_sector, u32 end_sector, FIL* output_file, struct Context* ctx) {
|
|
|
|
u32 read_size = 1u * 1024 * 1024 / ctx->media_unit; // 1MiB default
|
2015-05-28 16:51:39 -03:00
|
|
|
|
|
|
|
// Dump remaining data
|
|
|
|
u32 current_sector = start_sector;
|
|
|
|
while (current_sector < end_sector) {
|
|
|
|
unsigned int percentage = current_sector * 100 / ctx->cart_size;
|
|
|
|
Debug("Dumping %08X / %08X - %3u%%", current_sector, ctx->cart_size, percentage);
|
|
|
|
|
|
|
|
u8* read_ptr = ctx->buffer;
|
|
|
|
while (read_ptr < ctx->buffer + ctx->buffer_size && current_sector < end_sector) {
|
|
|
|
Cart_Dummy();
|
|
|
|
Cart_Dummy();
|
2016-06-07 02:34:07 -04:00
|
|
|
|
2016-06-07 23:15:14 -04:00
|
|
|
// If there is less data to read than the current read_size, fix it
|
|
|
|
if (end_sector - current_sector < read_size)
|
2016-06-07 02:30:30 -04:00
|
|
|
{
|
|
|
|
read_size = end_sector - current_sector;
|
|
|
|
}
|
2015-05-28 16:51:39 -03:00
|
|
|
CTR_CmdReadData(current_sector, ctx->media_unit, read_size, read_ptr);
|
|
|
|
read_ptr += ctx->media_unit * read_size;
|
|
|
|
current_sector += read_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
u8* write_ptr = ctx->buffer;
|
|
|
|
while (write_ptr < read_ptr) {
|
|
|
|
unsigned int bytes_written = 0;
|
2016-06-07 02:30:30 -04:00
|
|
|
f_write(output_file, write_ptr, (size_t)(read_ptr - write_ptr), &bytes_written);
|
|
|
|
Debug("Wrote 0x%x bytes...", bytes_written);
|
2015-05-28 16:51:39 -03:00
|
|
|
|
|
|
|
if (bytes_written == 0) {
|
|
|
|
Debug("Writing failed! :( SD full?");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
write_ptr += bytes_written;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-05-04 15:32:23 -07:00
|
|
|
int main() {
|
2016-06-07 23:15:14 -04:00
|
|
|
// Saves the framebuffer information somewhere safe.
|
|
|
|
DrawInit();
|
|
|
|
|
2016-06-07 02:34:07 -04:00
|
|
|
// Arbitrary target buffer
|
|
|
|
// aligning to 32 bits in case other parts of the software assume alignment
|
|
|
|
const u32 target_buf_size = 16u * 1024u * 1024u; // 16MB
|
2016-06-07 23:15:14 -04:00
|
|
|
u32* const target = memalign(4, target_buf_size);
|
2016-06-07 02:34:07 -04:00
|
|
|
|
2016-06-07 23:15:14 -04:00
|
|
|
u32* const ncchHeaderData = memalign(4, sizeof(NCCH_HEADER));
|
|
|
|
NCCH_HEADER* const ncchHeader = (NCCH_HEADER*)ncchHeaderData;
|
2016-06-07 02:34:07 -04:00
|
|
|
|
2016-06-07 23:15:14 -04:00
|
|
|
NCSD_HEADER* const ncsdHeader = (NCSD_HEADER*)target;
|
2015-05-04 15:32:23 -07:00
|
|
|
|
2015-05-24 23:29:37 -03:00
|
|
|
restart_program:
|
2015-05-04 15:32:23 -07:00
|
|
|
// Setup boring stuff - clear the screen, initialize SD output, etc...
|
|
|
|
ClearTop();
|
2016-06-07 02:34:07 -04:00
|
|
|
|
2016-06-07 02:30:30 -04:00
|
|
|
Debug("Uncart: ROM dump tool v0.2");
|
2015-05-28 16:51:39 -03:00
|
|
|
Debug("Insert your game cart now.");
|
2015-05-04 15:32:23 -07:00
|
|
|
wait_key();
|
|
|
|
|
2015-05-25 04:52:32 -03:00
|
|
|
memset(target, 0, target_buf_size); // Clear our buffer
|
2015-05-04 15:32:23 -07:00
|
|
|
|
|
|
|
// ROM DUMPING CODE STARTS HERE
|
|
|
|
|
|
|
|
Cart_Init();
|
2015-05-29 14:46:45 -03:00
|
|
|
Debug("Cart id is %08x", Cart_GetID());
|
2016-06-07 02:30:30 -04:00
|
|
|
Debug("Reading NCCH header...");
|
|
|
|
CTR_CmdReadHeader(ncchHeader);
|
|
|
|
Debug("Done reading NCCH header.");
|
2015-05-04 15:32:23 -07:00
|
|
|
|
2016-06-07 02:34:07 -04:00
|
|
|
// Check that the NCCH header magic is there
|
2016-06-07 23:15:14 -04:00
|
|
|
if (strncmp((const char*)(ncchHeader->magic), "NCCH", 4)) {
|
2016-06-07 02:30:30 -04:00
|
|
|
Debug("NCCH magic not found in header!!!");
|
|
|
|
Debug("Press A to continue anyway.");
|
|
|
|
if (!(InputWait() & BUTTON_A))
|
|
|
|
goto restart_prompt;
|
|
|
|
}
|
2015-05-04 15:32:23 -07:00
|
|
|
|
2016-06-07 02:30:30 -04:00
|
|
|
u32 sec_keys[4];
|
|
|
|
Cart_Secure_Init(ncchHeaderData, sec_keys);
|
2015-05-04 15:32:23 -07:00
|
|
|
|
2016-06-07 02:34:07 -04:00
|
|
|
// Guess 0x200 first for the media size. this will be set correctly once the cart header is read
|
2015-05-04 15:32:23 -07:00
|
|
|
// Read out the header 0x0000-0x1000
|
2015-05-25 17:46:00 -03:00
|
|
|
Cart_Dummy();
|
2016-06-07 02:30:30 -04:00
|
|
|
Debug("Reading NCSD header...");
|
|
|
|
CTR_CmdReadData(0, 0x200, 0x1000 / 0x200, target);
|
|
|
|
Debug("Done reading NCSD header.");
|
2016-06-07 02:34:07 -04:00
|
|
|
|
|
|
|
// Check for NCSD magic
|
2016-06-07 02:30:30 -04:00
|
|
|
if (strncmp((const char*)(ncsdHeader->magic), "NCSD", 4)) {
|
2015-05-24 23:29:37 -03:00
|
|
|
Debug("NCSD magic not found in header!!!");
|
|
|
|
Debug("Press A to continue anyway.");
|
2015-06-01 00:34:37 -04:00
|
|
|
if (!(InputWait() & BUTTON_A))
|
2015-05-28 16:51:39 -03:00
|
|
|
goto restart_prompt;
|
2015-05-04 15:32:23 -07:00
|
|
|
}
|
2015-05-10 00:14:42 -07:00
|
|
|
|
2016-06-07 02:34:07 -04:00
|
|
|
Debug("Uncart can either dump the entire ROM (including");
|
|
|
|
Debug("empty space), or a trimmed version based on the");
|
|
|
|
Debug("size of the cart partitions.");
|
|
|
|
Debug("");
|
|
|
|
|
|
|
|
u32 input;
|
2016-06-07 23:15:14 -04:00
|
|
|
do {
|
2016-06-07 02:34:07 -04:00
|
|
|
Debug("Press A to dump all of ROM, B for only the");
|
|
|
|
Debug("trimmed version.");
|
|
|
|
input = InputWait();
|
|
|
|
}
|
|
|
|
while (!(input & BUTTON_A) && !(input & BUTTON_B));
|
|
|
|
|
|
|
|
|
2016-06-07 02:30:30 -04:00
|
|
|
const u32 mediaUnit = 0x200 * (1u << ncsdHeader->partition_flags[MEDIA_UNIT_SIZE]); //Correctly set the media unit size
|
|
|
|
|
2016-06-07 02:34:07 -04:00
|
|
|
u32 cartSize;
|
|
|
|
// Maximum number of blocks in a single file
|
|
|
|
u32 file_max_blocks;
|
|
|
|
|
2016-06-07 23:15:14 -04:00
|
|
|
if (input & BUTTON_B) {
|
|
|
|
// Calculate the actual size by counting the adding the size of each
|
|
|
|
// partition, plus the initial offset size is in media units
|
|
|
|
|
|
|
|
// The 3DS carts have up to 8 partitions in their carts
|
2016-06-07 02:34:07 -04:00
|
|
|
cartSize = ncsdHeader->offsetsize_table[0].offset;
|
2016-06-07 23:15:14 -04:00
|
|
|
for(size_t i = 0; i < 8; i++) {
|
2016-06-07 02:34:07 -04:00
|
|
|
cartSize += ncsdHeader->offsetsize_table[i].size;
|
|
|
|
}
|
2016-06-07 02:30:30 -04:00
|
|
|
|
2016-06-07 02:34:07 -04:00
|
|
|
Debug("Cart data size: %llu MB", (u64)cartSize * (u64)mediaUnit / 1024ull / 1024ull);
|
|
|
|
// Maximum number of blocks in a single file
|
2016-06-07 23:15:14 -04:00
|
|
|
file_max_blocks = 0xFFFFFFFFu / mediaUnit + mediaUnit; // 4GiB - 512
|
2016-06-07 02:34:07 -04:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
cartSize = ncsdHeader->media_size;
|
|
|
|
// Maximum number of blocks in a single file
|
|
|
|
file_max_blocks = 0x80000000u / mediaUnit; // 2GiB
|
|
|
|
}
|
2016-06-07 02:30:30 -04:00
|
|
|
|
2015-05-28 16:51:39 -03:00
|
|
|
struct Context context = {
|
2016-06-07 02:30:30 -04:00
|
|
|
.buffer = (u8*)target,
|
2015-05-28 16:51:39 -03:00
|
|
|
.buffer_size = target_buf_size,
|
|
|
|
.cart_size = cartSize,
|
|
|
|
.media_unit = mediaUnit,
|
|
|
|
};
|
|
|
|
|
|
|
|
u32 current_part = 0;
|
|
|
|
|
|
|
|
while (current_part * file_max_blocks < cartSize) {
|
|
|
|
// Create output file
|
|
|
|
char filename_buf[32];
|
|
|
|
char extension_digit = cartSize <= file_max_blocks ? 's' : '0' + current_part;
|
2016-06-07 02:30:30 -04:00
|
|
|
snprintf(filename_buf, sizeof(filename_buf), "/%.16s.3d%c", ncchHeader->product_code, extension_digit);
|
2015-05-28 16:51:39 -03:00
|
|
|
Debug("Writing to file: \"%s\"", filename_buf);
|
|
|
|
Debug("Change the SD card now and/or press a key.");
|
|
|
|
Debug("(Or SELECT to cancel)");
|
2015-06-01 00:34:37 -04:00
|
|
|
if (InputWait() & BUTTON_SELECT)
|
2015-05-28 16:51:39 -03:00
|
|
|
break;
|
|
|
|
|
|
|
|
if (f_mount(&fs, "0:", 0) != FR_OK) {
|
|
|
|
Debug("Failed to f_mount... Retrying");
|
|
|
|
wait_key();
|
|
|
|
goto cleanup_none;
|
|
|
|
}
|
2015-05-04 15:32:23 -07:00
|
|
|
|
2015-05-28 16:51:39 -03:00
|
|
|
if (f_open(&file, filename_buf, FA_READ | FA_WRITE | FA_CREATE_ALWAYS) != FR_OK) {
|
|
|
|
Debug("Failed to create file... Retrying");
|
|
|
|
wait_key();
|
|
|
|
goto cleanup_mount;
|
2015-05-25 04:52:32 -03:00
|
|
|
}
|
2015-05-04 15:32:23 -07:00
|
|
|
|
2015-05-28 16:51:39 -03:00
|
|
|
f_lseek(&file, 0);
|
2015-05-04 15:32:23 -07:00
|
|
|
|
2015-05-28 16:51:39 -03:00
|
|
|
u32 region_start = current_part * file_max_blocks;
|
|
|
|
u32 region_end = region_start + file_max_blocks;
|
|
|
|
if (region_end > cartSize)
|
|
|
|
region_end = cartSize;
|
2015-05-24 23:29:37 -03:00
|
|
|
|
2015-05-28 16:51:39 -03:00
|
|
|
if (dump_cart_region(region_start, region_end, &file, &context) < 0)
|
|
|
|
goto cleanup_file;
|
|
|
|
|
|
|
|
if (current_part == 0) {
|
|
|
|
// Write header - TODO: Not sure why this is done at the very end..
|
|
|
|
f_lseek(&file, 0x1000);
|
|
|
|
unsigned int written = 0;
|
2015-05-29 14:46:22 -03:00
|
|
|
// Fill the 0x1200-0x4000 unused area with 0xFF instead of random garbage.
|
2016-06-07 02:30:30 -04:00
|
|
|
memset((u8*)ncchHeaderData + 0x200, 0xFF, 0x3000 - 0x200);
|
|
|
|
f_write(&file, ncchHeader, 0x3000, &written);
|
2015-05-25 04:52:32 -03:00
|
|
|
}
|
2015-05-04 15:32:23 -07:00
|
|
|
|
2015-05-28 16:51:39 -03:00
|
|
|
Debug("Done!");
|
|
|
|
current_part += 1;
|
2015-05-04 15:32:23 -07:00
|
|
|
|
2015-05-25 04:52:32 -03:00
|
|
|
cleanup_file:
|
2015-05-28 16:51:39 -03:00
|
|
|
// Done, clean up...
|
|
|
|
f_sync(&file);
|
|
|
|
f_close(&file);
|
2015-05-24 23:29:37 -03:00
|
|
|
cleanup_mount:
|
2015-05-28 16:51:39 -03:00
|
|
|
f_mount(NULL, "0:", 0);
|
|
|
|
cleanup_none:
|
|
|
|
;
|
|
|
|
}
|
2015-05-04 15:32:23 -07:00
|
|
|
|
2015-05-28 16:51:39 -03:00
|
|
|
restart_prompt:
|
2015-05-24 23:29:37 -03:00
|
|
|
Debug("Press B to exit, any other key to restart.");
|
2015-06-01 00:34:37 -04:00
|
|
|
if (!(InputWait() & BUTTON_B))
|
2015-05-24 23:29:37 -03:00
|
|
|
goto restart_program;
|
|
|
|
|
2016-06-07 02:34:07 -04:00
|
|
|
free(ncchHeaderData);
|
|
|
|
free(target);
|
|
|
|
|
2015-12-16 15:47:09 +01:00
|
|
|
Reboot();
|
2015-05-04 15:32:23 -07:00
|
|
|
return 0;
|
|
|
|
}
|