mirror of
https://github.com/Luca1991/NDSFactory.git
synced 2025-07-08 21:20:13 +02:00
319 lines
14 KiB
C++
319 lines
14 KiB
C++
#include <QDir>
|
|
#include <stdlib.h>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
#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<char> fatBytes(static_cast<unsigned long>(fatSize));
|
|
std::vector<char> fntBytes(static_cast<unsigned long>(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<FatRange*>(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<char> nameString;
|
|
for(size_t i = 0 ; i<nameLength ; i++) nameString.push_back(fntBytes[currentOffset++]);
|
|
std::string name(&nameString[0], (size_t)nameLength);
|
|
|
|
// We'll need this either way
|
|
|
|
QString newPath(QDir::toNativeSeparators(QString::fromStdString(curPath+"/"+name)));
|
|
|
|
if(isDir){
|
|
|
|
// Get the 2-byte address for this folder's info in the FNT header
|
|
uint16_t subFolderId = ((unsigned char) fntBytes[currentOffset+1] << SECOND_BYTE_SHIFT |
|
|
(unsigned char) fntBytes[currentOffset]);
|
|
currentOffset+=2;
|
|
|
|
// Now the QDir we created earlier comes into play :
|
|
// C++ doesn't automatically create directories (!!!) so we have to rely on QT to do that manually.
|
|
// Otherwise, the ofstream will not open,
|
|
// And even if we force it open, it will just write into nothingness !!!
|
|
|
|
if(!curDir.exists(newPath)) curDir.mkdir(newPath); // I don't think the check is even necessary, actually
|
|
|
|
// Jump back to the FNT header and repeat the process for subdirectory !
|
|
|
|
if(!parseFolder(subFolderId,newPath.toStdString(),parseFolder)) return false;
|
|
|
|
}
|
|
else{
|
|
|
|
// Remember we have the offset for the directory's first file in the FAT.
|
|
// From then, every file is just the next entry.
|
|
// So we just have to use that offset and increment it every time.
|
|
|
|
if(!ndsFactory.writeFatSectionToFile(
|
|
romPath,
|
|
pfatrange+fatOffset,
|
|
newPath.toStdString()))
|
|
return false;
|
|
fatOffset++;
|
|
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// The root folder's ID is, obviously, 0 (only the lower 12-bit count!)
|
|
|
|
return parseFolder(ROOT_DIRECTORY_ADDRESS,dirPath.toStdString(),parseFolder);
|
|
}
|