diff --git a/arm9/source/date.cpp b/arm9/source/date.cpp index 35c7c04..b999778 100644 --- a/arm9/source/date.cpp +++ b/arm9/source/date.cpp @@ -23,17 +23,17 @@ std::string RetTime() } /** - * Get the current time formatted for filenames. + * Get the current time formatted as specified. * @return std::string containing the time. */ -std::string RetTimeForFilename() +std::string RetTime(const char *format) { time_t raw; time(&raw); const struct tm *Time = localtime(&raw); - char tmp[8]; - strftime(tmp, sizeof(tmp), "%H%M%S", Time); + char tmp[64]; + strftime(tmp, sizeof(tmp), format, Time); return tmp; } diff --git a/arm9/source/date.h b/arm9/source/date.h index 84656ed..6e497b9 100644 --- a/arm9/source/date.h +++ b/arm9/source/date.h @@ -10,9 +10,9 @@ std::string RetTime(); /** - * Get the current time formatted for filenames. + * Get the current time formatted as specified. * @return std::string containing the time. */ -std::string RetTimeForFilename(); +std::string RetTime(const char *format); #endif // DATE_H diff --git a/arm9/source/driveMenu.cpp b/arm9/source/driveMenu.cpp index 451ad12..62ab830 100644 --- a/arm9/source/driveMenu.cpp +++ b/arm9/source/driveMenu.cpp @@ -35,6 +35,7 @@ #include "fileOperations.h" #include "font.h" #include "language.h" +#include "read_card.h" #include "startMenu.h" #define ENTRIES_START_ROW 1 @@ -59,6 +60,8 @@ bool flashcardMountSkipped = true; static bool flashcardMountRan = true; static int dmCursorPosition = 0; static std::vector dmOperations; +static char romTitle[13] = {0}; +static u32 romSize, romSizeTrimmed; static u8 gbaFixedValue = 0; @@ -104,7 +107,7 @@ void dm_drawTopScreen(void) { || (ramdrive2Mounted && nitroCurrentDrive == Drive::ramDrive2) || (nandMounted && nitroCurrentDrive == Drive::nand) || (imgMounted && nitroCurrentDrive == Drive::fatImg))) - font->print(256 - font->width(), i + 1, true, "[x]", Alignment::right, pal); + font->print(-1, i + 1, true, "[x]", Alignment::right, pal); break; case DriveMenuOperation::fatImage: if ((sdMounted && imgCurrentDrive == Drive::sdCard) @@ -115,16 +118,14 @@ void dm_drawTopScreen(void) { font->printf(0, i + 1, true, Alignment::left, pal, STR_FAT_LABEL_NAMED.c_str(), imgLabel[0] == 0 ? STR_UNTITLED.c_str() : imgLabel); } else { font->print(0, i + 1, true, STR_FAT_LABEL, Alignment::left, pal); - font->print(256 - font->width(), i + 1, true, "[x]", Alignment::right, pal); + font->print(-1, i + 1, true, "[x]", Alignment::right, pal); } break; case DriveMenuOperation::gbaCart: - font->print(0, i + 1, true, STR_GBA_GAMECART, Alignment::left, pal); - if (gbaFixedValue != 0x96) - font->print(256 - font->width(), i + 1, true, "[x]", Alignment::right, pal); + font->printf(0, i + 1, true, Alignment::left, pal, STR_GBA_GAMECART.c_str(), romTitle); break; case DriveMenuOperation::ndsCard: - font->print(0, i + 1, true, STR_NDS_GAMECARD, Alignment::left, pal); + font->printf(0, i + 1, true, Alignment::left, pal, STR_NDS_GAMECARD.c_str(), romTitle); break; case DriveMenuOperation::none: break; @@ -176,16 +177,16 @@ void dm_drawBottomScreen(void) { font->printf(0, 2, false, Alignment::left, Palette::white, STR_N_FREE.c_str(), getDriveBytes(getBytesFree("fat:/")).c_str()); break; case DriveMenuOperation::gbaCart: - font->print(0, 0, false, STR_GBA_GAMECART); - font->print(0, 1, false, STR_GBA_GAME); + font->printf(0, 0, false, Alignment::left, Palette::white, STR_GBA_GAMECART.c_str(), romTitle); + font->printf(0, 1, false, Alignment::left, Palette::white, STR_GBA_GAME.c_str(), getBytes(romSize).c_str()); break; case DriveMenuOperation::nitroFs: font->print(0, 0, false, STR_NITROFS_LABEL); font->print(0, 1, false, STR_GAME_VIRTUAL); break; case DriveMenuOperation::ndsCard: - font->print(0, 0, false, STR_NDS_GAMECARD); - font->print(0, 1, false, STR_NDS_GAME); + font->printf(0, 0, false, Alignment::left, Palette::white, STR_NDS_GAMECARD.c_str(), romTitle); + font->printf(0, 1, false, Alignment::left, Palette::white, STR_NDS_GAME.c_str(), getBytes(romSize).c_str(), getBytes(romSizeTrimmed).c_str()); break; case DriveMenuOperation::ramDrive1: font->print(0, 0, false, STR_RAMDRIVE1_LABEL); @@ -237,10 +238,39 @@ void driveMenu (void) { dmOperations.push_back(DriveMenuOperation::nitroFs); if (expansionPakFound || (io_dldi_data->ioInterface.features & FEATURE_SLOT_GBA) - || (isDSiMode() && !arm7SCFGLocked && !(REG_SCFG_MC & BIT(0)))) + || (isDSiMode() && !arm7SCFGLocked && !(REG_SCFG_MC & BIT(0)))) { dmOperations.push_back(DriveMenuOperation::ndsCard); - if (!isDSiMode() && isRegularDS) + if(romTitle[0] == 0) { + sNDSHeaderExt ndsHeader; + cardInit(&ndsHeader); + tonccpy(romTitle, ndsHeader.gameTitle, 12); + romSize = 0x20000 << ndsHeader.deviceSize; + romSizeTrimmed = (isDSiMode() && (ndsHeader.unitCode != 0) && (ndsHeader.twlRomSize > 0)) + ? ndsHeader.twlRomSize : ndsHeader.romSize + 0x88; + } + } else if (!isDSiMode() && isRegularDS && gbaFixedValue == 0x96) { dmOperations.push_back(DriveMenuOperation::gbaCart); + if(romTitle[0] == 0) { + tonccpy(romTitle, (char*)0x080000A0, 12); + romSize = 0; + for (romSize = (1 << 20); romSize < (1 << 25); romSize <<= 1) { + vu16 *rompos = (vu16*)(0x08000000 + romSize); + bool romend = true; + for (int j = 0; j < 0x1000; j++) { + if (rompos[j] != j) { + romend = false; + break; + } + } + if (romend) + break; + } + romSizeTrimmed = romSize; + } + } else if (romTitle[0] != 0) { + romTitle[0] = 0; + romSizeTrimmed = romSize = 0; + } dm_drawBottomScreen(); dm_drawTopScreen(); diff --git a/arm9/source/dumpOperations.cpp b/arm9/source/dumpOperations.cpp index 5f7e388..bf5f98b 100644 --- a/arm9/source/dumpOperations.cpp +++ b/arm9/source/dumpOperations.cpp @@ -9,12 +9,15 @@ #include "read_card.h" #include "tonccpy.h" #include "language.h" +#include "screenshot.h" +#include "version.h" #include #include #include -#include #include +#include +#include extern u8 copyBuf[]; @@ -22,6 +25,112 @@ extern bool expansionPakFound; static sNDSHeaderExt ndsCardHeader; +enum DumpOption { + none = 0, + rom = 1, + romTrimmed = 2, + save = 4, + metadata = 8, + all = rom | save | metadata, + allTrimmed = romTrimmed | save | metadata +}; + +DumpOption dumpMenu(std::vector allowedOptions, const char *dumpName) { + u16 pressed = 0, held = 0; + int optionOffset = 0; + + char dumpToStr[256]; + snprintf(dumpToStr, sizeof(dumpToStr), STR_DUMP_TO.c_str(), dumpName, sdMounted ? "sd" : "fat"); + + int y = font->calcHeight(dumpToStr) + 1; + + while (true) { + font->clear(false); + + font->print(0, 0, false, dumpToStr); + + int row = y; + for(DumpOption option : allowedOptions) { + switch(option) { + case DumpOption::all: + font->print(3, row++, false, STR_DUMP_ALL); + break; + case DumpOption::allTrimmed: + font->print(3, row++, false, STR_DUMP_ALL_TRIMMED); + break; + case DumpOption::rom: + font->print(3, row++, false, STR_DUMP_ROM); + break; + case DumpOption::romTrimmed: + font->print(3, row++, false, STR_DUMP_ROM_TRIMMED); + break; + case DumpOption::save: + font->print(3, row++, false, STR_DUMP_SAVE); + break; + case DumpOption::metadata: + font->print(3, row++, false, STR_DUMP_METADATA); + break; + case DumpOption::none: + row++; + break; + } + } + + font->print(3, ++row, false, STR_A_SELECT_B_CANCEL); + + // Show cursor + font->print(0, y + optionOffset, false, "->"); + + font->update(false); + + // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do + do { + // Print time + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); + + scanKeys(); + pressed = keysDownRepeat(); + held = keysHeld(); + swiWaitForVBlank(); + } while (!(pressed & (KEY_UP| KEY_DOWN | KEY_A | KEY_B | KEY_L)) +#ifdef SCREENSWAP + && !(pressed & KEY_TOUCH) +#endif + ); + + if (pressed & KEY_UP) + optionOffset--; + if (pressed & KEY_DOWN) + optionOffset++; + + if (optionOffset < 0) // Wrap around to bottom of list + optionOffset = allowedOptions.size() - 1; + + if (optionOffset >= (int)allowedOptions.size()) // Wrap around to top of list + optionOffset = 0; + + if (pressed & KEY_A) + return allowedOptions[optionOffset]; + + if (pressed & KEY_B) + return DumpOption::none; + +#ifdef SCREENSWAP + // Swap screens + if (pressed & KEY_TOUCH) { + screenSwapped = !screenSwapped; + screenSwapped ? lcdMainOnBottom() : lcdMainOnTop(); + } +#endif + + // Make a screenshot + if ((held & KEY_R) && (pressed & KEY_L)) { + screenshot(); + } + } +} + void dumpFailMsg(std::string_view msg) { font->clear(false); font->print(0, 0, false, msg, Alignment::left, Palette::red); @@ -423,26 +532,66 @@ void ndsCardSaveRestore(const char *filename) { } void ndsCardDump(void) { - int pressed = 0; - //bool showGameCardMsgAgain = false; - font->clear(false); - font->printf(0, 0, false, Alignment::left, Palette::white, STR_DUMP_NDS_ROM_TO.c_str(), sdMounted ? "sd" : "fat"); - font->print(0, 2, false, STR_A_YES_Y_TRIM_B_NO_X_SAVE_ONLY); + font->print(0, 0, false, STR_LOADING); font->update(false); - // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do - do { - // Print time - font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); - font->update(true); + std::vector allowedOptions = {DumpOption::all}; + u8 allowedBitfield = DumpOption::metadata; + char gameTitle[13] = {0}; + char gameCode[7] = {0}; + char fileName[32] = {0}; + bool spiSave = cardEepromGetTypeFixed() != -1; + bool nandSave = false; - scanKeys(); - pressed = keysDownRepeat(); - swiWaitForVBlank(); - } while (!(pressed & (KEY_A | KEY_Y | KEY_B | KEY_X))); + int cardInited = cardInit(&ndsCardHeader); + if(cardInited == 0) { + allowedOptions.push_back(DumpOption::allTrimmed); + allowedOptions.push_back(DumpOption::rom); + allowedOptions.push_back(DumpOption::romTrimmed); + allowedBitfield |= DumpOption::rom | DumpOption::romTrimmed; - if (pressed & KEY_X) { + nandSave = cardNandGetSaveSize() != 0; + + if(spiSave || nandSave) { + allowedOptions.push_back(DumpOption::save); + allowedBitfield |= DumpOption::save; + } + } + allowedOptions.push_back(DumpOption::metadata); + + tonccpy(gameTitle, ndsCardHeader.gameTitle, 12); + tonccpy(gameCode, ndsCardHeader.gameCode, 6); + if (gameTitle[0] == 0 || gameTitle[0] == 0x2E || gameTitle[0] == 0xFF) { + sprintf(gameTitle, "NO-TITLE"); + } else { + for(uint i = 0; i < sizeof(gameTitle); i++) { + switch(gameTitle[i]) { + case '>': + case '<': + case ':': + case '"': + case '/': + case '\x5C': + case '|': + case '?': + case '*': + gameTitle[i] = '_'; + } + } + } + if (gameCode[0] == 0 || gameCode[0] == 0x23 || gameCode[0] == 0xFF) { + sprintf(gameCode, "NONE00"); + } + sprintf(fileName, "%s_%s_%02X", gameTitle, gameCode, ndsCardHeader.romversion); + + DumpOption dumpOption = dumpMenu(allowedOptions, fileName); + + if(dumpOption & DumpOption::romTrimmed) + strcat(fileName, "_trim"); + + // Ensure directories exist + if((dumpOption & allowedBitfield) != DumpOption::none) { char folderPath[2][256]; sprintf(folderPath[0], "%s:/gm9i", (sdMounted ? "sd" : "fat")); sprintf(folderPath[1], "%s:/gm9i/out", (sdMounted ? "sd" : "fat")); @@ -458,272 +607,95 @@ void ndsCardDump(void) { font->update(false); mkdir(folderPath[1], 0777); } + } - if (cardInit(&ndsCardHeader) != 0) { - dumpFailMsg(STR_UNABLE_TO_DUMP_SAVE); - return; - } - char gameTitle[13] = {0}; - tonccpy(gameTitle, ndsCardHeader.gameTitle, 12); - char gameCode[7] = {0}; - tonccpy(gameCode, ndsCardHeader.gameCode, 6); - char destSavPath[256]; - sprintf(destSavPath, "%s:/gm9i/out/%s_%s_%02x.sav", (sdMounted ? "sd" : "fat"), gameTitle, gameCode, ndsCardHeader.romversion); - ndsCardSaveDump(destSavPath); - } else if ((pressed & KEY_A) || (pressed & KEY_Y)) { - bool trimRom = (pressed & KEY_Y); - char folderPath[2][256]; - sprintf(folderPath[0], "%s:/gm9i", (sdMounted ? "sd" : "fat")); - sprintf(folderPath[1], "%s:/gm9i/out", (sdMounted ? "sd" : "fat")); - if (access(folderPath[0], F_OK) != 0) { - font->clear(false); - font->print(0, 0, false, STR_CREATING_DIRECTORY); - font->update(false); - mkdir(folderPath[0], 0777); - } - if (access(folderPath[1], F_OK) != 0) { - font->clear(false); - font->print(0, 0, false, STR_CREATING_DIRECTORY); - font->update(false); - mkdir(folderPath[1], 0777); - } - /*if (expansionPakFound) { - font->clear(false) - font->print(0, 0, false, "Please switch to the game card, then press A."); - font->update(false); - //flashcardUnmount(); - io_dldi_data->ioInterface.shutdown(); + // Dump ROM + if((dumpOption & allowedBitfield) & (DumpOption::rom | DumpOption::romTrimmed)) { + font->clear(false); + font->printf(0, 0, false, Alignment::left, Palette::white, STR_NDS_IS_DUMPING.c_str(), fileName); + font->print(0, 2, false, STR_DO_NOT_REMOVE_CARD); + font->update(false); - // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do - do { + // Determine ROM size + u32 romSize; + if (dumpOption & DumpOption::romTrimmed) { + romSize = (isDSiMode() && (ndsCardHeader.unitCode != 0) && (ndsCardHeader.twlRomSize > 0)) + ? ndsCardHeader.twlRomSize : ndsCardHeader.romSize+0x88; + } else { + romSize = 0x20000 << ndsCardHeader.deviceSize; + } + + // Dump! + char destPath[256]; + sprintf(destPath, "%s:/gm9i/out/%s.nds", (sdMounted ? "sd" : "fat"), fileName); + u32 currentSize = romSize; + FILE* destinationFile = fopen(destPath, "wb"); + if (destinationFile) { + font->print(0, 4, false, STR_PROGRESS); + font->print(0, 5, false, "["); + font->print(-1, 5, false, "]"); + for (u32 src = 0; src < romSize; src += 0x8000) { // Print time font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); font->update(true); - scanKeys(); - pressed = keysDownRepeat(); - swiWaitForVBlank(); - } while (!(pressed & KEY_A)); - }*/ + font->print((src / (romSize / (SCREEN_COLS - 2))) + 1, 5, false, "="); + font->printf(0, 6, false, Alignment::left, Palette::white, STR_N_OF_N_BYTES.c_str(), src, romSize); + font->update(false); - int cardInited = cardInit(&ndsCardHeader); - char gameTitle[13] = {0}; - char gameCode[7] = {0}; - char destPath[256] = {0}; - char destSavPath[256] = {0}; - char fileName[32] = {0}; - tonccpy(gameTitle, ndsCardHeader.gameTitle, 12); - tonccpy(gameCode, ndsCardHeader.gameCode, 6); - if (gameTitle[0] == 0 || gameTitle[0] == 0x2E || gameTitle[0] == 0xFF) { - sprintf(gameTitle, "NO-TITLE"); - } else { - for(uint i = 0; i < sizeof(gameTitle); i++) { - switch(gameTitle[i]) { - case '>': - case '<': - case ':': - case '"': - case '/': - case '\x5C': - case '|': - case '?': - case '*': - gameTitle[i] = '_'; + for (u32 i = 0; i < 0x8000; i += 0x200) { + cardRead (src+i, copyBuf+i, false); } - } - } - if (gameCode[0] == 0 || gameCode[0] == 0x23 || gameCode[0] == 0xFF) { - sprintf(gameCode, "NONE00"); - } - sprintf(fileName, "%s_%s_%02x%s", gameTitle, gameCode, ndsCardHeader.romversion, (trimRom ? "_trim" : "")); - sprintf(destPath, "%s:/gm9i/out/%s.nds", (sdMounted ? "sd" : "fat"), fileName); - sprintf(destSavPath, "%s:/gm9i/out/%s.sav", (sdMounted ? "sd" : "fat"), fileName); - - if (cardInited == 0) { - font->clear(false); - font->printf(0, 0, false, Alignment::left, Palette::white, STR_NDS_IS_DUMPING.c_str(), fileName); - font->print(0, 2, false, STR_DO_NOT_REMOVE_CARD); - font->update(false); - } else { - font->clear(false); - font->print(0, 0, false, STR_UNABLE_TO_DUMP_ROM); - font->update(false); - for (int i = 0; i < 60*2; i++) { - swiWaitForVBlank(); - } - return; - } - // Determine ROM size - u32 romSize = 0; - if (trimRom) { - romSize = (isDSiMode() && (ndsCardHeader.unitCode != 0) && (ndsCardHeader.twlRomSize > 0)) - ? ndsCardHeader.twlRomSize : ndsCardHeader.romSize+0x88; - } else switch (ndsCardHeader.deviceSize) { - case 0x00: - romSize = 0x20000; - break; - case 0x01: - romSize = 0x40000; - break; - case 0x02: - romSize = 0x80000; - break; - case 0x03: - romSize = 0x100000; - break; - case 0x04: - romSize = 0x200000; - break; - case 0x05: - romSize = 0x400000; - break; - case 0x06: - romSize = 0x800000; - break; - case 0x07: - romSize = 0x1000000; - break; - case 0x08: - romSize = 0x2000000; - break; - case 0x09: - romSize = 0x4000000; - break; - case 0x0A: - romSize = 0x8000000; - break; - case 0x0B: - romSize = 0x10000000; - break; - case 0x0C: - romSize = 0x20000000; - break; - } - // Dump! - /*if (expansionPakFound) { - u32 currentSize = ((expansionPakFound && romSize > 0x800000) ? 0x800000 : romSize); - u32 src = 0; - u32 writeSrc = 0; - FILE* destinationFile; - bool destinationFileOpened = false; - while (currentSize > 0) { - if (showGameCardMsgAgain) { - iprintf ("\x1b[8;0H"); - iprintf (" \n"); - - iprintf("\x1b[15;0H"); - iprintf("Please switch to the\ngame card, then press A.\n"); - //flashcardUnmount(); - io_dldi_data->ioInterface.shutdown(); - - // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do - do { - // Print time - font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); - font->update(true); - - scanKeys(); - pressed = keysDownRepeat(); - swiWaitForVBlank(); - } while (!(pressed & KEY_A)); - - consoleSelect(&bottomConsole); - iprintf ("\x1b[15;0H"); - iprintf(" \n \n"); - cardInit(&ndsCardHeader); + if (fwrite(copyBuf, 1, (currentSize>=0x8000 ? 0x8000 : currentSize), destinationFile) < 1) { + dumpFailMsg(STR_FAILED_TO_DUMP_ROM); + break; } - showGameCardMsgAgain = true; - - // Read from game card - for (src = src; src < currentSize; src += 0x200) { - // Print time - font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); - font->update(true); - - consoleSelect(&bottomConsole); - iprintf ("\x1B[47m"); // Print foreground white color - iprintf ("\x1b[8;0H"); - iprintf ("Read:\n"); - iprintf ("%i/%i Bytes ", (int)src, (int)romSize); - cardRead (src, (void*)0x09000000+(src % 0x800000), false); - } - iprintf("\x1b[15;0H"); - iprintf("Please switch to the\nflashcard, then press A.\n"); - // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do - do { - // Print time - font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); - font->update(true); - - scanKeys(); - pressed = keysDownRepeat(); - swiWaitForVBlank(); - } while (!(pressed & KEY_A)); - - consoleSelect(&bottomConsole); - iprintf("\x1b[15;0H"); - iprintf(" \n \n"); - - iprintf ("\x1B[47m"); // Print foreground white color - iprintf ("\x1b[11;0H"); - iprintf ("Written:\n"); - - // Write back to flashcard - cardInit(&ndsCardHeader); - io_dldi_data->ioInterface.startup(); - //flashcardMounted = flashcardMount(); - if (!destinationFileOpened) { - destinationFile = fopen(destPath, "wb"); - destinationFileOpened = true; - } - for (writeSrc = writeSrc; writeSrc < currentSize; writeSrc += 0x200) { - // Print time - font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); - font->update(true); - - consoleSelect(&bottomConsole); - printf ("\x1B[47m"); // Print foreground white color - printf ("\x1b[12;0H"); - printf ("%i/%i Bytes ", (int)writeSrc, (int)romSize); - fwrite((void*)0x09000000+(writeSrc % 0x800000), 1, currentSize, destinationFile); - } - - currentSize -= 0x800000; + currentSize -= 0x8000; } fclose(destinationFile); - } else {*/ - remove(destPath); - u32 currentSize = romSize; - FILE* destinationFile = fopen(destPath, "wb"); - if (destinationFile) { + } else { + dumpFailMsg(STR_FAILED_TO_DUMP_ROM); + } + } - font->print(0, 4, false, STR_PROGRESS); - font->print(0, 5, false, "["); - font->print(-1, 5, false, "]"); - for (u32 src = 0; src < romSize; src += 0x8000) { - // Print time - font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); - font->update(true); + // Dump save + if ((dumpOption & allowedBitfield) & DumpOption::save) { + char destPath[256]; + sprintf(destPath, "%s:/gm9i/out/%s.sav", (sdMounted ? "sd" : "fat"), fileName); + ndsCardSaveDump(destPath); + } - font->print((src / (romSize / (SCREEN_COLS - 2))) + 1, 5, false, "="); - font->printf(0, 6, false, Alignment::left, Palette::white, STR_N_OF_N_BYTES.c_str(), src, romSize); - font->update(false); + // Dump metadata + if ((dumpOption & allowedBitfield) & DumpOption::metadata) { + font->clear(false); + font->print(0, 0, false, STR_DUMPING_METADATA); + font->update(false); - for (u32 i = 0; i < 0x8000; i += 0x200) { - cardRead (src+i, copyBuf+i, false); - } - if (fwrite(copyBuf, 1, (currentSize>=0x8000 ? 0x8000 : currentSize), destinationFile) < 1) { - dumpFailMsg(STR_FAILED_TO_DUMP_ROM); - break; - } - currentSize -= 0x8000; - } - fclose(destinationFile); - } else { - dumpFailMsg(STR_FAILED_TO_DUMP_ROM); - } - ndsCardSaveDump(destSavPath); - //} + char destPath[256]; + sprintf(destPath, "%s:/gm9i/out/%s.txt", (sdMounted ? "sd" : "fat"), fileName); + FILE* destinationFile = fopen(destPath, "wb"); + if (destinationFile) { + fprintf(destinationFile, + "Title String : %.12s\n" + "Product Code : %.6s\n" + "Revision : %u\n" + "Cart ID : %08lX\n" + "Platform : %s\n" + "Save Type : %s\n", + gameTitle, gameCode, ndsCardHeader.romversion, cardGetId(), + (ndsCardHeader.unitCode == 0x2) ? "DSi Enhanced" : (ndsCardHeader.unitCode == 0x3) ? "DSi Exclusive" : "DS", + spiSave ? "SPI" : (nandSave ? "RETAIL_NAND" : "NONE")); + + if(spiSave) + fprintf(destinationFile, "Save chip ID : 0x%06lX\n", cardEepromReadID()); + + fprintf(destinationFile, + "Timestamp : %s\n" + "GM9i Version : " VER_NUMBER "\n", + RetTime("%Y-%m-%d %H:%M:%S").c_str()); + + fclose(destinationFile); + } } } @@ -742,7 +714,6 @@ void gbaCartSaveDump(const char *filename) { u8 *buffer = new u8[size]; gbaReadSave(buffer, 0, size, type); - remove(filename); FILE *destinationFile = fopen(filename, "wb"); fwrite(buffer, 1, size, destinationFile); fclose(destinationFile); @@ -825,36 +796,31 @@ void readChange(void) { } void gbaCartDump(void) { - int pressed = 0; - font->clear(false); - font->printf(0, 0, false, Alignment::left, Palette::white, STR_DUMP_GBA_ROM_TO.c_str(), sdMounted ? "sd" : "fat"); - font->print(0, 2, false, STR_A_YES_B_NO_X_SAVE_ONLY); + font->print(0, 0, false, STR_LOADING); font->update(false); - // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do - do { - // Print time - font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); - font->update(true); + std::vector allowedOptions = {DumpOption::all, DumpOption::rom}; + u8 allowedBitfield = DumpOption::rom | DumpOption::metadata; + char gameTitle[13] = {0}; + char gameCode[7] = {0}; + char fileName[32] = {0}; + saveTypeGBA saveType = gbaGetSaveType(); - scanKeys(); - pressed = keysDownRepeat(); - swiWaitForVBlank(); - } while (!(pressed & (KEY_A | KEY_B | KEY_X))); + if(saveType != saveTypeGBA::SAVE_GBA_NONE) { + allowedOptions.push_back(DumpOption::save); + allowedBitfield |= DumpOption::save; + } + allowedOptions.push_back(DumpOption::metadata); // Get name - char gbaHeaderGameTitle[13] = {0}; - tonccpy(gbaHeaderGameTitle, (u8*)(0x080000A0), 12); - char gbaHeaderGameCode[5] = {0}; - tonccpy(gbaHeaderGameCode, (u8*)(0x080000AC), 4); - char gbaHeaderMakerCode[3] = {0}; - tonccpy(gbaHeaderMakerCode, (u8*)(0x080000B0), 2); - if (gbaHeaderGameTitle[0] == 0 || gbaHeaderGameTitle[0] == 0xFF) { - sprintf(gbaHeaderGameTitle, "NO-TITLE"); + tonccpy(gameTitle, (u8*)(0x080000A0), 12); + tonccpy(gameCode, (u8*)(0x080000AC), 6); + if (gameTitle[0] == 0 || gameTitle[0] == 0xFF) { + sprintf(gameTitle, "NO-TITLE"); } else { - for(uint i = 0; i < sizeof(gbaHeaderGameTitle); i++) { - switch(gbaHeaderGameTitle[i]) { + for(uint i = 0; i < sizeof(gameTitle); i++) { + switch(gameTitle[i]) { case '>': case '<': case ':': @@ -864,37 +830,39 @@ void gbaCartDump(void) { case '|': case '?': case '*': - gbaHeaderGameTitle[i] = '_'; + gameTitle[i] = '_'; } } } - if (gbaHeaderGameCode[0] == 0 || gbaHeaderGameCode[0] == 0xFF) { - sprintf(gbaHeaderGameCode, "NONE"); + if (gameCode[0] == 0 || gameCode[0] == 0xFF) { + sprintf(gameCode, "NONE00"); } - if (gbaHeaderMakerCode[0] == 0 || gbaHeaderMakerCode[0] == 0xFF) { - sprintf(gbaHeaderMakerCode, "00"); - } - u8 gbaHeaderSoftwareVersion = *(u8*)(0x080000BC); - char fileName[32] = {0}; - sprintf(fileName, "%s_%s%s_%x", gbaHeaderGameTitle, gbaHeaderGameCode, gbaHeaderMakerCode, gbaHeaderSoftwareVersion); + u8 romVersion = *(u8*)(0x080000BC); + sprintf(fileName, "%s_%s_%02X", gameTitle, gameCode, romVersion); - if (pressed & KEY_A) { - if (access("fat:/gm9i", F_OK) != 0) { + DumpOption dumpOption = dumpMenu(allowedOptions, fileName); + + // Ensure directories exist + if((dumpOption & allowedBitfield) != DumpOption::none) { + char folderPath[2][256]; + sprintf(folderPath[0], "%s:/gm9i", (sdMounted ? "sd" : "fat")); + sprintf(folderPath[1], "%s:/gm9i/out", (sdMounted ? "sd" : "fat")); + if (access(folderPath[0], F_OK) != 0) { font->clear(false); font->print(0, 0, false, STR_CREATING_DIRECTORY); font->update(false); - mkdir("fat:/gm9i", 0777); + mkdir(folderPath[0], 0777); } - if (access("fat:/gm9i/out", F_OK) != 0) { + if (access(folderPath[1], F_OK) != 0) { font->clear(false); font->print(0, 0, false, STR_CREATING_DIRECTORY); font->update(false); - mkdir("fat:/gm9i/out", 0777); + mkdir(folderPath[1], 0777); } + } - char destPath[256] = {0}; - sprintf(destPath, "fat:/gm9i/out/%s.gba", fileName); - + // Dump ROM + if ((dumpOption & allowedBitfield) & DumpOption::rom) { font->clear(false); font->printf(0, 0, false, Alignment::left, Palette::white, STR_GBA_IS_DUMPING.c_str(), fileName); font->print(0, 2, false, STR_DO_NOT_REMOVE_CART); @@ -916,7 +884,6 @@ void gbaCartDump(void) { } // Dump! - remove(destPath); // Reset data at virtual address u32 rstCmd[4] = { 0x11, // Command @@ -925,6 +892,9 @@ void gbaCartDump(void) { 0x8, // Size (in 0x200 byte blocks) }; writeChange(rstCmd); + + char destPath[256]; + sprintf(destPath, "fat:/gm9i/out/%s.gba", fileName); FILE* destinationFile = fopen(destPath, "wb"); if (destinationFile) { bool failed = false; @@ -989,23 +959,48 @@ void gbaCartDump(void) { } } - if(pressed & (KEY_A | KEY_X)) { - if (access("fat:/gm9i", F_OK) != 0) { - font->clear(false); - font->print(0, 0, false, STR_CREATING_DIRECTORY); - font->update(false); - mkdir("fat:/gm9i", 0777); - } - if (access("fat:/gm9i/out", F_OK) != 0) { - font->clear(false); - font->print(0, 0, false, STR_CREATING_DIRECTORY); - font->update(false); - mkdir("fat:/gm9i/out", 0777); - } + // Dump save + if((dumpOption & allowedBitfield) & DumpOption::save) { + char destPath[256]; + sprintf(destPath, "fat:/gm9i/out/%s.sav", fileName); + gbaCartSaveDump(destPath); + } - char destSavPath[256] = {0}; - sprintf(destSavPath, "fat:/gm9i/out/%s.sav", fileName); + // Dump metadata + if ((dumpOption & allowedBitfield) & DumpOption::metadata) { + font->clear(false); + font->print(0, 0, false, STR_DUMPING_METADATA); + font->update(false); - gbaCartSaveDump(destSavPath); + char destPath[256]; + sprintf(destPath, "%s:/gm9i/out/%s.txt", (sdMounted ? "sd" : "fat"), fileName); + FILE* destinationFile = fopen(destPath, "wb"); + if (destinationFile) { + fprintf(destinationFile, + "Title String : %.12s\n" + "Product Code : %.6s\n" + "Revision : %u\n" + "Platform : GBA\n", + gameTitle, gameCode, romVersion); + + fprintf(destinationFile, + "Save Type : %s\n", + saveType == SAVE_GBA_NONE ? "NONE" : + saveType == SAVE_GBA_EEPROM_05 ? "EEPROM 4K" : + saveType == SAVE_GBA_EEPROM_8 ? "EEPROM 64K" : + saveType == SAVE_GBA_SRAM_32 ? "SRAM" : + saveType == SAVE_GBA_FLASH_64 ? "FLASH 512K" : + saveType == SAVE_GBA_FLASH_128 ? "FLASH 1M" : "UNK"); + + if(saveType == SAVE_GBA_FLASH_64 || saveType == SAVE_GBA_FLASH_128) + fprintf(destinationFile, "Save chip ID : 0x%04X\n", gbaGetFlashId()); + + fprintf(destinationFile, + "Timestamp : %s\n" + "GM9i Version : " VER_NUMBER "\n", + RetTime("%Y-%m-%d %H:%M:%S").c_str()); + + fclose(destinationFile); + } } } diff --git a/arm9/source/file_browse.cpp b/arm9/source/file_browse.cpp index 267f9b5..a27b050 100644 --- a/arm9/source/file_browse.cpp +++ b/arm9/source/file_browse.cpp @@ -303,7 +303,7 @@ FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { switch(operations[optionOffset]) { case FileOperation::bootFile: { applaunch = true; - font->print(3, optionOffset + y, false, STR_NOW_LOADING); + font->print(3, optionOffset + y, false, STR_LOADING); font->update(false); break; } case FileOperation::bootstrapFile: { diff --git a/arm9/source/gba.cpp b/arm9/source/gba.cpp index 89993dc..ba74461 100644 --- a/arm9/source/gba.cpp +++ b/arm9/source/gba.cpp @@ -222,7 +222,7 @@ bool gbaReadSave(u8 *dst, u32 src, u32 len, saveTypeGBA type) return true; } -bool gbaIsAtmel() +u16 gbaGetFlashId() { *(vu8*)0x0a005555 = 0xaa; swiDelay(10); @@ -244,10 +244,13 @@ bool gbaIsAtmel() //char txt[128]; // sprintf(txt, "Man: %x, Dev: %x", man, dev); // displayStateF(STR_STR, txt); - if ((man == 0x3d) && (dev == 0x1f)) - return true; - else - return false; + + return dev << 8 | man; +} + +bool gbaIsAtmel() +{ + return gbaGetFlashId() == 0x3d1f; } bool gbaWriteSave(u32 dst, u8 *src, u32 len, saveTypeGBA type) diff --git a/arm9/source/gba.h b/arm9/source/gba.h index 55a7566..6a22901 100644 --- a/arm9/source/gba.h +++ b/arm9/source/gba.h @@ -40,6 +40,7 @@ bool gbaIsGame(); saveTypeGBA gbaGetSaveType(); uint32 gbaGetSaveSize(saveTypeGBA type = SAVE_GBA_NONE); uint32 gbaGetSaveSizeLog2(saveTypeGBA type = SAVE_GBA_NONE); +u16 gbaGetFlashId(); bool gbaReadSave(u8 *dst, u32 src, u32 len, saveTypeGBA type); bool gbaWriteSave(u32 dst, u8 *src, u32 len, saveTypeGBA type); diff --git a/arm9/source/language.inl b/arm9/source/language.inl index 644ee1a..6f746fe 100644 --- a/arm9/source/language.inl +++ b/arm9/source/language.inl @@ -4,7 +4,7 @@ STRING(UNTITLED, "UNTITLED") STRING(ROOT, "[root]") STRING(DIR, "(dir)") STRING(TIME_FORMAT, "%k:%M") -STRING(NOW_LOADING, "Now loading...") +STRING(LOADING, "Loading...") STRING(CREATING_DIRECTORY, "Creating directory...") STRING(ENTERING_DIRECTORY, "Entering directory...") STRING(COPYING, "Copying...") @@ -45,15 +45,15 @@ STRING(SYSNAND_LABEL, "[nand:] SYSNAND") STRING(NITROFS_LABEL, "[nitro:] NDS GAME IMAGE") STRING(FAT_LABEL_NAMED, "[nitro:] FAT IMAGE (%s)") STRING(FAT_LABEL, "[nitro:] FAT IMAGE") -STRING(GBA_GAMECART, "GBA GAMECART") -STRING(NDS_GAMECARD, "NDS GAMECARD") +STRING(GBA_GAMECART, "GBA GAMECART (%s)") +STRING(NDS_GAMECARD, "NDS GAMECARD (%s)") // Drive bottom screen labels STRING(SD_FAT, "(SD FAT, %s)") STRING(N_FREE, "%s free") STRING(SLOT1_FAT, "(Slot-1 SD FAT, %s)") -STRING(GBA_GAME, "(GBA Game)") -STRING(NDS_GAME, "(NDS Game)") +STRING(GBA_GAME, "(GBA Game, %s)") +STRING(NDS_GAME, "(NDS Game, %s (%s trimmed))") STRING(GAME_VIRTUAL, "(Game Virtual)") STRING(RAMDRIVE_9MB, "(RAMdrive FAT, 9 MB)") STRING(RAMDRIVE_16MB, "(RAMdrive FAT, 16 MB)") @@ -129,12 +129,18 @@ STRING(PRESS_B_TO_CANCEL, "Press \\B to cancel") STRING(EOF_NO_RESULTS, "Reached end of file\nwith no results") // Dumping -STRING(DUMP_NDS_ROM_TO, "Dump NDS card ROM to\n\"%s:/gm9i/out\"?") -STRING(DUMP_GBA_ROM_TO, "Dump GBA cart ROM to\n\"%s:/gm9i/out\"?") +STRING(DUMP_TO, "Dump \"%s\" to\n\"%s:/gm9i/out\"?") +STRING(DUMP_ALL, "All") +STRING(DUMP_ALL_TRIMMED, "All (Trimmed ROM)") +STRING(DUMP_ROM, "ROM") +STRING(DUMP_ROM_TRIMMED, "ROM (Trimmed)") +STRING(DUMP_SAVE, "Save") +STRING(DUMP_METADATA, "Metadata") STRING(DO_NOT_REMOVE_CARD, "Do not remove the NDS card.") STRING(DO_NOT_REMOVE_CART, "Do not remove the GBA cart.") STRING(DUMPING_SAVE, "Dumping save...") STRING(RESTORING_SAVE, "Restoring save...") +STRING(DUMPING_METADATA, "Dumping metadata...") STRING(FAILED_TO_DUMP_ROM, "Failed to dump the ROM.") STRING(UNABLE_TO_DUMP_ROM, "Unable to dump the ROM.") STRING(FAILED_TO_DUMP_SAVE, "Failed to dump the save.") diff --git a/arm9/source/read_card.c b/arm9/source/read_card.c index c247511..d552c14 100644 --- a/arm9/source/read_card.c +++ b/arm9/source/read_card.c @@ -66,6 +66,7 @@ static bool normalChip = false; // As defined by GBAtek, normal chip secure area static u32 portFlags = 0; static u32 headerData[0x1000/sizeof(u32)] = {0}; static u32 secureArea[CARD_SECURE_AREA_SIZE/sizeof(u32)] = {0}; +static u32 iCardId; static bool nandChip = false; static int nandSection = -1; // -1 = ROM, above that is the current 128 KiB section in RW @@ -355,7 +356,7 @@ int cardInit (sNDSHeaderExt* ndsHeader) toncset(headerData, 0, 0x1000); - u32 iCardId=cardReadID(CARD_CLK_SLOW); + iCardId=cardReadID(CARD_CLK_SLOW); while(REG_ROMCTRL & CARD_BUSY); normalChip = (iCardId & BIT(31)) != 0; // ROM chip ID MSB @@ -510,6 +511,10 @@ int cardInit (sNDSHeaderExt* ndsHeader) return ERR_NONE; } +u32 cardGetId() { + return iCardId; +} + void cardRead (u32 src, void* dest, bool nandSave) { sNDSHeaderExt* ndsHeader = (sNDSHeaderExt*)headerData; diff --git a/arm9/source/read_card.h b/arm9/source/read_card.h index 56f94a8..300e860 100644 --- a/arm9/source/read_card.h +++ b/arm9/source/read_card.h @@ -43,6 +43,8 @@ int cardInit (sNDSHeaderExt* ndsHeader); void cardRead (u32 src, void* dest, bool nandSave); +u32 cardGetId (void); + void cardWriteNand (void* src, u32 dest); #ifdef __cplusplus diff --git a/arm9/source/screenshot.cpp b/arm9/source/screenshot.cpp index 48fdd01..100c4a9 100644 --- a/arm9/source/screenshot.cpp +++ b/arm9/source/screenshot.cpp @@ -102,7 +102,7 @@ bool screenshot(void) { mkdir((sdMounted ? "sd:/gm9i/out" : "fat:/gm9i/out"), 0777); } - std::string fileTimeText = RetTimeForFilename(); + std::string fileTimeText = RetTime("%H%M%S"); char snapPath[40]; // Take top screenshot snprintf(snapPath, sizeof(snapPath), "%s:/gm9i/out/snap_%s_top.bmp", (sdMounted ? "sd" : "fat"), fileTimeText.c_str()); diff --git a/nitrofiles/languages/en-US/language.ini b/nitrofiles/languages/en-US/language.ini index 44757c9..9a30b39 100644 --- a/nitrofiles/languages/en-US/language.ini +++ b/nitrofiles/languages/en-US/language.ini @@ -4,7 +4,7 @@ UNTITLED=UNTITLED ROOT=[root] DIR=(dir) TIME_FORMAT=%k:%M -NOW_LOADING=Now loading... +LOADING=Loading... CREATING_DIRECTORY=Creating directory... ENTERING_DIRECTORY=Entering directory... COPYING=Copying... @@ -44,14 +44,14 @@ SYSNAND_LABEL=[nand:] SYSNAND NITROFS_LABEL=[nitro:] NDS GAME IMAGE FAT_LABEL_NAMED=[nitro:] FAT IMAGE (%s) FAT_LABEL=[nitro:] FAT IMAGE -GBA_GAMECART=GBA GAMECART -NDS_GAMECARD=NDS GAMECARD +GBA_GAMECART=GBA GAMECART (%s) +NDS_GAMECARD=NDS GAMECARD (%s) SD_FAT=(SD FAT, %s) N_FREE=%s free SLOT1_FAT=(Slot-1 SD FAT, %s) -GBA_GAME=(GBA Game) -NDS_GAME=(NDS Game) +GBA_GAME=(GBA Game, %s) +NDS_GAME=(NDS Game, %s (%s trimmed)) GAME_VIRTUAL=(Game Virtual) RAMDRIVE_9MB=(RAMdrive FAT, 9 MB) RAMDRIVE_16MB=(RAMdrive FAT, 16 MB) @@ -84,7 +84,7 @@ REBOOT=Reboot LANGUAGE=Language... SELECT_LANGUAGE=Select Language NITROFS_NOT_MOUNTED=NitroFS could not be mounted, please load GodMode9i from TWiLight Menu++ or nds-hb-menu. -STR_NITROFS_UNMOUNTED=Another title's NitroFS has been mounted, please reload GodMode9i to change the language. +NITROFS_UNMOUNTED=Another title's NitroFS has been mounted, please reload GodMode9i to change the language. BOOT_FILE=Boot file BOOT_FILE_DIRECT=Boot file (Direct) @@ -121,12 +121,18 @@ SEARCHING=Searching PRESS_B_TO_CANCEL=Press \B to cancel EOF_NO_RESULTS=Reached end of file\nwith no results -DUMP_NDS_ROM_TO=Dump NDS card ROM to\n"%s:/gm9i/out"? -DUMP_GBA_ROM_TO=Dump GBA cart ROM to\n"%s:/gm9i/out"? +DUMP_TO=Dump "%s" to\n"%s:/gm9i/out"? +DUMP_ALL=All +DUMP_ALL_TRIMMED=All (Trimmed ROM) +DUMP_ROM=ROM +DUMP_ROM_TRIMMED=ROM (Trimmed) +DUMP_SAVE=Save +DUMP_METADATA=Metadata DO_NOT_REMOVE_CARD=Do not remove the NDS card. DO_NOT_REMOVE_CART=Do not remove the GBA cart. DUMPING_SAVE=Dumping save... RESTORING_SAVE=Restoring save... +DUMPING_METADATA=Dumping metadata... FAILED_TO_DUMP_ROM=Failed to dump the ROM. UNABLE_TO_DUMP_ROM=Unable to dump the ROM. FAILED_TO_DUMP_SAVE=Failed to dump the save.