Skip to content

Instantly share code, notes, and snippets.

@dogtopus
Created March 2, 2024 08:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dogtopus/637097a0b68ba503b8783f58e36f05ba to your computer and use it in GitHub Desktop.
Save dogtopus/637097a0b68ba503b8783f58e36f05ba to your computer and use it in GitHub Desktop.
Besta RTOS NAND dumper
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <muteki/ui/event.h>
#include <muteki/ui/canvas.h>
#include <muteki/errno.h>
#include <muteki/ftl.h>
#include <mutekix/console.h>
#define dbgprintf mutekix_console_printf
#define dbgputs mutekix_console_puts
// NDSF
const unsigned int MAGIC = 0x4653444e;
#define CHECKPOINT_FILE "C:\\nand.sav"
#define DUMP_PART_FILE "C:\\nand.bin"
#define PART_SIZE_MIB 16
int wait_for_key_press() {
ui_event_t uievent = {0};
while (true) {
if ((TestPendEvent(&uievent) || TestKeyEvent(&uievent)) && GetEvent(&uievent)) {
return uievent.key_code0;
}
}
}
void print_nand_info() {
nand_params_t nand_info = {0};
nand_info.magic = 0x44;
int result = nand_get_params(0, &nand_info);
if (result < 0) {
dbgputs("Failed to read NAND info.");
return;
}
size_t nand_id_len_rational = (nand_info.nand_id_length > 8 || nand_info.nand_id_length <= 0) ? 8 : nand_info.nand_id_length;
dbgprintf("NAND ID: ");
for (size_t i = 0; i < nand_id_len_rational; i++) {
dbgprintf("%02x", nand_info.nand_id[i]);
}
dbgputs("");
dbgprintf("Name: %s\n", nand_info.name);
dbgprintf("Size: %zd MiB\n", nand_info.size_mib);
dbgprintf("Data page size: %zd\n", nand_info.data_page_size);
dbgprintf("Spare page size: %zd\n", nand_info.spare_page_size);
dbgprintf("Erase size: %zd\n", nand_info.erase_size);
dbgprintf("unk_0x1c: %d, unk_0x40: %d\n", nand_info.unk_0x1c, nand_info.unk_0x40);
dbgputs("");
}
size_t load_offset() {
unsigned int magic;
size_t offset;
FILE *f = fopen(CHECKPOINT_FILE, "rb");
if (f == NULL) {
return 0;
}
fread(&magic, 1, sizeof(magic), f);
if (magic != MAGIC) {
dbgputs("Invalid checkpoint file. Starting over.");
fclose(f);
return 0;
}
fread(&offset, 1, sizeof(offset), f);
fclose(f);
return offset;
}
void save_offset(size_t offset) {
FILE *f = fopen(CHECKPOINT_FILE, "wb");
if (f == NULL) {
dbgputs("Failed to save checkpoint file.");
return;
}
fwrite(&MAGIC, 1, sizeof(MAGIC), f);
fwrite(&offset, 1, sizeof(offset), f);
fclose(f);
}
void dump_nand() {
bool page_addressing = false;
size_t mib_offset = load_offset();
bool bad_read_in_chunk = false;
bool abort = false;
nand_params_t nand_info = {0};
nand_info.magic = 0x44;
int result = nand_get_params(0, &nand_info);
if (result < 0) {
dbgputs("Failed to read NAND info.");
return;
}
if (nand_info.nand_id_length == 0) {
dbgputs("Emulated NAND detected. Using page addressing mode.");
page_addressing = true;
}
size_t pages_per_mib = 0x100000 / nand_info.data_page_size;
size_t leftover_size_mib = nand_info.size_mib - mib_offset;
size_t actual_size_mib = (leftover_size_mib < PART_SIZE_MIB) ? leftover_size_mib : PART_SIZE_MIB;
size_t page_offset = mib_offset * pages_per_mib;
if (!page_addressing) {
page_offset *= nand_info.data_page_size;
}
void *buffer = malloc(nand_info.data_page_size);
if (buffer == NULL) {
dbgputs("Failed to allocate memory.");
return;
}
FILE *f = fopen(DUMP_PART_FILE, "wb");
if (f == NULL) {
dbgputs("Failed to open file.");
free(buffer);
return;
}
dbgprintf("Dumping NAND region %d MiB - %d MiB", mib_offset, mib_offset + actual_size_mib);
for (size_t rel_offset = 0; rel_offset < actual_size_mib * 0x100000; rel_offset += nand_info.data_page_size) {
if (page_addressing) {
int read_result = nand_read_page(0, page_offset, buffer, 1, 0);
if (read_result < 0) {
dbgputs("Unrecoverable read error. Aborting.");
abort = true;
break;
}
fwrite(buffer, 1, nand_info.data_page_size, f);
page_offset++;
} else {
size_t sub_page_offset = page_offset;
while (sub_page_offset < page_offset + nand_info.data_page_size) {
// Try to read a page. Continue when there's an error.
int read_result = nand_read_page(0, sub_page_offset, buffer, 1, 0);
if (read_result < 0) {
bad_read_in_chunk = true;
}
if (read_result < 0 || sub_page_offset != page_offset) {
// There's some error. Read sub-page by sub-page until we got the entire page.
fwrite(buffer, 1, 512, f);
sub_page_offset += 512;
} else {
// We got it on first try. Break out of the loop.
fwrite(buffer, 1, nand_info.data_page_size, f);
break;
}
}
page_offset += nand_info.data_page_size;
}
if (rel_offset != 0 && rel_offset % 0x100000 == 0) {
dbgprintf("%c", bad_read_in_chunk ? '!' : '.');
bad_read_in_chunk = false;
mib_offset++;
}
}
if (!abort) {
dbgprintf("%c", bad_read_in_chunk ? '!' : '.');
mib_offset++;
save_offset(mib_offset);
}
fclose(f);
free(buffer);
dbgputs("");
}
int main() {
bool waiting_for_option = true;
bool reprint_options = true;
mutekix_console_init(NULL);
dbgputs("Besta RTOS NAND Dumper");
while (waiting_for_option) {
if (reprint_options) {
dbgputs("Select an option:");
dbgputs("1: NAND Info");
dbgputs("2: Continue from last checkpoint");
dbgputs("3: Start over");
dbgputs("Q: Quit\n");
}
switch (wait_for_key_press()) {
case KEY_1:
print_nand_info();
reprint_options = true;
break;
case KEY_2:
dump_nand();
reprint_options = true;
break;
case KEY_3:
unlink(CHECKPOINT_FILE);
dump_nand();
reprint_options = true;
break;
case KEY_Q: // fall-through
case KEY_ESC:
waiting_for_option = false;
reprint_options = false;
break;
default:
reprint_options = false;
break;
}
ClearAllEvents();
}
mutekix_console_fini();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment