From bec149de03cb37556967e1f547525e4a46e56da2 Mon Sep 17 00:00:00 2001 From: Pk11 Date: Tue, 4 Jan 2022 21:54:45 -0600 Subject: [PATCH] Add dumping DS saves using GBA cart save data (#138) * Simplify GBA EEPROM FIFO wait * Add dumping DS saves using a GBA cart's save data * Don't try dump very large DS saves to GBA carts * Don't show DS cart info on regular DS you need to take the cart out to reinit it so info isn't loaded * Change to 'writing save' for DS to GBA dumps * Fix a couple bugs and typos --- arm7/source/main.c | 2 +- arm9/source/driveMenu.cpp | 70 +++-- arm9/source/dumpOperations.cpp | 343 +++++++++++++++++---- arm9/source/gba.cpp | 5 +- arm9/source/language.inl | 12 + arm9/source/lzss.c | 387 ++++++++++++++++++++++-- arm9/source/lzss.h | 18 +- nitrofiles/languages/en-US/language.ini | 12 + 8 files changed, 720 insertions(+), 129 deletions(-) diff --git a/arm7/source/main.c b/arm7/source/main.c index cb288c4..349a8c5 100644 --- a/arm7/source/main.c +++ b/arm7/source/main.c @@ -200,7 +200,7 @@ int main() { case 0x44414552: // 'READ' readEeprom((u8 *)fifoGetAddress(FIFO_USER_01), fifoGetValue32(FIFO_USER_01), fifoGetValue32(FIFO_USER_01)); break; - case 0x54495257: // 'WRITE' + case 0x54495257: // 'WRIT' writeEeprom(fifoGetValue32(FIFO_USER_01), (u8 *)fifoGetAddress(FIFO_USER_01), fifoGetValue32(FIFO_USER_01)); break; } diff --git a/arm9/source/driveMenu.cpp b/arm9/source/driveMenu.cpp index a675f5e..abc8a8a 100644 --- a/arm9/source/driveMenu.cpp +++ b/arm9/source/driveMenu.cpp @@ -60,8 +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 char romTitle[2][13] = {0}; +static u32 romSize[2], romSizeTrimmed; static u8 gbaFixedValue = 0; static u8 stored_SCFG_MC = 0; @@ -111,10 +111,13 @@ void dm_drawTopScreen(void) { font->print(-1, i + 1, true, "[R]", Alignment::right, pal); break; case DriveMenuOperation::gbaCart: - font->printf(0, i + 1, true, Alignment::left, pal, STR_GBA_GAMECART.c_str(), romTitle); + font->printf(0, i + 1, true, Alignment::left, pal, STR_GBA_GAMECART.c_str(), romTitle[1]); break; case DriveMenuOperation::ndsCard: - font->printf(0, i + 1, true, Alignment::left, pal, STR_NDS_GAMECARD.c_str(), romTitle); + if(romTitle[0][0] != 0) + font->printf(0, i + 1, true, Alignment::left, pal, STR_NDS_GAMECARD.c_str(), romTitle[0]); + else + font->print(0, i + 1, true, STR_NDS_GAMECARD_NO_TITLE, Alignment::left, pal); break; case DriveMenuOperation::none: break; @@ -165,16 +168,20 @@ 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->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()); + font->printf(0, 0, false, Alignment::left, Palette::white, STR_GBA_GAMECART.c_str(), romTitle[1]); + font->printf(0, 1, false, Alignment::left, Palette::white, STR_GBA_GAME.c_str(), getBytes(romSize[1]).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->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()); + if(romTitle[0][0] != 0) { + font->printf(0, 0, false, Alignment::left, Palette::white, STR_NDS_GAMECARD.c_str(), romTitle[0]); + font->printf(0, 1, false, Alignment::left, Palette::white, STR_NDS_GAME.c_str(), getBytes(romSize[0]).c_str(), getBytes(romSizeTrimmed).c_str()); + } else { + font->print(0, 0, false, STR_NDS_GAMECARD_NO_TITLE); + } break; case DriveMenuOperation::ramDrive: font->print(0, 0, false, STR_RAMDRIVE_LABEL); @@ -219,24 +226,13 @@ void driveMenu (void) { dmOperations.push_back(DriveMenuOperation::fatImage); if (nitroMounted) dmOperations.push_back(DriveMenuOperation::nitroFs); - if ((io_dldi_data->ioInterface.features & FEATURE_SLOT_GBA) - || (isDSiMode() && !arm7SCFGLocked && !(REG_SCFG_MC & BIT(0)))) { - dmOperations.push_back(DriveMenuOperation::ndsCard); - if(romTitle[0] == 0 && ((io_dldi_data->ioInterface.features & FEATURE_SLOT_GBA) || !flashcardMounted)) { - 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) { + 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); + if(romTitle[1][0] == 0) { + tonccpy(romTitle[1], (char*)0x080000A0, 12); + romSize[1] = 0; + for (romSize[1] = (1 << 20); romSize[1] < (1 << 25); romSize[1] <<= 1) { + vu16 *rompos = (vu16*)(0x08000000 + romSize[1]); bool romend = true; for (int j = 0; j < 0x1000; j++) { if (rompos[j] != j) { @@ -247,11 +243,25 @@ void driveMenu (void) { if (romend) break; } - romSizeTrimmed = romSize; } - } else if (romTitle[0] != 0) { - romTitle[0] = 0; - romSizeTrimmed = romSize = 0; + } else if (romTitle[1][0] != 0) { + romTitle[1][0] = 0; + romSize[1] = 0; + } + if (((io_dldi_data->ioInterface.features & FEATURE_SLOT_GBA) || (isRegularDS && !flashcardMounted && romTitle[1][0] != 0)) + || (isDSiMode() && !arm7SCFGLocked && !(REG_SCFG_MC & BIT(0)))) { + dmOperations.push_back(DriveMenuOperation::ndsCard); + if(romTitle[0][0] == 0 && ((io_dldi_data->ioInterface.features & FEATURE_SLOT_GBA) || !flashcardMounted) && !isRegularDS) { + sNDSHeaderExt ndsHeader; + cardInit(&ndsHeader); + tonccpy(romTitle[0], ndsHeader.gameTitle, 12); + romSize[0] = 0x20000 << ndsHeader.deviceSize; + romSizeTrimmed = (isDSiMode() && (ndsHeader.unitCode != 0) && (ndsHeader.twlRomSize > 0)) + ? ndsHeader.twlRomSize : ndsHeader.romSize + 0x88; + } + } else if (romTitle[0][0] != 0) { + romTitle[0][0] = 0; + romSizeTrimmed = romSize[0] = 0; } if(dmCursorPosition >= (int)dmOperations.size()) @@ -339,7 +349,7 @@ void driveMenu (void) { screenMode = 1; break; } - } else if (dmOperations[dmCursorPosition] == DriveMenuOperation::ndsCard && (sdMounted || flashcardMounted)) { + } else if (dmOperations[dmCursorPosition] == DriveMenuOperation::ndsCard && (sdMounted || flashcardMounted || romTitle[1][0] != 0)) { ndsCardDump(); } else if (dmOperations[dmCursorPosition] == DriveMenuOperation::ramDrive && ramdriveMounted) { currentDrive = Drive::ramDrive; diff --git a/arm9/source/dumpOperations.cpp b/arm9/source/dumpOperations.cpp index 01a0bac..7909130 100644 --- a/arm9/source/dumpOperations.cpp +++ b/arm9/source/dumpOperations.cpp @@ -3,8 +3,10 @@ #include "auxspi.h" #include "date.h" #include "driveOperations.h" +#include "fileOperations.h" #include "font.h" #include "gba.h" +#include "lzss.h" #include "ndsheaderbanner.h" #include "read_card.h" #include "tonccpy.h" @@ -29,6 +31,7 @@ enum DumpOption { romTrimmed = 2, save = 4, metadata = 8, + ndsSave = 16, all = rom | save | metadata, allTrimmed = romTrimmed | save | metadata }; @@ -38,7 +41,10 @@ DumpOption dumpMenu(std::vector allowedOptions, const char *dumpName int optionOffset = 0; char dumpToStr[256]; - snprintf(dumpToStr, sizeof(dumpToStr), STR_DUMP_TO.c_str(), dumpName, sdMounted ? "sd" : "fat"); + if(sdMounted || flashcardMounted) + snprintf(dumpToStr, sizeof(dumpToStr), STR_DUMP_TO.c_str(), dumpName, sdMounted ? "sd" : "fat"); + else + snprintf(dumpToStr, sizeof(dumpToStr), STR_DUMP_TO_GBA.c_str(), dumpName); int y = font->calcHeight(dumpToStr) + 1; @@ -65,6 +71,9 @@ DumpOption dumpMenu(std::vector allowedOptions, const char *dumpName case DumpOption::save: font->print(3, row++, false, STR_DUMP_SAVE); break; + case DumpOption::ndsSave: + font->print(3, row++, false, STR_DUMP_DS_SAVE); + break; case DumpOption::metadata: font->print(3, row++, false, STR_DUMP_METADATA); break; @@ -297,77 +306,270 @@ u32 cardNandGetSaveSize(void) { return 0; } -void ndsCardSaveDump(const char* filename) { - FILE *out = fopen(filename, "wb"); - if(out) { +bool writeToGbaSave(const char* fileName, u8* buffer, u32 size) { + font->clear(false); + font->print(0, 0, false, STR_COMPRESSING_SAVE); + font->update(false); + int compressedSize = 0; + u8 *compressedBuffer = LZS_Encode(buffer, size, LZS_VFAST, &compressedSize); + + u8 section = 0; + u32 bytesWritten = 0; + while((int)bytesWritten < compressedSize) { font->clear(false); - font->print(0, 0, false, STR_DUMPING_SAVE); - font->print(0, 1, false, STR_DO_NOT_REMOVE_CARD); + font->print(0, 0, false, STR_LOADING); + font->update(false); + saveTypeGBA type = gbaGetSaveType(); + u32 gbaSize = gbaGetSaveSize(type); + + u32 writeSize = std::min(gbaSize - 0x30, (u32)(compressedSize - bytesWritten)); + + font->clear(false); + font->printf(0, 0, false, Alignment::left, Palette::white, (STR_WRITE_TO_GBA + "\n\n" + STR_A_YES_B_NO).c_str(), getBytes(writeSize).c_str(), getBytes(compressedSize - bytesWritten).c_str()); font->update(false); - int type = cardEepromGetTypeFixed(); + u16 pressed; + do { + // Print time + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); - if(type == -1) { // NAND - u32 saveSize = cardNandGetSaveSize(); + swiWaitForVBlank(); + scanKeys(); + pressed = keysDownRepeat(); + } while (!(pressed & (KEY_A | KEY_B)) && *(u8*)(0x080000B2) == 0x96); - if(saveSize == 0) { - dumpFailMsg(STR_FAILED_TO_DUMP_SAVE); - return; + if(pressed & KEY_A) { + font->clear(false); + font->print(0, 0, false, STR_WRITING_SAVE); + font->update(false); + + u8* writeBuffer = (u8*)memalign(4, gbaSize); + // 0x30 byte header + tonccpy(writeBuffer, "9i", 3); // Magic + writeBuffer[3] = section; // Section of the save + tonccpy(writeBuffer + 0x4, &size, 4); // Total original size + tonccpy(writeBuffer + 0x8, &compressedSize, 4); // Total compressed size + tonccpy(writeBuffer + 0xC, &writeSize, 4); // Size of current section (excluding header) + tonccpy(writeBuffer + 0x10, fileName, 0x20); // File name + // Actual save data + tonccpy(writeBuffer + 0x30, compressedBuffer + bytesWritten, writeSize); + + gbaFormatSave(type); + gbaWriteSave(0, writeBuffer, gbaSize, type); + free(writeBuffer); + + bytesWritten += writeSize; + section++; + } + + if(pressed & KEY_B) { + free(compressedBuffer); + return false; + } + + if((int)bytesWritten < compressedSize) { + font->clear(false); + font->print(0, 0, false, STR_SWITCH_CART); + font->update(false); + + // Wait for GBA cart to be removed and reinserted + if(*(u8*)(0x080000B2) == 0x96) + while(*(u8*)(0x080000B2) == 0x96) swiWaitForVBlank(); + while(*(u8*)(0x080000B2) != 0x96) swiWaitForVBlank(); + } + } + + free(compressedBuffer); + + return true; +} + +bool readFromGbaCart() { + u32 size, compressedSize; + char fileName[0x20] = {0}; + u8 *compressedBuffer = nullptr; + + u8 currentSection = 0; + u32 bytesRead = 0; + do { + font->clear(false); + font->print(0, 0, false, STR_LOADING); + font->update(false); + + saveTypeGBA saveType = gbaGetSaveType(); + u32 gbaSize = gbaGetSaveSize(saveType); + u8 *buffer = new u8[gbaSize]; + gbaReadSave(buffer, 0, gbaSize, saveType); + + int section = -1; + if(memcmp(buffer, "9i", 3) == 0) { + // Only load the first time + if(fileName[0] == 0) { + tonccpy(&size, buffer + 0x4, 4); // Total original size + tonccpy(&compressedSize, buffer + 0x8, 4); // Total compressed size + tonccpy(fileName, buffer + 0x10, 0x20); // File name + + compressedBuffer = new u8[compressedSize]; } - u32 currentSize = saveSize; - FILE* destinationFile = fopen(filename, "wb"); - if (destinationFile) { + u32 compressedSizeTemp; + tonccpy(&compressedSizeTemp, buffer + 0x8, 4); // Total compressed size + if(compressedSizeTemp == compressedSize) { // Probably matching DS dump + section = buffer[3]; // Section of the save - font->print(0, 4, false, STR_PROGRESS); - font->print(0, 5, false, "["); - font->print(-1, 5, false, "]"); - for (u32 src = 0; src < saveSize; src += 0x8000) { + if(section == currentSection) { + u32 readSize = 0; + tonccpy(&readSize, buffer + 0xC, 4); // Size of current section (excluding header) + + // Copy to output buffer + tonccpy(compressedBuffer + bytesRead, buffer + 0x30, readSize); + + bytesRead += readSize; + currentSection++; + } + } else { + dumpFailMsg(STR_WRONG_DS_SAVE); + } + } else { + dumpFailMsg(STR_NO_DS_SAVE); + } + + delete[] buffer; + + if(bytesRead < compressedSize) { + font->clear(false); + if(section != -1) + font->printf(0, 0, false, Alignment::left, Palette::white, (STR_SWITCH_CART_TO_SECTION_THIS_WAS + "\n\n" + STR_B_CANCEL).c_str(), currentSection + 1, section + 1); + else + font->printf(0, 0, false, Alignment::left, Palette::white, (STR_SWITCH_CART_TO_SECTION + "\n\n" + STR_B_CANCEL).c_str(), currentSection + 1); + font->update(false); + + if(*(u8*)(0x080000B2) == 0x96) { + while(*(u8*)(0x080000B2) == 0x96) { // Print time font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); font->update(true); - font->print((src / (saveSize / (SCREEN_COLS - 2))) + 1, 5, false, "="); - font->printf(0, 6, false, Alignment::left, Palette::white, STR_N_OF_N_BYTES.c_str(), src, saveSize); - font->update(false); + swiWaitForVBlank(); + scanKeys(); - for (u32 i = 0; i < 0x8000; i += 0x200) { - cardRead(cardNandRwStart + src + i, copyBuf + i, true); + if(keysDown() & KEY_B) { + delete[] compressedBuffer; + return false; } - if (fwrite(copyBuf, 1, (currentSize >= 0x8000 ? 0x8000 : currentSize), destinationFile) < 1) { - dumpFailMsg(STR_FAILED_TO_DUMP_SAVE); - break; - } - currentSize -= 0x8000; } - fclose(destinationFile); - } else { - dumpFailMsg(STR_FAILED_TO_DUMP_SAVE); } - } else { // SPI - unsigned char *buffer; - auxspi_extra card_type = auxspi_has_extra(); - if(card_type == AUXSPI_INFRARED) { - int size = auxspi_save_size_log_2(card_type); - int size_blocks; - int type = auxspi_save_type(card_type); - if(size < 16) - size_blocks = 1; - else - size_blocks = 1 << (size - 16); - u32 LEN = std::min(1 << size, 1 << 16); - buffer = new unsigned char[LEN*size_blocks]; - auxspi_read_data(0, buffer, LEN*size_blocks, type, card_type); - fwrite(buffer, 1, LEN*size_blocks, out); - } else { - int size = cardEepromGetSizeFixed(); - buffer = new unsigned char[size]; - cardReadEeprom(0, buffer, size, type); + while(*(u8*)(0x080000B2) != 0x96) { + // Print time + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); + + swiWaitForVBlank(); + scanKeys(); + + if(keysDown() & KEY_B) { + delete[] compressedBuffer; + return false; + } + } + } else { + u8 *finalBuffer = new u8[size]; + decompress(compressedBuffer, finalBuffer, LZ77); + + char destPath[256]; + sprintf(destPath, "%s:/gm9i/out/%s.sav", (sdMounted ? "sd" : "fat"), fileName); + FILE *destinationFile = fopen(destPath, "wb"); + if(destinationFile) { + fwrite(finalBuffer, 1, size, destinationFile); + fclose(destinationFile); + } + + delete[] finalBuffer; + } + } while(bytesRead < compressedSize); + + delete[] compressedBuffer; + + return true; +} + +void ndsCardSaveDump(const char* filename) { + font->clear(false); + font->print(0, 0, false, STR_DUMPING_SAVE); + font->print(0, 1, false, STR_DO_NOT_REMOVE_CARD); + font->update(false); + + int type = cardEepromGetTypeFixed(); + + if(type == -1) { // NAND + u32 saveSize = cardNandGetSaveSize(); + + if(saveSize == 0) { + dumpFailMsg(STR_FAILED_TO_DUMP_SAVE); + return; + } + + u32 currentSize = saveSize; + FILE* destinationFile = fopen(filename, "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 < saveSize; src += 0x8000) { + // Print time + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); + + font->print((src / (saveSize / (SCREEN_COLS - 2))) + 1, 5, false, "="); + font->printf(0, 6, false, Alignment::left, Palette::white, STR_N_OF_N_BYTES.c_str(), src, saveSize); + font->update(false); + + for (u32 i = 0; i < 0x8000; i += 0x200) { + cardRead(cardNandRwStart + src + i, copyBuf + i, true); + } + if (fwrite(copyBuf, 1, (currentSize >= 0x8000 ? 0x8000 : currentSize), destinationFile) < 1) { + dumpFailMsg(STR_FAILED_TO_DUMP_SAVE); + break; + } + currentSize -= 0x8000; + } + fclose(destinationFile); + } else { + dumpFailMsg(STR_FAILED_TO_DUMP_SAVE); + } + } else { // SPI + unsigned char *buffer; + auxspi_extra card_type = auxspi_has_extra(); + int size = 0; + if(card_type == AUXSPI_INFRARED) { + int sizeLog2 = auxspi_save_size_log_2(card_type); + int size_blocks; + int type = auxspi_save_type(card_type); + if(sizeLog2 < 16) + size_blocks = 1; + else + size_blocks = 1 << (sizeLog2 - 16); + u32 LEN = std::min(1 << sizeLog2, 1 << 16); + size = LEN * size_blocks; + buffer = new unsigned char[size]; + auxspi_read_data(0, buffer, size, type, card_type); + } else { + size = cardEepromGetSizeFixed(); + buffer = new unsigned char[size]; + cardReadEeprom(0, buffer, size, type); + } + if(sdMounted || flashcardMounted) { + FILE *out = fopen(filename, "wb"); + if(out) { fwrite(buffer, 1, size, out); } - delete[] buffer; fclose(out); + } else { + writeToGbaSave(filename, buffer, size); } + delete[] buffer; } } @@ -561,7 +763,7 @@ void ndsCardDump(void) { font->update(false); std::vector allowedOptions = {DumpOption::all}; - u8 allowedBitfield = DumpOption::metadata; + u8 allowedBitfield = 0; char gameTitle[13] = {0}; char gameCode[7] = {0}; char fileName[32] = {0}; @@ -570,19 +772,24 @@ void ndsCardDump(void) { 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(sdMounted || flashcardMounted) { + allowedOptions.push_back(DumpOption::allTrimmed); + allowedOptions.push_back(DumpOption::rom); + allowedOptions.push_back(DumpOption::romTrimmed); + allowedBitfield |= DumpOption::rom | DumpOption::romTrimmed; + } nandSave = cardNandGetSaveSize() != 0; - if(spiSave || nandSave) { + if((spiSave && cardEepromGetSizeFixed() <= (1 << 20)) || (nandSave && (sdMounted || flashcardMounted))) { allowedOptions.push_back(DumpOption::save); allowedBitfield |= DumpOption::save; } } - allowedOptions.push_back(DumpOption::metadata); + if(sdMounted || flashcardMounted) { + allowedBitfield |= DumpOption::metadata; + allowedOptions.push_back(DumpOption::metadata); + } tonccpy(gameTitle, ndsCardHeader.gameTitle, 12); tonccpy(gameCode, ndsCardHeader.gameCode, 6); @@ -615,7 +822,7 @@ void ndsCardDump(void) { strcat(fileName, "_trim"); // Ensure directories exist - if((dumpOption & allowedBitfield) != DumpOption::none) { + if((dumpOption & allowedBitfield) != DumpOption::none && (sdMounted || flashcardMounted)) { char folderPath[2][256]; sprintf(folderPath[0], "%s:/gm9i", (sdMounted ? "sd" : "fat")); sprintf(folderPath[1], "%s:/gm9i/out", (sdMounted ? "sd" : "fat")); @@ -686,7 +893,7 @@ void ndsCardDump(void) { if ((dumpOption & allowedBitfield) & DumpOption::save) { char destPath[256]; sprintf(destPath, "%s:/gm9i/out/%s.sav", (sdMounted ? "sd" : "fat"), fileName); - ndsCardSaveDump(destPath); + ndsCardSaveDump((sdMounted || flashcardMounted) ? destPath : fileName); } // Dump metadata @@ -834,6 +1041,15 @@ void gbaCartDump(void) { if(saveType != saveTypeGBA::SAVE_GBA_NONE) { allowedOptions.push_back(DumpOption::save); allowedBitfield |= DumpOption::save; + + u32 size = gbaGetSaveSize(saveType); + u8 *buffer = new u8[size]; + gbaReadSave(buffer, 0, size, saveType); + if(memcmp(buffer, "9i", 3) == 0) { + allowedOptions.push_back(DumpOption::ndsSave); + allowedBitfield |= DumpOption::ndsSave; + } + delete[] buffer; } allowedOptions.push_back(DumpOption::metadata); @@ -990,6 +1206,11 @@ void gbaCartDump(void) { gbaCartSaveDump(destPath); } + // Dump NDS save previously saved to this cart + if ((dumpOption & allowedBitfield) & DumpOption::ndsSave) { + readFromGbaCart(); + } + // Dump metadata if ((dumpOption & allowedBitfield) & DumpOption::metadata) { font->clear(false); diff --git a/arm9/source/gba.cpp b/arm9/source/gba.cpp index ba74461..00a2c33 100644 --- a/arm9/source/gba.cpp +++ b/arm9/source/gba.cpp @@ -98,9 +98,8 @@ void writeEeprom(u32 dst, u8 *src, u32 len) { fifoSendValue32(FIFO_USER_01, len); // Wait for it to finish - while(!(fifoCheckValue32(FIFO_USER_02) && fifoGetValue32(FIFO_USER_02) == 0x454E4F44 /* 'DONE' */)) { - swiWaitForVBlank(); - } + fifoWaitValue32(FIFO_USER_02); + fifoGetValue32(FIFO_USER_02); sysSetCartOwner(BUS_OWNER_ARM9); } diff --git a/arm9/source/language.inl b/arm9/source/language.inl index e0889e6..9197db7 100644 --- a/arm9/source/language.inl +++ b/arm9/source/language.inl @@ -45,6 +45,7 @@ STRING(NITROFS_LABEL, "[nitro:] NDS GAME IMAGE") STRING(FAT_LABEL, "[img:] FAT IMAGE (%s)") STRING(GBA_GAMECART, "GBA GAMECART (%s)") STRING(NDS_GAMECARD, "NDS GAMECARD (%s)") +STRING(NDS_GAMECARD_NO_TITLE, "NDS GAMECARD") // Drive bottom screen labels STRING(SD_FAT, "(SD FAT, %s)") @@ -129,11 +130,13 @@ STRING(EOF_NO_RESULTS, "Reached end of file\nwith no results") // Dumping STRING(FLASHCARD_WILL_UNMOUNT, "Flashcard will be unmounted.\nIs this okay?") STRING(DUMP_TO, "Dump \"%s\" to\n\"%s:/gm9i/out\"?") +STRING(DUMP_TO_GBA, "Dump \"%s\" to GBA cart?") 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_DS_SAVE, "DS 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.") @@ -154,11 +157,20 @@ STRING(PROGRESS, "Progress:") STRING(N_OF_N_BYTES, "%d/%d Bytes") STRING(NDS_IS_DUMPING, "%s.nds\nis dumping...") STRING(GBA_IS_DUMPING, "%s.gba\nis dumping...") +STRING(COMPRESSING_SAVE, "Compressing save...") +STRING(WRITING_SAVE, "Writing save...") +STRING(WRITE_TO_GBA, "Write %s to GBA cart? (%s remaining)\n\nMake sure to back up your GBA save first!") +STRING(SWITCH_CART, "Please switch to a different GBA cart.") +STRING(SWITCH_CART_TO_SECTION, "Please switch to the GBA cart containing section %d.") +STRING(SWITCH_CART_TO_SECTION_THIS_WAS, "Please switch to the GBA cart containing section %d. (This was section %d)") +STRING(WRONG_DS_SAVE, "This cart contains a save file from a different DS game.") +STRING(NO_DS_SAVE, "This cart doesn't contain a DS save.") // Confirmation/option button info STRING(A_OK, "(\\A OK)") STRING(A_YES_B_NO, "(\\A yes, \\B no)") STRING(A_CONTINUE, "(\\A continue)") +STRING(B_CANCEL, "(\\B cancel)") STRING(A_SELECT_B_CANCEL, "(\\A select, \\B cancel)") STRING(START_CANCEL, "(START cancel)") STRING(UDLR_CHANGE_ATTRIBUTES, "(\\D change attributes)") diff --git a/arm9/source/lzss.c b/arm9/source/lzss.c index 4ca6b34..ee1054d 100644 --- a/arm9/source/lzss.c +++ b/arm9/source/lzss.c @@ -1,34 +1,361 @@ -#include +/*----------------------------------------------------------------------------*/ +/*-- lzss.c - LZSS coding for Nintendo GBA/DS --*/ +/*-- Copyright (C) 2011 CUE --*/ +/*-- --*/ +/*-- This program is free software: you can redistribute it and/or modify --*/ +/*-- it under the terms of the GNU General Public License as published by --*/ +/*-- the Free Software Foundation, either version 3 of the License, or --*/ +/*-- (at your option) any later version. --*/ +/*-- --*/ +/*-- This program is distributed in the hope that it will be useful, --*/ +/*-- but WITHOUT ANY WARRANTY; without even the implied warranty of --*/ +/*-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --*/ +/*-- GNU General Public License for more details. --*/ +/*-- --*/ +/*-- You should have received a copy of the GNU General Public License --*/ +/*-- along with this program. If not, see . --*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +#include +#include #include -#include "lzss.h" -#define __itcm __attribute__((section(".itcm"))) +/*----------------------------------------------------------------------------*/ +#define CMD_DECODE 0x00 // decode +#define CMD_CODE_10 0x10 // LZSS magic number -void __itcm -LZ77_Decompress(u8* source, u8* destination){ - u32 leng = (source[1] | (source[2] << 8) | (source[3] << 16)); - int Offs = 4; - int dstoffs = 0; - while (true) - { - u8 header = source[Offs++]; - for (int i = 0; i < 8; i++) - { - if ((header & 0x80) == 0) destination[dstoffs++] = source[Offs++]; - else - { - u8 a = source[Offs++]; - u8 b = source[Offs++]; - int offs = (((a & 0xF) << 8) | b) + 1; - int length = (a >> 4) + 3; - for (int j = 0; j < length; j++) - { - destination[dstoffs] = destination[dstoffs - offs]; - dstoffs++; - } - } - if (dstoffs >= (int)leng) return; - header <<= 1; - } - } +#define LZS_NORMAL 0x00 // normal mode, (0) +#define LZS_FAST 0x80 // fast mode, (1 << 7) +#define LZS_BEST 0x40 // best mode, (1 << 6) + +#define LZS_WRAM 0x00 // VRAM not compatible (LZS_WRAM | LZS_NORMAL) +#define LZS_VRAM 0x01 // VRAM compatible (LZS_VRAM | LZS_NORMAL) +#define LZS_WFAST 0x80 // LZS_WRAM fast (LZS_WRAM | LZS_FAST) +#define LZS_VFAST 0x81 // LZS_VRAM fast (LZS_VRAM | LZS_FAST) +#define LZS_WBEST 0x40 // LZS_WRAM best (LZS_WRAM | LZS_BEST) +#define LZS_VBEST 0x41 // LZS_VRAM best (LZS_VRAM | LZS_BEST) + +#define LZS_SHIFT 1 // bits to shift +#define LZS_MASK 0x80 // bits to check: + // ((((1 << LZS_SHIFT) - 1) << (8 - LZS_SHIFT) + +#define LZS_THRESHOLD 2 // max number of bytes to not encode +#define LZS_N 0x1000 // max offset (1 << 12) +#define LZS_F 0x12 // max coded ((1 << 4) + LZS_THRESHOLD) +#define LZS_NIL LZS_N // index for root of binary search trees + +#define RAW_MINIM 0x00000000 // empty file, 0 bytes +#define RAW_MAXIM 0x00FFFFFF // 3-bytes length, 16MB - 1 + +#define LZS_MINIM 0x00000004 // header only (empty RAW file) +#define LZS_MAXIM 0x01400000 // 0x01200003, padded to 20MB: + // * header, 4 + // * length, RAW_MAXIM + // * flags, (RAW_MAXIM + 7) / 8 + // 4 + 0x00FFFFFF + 0x00200000 + padding + +/*----------------------------------------------------------------------------*/ +unsigned char ring[LZS_N + LZS_F - 1]; +int dad[LZS_N + 1], lson[LZS_N + 1], rson[LZS_N + 1 + 256]; +int pos_ring, len_ring, lzs_vram; + +/*----------------------------------------------------------------------------*/ +#define BREAK(text) { printf(text); return; } +#define EXIT(text) { printf(text); exit(-1); } + +/*----------------------------------------------------------------------------*/ +unsigned char *Memory(int length, int size); + +unsigned char *LZS_Encode(unsigned char *raw_buffer, int raw_len, int mode, int *pak_len); +unsigned char *LZS_Code(unsigned char *raw_buffer, int raw_len, unsigned int *new_len, int best); + +unsigned char *LZS_Fast(unsigned char *raw_buffer, int raw_len, unsigned int *new_len); +void LZS_InitTree(void); +void LZS_InsertNode(int r); +void LZS_DeleteNode(int p); + +/*----------------------------------------------------------------------------*/ +unsigned char *Memory(int length, int size) { + unsigned char *fb; + + fb = (unsigned char *) calloc(length, size); + if (fb == NULL) EXIT("\nMemory error\n"); + + return(fb); } + +/*----------------------------------------------------------------------------*/ +unsigned char *LZS_Encode(unsigned char *raw_buffer, int raw_len, int mode, int *pak_len) { + unsigned char *pak_buffer, *new_buffer; + unsigned int new_len; + + lzs_vram = mode & 0xF; + + // printf("- encoding '%s'", filename); + + // raw_buffer = Load(filename, &raw_len, RAW_MINIM, RAW_MAXIM); + + pak_buffer = NULL; + *pak_len = LZS_MAXIM + 1; + + if (!(mode & LZS_FAST)) { + mode = mode & LZS_BEST ? 1 : 0; + new_buffer = LZS_Code(raw_buffer, raw_len, &new_len, mode); + } else { + new_buffer = LZS_Fast(raw_buffer, raw_len, &new_len); + } + if (new_len < *pak_len) { + if (pak_buffer != NULL) free(pak_buffer); + pak_buffer = new_buffer; + if(pak_len) + *pak_len = new_len; + } + + // Save(filename, pak_buffer, pak_len); + + // free(pak_buffer); + // free(raw_buffer); + + // printf("\n"); + + return pak_buffer; +} + +/*----------------------------------------------------------------------------*/ +unsigned char *LZS_Code(unsigned char *raw_buffer, int raw_len, unsigned int *new_len, int best) { + unsigned char *pak_buffer, *pak, *raw, *raw_end, *flg; + unsigned int pak_len, len, pos, len_best, pos_best; + unsigned int len_next, pos_next, len_post, pos_post; + unsigned char mask; + +#define SEARCH(l,p) { \ + l = LZS_THRESHOLD; \ + \ + pos = raw - raw_buffer >= LZS_N ? LZS_N : raw - raw_buffer; \ + for ( ; pos > lzs_vram; pos--) { \ + for (len = 0; len < LZS_F; len++) { \ + if (raw + len == raw_end) break; \ + if (*(raw + len) != *(raw + len - pos)) break; \ + } \ + \ + if (len > l) { \ + p = pos; \ + if ((l = len) == LZS_F) break; \ + } \ + } \ +} + + pak_len = 4 + raw_len + ((raw_len + 7) / 8); + pak_buffer = (unsigned char *) Memory(pak_len, sizeof(char)); + + *(unsigned int *)pak_buffer = CMD_CODE_10 | (raw_len << 8); + + pak = pak_buffer + 4; + raw = raw_buffer; + raw_end = raw_buffer + raw_len; + + mask = 0; + + while (raw < raw_end) { + if (!(mask >>= LZS_SHIFT)) { + *(flg = pak++) = 0; + mask = LZS_MASK; + } + + SEARCH(len_best, pos_best); + + // LZ-CUE optimization start + if (best) { + if (len_best > LZS_THRESHOLD) { + if (raw + len_best < raw_end) { + raw += len_best; + SEARCH(len_next, pos_next); + raw -= len_best - 1; + SEARCH(len_post, pos_post); + raw--; + + if (len_next <= LZS_THRESHOLD) len_next = 1; + if (len_post <= LZS_THRESHOLD) len_post = 1; + + if (len_best + len_next <= 1 + len_post) len_best = 1; + } + } + } + // LZ-CUE optimization end + + if (len_best > LZS_THRESHOLD) { + raw += len_best; + *flg |= mask; + *pak++ = ((len_best - (LZS_THRESHOLD + 1)) << 4) | ((pos_best - 1) >> 8); + *pak++ = (pos_best - 1) & 0xFF; + } else { + *pak++ = *raw++; + } + } + + *new_len = pak - pak_buffer; + + return(pak_buffer); +} + +/*----------------------------------------------------------------------------*/ +unsigned char *LZS_Fast(unsigned char *raw_buffer, int raw_len, unsigned int *new_len) { + unsigned char *pak_buffer, *pak, *raw, *raw_end, *flg; + unsigned int pak_len, len, r, s, len_tmp, i; + unsigned char mask; + + pak_len = 4 + raw_len + ((raw_len + 7) / 8); + pak_buffer = (unsigned char *) Memory(pak_len, sizeof(char)); + + *(unsigned int *)pak_buffer = CMD_CODE_10 | (raw_len << 8); + + pak = pak_buffer + 4; + raw = raw_buffer; + raw_end = raw_buffer + raw_len; + + LZS_InitTree(); + + r = s = 0; + + len = raw_len < LZS_F ? raw_len : LZS_F; + while (r < LZS_N - len) ring[r++] = 0; + + for (i = 0; i < len; i++) ring[r + i] = *raw++; + + LZS_InsertNode(r); + + mask = 0; + + while (len) { + if (!(mask >>= LZS_SHIFT)) { + *(flg = pak++) = 0; + mask = LZS_MASK; + } + + if (len_ring > len) len_ring = len; + + if (len_ring > LZS_THRESHOLD) { + *flg |= mask; + pos_ring = ((r - pos_ring) & (LZS_N - 1)) - 1; + *pak++ = ((len_ring - LZS_THRESHOLD - 1) << 4) | (pos_ring >> 8); + *pak++ = pos_ring & 0xFF; + } else { + len_ring = 1; + *pak++ = ring[r]; + } + + len_tmp = len_ring; + for (i = 0; i < len_tmp; i++) { + if (raw == raw_end) break; + LZS_DeleteNode(s); + ring[s] = *raw++; + if (s < LZS_F - 1) ring[s + LZS_N] = ring[s]; + s = (s + 1) & (LZS_N - 1); + r = (r + 1) & (LZS_N - 1); + LZS_InsertNode(r); + } + while (i++ < len_tmp) { + LZS_DeleteNode(s); + s = (s + 1) & (LZS_N - 1); + r = (r + 1) & (LZS_N - 1); + if (--len) LZS_InsertNode(r); + } + } + + *new_len = pak - pak_buffer; + + return(pak_buffer); +} + +/*----------------------------------------------------------------------------*/ +void LZS_InitTree(void) { + int i; + + for (i = LZS_N + 1; i <= LZS_N + 256; i++) + rson[i] = LZS_NIL; + + for (i = 0; i < LZS_N; i++) + dad[i] = LZS_NIL; +} + +/*----------------------------------------------------------------------------*/ +void LZS_InsertNode(int r) { + unsigned char *key; + int i, p, cmp, prev; + + prev = (r - 1) & (LZS_N - 1); + + cmp = 1; + len_ring = 0; + + key = &ring[r]; + p = LZS_N + 1 + key[0]; + + rson[r] = lson[r] = LZS_NIL; + + for ( ; ; ) { + if (cmp >= 0) { + if (rson[p] != LZS_NIL) p = rson[p]; + else { rson[p] = r; dad[r] = p; return; } + } else { + if (lson[p] != LZS_NIL) p = lson[p]; + else { lson[p] = r; dad[r] = p; return; } + } + + for (i = 1; i < LZS_F; i++) + if ((cmp = key[i] - ring[p + i])) break; + + if (i > len_ring) { + if (!lzs_vram || (p != prev)) { + pos_ring = p; + if ((len_ring = i) == LZS_F) break; + } + } + } + + dad[r] = dad[p]; lson[r] = lson[p]; rson[r] = rson[p]; + + dad[lson[p]] = r; dad[rson[p]] = r; + + if (rson[dad[p]] == p) rson[dad[p]] = r; + else lson[dad[p]] = r; + + dad[p] = LZS_NIL; +} + +/*----------------------------------------------------------------------------*/ +void LZS_DeleteNode(int p) { + int q; + + if (dad[p] == LZS_NIL) return; + + if (rson[p] == LZS_NIL) { + q = lson[p]; + } else if (lson[p] == LZS_NIL) { + q = rson[p]; + } else { + q = lson[p]; + if (rson[q] != LZS_NIL) { + do { + q = rson[q]; + } while (rson[q] != LZS_NIL); + + rson[dad[q]] = lson[q]; dad[lson[q]] = dad[q]; + lson[q] = lson[p]; dad[lson[p]] = q; + } + + rson[q] = rson[p]; dad[rson[p]] = q; + } + + dad[q] = dad[p]; + + if (rson[dad[p]] == p) rson[dad[p]] = q; + else lson[dad[p]] = q; + + dad[p] = LZS_NIL; +} + +/*----------------------------------------------------------------------------*/ +/*-- EOF Copyright (C) 2011 CUE --*/ +/*----------------------------------------------------------------------------*/ diff --git a/arm9/source/lzss.h b/arm9/source/lzss.h index 7426a1e..6fe63fd 100644 --- a/arm9/source/lzss.h +++ b/arm9/source/lzss.h @@ -1,12 +1,22 @@ -#ifndef LZ77_DECOMPRESS_H -#define LZ77_DECOMPRESS_H +#ifndef LZ77_COMPRESS_H +#define LZ77_COMPRESS_H #ifdef __cplusplus extern "C" { #endif -void LZ77_Decompress(u8* source, u8* destination); + +#define LZS_WRAM 0x00 // VRAM not compatible (LZS_WRAM | LZS_NORMAL) +#define LZS_VRAM 0x01 // VRAM compatible (LZS_VRAM | LZS_NORMAL) +#define LZS_WFAST 0x80 // LZS_WRAM fast (LZS_WRAM | LZS_FAST) +#define LZS_VFAST 0x81 // LZS_VRAM fast (LZS_VRAM | LZS_FAST) +#define LZS_WBEST 0x40 // LZS_WRAM best (LZS_WRAM | LZS_BEST) +#define LZS_VBEST 0x41 // LZS_VRAM best (LZS_VRAM | LZS_BEST) + +// Returned buffer must be freed manually +// pak_len will be the length of the compressed output +unsigned char *LZS_Encode(unsigned char *raw_buffer, int raw_len, int mode, int *pak_len); #ifdef __cplusplus } #endif -#endif /* DECOMPRESS_H */ +#endif /* LZ77_COMPRESS_H */ diff --git a/nitrofiles/languages/en-US/language.ini b/nitrofiles/languages/en-US/language.ini index 8cd9bff..2144496 100644 --- a/nitrofiles/languages/en-US/language.ini +++ b/nitrofiles/languages/en-US/language.ini @@ -44,6 +44,7 @@ NITROFS_LABEL=[nitro:] NDS GAME IMAGE FAT_LABEL=[img:] FAT IMAGE (%s) GBA_GAMECART=GBA GAMECART (%s) NDS_GAMECARD=NDS GAMECARD (%s) +NDS_GAMECARD_NO_TITLE=NDS GAMECARD SD_FAT=(SD FAT, %s) N_FREE=%s free @@ -121,11 +122,13 @@ EOF_NO_RESULTS=Reached end of file\nwith no results FLASHCARD_WILL_UNMOUNT=Flashcard will be unmounted.\nIs this okay? DUMP_TO=Dump "%s" to\n"%s:/gm9i/out"? +DUMP_TO_GBA=Dump "%s" to GBA cart? DUMP_ALL=All DUMP_ALL_TRIMMED=All (Trimmed ROM) DUMP_ROM=ROM DUMP_ROM_TRIMMED=ROM (Trimmed) DUMP_SAVE=Save +DUMP_DS_SAVE=DS Save DUMP_METADATA=Metadata DO_NOT_REMOVE_CARD=Do not remove the NDS card. DO_NOT_REMOVE_CART=Do not remove the GBA cart. @@ -146,10 +149,19 @@ PROGRESS=Progress: N_OF_N_BYTES=%d/%d Bytes NDS_IS_DUMPING=%s.nds\nis dumping... GBA_IS_DUMPING=%s.gba\nis dumping... +COMPRESSING_SAVE=Compressing save... +WRITING_SAVE=Writing save... +WRITE_TO_GBA=Write %s to GBA cart? (%s remaining)\n\nMake sure to back up your GBA save first! +SWITCH_CART=Please switch to a different GBA cart. +SWITCH_CART_TO_SECTION=Please switch to the GBA cart containing section %d. +SWITCH_CART_TO_SECTION_THIS_WAS=Please switch to the GBA cart containing section %d. (This was section %d) +WRONG_DS_SAVE=This cart contains a save file from a different DS game. +NO_DS_SAVE=This cart doesn't contain a DS save. A_OK=(\A OK) A_YES_B_NO=(\A yes, \B no) A_CONTINUE=(\A continue) +B_CANCEL=(\B cancel) A_SELECT_B_CANCEL=(\A select, \B cancel) START_CANCEL=(START cancel) UDLR_CHANGE_ATTRIBUTES=(\D change attributes)