#include #include #include #include #include "../../mainwindow.h" #include "../../ui_mainwindow.h" #include "../commons/headernames.h" #include "../../models/ndsheadermodel.h" #include "../../../ndsfactory/fatstruct.h" // Byte offsets for interpreting memory #define SECOND_BYTE_SHIFT 8 #define THIRD_BYTE_SHIFT 16 #define FOURTH_BYTE_SHIFT 24 // Magic values for FAT extraction #define CONTROL_BYTE_LENGTH_MASK 0x7F #define CONTROL_BYTE_DIR_MASK 0x80 #define DUMMY_CONTROL_VALUE 0xFF #define FNT_HEADER_OFFSET_MASK 0XFFF #define ROOT_DIRECTORY_ADDRESS 0xF000 // Size constants #define ICON_TITLE_SIZE 0xA00 void MainWindow::populateHeader(NDSHeader* ndsHeader) { auto* headerDataModel = new NDSHeaderModel(ndsHeader); ui->unpackerHeaderDataTable->setModel(headerDataModel); ui->unpackerHeaderDataTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeMode::ResizeToContents); ui->unpackerHeaderDataTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeMode::Stretch); } void MainWindow::enableExtractionButtons() { ui->unpackerExtractorGbx->setEnabled(true); ui->unpackerExtraGbx->setEnabled(true); if (ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::ARM9OverlayAddress, 1).data().toString().toUInt(nullptr,16) == 0){ ui->unpackerDumpArm9OverlayBtn->setEnabled(false); ui->unpackerDumpArm9OverlayFilesBtn->setEnabled(false); } if (ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::ARM7OverlayAddress, 1).data().toString().toUInt(nullptr,16) == 0){ ui->unpackerDumpArm7OverlayBtn->setEnabled(false); ui->unpackerDumpArm7OverlayFilesBtn->setEnabled(false); } } void MainWindow::disableExtractionButtons() { ui->unpackerExtractorGbx->setEnabled(false); ui->unpackerExtraGbx->setEnabled(false); } bool MainWindow::dumpHeader(const std::string& dirPath) { return ndsFactory.dumpDataFromFile( ui->loadedRomPath->text().toStdString(), dirPath, 0, ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::HeaderSize, 1).data().toString().toUInt(nullptr,16)); } bool MainWindow::dumpArm9Bin(const std::string& dirPath, bool dumpExtraBytes) { uint32_t size = ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::ARM9Size, 1).data().toString().toUInt(nullptr,16); if (dumpExtraBytes) size += 12; return ndsFactory.dumpDataFromFile( ui->loadedRomPath->text().toStdString(), dirPath, ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::ARM9RomAddress, 1).data().toString().toUInt(nullptr,16), size); } bool MainWindow::dumpArm7Bin(const std::string& dirPath) { return ndsFactory.dumpDataFromFile( ui->loadedRomPath->text().toStdString(), dirPath, ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::ARM7RomAddress, 1).data().toString().toUInt(nullptr,16), ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::ARM7Size, 1).data().toString().toUInt(nullptr,16)); } bool MainWindow::dumpFnt(const std::string& dirPath) { return ndsFactory.dumpDataFromFile( ui->loadedRomPath->text().toStdString(), dirPath, ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::FilenameTableAddress, 1).data().toString().toUInt(nullptr,16), ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::FilenameTableSize, 1).data().toString().toUInt(nullptr,16)); } bool MainWindow::dumpFat(const std::string& dirPath) { return ndsFactory.dumpDataFromFile( ui->loadedRomPath->text().toStdString(), dirPath, ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::FATAddress, 1).data().toString().toUInt(nullptr,16), ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::FATSize, 1).data().toString().toUInt(nullptr,16)); } bool MainWindow::dumpArm9Overlay(const std::string& dirPath) { return ndsFactory.dumpDataFromFile( ui->loadedRomPath->text().toStdString(), dirPath, ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::ARM9OverlayAddress, 1).data().toString().toUInt(nullptr,16), ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::ARM9OverlaySize, 1).data().toString().toUInt(nullptr,16)); } bool MainWindow::dumpArm9OverlayFiles([[maybe_unused]] const std::string& dirPath) { return false; // TODO: implement me! } bool MainWindow::dumpArm7Overlay(const std::string& dirPath) { return ndsFactory.dumpDataFromFile( ui->loadedRomPath->text().toStdString(), dirPath, ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::ARM7OverlayAddress, 1).data().toString().toUInt(nullptr,16), ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::ARM7OverlaySize, 1).data().toString().toUInt(nullptr,16)); } bool MainWindow::dumpArm7OverlayFiles([[maybe_unused]] const std::string& dirPath) { return false; // TODO: implement me! } bool MainWindow::dumpIconTitle(const std::string& dirPath) { return ndsFactory.dumpDataFromFile( ui->loadedRomPath->text().toStdString(), dirPath, ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::IconTitleAddress, 1).data().toString().toUInt(nullptr,16), ICON_TITLE_SIZE); } bool MainWindow::dumpFatFiles(const std::string& dirPath) { uint32_t startAddr = ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::IconTitleAddress, 1).data().toString().toUInt(nullptr,16) + IconTitleSize ; uint32_t size = ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::UsedRomSize, 1).data().toString().toUInt(nullptr,16) - startAddr; return ndsFactory.dumpDataFromFile( ui->loadedRomPath->text().toStdString(), dirPath, startAddr, size); } bool MainWindow::dumpEverything(QString dirPath) { bool result = true; result &= dumpHeader(QDir::toNativeSeparators(dirPath+"/header.bin").toStdString()); result &= dumpArm9Bin(QDir::toNativeSeparators(dirPath+"/arm9.bin").toStdString(), true); result &= dumpArm7Bin(QDir::toNativeSeparators(dirPath+"/arm7.bin").toStdString()); result &= dumpFnt(QDir::toNativeSeparators(dirPath+"/fnt.bin").toStdString()); result &= dumpFat(QDir::toNativeSeparators(dirPath+"/fat.bin").toStdString()); if(ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::ARM9OverlayAddress, 1).data().toString().toUInt(nullptr,16) != 0) { result &= dumpArm9Overlay(QDir::toNativeSeparators(dirPath+"/a9ovr.bin").toStdString()); result &= dumpArm9OverlayFiles(QDir::toNativeSeparators(dirPath+"/a9ovr_data.bin").toStdString()); } if(ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::ARM7OverlayAddress, 1).data().toString().toUInt(nullptr,16) != 0) { result &= dumpArm7Overlay(QDir::toNativeSeparators(dirPath+"/a7ovr.bin").toStdString()); result &= dumpArm7OverlayFiles(QDir::toNativeSeparators(dirPath+"/a7ovr_data.bin").toStdString()); } result &= dumpIconTitle(QDir::toNativeSeparators(dirPath+"/itl.bin").toStdString()); result &= dumpFatFiles(QDir::toNativeSeparators(dirPath+"/fat_data.bin").toStdString()); return result; } bool MainWindow::decodeFatFiles(QString dirPath) { // Prepare necessary info from ROM std::string romPath = ui->loadedRomPath->text().toStdString(); // ROM itself // Addresses of the file allocation table and file name table uint32_t fatAddr = ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::FATAddress, 1).data().toString().toUInt(nullptr,16); uint32_t fatSize = ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::FATSize, 1).data().toString().toUInt(nullptr,16); // Sizes of these tables uint32_t fntAddr = ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::FilenameTableAddress, 1).data().toString().toUInt(nullptr,16); uint32_t fntSize = ui->unpackerHeaderDataTable->model()->index(NDSHeaderNames::FilenameTableSize, 1).data().toString().toUInt(nullptr,16); // Buffers to receive the contents of the FAT and FNT std::vector fatBytes(static_cast(fatSize)); std::vector fntBytes(static_cast(fntSize)); // Fill them if(!ndsFactory.readBytesFromFile(fatBytes, romPath, fatAddr, fatSize)) return false; if(!ndsFactory.readBytesFromFile(fntBytes, romPath, fntAddr, fntSize)) return false; // Use the available FAT range struct and read the FAT bytes as such FatRange* pfatrange = reinterpret_cast(fatBytes.data()); // Recursive function that looks up FNT info to find file names and directory structures, // And writes the ROM data in the ranges indicated by the FAT simultaneously. auto parseFolder = [this, fntBytes, pfatrange, romPath](uint32_t folderId, std::string curPath, auto& parseFolder){ if(false) return false; // this is stupid, but it's C++ // If we take it out, the compiler will complain because it doesn't known the return type of the lambda // But we can't make the lambda bool either...so this is the best option... QDir curDir(QString::fromStdString(curPath)); // useful a bit later uint32_t currentOffset = 8 * (folderId & FNT_HEADER_OFFSET_MASK); // offset for the current directory's info in the FNT header // Only the lower 12 bit of the given offset are relevant // --------------------------------------------------------------------- // About how the FAT and FNT work : // The FNT has two sections : // a "header" where every entry contains : // - a 4-byte address where the corresponding directory's data starts in the body // - a 2-byte offset that is the index of the first file of the directory in the FAT // (e.g. : if the offset is 42, the first file in the directory is situated at the ROM addresses stored in the 42nd FAT entry) // (and its second will be 43, etc.) // a "body" where every entry contains : // - a length+status/control byte : lower 7 bits (control byte & 0x7F) are a length, highest bit (control byte & 0x80) is set if entry is a directory, and not set if it's a file // - a name which length is the length portion of the previous control byte (e.g. : if the control byte was 0x83, the name is three bytes long) // - if the entry is a directory, a 2-byte address (where only the lower 12 bit are relevant for some reason) at which this directory's info is located in the FNT header // Thus, the FNT reading operation will consist in bouncing back and forth between body and header every time we must process a subdirectory // Thank Heavens for random-access containers ! // --------------------------------------------------------------------- // Get the 4-byte address for the folder data uint32_t fntBodyOffset = (uint32_t)((unsigned char) fntBytes[currentOffset+3] << (uint32_t) FOURTH_BYTE_SHIFT | (unsigned char) fntBytes[currentOffset+2] << (uint32_t) THIRD_BYTE_SHIFT | (unsigned char) fntBytes[currentOffset + 1] << (uint32_t) SECOND_BYTE_SHIFT | (unsigned char) fntBytes[currentOffset]); currentOffset+=4; // Get the 2-byte offset for the folder's first file in the FAT uint16_t fatOffset = (uint16_t)((unsigned char) fntBytes[currentOffset+1] << SECOND_BYTE_SHIFT | (unsigned char) fntBytes[currentOffset]); // Jump to FNT body a specified address currentOffset = fntBodyOffset; uint8_t controlByte = DUMMY_CONTROL_VALUE; while(true){ controlByte = fntBytes[currentOffset]; // Entry's control byte if(controlByte==0) break; // A control byte of 0 terminates the directory's contents currentOffset++; uint8_t nameLength = controlByte & CONTROL_BYTE_LENGTH_MASK; // length of entry name bool isDir = controlByte & CONTROL_BYTE_DIR_MASK; // set if entry is a directory // Reconstitute name from bytes // Btw I wish I could use the actual byte type but I have to comply with the software's choice of using char std::vector nameString; for(size_t i = 0 ; i