Last active
January 12, 2025 06:48
-
-
Save dogtopus/637097a0b68ba503b8783f58e36f05ba to your computer and use it in GitHub Desktop.
Besta RTOS NAND dumper. Note that most boards I came across don't have proper support for the nand_* API so this likely wouldn't work as expected on boards other than BA110L, BA742 and KA745C.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 "A:\\nand.sav" | |
#define DUMP_PART_FILE "A:\\nand.bin" | |
#define PART_SIZE_MIB 1024 | |
int wait_for_key_press() { | |
ui_event_t uievent = {0}; | |
while (true) { | |
if ((TestPendEvent(&uievent) || TestKeyEvent(&uievent)) && GetEvent(&uievent) && uievent.event_type == UI_EVENT_TYPE_KEY) { | |
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