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
This commit is contained in:
Pk11 2022-01-04 21:54:45 -06:00 committed by GitHub
parent a9a29204ab
commit bec149de03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 720 additions and 129 deletions

View File

@ -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;
}

View File

@ -60,8 +60,8 @@ bool flashcardMountSkipped = true;
static bool flashcardMountRan = true;
static int dmCursorPosition = 0;
static std::vector<DriveMenuOperation> 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;

View File

@ -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<DumpOption> 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<DumpOption> 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);

View File

@ -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);
}

View File

@ -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)")

View File

@ -1,34 +1,361 @@
#include <nds.h>
/*----------------------------------------------------------------------------*/
/*-- 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 <http://www.gnu.org/licenses/>. --*/
/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 --*/
/*----------------------------------------------------------------------------*/

View File

@ -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 */

View File

@ -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)