From 47cca1165de8c6bb9057f62f1f897768a4f26075 Mon Sep 17 00:00:00 2001 From: Maschell Date: Fri, 15 Oct 2021 15:41:16 +0200 Subject: [PATCH] Refactor menus and overall structure. NTFS only --- Makefile | 2 +- source/ApplicationState.cpp | 858 --------------------------- source/ApplicationState.h | 156 ++--- source/GMPartitionsDumperState.cpp | 495 ++++++++++++++++ source/GMPartitionsDumperState.h | 110 ++++ source/MainApplicationState.cpp | 83 +++ source/MainApplicationState.h | 49 ++ source/WUD/DiscReader.cpp | 5 +- source/WUD/DiscReaderDiscDrive.cpp | 2 +- source/WUD/DiscReaderDiscDrive.h | 1 + source/WUDDumperState.cpp | 326 ++++++++++ source/WUDDumperState.h | 108 ++++ source/common/common.h | 5 +- source/fs/CFile.cpp | 30 +- source/fs/CFile.hpp | 39 +- source/fs/WUDFileWriter.cpp | 35 ++ source/fs/WUDFileWriter.h | 31 + source/fs/WUXFileWriter.cpp | 113 ++++ source/fs/WUXFileWriter.h | 56 ++ source/fs/WriteOnlyFileWithCache.cpp | 96 +++ source/fs/WriteOnlyFileWithCache.h | 38 ++ source/main.cpp | 15 +- source/utils/utils.cpp | 25 + source/utils/utils.h | 6 + 24 files changed, 1657 insertions(+), 1027 deletions(-) delete mode 100644 source/ApplicationState.cpp create mode 100644 source/GMPartitionsDumperState.cpp create mode 100644 source/GMPartitionsDumperState.h create mode 100644 source/MainApplicationState.cpp create mode 100644 source/MainApplicationState.h create mode 100644 source/WUDDumperState.cpp create mode 100644 source/WUDDumperState.h create mode 100644 source/fs/WUDFileWriter.cpp create mode 100644 source/fs/WUDFileWriter.h create mode 100644 source/fs/WUXFileWriter.cpp create mode 100644 source/fs/WUXFileWriter.h create mode 100644 source/fs/WriteOnlyFileWithCache.cpp create mode 100644 source/fs/WriteOnlyFileWithCache.h diff --git a/Makefile b/Makefile index c1f787d..42cd5bc 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ INCLUDES := include source #------------------------------------------------------------------------------- # options for code generation #------------------------------------------------------------------------------- -CFLAGS := -g -Wall -O2 -ffunction-sections \ +CFLAGS := -g -Wall -O0 -ffunction-sections \ $(MACHDEP) CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ diff --git a/source/ApplicationState.cpp b/source/ApplicationState.cpp deleted file mode 100644 index 07cf09e..0000000 --- a/source/ApplicationState.cpp +++ /dev/null @@ -1,858 +0,0 @@ -#include "ApplicationState.h" -#include "utils/WiiUScreen.h" -#include "utils/ScreenUtils.h" -#include "common/common.h" -#include "utils/utils.h" -#include "utils/StringTools.h" -#include "utils/rijndael.h" -#include "fs/FSUtils.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -extern ntfs_md *ntfs_mounts; -extern int ntfs_mount_count; - - -unsigned int swap_uint32(unsigned int val) { - val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF); - return (val << 16) | (val >> 16); -} - -unsigned long long swap_uint64(unsigned long long val) { - val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL); - val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL); - return (val << 32) | (val >> 32); -} - -/* - * Hash function used to create a hash of each sector - * The hashes are then compared to find duplicate sectors - */ -void calculateHash256(unsigned char *data, unsigned int length, unsigned char *hashOut) { - // cheap and simple hash implementation - // you can replace this part with your favorite hash method - memset(hashOut, 0x00, 32); - for (unsigned int i = 0; i < length; i++) { - hashOut[i % 32] ^= data[i]; - hashOut[(i + 7) % 32] += data[i]; - } -} - -ApplicationState::ApplicationState() : log("fs:/vol/external01/wudump.log", CFile::WriteOnly) { - this->log.fwrite("Started wudump\n"); - this->state = STATE_WELCOME_SCREEN; -} - -void ApplicationState::printHeader() { - WiiUScreen::drawLine("Wudump"); - WiiUScreen::drawLine("=================="); - WiiUScreen::drawLine(""); -} - -void ApplicationState::render() { - WiiUScreen::clearScreen(); - - if (this->state == STATE_ERROR) { - WiiUScreen::drawLine(); - WiiUScreen::drawLinef("Error: %s", ErrorMessage().c_str()); - WiiUScreen::drawLinef("Description: %s", ErrorDescription().c_str()); - WiiUScreen::drawLine(); - WiiUScreen::drawLine("Press A to return to the Wii U Menu."); - } else if (this->state == STATE_WELCOME_SCREEN) { - WiiUScreen::drawLine("Welcome to Wudump"); - WiiUScreen::drawLine("Press A to dump the currently inserted Disc"); - WiiUScreen::drawLine(""); - if (this->selectedOption == 0) { - WiiUScreen::drawLine("> Dump as WUX Dump as WUD Dump as .app Exit"); - } else if (this->selectedOption == 1) { - WiiUScreen::drawLine(" Dump as WUX > Dump as WUD Dump as .app Exit"); - } else if (this->selectedOption == 2) { - WiiUScreen::drawLine(" Dump as WUX Dump as WUD > Dump as .app Exit"); - } else if (this->selectedOption == 3) { - WiiUScreen::drawLine(" Dump as WUX Dump as WUD Dump as .app > Exit"); - } - } else if (this->state == STATE_CHOOSE_TARGET) { - printHeader(); - WiiUScreen::drawLine("Please choose your target:"); - std::vector options; - int32_t targetCount = 0; - if (this->dumpFormat == DUMP_AS_APP) { - options.emplace_back("SD"); - targetCount++; - } - for (int i = 0; i < ntfs_mount_count; i++) { - options.emplace_back(ntfs_mounts[i].name); - targetCount++; - } - - std::string curLine = ""; - if (this->selectedOption == 0) { - curLine = "> Back\t"; - } else { - curLine = " Back\t"; - } - - if (targetCount == 0) { - WiiUScreen::drawLine("Please insert a NTFS formatted USB drive and restart wudump\n"); - } else { - for (int32_t i = 0; i < targetCount; i++) { - if (this->selectedOption - 1 == i) { - curLine += "> " + options[i]; - } else { - curLine += " " + options[i]; - } - curLine += "\t"; - } - } - WiiUScreen::drawLine(curLine.c_str()); - } else if (this->state == STATE_OPEN_ODD1) { - WiiUScreen::drawLine("Open /dev/odd01"); - } else if (this->state == STATE_READ_DISC_INFO) { - WiiUScreen::drawLine("Read disc information"); - } else if (this->state == STATE_READ_DISC_INFO_DONE) { - WiiUScreen::drawLinef("Dumping: %s", this->discId); - } else if (this->state == STATE_DUMP_DISC_KEY) { - WiiUScreen::drawLinef("Dumping game.key"); - } else if (this->state == STATE_DUMP_DISC_START || this->state == STATE_DUMP_DISC || this->state == STATE_WAIT_USER_ERROR_CONFIRM) { - WiiUScreen::drawLinef("Dumping: %s", this->discId); - - float percent = this->currentSector / (WUD_FILE_SIZE / READ_SECTOR_SIZE * 1.0f) * 100.0f; - WiiUScreen::drawLinef("Progress: %0.2f MiB / %5.2f MiB (%2.1f %%)", this->currentSector * (READ_SECTOR_SIZE / 1024.0f / 1024.0f), WUD_FILE_SIZE / 1024.0f / 1024.0f, percent); - if (doWUX) { - WiiUScreen::drawLinef("Written %0.2f MiB. Compression ratio 1:%0.2f", this->hashMap.size() * (READ_SECTOR_SIZE / 1024.0f / 1024.0f), - 1.0f / (this->hashMap.size() / (float) this->currentSector)); - } - - if (this->readResult < 0 || this->oddFd < 0) { - WiiUScreen::drawLine(); - - if (this->oddFd < 0) { - WiiUScreen::drawLine("Failed to open disc, try again."); - } else { - WiiUScreen::drawLinef("Error: Failed to read sector - Error %d", this->readResult); - } - WiiUScreen::drawLine(); - WiiUScreen::drawLine("Press A to skip this sector (will be replaced by 0's)"); - WiiUScreen::drawLine("Press B to try again"); - } else { - OSTime curTime = OSGetTime(); - float remaining = (WUD_FILE_SIZE - (READ_SECTOR_SIZE * this->currentSector)) / 1024.0f / 1024.0f; - float curSpeed = READ_SECTOR_SIZE * ((this->readSectors / 1000.0f) / OSTicksToMilliseconds(curTime - startTime)); - int32_t remainingSec = remaining / curSpeed; - int32_t minutes = (remainingSec / 60) % 60; - int32_t seconds = remainingSec % 60; - int32_t hours = remainingSec / 3600; - - WiiUScreen::drawLinef("Speed: %.2f MiB/s ETA: %02dh %02dm %02ds", curSpeed, remaining, hours, minutes, seconds); - } - - WiiUScreen::drawLine(); - if (!this->skippedSectors.empty()) { - WiiUScreen::drawLinef("Skipped dumping %d sectors", this->skippedSectors.size()); - } - } else if (this->state == STATE_DUMP_DISC_DONE) { - if (!flushWriteCache()) { - setError(ERROR_WRITE_FAILED); - } - WiiUScreen::drawLinef("Dumping done! Press A to continue"); - } else if (this->state == STATE_DUMP_APP_FILES_DONE) { - WiiUScreen::drawLinef("Dumping done! Press A to continue"); - } else if (this->state == STATE_PLEASE_INSERT_DISC) { - WiiUScreen::drawLinef("Please insert a Disc. Press A to continue"); - } - printFooter(); - WiiUScreen::flipBuffers(); -} - -void ApplicationState::update(Input *input) { - if (this->state == STATE_ERROR) { - OSEnableHomeButtonMenu(true); - if (entrySelected(input)) { - SYSLaunchMenu(); - } - } else if (this->state == STATE_WELCOME_SCREEN) { - proccessMenuNavigation(input, 4); - if (entrySelected(input)) { - if (this->selectedOption == 0) { - this->retryCount = 10; - this->state = STATE_CHOOSE_TARGET; - this->dumpFormat = DUMP_AS_WUX; - } else if (this->selectedOption == 1) { - this->retryCount = 10; - this->state = STATE_CHOOSE_TARGET; - this->dumpFormat = DUMP_AS_WUD; - } else if (this->selectedOption == 2) { - this->retryCount = 10; - this->state = STATE_CHOOSE_TARGET; - this->dumpFormat = DUMP_AS_APP; - } else { - SYSLaunchMenu(); - } - this->selectedOption = 0; - return; - } - } else if (this->state == STATE_CHOOSE_TARGET) { - WiiUScreen::drawLine("Please choose your target"); - std::vector options; - uint32_t targetCount = 0; - - if (this->dumpFormat == DUMP_AS_APP) { - options.emplace_back("fs:/vol/external01/"); - targetCount++; - } - if (ntfs_mount_count > 0) { - - for (int i = 0; i < ntfs_mount_count; i++) { - options.emplace_back(std::string(ntfs_mounts[i].name) + ":/"); - targetCount++; - } - } - - proccessMenuNavigation(input, targetCount + 1); - if (entrySelected(input)) { - if (this->selectedOption == 0) { - this->state = STATE_WELCOME_SCREEN; - } else if (targetCount > 0) { - target = options[selectedOption - 1]; - this->state = STATE_OPEN_ODD1; - } - this->selectedOption = 0; - } - } else if (this->state == STATE_OPEN_ODD1) { - if (this->readSectors > 0) { - auto ret = IOSUHAX_FSA_RawOpen(gFSAfd, "/dev/odd01", &(this->oddFd)); - if (ret >= 0) { - // continue! - this->state = STATE_DUMP_DISC; - } else { - this->oddFd = -1; - this->state = STATE_WAIT_USER_ERROR_CONFIRM; - } - return; - } - DEBUG_FUNCTION_LINE("STATE_OPEN_ODD1"); - if (this->retryCount-- <= 0) { - this->state = STATE_PLEASE_INSERT_DISC; - return; - } - auto ret = IOSUHAX_FSA_RawOpen(gFSAfd, "/dev/odd01", &(this->oddFd)); - if (ret >= 0) { - if (this->sectorBuf == nullptr) { - this->sectorBuf = (void *) memalign(0x100, this->sectorBufSize); - if (this->sectorBuf == nullptr) { - this->setError(ERROR_MALLOC_FAILED); - return; - } - } - DEBUG_FUNCTION_LINE("Opened /dev/odd01 %d", this->oddFd); - this->state = STATE_READ_DISC_INFO; - } - } else if (this->state == STATE_READ_DISC_INFO) { - DEBUG_FUNCTION_LINE("STATE_READ_DISC_INFO"); - if (IOSUHAX_FSA_RawRead(gFSAfd, this->sectorBuf, READ_SECTOR_SIZE, 1, 0, this->oddFd) >= 0) { - this->discId[10] = '\0'; - memcpy(this->discId, sectorBuf, 10); - if (this->discId[0] == 0) { - setError(ERROR_NO_DISC_ID); - return; - } - this->state = STATE_READ_DISC_INFO_DONE; - return; - } - - this->setError(ERROR_READ_FIRST_SECTOR); - return; - } else if (this->state == STATE_READ_DISC_INFO_DONE) { - DEBUG_FUNCTION_LINE("STATE_READ_DISC_INFO_DONE"); - this->state = STATE_DUMP_DISC_KEY; - } else if (this->state == STATE_DUMP_DISC_KEY) { - DEBUG_FUNCTION_LINE("STATE_DUMP_DISC_KEY"); - - auto res = IOSUHAX_FSA_RawRead(gFSAfd, this->sectorBuf, READ_SECTOR_SIZE, 1, 3, this->oddFd); - uint8_t discKey[16]; - bool hasDiscKey = false; - if (res >= 0) { - if (((uint32_t *) this->sectorBuf)[0] != 0xCCA6E67B) { - auto discKeyRes = IOSUHAX_ODM_GetDiscKey(discKey); - if (discKeyRes >= 0) { - hasDiscKey = true; - } - } - } - - if (hasDiscKey) { - if (!FSUtils::CreateSubfolder(StringTools::fmt("%swudump/%s", target.c_str(), discId))) { - setError(ERROR_WRITE_FAILED); - return; - } - if (!FSUtils::saveBufferToFile(StringTools::fmt("%swudump/%s/game.key", target.c_str(), discId), discKey, 16)) { - setError(ERROR_WRITE_FAILED); - return; - } - } - if (this->dumpFormat == DUMP_AS_WUX || this->dumpFormat == DUMP_AS_WUD) { - if (this->dumpFormat == DUMP_AS_WUX) { - this->doWUX = true; - } - this->state = STATE_DUMP_DISC_START; - } else { - this->state = STATE_DUMP_APP_FILES; - } - } else if (this->state == STATE_DUMP_APP_FILES) { - ApplicationState::dumpAppFiles(); - if (this->state != STATE_ERROR) { - this->state = STATE_DUMP_APP_FILES_DONE; - } - } else if (this->state == STATE_DUMP_APP_FILES_DONE) { - if (entrySelected(input)) { - this->state = STATE_WELCOME_SCREEN; - if (this->oddFd >= 0) { - IOSUHAX_FSA_RawClose(gFSAfd, this->oddFd); - this->oddFd = -1; - } - this->currentSector = 0; - this->readSectors = 0; - } - } else if (this->state == STATE_DUMP_DISC_START) { - ApplicationState::clearWriteCache(); - DEBUG_FUNCTION_LINE("STATE_DUMP_DISC_START"); - if (!FSUtils::CreateSubfolder(StringTools::fmt("%swudump/%s", target.c_str(), discId))) { - setError(ERROR_WRITE_FAILED); - return; - } - this->fileHandle = new CFile(StringTools::fmt("%swudump/%s/game.%s", target.c_str(), discId, doWUX ? "wux" : "wud"), CFile::WriteOnly); - - this->totalSectorCount = WUD_FILE_SIZE / SECTOR_SIZE; - - if (!this->fileHandle->isOpen()) { - DEBUG_FUNCTION_LINE("Failed to open file"); - this->setError(ERROR_FILE_OPEN_FAILED); - return; - } - - if (doWUX) { - wuxHeader_t wuxHeader = {0}; - wuxHeader.magic0 = WUX_MAGIC_0; - wuxHeader.magic1 = WUX_MAGIC_1; - wuxHeader.sectorSize = swap_uint32(SECTOR_SIZE); - wuxHeader.uncompressedSize = swap_uint64(WUD_FILE_SIZE); - wuxHeader.flags = 0; - - DEBUG_FUNCTION_LINE("Write header"); - this->fileHandle->write((uint8_t *) &wuxHeader, sizeof(wuxHeader_t)); - this->sectorTableStart = this->fileHandle->tell(); - - this->sectorIndexTable = (void *) malloc(totalSectorCount * 4); - if (sectorIndexTable == nullptr) { - this->setError(ERROR_MALLOC_FAILED); - return; - } - memset(this->sectorIndexTable, 0, totalSectorCount * 4); - - DEBUG_FUNCTION_LINE("Write empty sectorIndexTable"); - this->fileHandle->write((uint8_t *) this->sectorIndexTable, totalSectorCount * 4); - - DEBUG_FUNCTION_LINE("Get sector table end"); - this->sectorTableEnd = this->fileHandle->tell(); - uint64_t tableEnd = this->sectorTableEnd; - - this->sectorTableEnd += SECTOR_SIZE - 1; - this->sectorTableEnd -= (this->sectorTableEnd % SECTOR_SIZE); - - uint64_t padding = this->sectorTableEnd - tableEnd; - auto *paddingData = (uint8_t *) malloc(padding); - memset(paddingData, 0, padding); - this->fileHandle->write(reinterpret_cast(paddingData), padding); - free(paddingData); - this->hashMap.clear(); - } - - this->writeBufferSize = READ_SECTOR_SIZE * WRITE_BUFFER_NUM_SECTORS; - this->writeBuffer = (void *) memalign(0x1000, this->writeBufferSize); - if (this->writeBuffer == nullptr) { - this->setError(ERROR_MALLOC_FAILED); - return; - } - this->writeBufferPos = 0; - - this->startTime = OSGetTime(); - - this->state = STATE_DUMP_DISC; - this->currentSector = 0; - this->retryCount = 10; - this->selectedOption = 0; - this->readSectors = 0; - } else if (this->state == STATE_DUMP_DISC) { - //DEBUG_FUNCTION_LINE("STATE_DUMP_DISC"); - int32_t numSectors = this->currentSector + READ_NUM_SECTORS > this->totalSectorCount ? this->totalSectorCount - this->currentSector : READ_NUM_SECTORS; - if ((this->readResult = IOSUHAX_FSA_RawRead(gFSAfd, sectorBuf, READ_SECTOR_SIZE, numSectors, this->currentSector, this->oddFd)) >= 0) { - if (!writeDataToFile(this->sectorBuf, numSectors)) { - this->setError(ERROR_WRITE_FAILED); - return; - } - //DEBUG_FUNCTION_LINE("Read done %lld %lld", this->currentSector, this->totalSectorCount); - this->retryCount = 10; - if (this->currentSector >= this->totalSectorCount) { - this->state = STATE_DUMP_DISC_DONE; - - if (this->fileHandle->isOpen()) { - if (!this->flushWriteCache()) { - this->setError(ERROR_WRITE_FAILED); - return; - } - if (doWUX) { - this->writeSectorIndexTable(); - } - this->fileHandle->close(); - } - } - } else { - this->state = STATE_WAIT_USER_ERROR_CONFIRM; - if (this->oddFd >= 0) { - IOSUHAX_FSA_RawClose(gFSAfd, this->oddFd); - this->oddFd = -1; - } - return; - } - } else if (this->state == STATE_WAIT_USER_ERROR_CONFIRM) { - if (this->autoSkip) { - if (this->oddFd >= 0) { - IOSUHAX_FSA_RawClose(gFSAfd, this->oddFd); - this->oddFd = -1; - } - } - if (this->autoSkip || (input->data.buttons_d & Input::BUTTON_A)) { - this->log.fwrite("Skipped sector %d : 0x%ll016X-0x%ll016X, filled with 0's\n", this->currentSector, this->currentSector * READ_SECTOR_SIZE, (this->currentSector + 1) * READ_SECTOR_SIZE); - this->state = STATE_OPEN_ODD1; - this->skippedSectors.push_back(this->currentSector); - // We can't use seek because we may have cached values. - - if (this->emptySector == nullptr) { - this->emptySector = memalign(0x100, READ_SECTOR_SIZE); - if (this->emptySector == nullptr) { - this->setError(ERROR_MALLOC_FAILED); - return; - } - } - - if (!this->writeCached(reinterpret_cast(emptySector), READ_SECTOR_SIZE)) { - this->setError(ERROR_WRITE_FAILED); - return; - } - - this->currentSector += 1; - this->readResult = 0; - } else if (input->data.buttons_d & Input::BUTTON_B) { - this->state = STATE_OPEN_ODD1; - this->readResult = 0; - } else if (input->data.buttons_d & Input::BUTTON_Y) { - this->autoSkip = true; - } - } else if (this->state == STATE_DUMP_DISC_DONE) { - if (entrySelected(input)) { - if (this->oddFd >= 0) { - IOSUHAX_FSA_RawClose(gFSAfd, this->oddFd); - this->oddFd = -1; - } - this->state = STATE_WELCOME_SCREEN; - this->selectedOption = 0; - this->currentSector = 0; - this->readSectors = 0; - this->writtenSector = 0; - return; - } - } else if (this->state == STATE_PLEASE_INSERT_DISC) { - if (entrySelected(input)) { - this->state = STATE_WELCOME_SCREEN; - } - } -} - -std::string ApplicationState::ErrorMessage() { - if (this->error == ERROR_NONE) { - return "NONE"; - } else if (this->error == ERROR_IOSUHAX_FAILED) { - return "ERROR_IOSUHAX_FAILED"; - } else if (this->error == ERROR_MALLOC_FAILED) { - return "ERROR_MALLOC_FAILED"; - } else if (this->error == ERROR_FILE_OPEN_FAILED) { - return "ERROR_FILE_OPEN_FAILED"; - } else if (this->error == ERROR_NO_DISC_ID) { - return "ERROR_NO_DISC_ID"; - } - DEBUG_FUNCTION_LINE("Error: %d", this->error); - return "UNKNOWN_ERROR"; -} - -std::string ApplicationState::ErrorDescription() { - if (this->error == ERROR_NONE) { - return "-"; - } else if (this->error == ERROR_IOSUHAX_FAILED) { - return "Failed to init IOSUHAX."; - } else if (this->error == ERROR_MALLOC_FAILED) { - return "Failed to allocate data."; - } else if (this->error == ERROR_FILE_OPEN_FAILED) { - return "Failed to create file"; - } else if (this->error == ERROR_NO_DISC_ID) { - return "Failed to get the disc id"; - } - DEBUG_FUNCTION_LINE("Error: %d", this->error); - return "UNKNOWN_ERROR"; -} - -void ApplicationState::setError(eErrorState err) { - this->state = STATE_ERROR; - this->error = err; - //OSEnableHomeButtonMenu(true); -} - -void ApplicationState::printFooter() { - ScreenUtils::printTextOnScreen(CONSOLE_SCREEN_TV, 0, 27, "By Maschell"); - ScreenUtils::printTextOnScreen(CONSOLE_SCREEN_DRC, 0, 17, "By Maschell"); -} - -void ApplicationState::proccessMenuNavigation(Input *input, int maxOptionValue) { - if (input->data.buttons_d & Input::BUTTON_LEFT) { - this->selectedOption--; - } else if (input->data.buttons_d & Input::BUTTON_RIGHT) { - this->selectedOption++; - } - if (this->selectedOption < 0) { - this->selectedOption = maxOptionValue; - } else if (this->selectedOption >= maxOptionValue) { - this->selectedOption = 0; - } -} - -bool ApplicationState::entrySelected(Input *input) { - return input->data.buttons_d & Input::BUTTON_A; -} - -ApplicationState::~ApplicationState() { - this->log.close(); - if (this->fileHandle->isOpen()) { - if (!this->flushWriteCache()) { - - } - if (doWUX) { - this->writeSectorIndexTable(); - } - this->fileHandle->close(); - } - if (this->emptySector != nullptr) { - free(this->emptySector); - this->emptySector = nullptr; - } - if (this->writeBuffer != nullptr) { - free(this->writeBuffer); - this->writeBuffer = nullptr; - } - if (this->sectorIndexTable != nullptr) { - free(this->sectorIndexTable); - this->sectorIndexTable = nullptr; - } - if (this->sectorBuf != nullptr) { - free(this->sectorBuf); - this->sectorBuf = nullptr; - } - if (this->oddFd >= 0) { - IOSUHAX_FSA_RawClose(gFSAfd, this->oddFd); - this->oddFd = -1; - } -} - -bool ApplicationState::writeDataToFile(void *buffer, int numberOfSectors) { - if (!doWUX) { - if (!writeCached(reinterpret_cast(buffer), numberOfSectors * READ_SECTOR_SIZE)) { - return false; - } - this->currentSector += numberOfSectors; - this->readSectors += numberOfSectors; - } else { - char hashOut[32]; - for (int i = 0; i < numberOfSectors; i++) { - uint32_t addr = ((uint32_t) buffer) + (i * READ_SECTOR_SIZE); - calculateHash256(reinterpret_cast(addr), READ_SECTOR_SIZE, reinterpret_cast(hashOut)); - char tmp[34]; - auto *test = (uint32_t *) hashOut; - snprintf(tmp, 33, "%08X%08X%08X%08X", test[0], test[1], test[2], test[3]); - std::string hash(tmp); - - auto *indexTable = (uint32_t *) this->sectorIndexTable; - - auto it = hashMap.find(hash); - if (it != hashMap.end()) { - indexTable[this->currentSector] = swap_uint32(this->hashMap[hash]); - } else { - indexTable[this->currentSector] = swap_uint32(this->writtenSector); - hashMap[hash] = this->writtenSector; - if (this->fileHandle->isOpen()) { - if (!writeCached(addr, READ_SECTOR_SIZE)) { - return false; - } - } - this->writtenSector++; - } - this->currentSector++; - this->readSectors++; - } - } - return true; -} - -bool ApplicationState::writeCached(uint32_t addr, uint32_t writeSize) { - if (writeSize == this->writeBufferSize) { - if (!this->flushWriteCache()) { - return false; - } - int32_t res = this->fileHandle->write(reinterpret_cast(addr), writeSize); - return res >= 0; - } - - uint32_t toWrite = writeSize; - if (toWrite == 0) { - return true; - } - - uint32_t written = 0; - - do { - uint32_t curWrite = toWrite; - - if (this->writeBufferPos + curWrite > this->writeBufferSize) { - curWrite = this->writeBufferSize - this->writeBufferPos; - } - OSBlockMove((void *) (((uint32_t) this->writeBuffer) + this->writeBufferPos), (void *) (addr + written), curWrite, 1); - this->writeBufferPos += curWrite; - - if (this->writeBufferPos == this->writeBufferSize) { - if (!flushWriteCache()) { - return false; - } - } - - toWrite -= curWrite; - written += curWrite; - } while (toWrite > 0); - return true; -} - -bool ApplicationState::flushWriteCache() { - if (this->writeBufferPos > 0) { - int32_t res = this->fileHandle->write(static_cast(this->writeBuffer), this->writeBufferPos); - if (res < 0) { - return false; - } - this->writeBufferPos = 0; - } - return true; -} - -void ApplicationState::clearWriteCache() { - this->writeBufferPos = 0; -} - -void ApplicationState::writeSectorIndexTable() { - if (this->fileHandle->isOpen() && doWUX) { - this->fileHandle->seek(this->sectorTableStart, SEEK_SET); - this->fileHandle->write((uint8_t *) this->sectorIndexTable, totalSectorCount * 4); - } -} - -void ApplicationState::printDumpState(const char *fmt, ...) { - WiiUScreen::clearScreen(); - ApplicationState::printHeader(); - char *buf = (char *) MEMAllocFromDefaultHeapEx(PRINTF_BUFFER_LENGTH, 4); - va_list va; - - if (!buf) { - return; - } - - va_start(va, fmt); - vsnprintf(buf, PRINTF_BUFFER_LENGTH, fmt, va); - - WiiUScreen::drawLine(buf); - - MEMFreeToDefaultHeap(buf); - va_end(va); - ApplicationState::printFooter(); - WiiUScreen::flipBuffers(); -} - -void ApplicationState::dumpAppFiles() { - uint8_t opt[0x400]; - IOSUHAX_read_otp(opt, 0x400); - std::array cKey{}; - memcpy(cKey.data(), opt + 0xE0, 0x10); - - DEBUG_FUNCTION_LINE("Reading Partitions"); - - printDumpState("Reading Partitions..."); - - auto discReader = std::make_shared(); - if (!discReader->IsReady()) { - DEBUG_FUNCTION_LINE("!IsReady"); - this->setError(ERROR_OPEN_ODD1); - return; - } - DEBUG_FUNCTION_LINE("Read DiscHeader"); - auto discHeader = WiiUDiscHeader::make_unique(discReader); - if (!discHeader.has_value()) { - DEBUG_FUNCTION_LINE("Failed to read discheader"); - return; - } - bool forceExit = false; - for (auto &partition: discHeader.value()->wiiUContentsInformation->partitions->partitions) { - auto gmPartition = std::dynamic_pointer_cast(partition); - if (gmPartition != nullptr) { - auto nusTitleOpt = NUSTitle::loadTitleFromGMPartition(gmPartition, discReader, cKey); - if (!nusTitleOpt.has_value()) { - DEBUG_FUNCTION_LINE("nusTitle was null"); - continue; - } - auto nusTitle = nusTitleOpt.value(); - auto dataProvider = nusTitle->dataProcessor->getDataProvider(); - - uint64_t partitionSize = 0; - uint64_t partitionSizeWritten = 0; - for (auto &content: nusTitle->tmd->contentList) { - partitionSize += ROUNDUP(content->encryptedFileSize, 16); - } - - auto partitionDumpInfo = StringTools::strfmt("Partition: %s\n\tProgress: %.2f MiB / %.2f MiB\n", partition->getVolumeId().c_str(), partitionSizeWritten / 1024.0f / 1024.0f, - partitionSize / 1024.0f / 1024.0f); - printDumpState("%s", partitionDumpInfo.c_str()); - - char buffer[512]; - snprintf(buffer, 500, "%swudump/%s/%s", target.c_str(), this->discId, gmPartition->getVolumeId().c_str()); - FSUtils::CreateSubfolder(buffer); - - std::vector wBuffer; - if (dataProvider->getRawTMD(wBuffer)) { - std::string fileName = std::string(buffer).append("/").append(WUD_TMD_FILENAME); - printDumpState("%s\nSaving %s", partitionDumpInfo.c_str(), WUD_TMD_FILENAME); - FSUtils::saveBufferToFile(fileName.c_str(), wBuffer.data(), wBuffer.size()); - wBuffer.clear(); - } - - if (dataProvider->getRawTicket(wBuffer)) { - std::string fileName = std::string(buffer).append("/").append(WUD_TICKET_FILENAME); - printDumpState("%s\nSaving %s", partitionDumpInfo.c_str(), WUD_TICKET_FILENAME); - FSUtils::saveBufferToFile(fileName.c_str(), wBuffer.data(), wBuffer.size()); - wBuffer.clear(); - } - - if (dataProvider->getRawCert(wBuffer)) { - std::string fileName = std::string(buffer).append("/").append(WUD_TICKET_FILENAME); - printDumpState("%s\nSaving %s", partitionDumpInfo.c_str(), WUD_CERT_FILENAME); - FSUtils::saveBufferToFile(fileName.c_str(), wBuffer.data(), wBuffer.size()); - wBuffer.clear(); - } - - auto contentCount = nusTitle->tmd->contentList.size(); - auto contentI = 1; - - for (auto &content: nusTitle->tmd->contentList) { - char bufApp[32]; - snprintf(bufApp, 31, "%08X.app", content->ID); - std::string appFileName = std::string(buffer) + "/" + bufApp; - - partitionDumpInfo = StringTools::strfmt("Partition: %s\n\tProgress: %.2f MiB / %.2f MiB\n", partition->getVolumeId().c_str(), partitionSizeWritten / 1024.0f / 1024.0f, - partitionSize / 1024.0f / 1024.0f); - auto contentDumpInfo = StringTools::strfmt("Saving %s (Content %02d/%02d)\n", bufApp, contentI, contentCount); - - printDumpState("%s\n%s", partitionDumpInfo.c_str(), contentDumpInfo.c_str()); - - uint32_t bufferSize = READ_NUM_SECTORS * READ_SECTOR_SIZE * 2; - - auto *readBuffer = (uint8_t *) malloc(bufferSize); - if (readBuffer == nullptr) { - DEBUG_FUNCTION_LINE("Failed to alloc buffer"); - continue; - } - - CFile file(appFileName, CFile::WriteOnly); - if (!file.isOpen()) { - free(readBuffer); - continue; - } - uint32_t readSoFar = 0; - uint64_t curOffset = 0; - uint32_t size = ROUNDUP(content->encryptedFileSize, 16); - OSTime startTimeApp = OSGetTime(); - do { - if (!WHBProcIsRunning()) { - forceExit = true; - break; - } - startTimeApp = OSGetTime(); - WiiUScreen::clearScreen(); - - uint32_t toRead = size - readSoFar; - if (toRead > bufferSize) { - toRead = bufferSize; - } - - if (!dataProvider->readRawContent(content, readBuffer, curOffset, toRead)) { - DEBUG_FUNCTION_LINE("Failed to read content"); - this->setError(ERROR_WRITE_FAILED); - forceExit = true; - break; - } - if (file.write((const uint8_t *) readBuffer, toRead) != (int32_t) toRead) { - DEBUG_FUNCTION_LINE("Failed to write"); - break; - } - - OSTime curTime = OSGetTime(); - auto curSpeed = (float) toRead / (float) OSTicksToMilliseconds(curTime - startTimeApp); - - readSoFar += toRead; - curOffset += toRead; - - partitionSizeWritten += toRead; - - partitionDumpInfo = StringTools::strfmt("Partition: %s\n\tProgress: %.2f MiB / %.2f MiB\n", partition->getVolumeId().c_str(), partitionSizeWritten / 1024.0f / 1024.0f, - partitionSize / 1024.0f / 1024.0f); - printDumpState("%s\n%s\tProgress: %.2f MiB / %.2f MiB (%0.2f%%)\n\tSpeed: %0.2f MiB/s", partitionDumpInfo.c_str(), contentDumpInfo.c_str(), readSoFar / 1024.0f / 1024.0f, - size / 1024.0f / 1024.0f, ((readSoFar * 1.0f) / size) * 100.0f, curSpeed / 1024.0f); - } while (readSoFar < size); - - file.close(); - - if (forceExit) { - break; - } - - std::vector h3Data; - if (dataProvider->getContentH3Hash(content, h3Data)) { - char bufh3[32]; - snprintf(bufh3, 31, "%08X.h3", content->ID); - std::string h3FileName = std::string(buffer) + "/" + bufh3; - printDumpState("%s\n%s", partitionDumpInfo.c_str(), contentDumpInfo.c_str()); - FSUtils::saveBufferToFile(h3FileName.c_str(), h3Data.data(), h3Data.size()); - } - contentI++; - } - if (forceExit) { - break; - } - } - } -} diff --git a/source/ApplicationState.h b/source/ApplicationState.h index e25baec..2b12bb4 100644 --- a/source/ApplicationState.h +++ b/source/ApplicationState.h @@ -1,140 +1,50 @@ #pragma once -#include -#include -#include -#include -#include -#include "input/Input.h" -#include "fs/CFile.hpp" -#define SECTOR_SIZE 0x8000 -#define READ_SECTOR_SIZE SECTOR_SIZE -#define READ_NUM_SECTORS 128 -#define WRITE_BUFFER_NUM_SECTORS 128 -#define WUD_FILE_SIZE 0x5D3A00000L - - -typedef struct { - unsigned int magic0; - unsigned int magic1; - unsigned int sectorSize; - unsigned long long uncompressedSize; - unsigned int flags; -} wuxHeader_t; - -#define WUX_MAGIC_0 0x57555830 -#define WUX_MAGIC_1 swap_uint32(0x1099d02e) +#include +#include class ApplicationState { public: - - enum eDumpTargetFormat { - DUMP_AS_WUX, - DUMP_AS_WUD, - DUMP_AS_APP, + enum eSubState { + SUBSTATE_RUNNING, + SUBSTATE_RETURN, }; + virtual ~ApplicationState() = default; - enum eErrorState { - ERROR_NONE, - ERROR_IOSUHAX_FAILED, - ERROR_OPEN_ODD1, - ERROR_READ_FIRST_SECTOR, - ERROR_FILE_OPEN_FAILED, - ERROR_MALLOC_FAILED, - ERROR_NO_DISC_ID, - ERROR_SECTOR_SIZE, - ERROR_MAGIC_NUMBER_WRONG, - ERROR_WRITE_FAILED, - }; + virtual void render() = 0; - enum eGameState { - STATE_ERROR, - STATE_WELCOME_SCREEN, - STATE_CHOOSE_TARGET, - STATE_OPEN_ODD1, - STATE_PLEASE_INSERT_DISC, - STATE_DUMP_APP_FILES, - STATE_DUMP_APP_FILES_DONE, - STATE_READ_DISC_INFO, - STATE_READ_DISC_INFO_DONE, - STATE_DUMP_DISC_KEY, - STATE_DUMP_DISC_START, - STATE_DUMP_DISC_DONE, - STATE_WAIT_USER_ERROR_CONFIRM, - STATE_DUMP_DISC, - }; + virtual eSubState update(Input *input) = 0; - ApplicationState(); + virtual void proccessMenuNavigation(Input *input, int32_t maxOptionValue) { + if (input->data.buttons_d & Input::BUTTON_UP) { + this->selectedOption--; + } else if (input->data.buttons_d & Input::BUTTON_DOWN) { + this->selectedOption++; + } + if (this->selectedOption < 0) { + this->selectedOption = maxOptionValue; + } else if (this->selectedOption >= maxOptionValue) { + this->selectedOption = 0; + } + } - ~ApplicationState(); + virtual bool entrySelected(Input *input) { + return input->data.buttons_d & Input::BUTTON_A; + } - void setError(eErrorState error); + virtual void printHeader() { + WiiUScreen::drawLine("Wudump"); + WiiUScreen::drawLine("=================="); + WiiUScreen::drawLine(""); + } - void render(); + virtual void printFooter() { + ScreenUtils::printTextOnScreen(CONSOLE_SCREEN_TV, 0, 27, "By Maschell"); + ScreenUtils::printTextOnScreen(CONSOLE_SCREEN_DRC, 0, 17, "By Maschell"); + } - void update(Input *input); - std::string ErrorMessage(); - - std::string ErrorDescription(); - - int selectedOption; - - static void printFooter(); - - void proccessMenuNavigation(Input *input, int maxOptionValue); - - static bool entrySelected(Input *input); - -private: - static void printHeader(); - - CFile log; - eGameState state; - eDumpTargetFormat dumpFormat; - eErrorState error = ERROR_NONE; - std::string target = "fs:/vol/external01/"; - int oddFd = -1; - int retryCount = 0; - void *sectorBuf = nullptr; - int sectorBufSize = READ_NUM_SECTORS * READ_SECTOR_SIZE; - char discId[11]; - uint64_t currentSector = 0; - std::vector skippedSectors; - int readResult = 0; - - [[nodiscard]] bool writeDataToFile(void *buffer, int numberOfSection); - - uint64_t totalSectorCount = 0; - - std::map hashMap; - CFile *fileHandle; - OSTime startTime; - void *writeBuffer = nullptr; - uint32_t writeBufferPos = 0; - uint32_t writeBufferSize = 0; - - [[nodiscard]] bool writeCached(uint32_t addr, uint32_t writeSize); - - void clearWriteCache(); - - [[nodiscard]] bool flushWriteCache(); - - uint32_t readSectors = 0; - uint64_t sectorTableStart = 0; - void *sectorIndexTable = nullptr; - uint64_t sectorTableEnd = 0; - - void writeSectorIndexTable(); - - void *emptySector = nullptr; - bool doWUX = false; - uint32_t writtenSector = 0; - bool autoSkip = false; - - void dumpAppFiles(); - - static void printDumpState(const char *fmt, ...); + int selectedOption = 0; }; \ No newline at end of file diff --git a/source/GMPartitionsDumperState.cpp b/source/GMPartitionsDumperState.cpp new file mode 100644 index 0000000..602acba --- /dev/null +++ b/source/GMPartitionsDumperState.cpp @@ -0,0 +1,495 @@ +/**************************************************************************** + * Copyright (C) 2021 Maschell + * + * 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 +#include +#include +#include +#include +#include "GMPartitionsDumperState.h" +#include + +#define READ_BUFFER_SIZE (SECTOR_SIZE * 128) + +GMPartitionsDumperState::GMPartitionsDumperState() { + this->sectorBufSize = SECTOR_SIZE; + this->state = STATE_OPEN_ODD1; +} + +GMPartitionsDumperState::~GMPartitionsDumperState() { + free(this->sectorBuf); + this->sectorBuf = nullptr; + free(this->readBuffer); + this->readBuffer = nullptr; +} + +void GMPartitionsDumperState::render() { + WiiUScreen::clearScreen(); + ApplicationState::printHeader(); + + if (this->state == STATE_ERROR) { + WiiUScreen::drawLine(); + WiiUScreen::drawLinef("Error: %s", ErrorMessage().c_str()); + WiiUScreen::drawLinef("Description: %s", ErrorDescription().c_str()); + WiiUScreen::drawLine(); + WiiUScreen::drawLine("Press A to return."); + } else if (this->state == STATE_OPEN_ODD1) { + WiiUScreen::drawLine("Open /dev/odd01"); + } else if (this->state == STATE_PLEASE_INSERT_DISC) { + WiiUScreen::drawLine("Please insert a Wii U disc and try again.\n\nPress A to return"); + } else if (this->state == STATE_READ_DISC_INFO) { + WiiUScreen::drawLine("Read disc information"); + } else if (this->state == STATE_READ_DISC_INFO_DONE) { + WiiUScreen::drawLine("Read disc information done"); + } else if (this->state == STATE_READ_COMMON_KEY) { + WiiUScreen::drawLine("Read common key"); + } else if (this->state == STATE_CREATE_DISC_READER) { + WiiUScreen::drawLine("Create disc reader"); + } else if (this->state == STATE_PARSE_DISC_HEADER) { + WiiUScreen::drawLine("Parse disc header"); + } else if (this->state == STATE_PROCESS_GM_PARTITIONS) { + WiiUScreen::drawLine("Process partitions"); + } else if (this->state == STATE_CHOOSE_PARTITION_TO_DUMP) { + WiiUScreen::drawLine("Choose the partition to dump:"); + WiiUScreen::drawLine(); + if (gmPartitionPairs.empty()) { + WiiUScreen::drawLine("This disc has no dumpable GM partitions "); + } else { + uint32_t index = 0; + for (auto &partitionPair: gmPartitionPairs) { + uint32_t size = 0; + for (auto &content: partitionPair.second->tmd->contentList) { + size += ROUNDUP(content->encryptedFileSize, 16); + } + WiiUScreen::drawLinef("%s %s (~%0.2f MiB)", index == (uint32_t) selectedOption ? ">" : " ", partitionPair.first->getVolumeId().c_str(), (float) size / 1024.0f / 1024.0f); + index++; + } + WiiUScreen::drawLine(); + WiiUScreen::drawLinef("%s Back", index == (uint32_t) selectedOption ? ">" : " "); + } + } else if (this->state == STATE_CREATE_DATA_PROVIDER) { + WiiUScreen::drawLine("Create data provider from partition"); + } else if (this->state == STATE_DUMP_PARTITION_TMD) { + WiiUScreen::drawLine("Dump title.tmd"); + } else if (this->state == STATE_DUMP_PARTITION_TICKET) { + WiiUScreen::drawLine("Dump title.tik"); + } else if (this->state == STATE_DUMP_PARTITION_CERT) { + WiiUScreen::drawLine("Dump title.cert"); + } else if (this->state == STATE_DUMP_PARTITION_CONTENTS) { + if (curPartition != nullptr) { + WiiUScreen::drawLinef("Dumping Partition %s", curPartition->getVolumeId().c_str()); + } else { + WiiUScreen::drawLine("Dumping Partition"); + } + + uint64_t partitionSize = 0; + uint64_t partitionOffset = curOffsetInContent; + if (curNUSTitle != nullptr) { + for (auto &content: curNUSTitle->tmd->contentList) { + partitionSize += ROUNDUP(content->encryptedFileSize, 16); + if (content->index < curContentIndex) { + partitionOffset += ROUNDUP(content->encryptedFileSize, 16); + } + } + WiiUScreen::drawLinef("Total Progress: %.2f MiB / %.2f MiB (%0.2f%%)", partitionOffset / 1024.0f / 1024.0f, + partitionSize / 1024.0f / 1024.0f, ((partitionOffset * 1.0f) / partitionSize) * 100.0f); + WiiUScreen::drawLine(); + } else { + WiiUScreen::drawLine(); + WiiUScreen::drawLine(); + } + + if (curNUSTitle != nullptr) { + WiiUScreen::drawLinef("Dumping Content %d / %d", curContentIndex, curNUSTitle->tmd->contentList.size()); + } else { + WiiUScreen::drawLine("Dumping Content ?? / ??"); + } + + auto offset = curOffsetInContent; + auto size = curContent != nullptr ? ROUNDUP(curContent->encryptedFileSize, 16) : 0; + + if (size > 0) { + WiiUScreen::drawLinef("Progress: %.2f MiB / %.2f MiB (%0.2f%%)", offset / 1024.0f / 1024.0f, + size / 1024.0f / 1024.0f, ((offset * 1.0f) / size) * 100.0f); + } + + } else if (this->state == STATE_DUMP_DONE) { + WiiUScreen::drawLine("Dumping done. Press A to return."); + } + + ApplicationState::printFooter(); + WiiUScreen::flipBuffers(); +} + +ApplicationState::eSubState GMPartitionsDumperState::update(Input *input) { + if (this->state == STATE_RETURN) { + return ApplicationState::SUBSTATE_RETURN; + } + if (this->state == STATE_ERROR) { + if (entrySelected(input)) { + return ApplicationState::SUBSTATE_RETURN; + } + return ApplicationState::SUBSTATE_RUNNING; + } + + if (this->state == STATE_OPEN_ODD1) { + auto ret = IOSUHAX_FSA_RawOpen(gFSAfd, "/dev/odd01", &(this->oddFd)); + if (ret >= 0) { + if (this->sectorBuf == nullptr) { + this->sectorBuf = (void *) memalign(0x100, this->sectorBufSize); + if (this->sectorBuf == nullptr) { + DEBUG_FUNCTION_LINE("ERROR_MALLOC_FAILED"); + this->setError(ERROR_MALLOC_FAILED); + return ApplicationState::SUBSTATE_RUNNING; + } + } + DEBUG_FUNCTION_LINE("Opened /dev/odd01 %d", this->oddFd); + this->state = STATE_READ_DISC_INFO; + } else { + this->state = STATE_PLEASE_INSERT_DISC; + } + } else if (this->state == STATE_PLEASE_INSERT_DISC) { + if (entrySelected(input)) { + return ApplicationState::SUBSTATE_RETURN; + } + } else if (this->state == STATE_READ_DISC_INFO) { + if (IOSUHAX_FSA_RawRead(gFSAfd, this->sectorBuf, READ_SECTOR_SIZE, 1, 0, this->oddFd) >= 0) { + this->discId[10] = '\0'; + memcpy(this->discId.data(), sectorBuf, 10); + if (this->discId[0] == 0) { + setError(ERROR_NO_DISC_ID); + return ApplicationState::SUBSTATE_RUNNING; + } + + this->state = STATE_READ_DISC_INFO_DONE; + return ApplicationState::SUBSTATE_RUNNING; + } + + this->setError(ERROR_READ_FIRST_SECTOR); + return ApplicationState::SUBSTATE_RUNNING; + } else if (this->state == STATE_READ_DISC_INFO_DONE) { + this->state = STATE_READ_COMMON_KEY; + } else if (this->state == STATE_READ_COMMON_KEY) { + uint8_t opt[0x400]; + IOSUHAX_read_otp(opt, 0x400); + memcpy(cKey.data(), opt + 0xE0, 0x10); + this->state = STATE_CREATE_DISC_READER; + } else if (this->state == STATE_CREATE_DISC_READER) { + this->discReader = std::make_shared(); + if (!discReader->IsReady()) { + this->setError(ERROR_OPEN_ODD1); + return SUBSTATE_RUNNING; + } + this->state = STATE_PARSE_DISC_HEADER; + } else if (this->state == STATE_PARSE_DISC_HEADER) { + auto discHeaderOpt = WiiUDiscHeader::make_unique(discReader); + if (!discHeaderOpt.has_value()) { + DEBUG_FUNCTION_LINE("Failed to read DiscHeader"); + this->setError(ERROR_PARSE_DISCHEADER); + return SUBSTATE_RUNNING; + } + this->discHeader = std::move(discHeaderOpt.value()); + this->state = STATE_PROCESS_GM_PARTITIONS; + } else if (this->state == STATE_PROCESS_GM_PARTITIONS) { + this->gmPartitionPairs.clear(); + for (auto &partition: discHeader->wiiUContentsInformation->partitions->partitions) { + auto gmPartition = std::dynamic_pointer_cast(partition); + if (gmPartition != nullptr) { + auto nusTitleOpt = NUSTitle::loadTitleFromGMPartition(gmPartition, discReader, cKey); + if (!nusTitleOpt.has_value()) { + this->setError(ERROR_FAILED_TO_GET_NUSTITLE); + return SUBSTATE_RUNNING; + } + + this->gmPartitionPairs.emplace_back(gmPartition, nusTitleOpt.value()); + } + } + this->state = STATE_CHOOSE_PARTITION_TO_DUMP; + } else if (this->state == STATE_CHOOSE_PARTITION_TO_DUMP) { + if (gmPartitionPairs.empty()) { + if (entrySelected(input)) { + return SUBSTATE_RETURN; + } + } + proccessMenuNavigation(input, (int32_t) gmPartitionPairs.size() + 1); + if (entrySelected(input)) { + if (selectedOption >= (int32_t) gmPartitionPairs.size()) { + return SUBSTATE_RETURN; + } + auto gmPartitionPair = gmPartitionPairs[selectedOption]; + if (gmPartitionPair.first != nullptr) { + this->curPartition = gmPartitionPair.first; + this->curNUSTitle = gmPartitionPair.second; + this->dataProvider = this->curNUSTitle->dataProcessor->getDataProvider(); + } else { + DEBUG_FUNCTION_LINE("Failed to find a GM partition"); + this->setError(ERROR_NO_GM_PARTITION); + return SUBSTATE_RUNNING; + } + + this->targetPath = StringTools::strfmt("%swudump/%s/%s", "ntfs0:/", this->discId, curPartition->getVolumeId().c_str()); + FSUtils::CreateSubfolder(targetPath.c_str()); + + this->state = STATE_DUMP_PARTITION_TMD; + } + } else if (this->state == STATE_DUMP_PARTITION_TMD) { + std::vector wBuffer; + if (dataProvider->getRawTMD(wBuffer)) { + std::string fileName = targetPath + "/" + WUD_TMD_FILENAME; + if (FSUtils::saveBufferToFile(fileName.c_str(), wBuffer.data(), wBuffer.size()) != (int32_t) wBuffer.size()) { + this->setError(ERROR_FAILED_WRITE_TMD); + return SUBSTATE_RUNNING; + } + wBuffer.clear(); + } + this->state = STATE_DUMP_PARTITION_TICKET; + } else if (this->state == STATE_DUMP_PARTITION_TICKET) { + std::vector wBuffer; + if (dataProvider->getRawTicket(wBuffer)) { + std::string fileName = targetPath + "/" + WUD_TICKET_FILENAME; + if (FSUtils::saveBufferToFile(fileName.c_str(), wBuffer.data(), wBuffer.size()) != (int32_t) wBuffer.size()) { + this->setError(ERROR_FAILED_WRITE_TICKET); + return SUBSTATE_RUNNING; + } + wBuffer.clear(); + } else { + this->setError(ERROR_FAILED_WRITE_TICKET); + return SUBSTATE_RUNNING; + } + this->state = STATE_DUMP_PARTITION_CERT; + } else if (this->state == STATE_DUMP_PARTITION_CERT) { + std::vector wBuffer; + if (dataProvider->getRawCert(wBuffer)) { + std::string fileName = targetPath + "/" + WUD_CERT_FILENAME; + if (FSUtils::saveBufferToFile(fileName.c_str(), wBuffer.data(), wBuffer.size()) != (int32_t) wBuffer.size()) { + this->setError(ERROR_FAILED_WRITE_CERT); + return SUBSTATE_RUNNING; + } + wBuffer.clear(); + } else { + this->setError(ERROR_FAILED_WRITE_TICKET); + return SUBSTATE_RUNNING; + } + this->curContentIndex = 0; + this->state = STATE_DUMP_PARTITION_CONTENTS; + } else if (this->state == STATE_DUMP_PARTITION_CONTENTS) { + // Get current content by index. + if (curContent == nullptr) { + auto curContentOpt = curNUSTitle->tmd->getContentByIndex(curContentIndex); + if (!curContentOpt.has_value()) { + this->setError(ERROR_FIND_CONTENT_BY_INDEX); + return SUBSTATE_RUNNING; + } + this->curContent = curContentOpt.value(); + } + + // Check if we're done reading the file. + uint32_t size = ROUNDUP(curContent->encryptedFileSize, 16); + if (curOffsetInContent >= size) { + // Close current file + this->contentFile->close(); + // Free content + this->contentFile.reset(); + this->curContent.reset(); + + curOffsetInContent = 0; + + auto contentListSize = curNUSTitle->tmd->contentList.size(); + if (curContentIndex + 1 < (int32_t) contentListSize) { + curContentIndex++; + return SUBSTATE_RUNNING; + } else { + this->state = STATE_DUMP_DONE; + return SUBSTATE_RUNNING; + } + } + + // Open the file if needed + if (this->contentFile == nullptr) { + char bufApp[32]; + snprintf(bufApp, 31, "%08X.app", curContent->ID); + std::string appFileName = targetPath + "/" + bufApp; + + this->contentFile = std::make_unique(appFileName, CFile::WriteOnly); + if (!contentFile->isOpen()) { + this->contentFile.reset(); + this->setError(ERROR_FAILED_CREATE_FILE); + return SUBSTATE_RUNNING; + } + + // dump .h3 file as well + std::vector h3Data; + if (dataProvider->getContentH3Hash(this->curContent, h3Data)) { + snprintf(bufApp, 31, "%08X.h3", curContent->ID); + std::string h3FileName = targetPath + "/" + bufApp; + if (FSUtils::saveBufferToFile(h3FileName.c_str(), h3Data.data(), h3Data.size()) != (int32_t) h3Data.size()) { + this->setError(ERROR_FAILED_WRITE_H3); + return SUBSTATE_RUNNING; + } + } + } + + // alloc readBuffer if needed + if (this->readBuffer == nullptr) { + readBuffer = (uint8_t *) malloc(READ_BUFFER_SIZE); + if (readBuffer == nullptr) { + this->setError(ERROR_MALLOC_FAILED); + return SUBSTATE_RUNNING; + } + } + + uint32_t toRead = size - curOffsetInContent; + if (toRead > READ_BUFFER_SIZE) { + toRead = READ_BUFFER_SIZE; + } + + if (!dataProvider->readRawContent(curContent, readBuffer, curOffsetInContent, toRead)) { + this->setError(ERROR_READ_CONTENT); + return SUBSTATE_RUNNING; + } + + if (contentFile->write((const uint8_t *) readBuffer, toRead) != (int32_t) toRead) { + this->setError(ERROR_WRITE_CONTENT); + return SUBSTATE_RUNNING; + } + curOffsetInContent += toRead; + + // Go on! + this->state = STATE_DUMP_PARTITION_CONTENTS; + return ApplicationState::SUBSTATE_RUNNING; + } else if (state == STATE_DUMP_DONE) { + if (entrySelected(input)) { + return ApplicationState::SUBSTATE_RETURN; + } + } + + return ApplicationState::SUBSTATE_RUNNING; +} + +void GMPartitionsDumperState::setError(GMPartitionsDumperState::eErrorState err) { + this->state = STATE_ERROR; + this->errorState = err; + //OSEnableHomeButtonMenu(true); +} + +std::string GMPartitionsDumperState::ErrorMessage() const { + if (this->errorState == ERROR_MALLOC_FAILED) { + return "ERROR_MALLOC_FAILED"; + } + if (this->errorState == ERROR_NO_DISC_ID) { + return "ERROR_NO_DISC_ID"; + } + if (this->errorState == ERROR_READ_FIRST_SECTOR) { + return "ERROR_READ_FIRST_SECTOR"; + } + if (this->errorState == ERROR_OPEN_ODD1) { + return "ERROR_OPEN_ODD1"; + } + if (this->errorState == ERROR_PARSE_DISCHEADER) { + return "ERROR_PARSE_DISCHEADER"; + } + if (this->errorState == ERROR_NO_GM_PARTITION) { + return "ERROR_NO_GM_PARTITION"; + } + if (this->errorState == ERROR_FAILED_TO_GET_NUSTITLE) { + return "ERROR_FAILED_TO_GET_NUSTITLE"; + } + if (this->errorState == ERROR_FAILED_WRITE_TMD) { + return "ERROR_FAILED_WRITE_TMD"; + } + if (this->errorState == ERROR_FAILED_WRITE_TICKET) { + return "ERROR_FAILED_WRITE_TICKET"; + } + if (this->errorState == ERROR_FAILED_WRITE_CERT) { + return "ERROR_FAILED_WRITE_CERT"; + } + if (this->errorState == ERROR_FIND_CONTENT_BY_INDEX) { + return "ERROR_FIND_CONTENT_BY_INDEX"; + } + if (this->errorState == ERROR_FAILED_CREATE_FILE) { + return "ERROR_FAILED_CREATE_FILE"; + } + if (this->errorState == ERROR_FAILED_WRITE_H3) { + return "ERROR_FAILED_WRITE_H3"; + } + if (this->errorState == ERROR_READ_CONTENT) { + return "ERROR_READ_CONTENT"; + } + if (this->errorState == ERROR_WRITE_CONTENT) { + return "ERROR_WRITE_CONTENT"; + } + if (this->errorState == ERROR_MALLOC_FAILED) { + return "ERROR_MALLOC_FAILED"; + } + return "UNKNOWN_ERROR"; +} + +std::string GMPartitionsDumperState::ErrorDescription() const { + if (this->errorState == ERROR_MALLOC_FAILED) { + return "ERROR_MALLOC_FAILED"; + } + if (this->errorState == ERROR_NO_DISC_ID) { + return "ERROR_NO_DISC_ID"; + } + if (this->errorState == ERROR_READ_FIRST_SECTOR) { + return "ERROR_READ_FIRST_SECTOR"; + } + if (this->errorState == ERROR_OPEN_ODD1) { + return "ERROR_OPEN_ODD1"; + } + if (this->errorState == ERROR_PARSE_DISCHEADER) { + return "ERROR_PARSE_DISCHEADER"; + } + if (this->errorState == ERROR_NO_GM_PARTITION) { + return "ERROR_NO_GM_PARTITION"; + } + if (this->errorState == ERROR_FAILED_TO_GET_NUSTITLE) { + return "ERROR_FAILED_TO_GET_NUSTITLE"; + } + if (this->errorState == ERROR_FAILED_WRITE_TMD) { + return "ERROR_FAILED_WRITE_TMD"; + } + if (this->errorState == ERROR_FAILED_WRITE_TICKET) { + return "ERROR_FAILED_WRITE_TICKET"; + } + if (this->errorState == ERROR_FAILED_WRITE_CERT) { + return "ERROR_FAILED_WRITE_CERT"; + } + if (this->errorState == ERROR_FIND_CONTENT_BY_INDEX) { + return "ERROR_FIND_CONTENT_BY_INDEX"; + } + if (this->errorState == ERROR_FAILED_CREATE_FILE) { + return "ERROR_FAILED_CREATE_FILE"; + } + if (this->errorState == ERROR_FAILED_WRITE_H3) { + return "ERROR_FAILED_WRITE_H3"; + } + if (this->errorState == ERROR_READ_CONTENT) { + return "ERROR_READ_CONTENT"; + } + if (this->errorState == ERROR_WRITE_CONTENT) { + return "ERROR_WRITE_CONTENT"; + } + if (this->errorState == ERROR_MALLOC_FAILED) { + return "ERROR_MALLOC_FAILED"; + } + return "UNKNOWN_ERROR"; +} + + diff --git a/source/GMPartitionsDumperState.h b/source/GMPartitionsDumperState.h new file mode 100644 index 0000000..ef155ac --- /dev/null +++ b/source/GMPartitionsDumperState.h @@ -0,0 +1,110 @@ +/**************************************************************************** + * Copyright (C) 2021 Maschell + * + * 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 . + ****************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include +#include "ApplicationState.h" +#include "fs/WriteOnlyFileWithCache.h" +#include "fs/WUXFileWriter.h" +#include +#include +#include + +class GMPartitionsDumperState : public ApplicationState { +public: + enum eDumpState { + STATE_ERROR, + STATE_RETURN, + STATE_OPEN_ODD1, + STATE_READ_DISC_INFO, + STATE_PLEASE_INSERT_DISC, + STATE_READ_DISC_INFO_DONE, + STATE_READ_COMMON_KEY, + STATE_CREATE_DISC_READER, + STATE_PARSE_DISC_HEADER, + STATE_PROCESS_GM_PARTITIONS, + STATE_CHOOSE_PARTITION_TO_DUMP, + STATE_CREATE_DATA_PROVIDER, + STATE_DUMP_PARTITION_TMD, + STATE_DUMP_PARTITION_TICKET, + STATE_DUMP_PARTITION_CERT, + STATE_DUMP_PARTITION_CONTENTS, + STATE_DUMP_DONE + }; + + enum eErrorState { + ERROR_NONE, + ERROR_MALLOC_FAILED, + ERROR_NO_DISC_ID, + ERROR_READ_FIRST_SECTOR, + ERROR_OPEN_ODD1, + ERROR_PARSE_DISCHEADER, + ERROR_NO_GM_PARTITION, + ERROR_FAILED_TO_GET_NUSTITLE, + ERROR_FAILED_WRITE_TMD, + ERROR_FAILED_WRITE_TICKET, + ERROR_FAILED_WRITE_CERT, + ERROR_FIND_CONTENT_BY_INDEX, + ERROR_FAILED_CREATE_FILE, + ERROR_FAILED_WRITE_H3, + ERROR_READ_CONTENT, + ERROR_WRITE_CONTENT + }; + + explicit GMPartitionsDumperState(); + + ~GMPartitionsDumperState() override; + + eDumpState state; + eErrorState errorState = ERROR_NONE; + + void render() override; + + eSubState update(Input *input) override; + + void setError(eErrorState err); + + std::array discId{}; + std::array cKey{}; + + int32_t oddFd = -1; + void *sectorBuf = nullptr; + uint32_t sectorBufSize; + std::shared_ptr discReader = nullptr; + std::unique_ptr discHeader = nullptr; + + std::shared_ptr curPartition = nullptr; + std::shared_ptr dataProvider = nullptr; + std::string targetPath; + std::shared_ptr curNUSTitle = nullptr; + + uint16_t curContentIndex = 0; + std::unique_ptr contentFile = nullptr; + std::shared_ptr curContent = nullptr; + uint8_t *readBuffer = nullptr; + uint32_t curOffsetInContent = 0; + + [[nodiscard]] std::string ErrorMessage() const; + + [[nodiscard]] std::string ErrorDescription() const; + + std::vector, std::shared_ptr>> gmPartitionPairs; +}; \ No newline at end of file diff --git a/source/MainApplicationState.cpp b/source/MainApplicationState.cpp new file mode 100644 index 0000000..23e338c --- /dev/null +++ b/source/MainApplicationState.cpp @@ -0,0 +1,83 @@ +/**************************************************************************** + * Copyright (C) 2021 Maschell + * + * 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 "MainApplicationState.h" +#include "WUDDumperState.h" +#include "GMPartitionsDumperState.h" +#include + +MainApplicationState::MainApplicationState() { + DEBUG_FUNCTION_LINE("Welcome!"); + this->state = STATE_WELCOME_SCREEN; +} + +MainApplicationState::~MainApplicationState() = default;; + +void MainApplicationState::render() { + if (this->state == STATE_DO_SUBSTATE) { + if (this->subState == nullptr) { + OSFatal("SubState was null"); + } + this->subState->render(); + return; + } + WiiUScreen::clearScreen(); + printHeader(); + + if (this->state == STATE_WELCOME_SCREEN) { + WiiUScreen::drawLine("Welcome to Wudump"); + WiiUScreen::drawLine(""); + WiiUScreen::drawLinef("%s Dump as WUX", this->selectedOption == 0 ? ">" : " "); + WiiUScreen::drawLinef("%s Dump as WUD", this->selectedOption == 1 ? ">" : " "); + WiiUScreen::drawLinef("%s Dump partition as .app", this->selectedOption == 2 ? ">" : " "); + WiiUScreen::drawLinef("%s Exit", this->selectedOption == 3 ? ">" : " "); + } + + printFooter(); + WiiUScreen::flipBuffers(); +} + +ApplicationState::eSubState MainApplicationState::update(Input *input) { + if (this->state == STATE_WELCOME_SCREEN) { + proccessMenuNavigation(input, 4); + if (entrySelected(input)) { + if (this->selectedOption == 0) { + this->state = STATE_DO_SUBSTATE; + this->subState = std::make_unique(WUDDumperState::DUMP_AS_WUX); + } else if (this->selectedOption == 1) { + this->state = STATE_DO_SUBSTATE; + this->subState = std::make_unique(WUDDumperState::DUMP_AS_WUD); + } else if (this->selectedOption == 2) { + this->state = STATE_DO_SUBSTATE; + this->subState = std::make_unique(); + } else { + SYSLaunchMenu(); + } + this->selectedOption = 0; + } + } else if (this->state == STATE_DO_SUBSTATE) { + auto retSubState = this->subState->update(input); + if (retSubState == SUBSTATE_RUNNING) { + // keep running. + return SUBSTATE_RUNNING; + } else if (retSubState == SUBSTATE_RETURN) { + DEBUG_FUNCTION_LINE("Delete sub state"); + this->subState.reset(); + this->state = STATE_WELCOME_SCREEN; + } + } + return SUBSTATE_RUNNING; +} diff --git a/source/MainApplicationState.h b/source/MainApplicationState.h new file mode 100644 index 0000000..14564c3 --- /dev/null +++ b/source/MainApplicationState.h @@ -0,0 +1,49 @@ +/**************************************************************************** + * Copyright (C) 2021 Maschell + * + * 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 . + ****************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "input/Input.h" +#include "fs/CFile.hpp" +#include "ApplicationState.h" + + +class MainApplicationState : public ApplicationState { +public: + enum eGameState { + STATE_WELCOME_SCREEN, + STATE_DO_SUBSTATE, + }; + + MainApplicationState(); + + ~MainApplicationState() override; + + void render() override; + + ApplicationState::eSubState update(Input *input) override; + +private: + std::unique_ptr subState{}; + + eGameState state = STATE_WELCOME_SCREEN; +}; \ No newline at end of file diff --git a/source/WUD/DiscReader.cpp b/source/WUD/DiscReader.cpp index c4a3d6c..57576aa 100644 --- a/source/WUD/DiscReader.cpp +++ b/source/WUD/DiscReader.cpp @@ -16,11 +16,10 @@ ****************************************************************************/ #include -#include -#include +#include #include #include -#include "DiscReader.h" +#include bool DiscReader::readDecryptedChunk(uint64_t readOffset, uint8_t *out_buffer, uint8_t *key, uint8_t *IV) const { int CHUNK_SIZE = 0x10000; diff --git a/source/WUD/DiscReaderDiscDrive.cpp b/source/WUD/DiscReaderDiscDrive.cpp index ef15a5f..4a33ed6 100644 --- a/source/WUD/DiscReaderDiscDrive.cpp +++ b/source/WUD/DiscReaderDiscDrive.cpp @@ -16,7 +16,7 @@ ****************************************************************************/ #include #include -#include +#include #include #include #include diff --git a/source/WUD/DiscReaderDiscDrive.h b/source/WUD/DiscReaderDiscDrive.h index 3dc4d8c..04091f4 100644 --- a/source/WUD/DiscReaderDiscDrive.h +++ b/source/WUD/DiscReaderDiscDrive.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include "DiscReader.h" class DiscReaderDiscDrive : public DiscReader { diff --git a/source/WUDDumperState.cpp b/source/WUDDumperState.cpp new file mode 100644 index 0000000..753d12b --- /dev/null +++ b/source/WUDDumperState.cpp @@ -0,0 +1,326 @@ +/**************************************************************************** + * Copyright (C) 2021 Maschell + * + * 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 +#include +#include +#include +#include +#include "WUDDumperState.h" + +WUDDumperState::WUDDumperState(WUDDumperState::eDumpTargetFormat pTarget) + : target(pTarget) { + this->sectorBufSize = READ_SECTOR_SIZE * READ_NUM_SECTORS; + this->state = STATE_OPEN_ODD1; +} + +WUDDumperState::~WUDDumperState() { + if (this->oddFd >= 0) { + IOSUHAX_FSA_RawClose(gFSAfd, oddFd); + } + free(sectorBuf); + free(emptySector); +} + + +ApplicationState::eSubState WUDDumperState::update(Input *input) { + if (this->state == STATE_RETURN) { + return ApplicationState::SUBSTATE_RETURN; + } + if (this->state == STATE_ERROR) { + if (entrySelected(input)) { + return ApplicationState::SUBSTATE_RETURN; + } + return ApplicationState::SUBSTATE_RUNNING; + } + if (this->state == STATE_OPEN_ODD1) { + if (this->currentSector > 0) { + auto ret = IOSUHAX_FSA_RawOpen(gFSAfd, "/dev/odd01", &(this->oddFd)); + if (ret >= 0) { + // continue! + this->state = STATE_DUMP_DISC; + } else { + this->oddFd = -1; + this->state = STATE_WAIT_USER_ERROR_CONFIRM; + } + return ApplicationState::SUBSTATE_RUNNING; + } + if (this->retryCount-- <= 0) { + this->state = STATE_PLEASE_INSERT_DISC; + return ApplicationState::SUBSTATE_RUNNING; + } + auto ret = IOSUHAX_FSA_RawOpen(gFSAfd, "/dev/odd01", &(this->oddFd)); + if (ret >= 0) { + if (this->sectorBuf == nullptr) { + this->sectorBuf = (void *) memalign(0x100, this->sectorBufSize); + if (this->sectorBuf == nullptr) { + DEBUG_FUNCTION_LINE("ERROR_MALLOC_FAILED"); + this->setError(ERROR_MALLOC_FAILED); + return ApplicationState::SUBSTATE_RUNNING; + } + } + DEBUG_FUNCTION_LINE("Opened /dev/odd01 %d", this->oddFd); + this->state = STATE_READ_DISC_INFO; + } + } else if (this->state == STATE_PLEASE_INSERT_DISC) { + if (entrySelected(input)) { + this->state = STATE_RETURN; + } + } else if (this->state == STATE_READ_DISC_INFO) { + if (IOSUHAX_FSA_RawRead(gFSAfd, this->sectorBuf, READ_SECTOR_SIZE, 1, 0, this->oddFd) >= 0) { + this->discId[10] = '\0'; + memcpy(this->discId.data(), sectorBuf, 10); + if (this->discId[0] == 0) { + setError(ERROR_NO_DISC_ID); + return ApplicationState::SUBSTATE_RUNNING; + } + this->state = STATE_READ_DISC_INFO_DONE; + return ApplicationState::SUBSTATE_RUNNING; + } + + this->setError(ERROR_READ_FIRST_SECTOR); + return ApplicationState::SUBSTATE_RUNNING; + } else if (this->state == STATE_READ_DISC_INFO_DONE) { + this->state = STATE_DUMP_DISC_KEY; + } else if (this->state == STATE_DUMP_DISC_KEY) { + // Read the WiiUDiscContentsHeader to determine if we need disckey and if it's the correct one. + auto res = IOSUHAX_FSA_RawRead(gFSAfd, this->sectorBuf, READ_SECTOR_SIZE, 1, 3, this->oddFd); + uint8_t discKey[16]; + bool hasDiscKey = false; + if (res >= 0) { + if (((uint32_t *) this->sectorBuf)[0] != WiiUDiscContentsHeader::MAGIC) { + auto discKeyRes = IOSUHAX_ODM_GetDiscKey(discKey); + if (discKeyRes >= 0) { + hasDiscKey = true; + } + } + } + + if (hasDiscKey) { + if (!FSUtils::CreateSubfolder(StringTools::fmt("%swudump/%s", "ntfs0:/", discId))) { + setError(ERROR_WRITE_FAILED); + return SUBSTATE_RUNNING; + } + if (!FSUtils::saveBufferToFile(StringTools::fmt("%swudump/%s/game.key", "ntfs0:/", discId), discKey, 16)) { + setError(ERROR_WRITE_FAILED); + return SUBSTATE_RUNNING; + } + } + this->state = STATE_DUMP_DISC_START; + } else if (this->state == STATE_DUMP_DISC_START) { + if (!FSUtils::CreateSubfolder(StringTools::fmt("%swudump/%s", "ntfs0:/", discId))) { + setError(ERROR_WRITE_FAILED); + return ApplicationState::SUBSTATE_RUNNING; + } + if (target == DUMP_AS_WUX) { + this->fileHandle = std::make_unique(StringTools::fmt("%swudump/%s/game.wux", "ntfs0:/", discId), CFile::WriteOnly, READ_SECTOR_SIZE * WRITE_BUFFER_NUM_SECTORS, SECTOR_SIZE); + } else { + this->fileHandle = std::make_unique(StringTools::fmt("%swudump/%s/game.wud", "ntfs0:/", discId), CFile::WriteOnly, READ_SECTOR_SIZE * WRITE_BUFFER_NUM_SECTORS, SECTOR_SIZE); + } + if (!this->fileHandle->isOpen()) { + DEBUG_FUNCTION_LINE("Failed to open file"); + this->setError(ERROR_FILE_OPEN_FAILED); + return ApplicationState::SUBSTATE_RUNNING; + } + + this->startTime = OSGetTime(); + + this->state = STATE_DUMP_DISC; + this->totalSectorCount = (WUD_FILE_SIZE / SECTOR_SIZE); + this->currentSector = 0; + this->writtenSectors = 0; + this->retryCount = 10; + } else if (this->state == STATE_DUMP_DISC) { + size_t numSectors = this->currentSector + READ_NUM_SECTORS > this->totalSectorCount ? this->totalSectorCount - this->currentSector : READ_NUM_SECTORS; + if ((this->readResult = IOSUHAX_FSA_RawRead(gFSAfd, sectorBuf, READ_SECTOR_SIZE, numSectors, this->currentSector, this->oddFd)) >= 0) { + auto curWrittenSectors = fileHandle->writeSector((const uint8_t *) this->sectorBuf, numSectors); + if (curWrittenSectors < 0) { + this->setError(ERROR_WRITE_FAILED); + return ApplicationState::SUBSTATE_RUNNING; + } + currentSector += numSectors; + this->writtenSectors += curWrittenSectors; + + this->retryCount = 10; + if (this->currentSector >= this->totalSectorCount) { + this->state = STATE_DUMP_DISC_DONE; + if (this->fileHandle->isOpen()) { + if (!this->fileHandle->flush()) { + DEBUG_FUNCTION_LINE("Flush failed"); + this->setError(ERROR_WRITE_FAILED); + return ApplicationState::SUBSTATE_RUNNING; + } + this->fileHandle->close(); + } + } + } else { + this->state = STATE_WAIT_USER_ERROR_CONFIRM; + if (this->oddFd >= 0) { + IOSUHAX_FSA_RawClose(gFSAfd, this->oddFd); + this->oddFd = -1; + } + return ApplicationState::SUBSTATE_RUNNING; + } + } else if (this->state == STATE_WAIT_USER_ERROR_CONFIRM) { + if (this->autoSkipOnError) { + if (this->oddFd >= 0) { + IOSUHAX_FSA_RawClose(gFSAfd, this->oddFd); + this->oddFd = -1; + } + } + if (this->autoSkipOnError || (input->data.buttons_d & Input::BUTTON_A)) { + // this->log.fwrite("Skipped sector %d : 0x%ll016X-0x%ll016X, filled with 0's\n", this->currentSector, this->currentSector * READ_SECTOR_SIZE, (this->currentSector + 1) * READ_SECTOR_SIZE); + this->state = STATE_OPEN_ODD1; + this->skippedSectors.push_back(this->currentSector); + // We can't use seek because we may have cached values. + + if (this->emptySector == nullptr) { + this->emptySector = memalign(0x100, READ_SECTOR_SIZE); + if (this->emptySector == nullptr) { + this->setError(ERROR_MALLOC_FAILED); + return ApplicationState::SUBSTATE_RUNNING; + } + } + + auto curWrittenSectors = fileHandle->writeSector((uint8_t *) emptySector, 1); + if (curWrittenSectors < 0) { + this->setError(ERROR_WRITE_FAILED); + return ApplicationState::SUBSTATE_RUNNING; + } + + this->currentSector += 1; + this->writtenSectors += curWrittenSectors; + this->readResult = 0; + } else if (input->data.buttons_d & Input::BUTTON_B) { + this->state = STATE_OPEN_ODD1; + this->readResult = 0; + } else if (input->data.buttons_d & Input::BUTTON_Y) { + this->autoSkipOnError = true; + } + } else if (this->state == STATE_DUMP_DISC_DONE) { + WiiUScreen::drawLinef("Dumping done! Press A to continue"); + if (entrySelected(input)) { + this->state = STATE_RETURN; + } + + return ApplicationState::SUBSTATE_RUNNING; + } + return ApplicationState::SUBSTATE_RUNNING; +} + + +void WUDDumperState::render() { + WiiUScreen::clearScreen(); + ApplicationState::printHeader(); + if (this->state == STATE_ERROR) { + WiiUScreen::drawLine(); + WiiUScreen::drawLinef("Error: %s", ErrorMessage().c_str()); + WiiUScreen::drawLinef("Description: %s", ErrorDescription().c_str()); + WiiUScreen::drawLine(); + WiiUScreen::drawLine("Press A to return."); + } else if (this->state == STATE_OPEN_ODD1) { + WiiUScreen::drawLine("Open /dev/odd01"); + } else if (this->state == STATE_PLEASE_INSERT_DISC) { + WiiUScreen::drawLine("Please insert a Wii U disc and try again.\n\nPress A to return"); + } else if (this->state == STATE_READ_DISC_INFO) { + WiiUScreen::drawLine("Read disc information"); + } else if (this->state == STATE_READ_DISC_INFO_DONE) { + WiiUScreen::drawLinef("Dumping: %s", this->discId); + } else if (this->state == STATE_DUMP_DISC_START || this->state == STATE_DUMP_DISC || this->state == STATE_WAIT_USER_ERROR_CONFIRM) { + WiiUScreen::drawLinef("Dumping: %s", this->discId); + + float percent = this->currentSector / (WUD_FILE_SIZE / READ_SECTOR_SIZE * 1.0f) * 100.0f; + WiiUScreen::drawLinef("Progress: %0.2f MiB / %5.2f MiB (%2.1f %%)", this->currentSector * (READ_SECTOR_SIZE / 1024.0f / 1024.0f), WUD_FILE_SIZE / 1024.0f / 1024.0f, percent); + if (target == DUMP_AS_WUX) { + WiiUScreen::drawLinef("Written %0.2f MiB. Compression ratio 1:%0.2f", writtenSectors * (READ_SECTOR_SIZE / 1024.0f / 1024.0f), + 1.0f / (writtenSectors / (float) this->currentSector)); + } + + if (this->readResult < 0 || this->oddFd < 0) { + WiiUScreen::drawLine(); + + if (this->oddFd < 0) { + WiiUScreen::drawLine("Failed to open disc, try again."); + } else { + WiiUScreen::drawLinef("Error: Failed to read sector - Error %d", this->readResult); + } + WiiUScreen::drawLine(); + WiiUScreen::drawLine("Press A to skip this sector (will be replaced by 0's)"); + WiiUScreen::drawLine("Press B to try again"); + } else { + OSTime curTime = OSGetTime(); + float remaining = (WUD_FILE_SIZE - (READ_SECTOR_SIZE * this->currentSector)) / 1024.0f / 1024.0f; + float curSpeed = READ_SECTOR_SIZE * ((this->currentSector / 1000.0f) / OSTicksToMilliseconds(curTime - startTime)); + int32_t remainingSec = remaining / curSpeed; + int32_t minutes = (remainingSec / 60) % 60; + int32_t seconds = remainingSec % 60; + int32_t hours = remainingSec / 3600; + + WiiUScreen::drawLinef("Speed: %.2f MiB/s ETA: %02dh %02dm %02ds", curSpeed, remaining, hours, minutes, seconds); + } + + WiiUScreen::drawLine(); + if (!this->skippedSectors.empty()) { + WiiUScreen::drawLinef("Skipped dumping %d sectors", this->skippedSectors.size()); + } + } else if (this->state == STATE_DUMP_DISC_DONE) { + WiiUScreen::drawLinef("Dumping done! Press A to continue"); + } else if (this->state == STATE_RETURN) { + WiiUScreen::drawLinef("Returning"); + } + + ApplicationState::printFooter(); + WiiUScreen::flipBuffers(); +} + +void WUDDumperState::setError(WUDDumperState::eErrorState err) { + this->state = STATE_ERROR; + this->errorState = err; + //OSEnableHomeButtonMenu(true); +} + +std::string WUDDumperState::ErrorMessage() const { + if (this->errorState == ERROR_READ_FIRST_SECTOR) { + return "ERROR_READ_FIRST_SECTOR"; + } else if (this->errorState == ERROR_FILE_OPEN_FAILED) { + return "ERROR_FILE_OPEN_FAILED"; + } else if (this->errorState == ERROR_MALLOC_FAILED) { + return "ERROR_MALLOC_FAILED"; + } else if (this->errorState == ERROR_NO_DISC_ID) { + return "ERROR_NO_DISC_ID"; + } else if (this->errorState == ERROR_WRITE_FAILED) { + return "ERROR_WRITE_FAILED"; + } + return "UNKNOWN_ERROR"; +} + +std::string WUDDumperState::ErrorDescription() const { + if (this->errorState == ERROR_READ_FIRST_SECTOR) { + return "Failed to read first sector."; + } else if (this->errorState == ERROR_MALLOC_FAILED) { + return "Failed to allocate data."; + } else if (this->errorState == ERROR_FILE_OPEN_FAILED) { + return "Failed to create file"; + } else if (this->errorState == ERROR_NO_DISC_ID) { + return "Failed to get the disc id"; + } else if (this->errorState == ERROR_WRITE_FAILED) { + return "Failed to write the file. \nMake sure to have enough space on the storage"; + } + return "UNKNOWN_ERROR"; +} diff --git a/source/WUDDumperState.h b/source/WUDDumperState.h new file mode 100644 index 0000000..1ed98d5 --- /dev/null +++ b/source/WUDDumperState.h @@ -0,0 +1,108 @@ +/**************************************************************************** + * Copyright (C) 2021 Maschell + * + * 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 . + ****************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include +#include "ApplicationState.h" +#include "fs/WriteOnlyFileWithCache.h" +#include "fs/WUXFileWriter.h" + +#define READ_NUM_SECTORS 128 +#define WRITE_BUFFER_NUM_SECTORS 128 +#define WUD_FILE_SIZE 0x5D3A00000L + +class WUDDumperState : public ApplicationState { +public: + + enum eDumpTargetFormat { + DUMP_AS_WUX, + DUMP_AS_WUD, + }; + + enum eDumpState { + STATE_ERROR, + STATE_RETURN, + STATE_OPEN_ODD1, + STATE_PLEASE_INSERT_DISC, + STATE_READ_DISC_INFO, + STATE_READ_DISC_INFO_DONE, + STATE_DUMP_DISC_KEY, + STATE_DUMP_DISC_START, + STATE_DUMP_DISC_DONE, + STATE_WAIT_USER_ERROR_CONFIRM, + STATE_DUMP_DISC, + }; + + enum eErrorState { + ERROR_NONE, + ERROR_READ_FIRST_SECTOR, + ERROR_FILE_OPEN_FAILED, + ERROR_MALLOC_FAILED, + ERROR_NO_DISC_ID, + ERROR_WRITE_FAILED, + ERROR_NO_DISC_FOUND + }; + + explicit WUDDumperState(eDumpTargetFormat pTarget); + + ~WUDDumperState() override; + + eDumpState state; + eDumpTargetFormat target; + WUDDumperState::eErrorState errorState = ERROR_NONE; + + void render() override; + + eSubState update(Input *input) override; + + void setError(eErrorState err); + + [[nodiscard]] std::string ErrorMessage() const; + + [[nodiscard]] std::string ErrorDescription() const; + + void *sectorBuf = nullptr; + + int readResult = 0; + int oddFd = -1; + int retryCount = 10; + + OSTime startTime{}; + + std::unique_ptr fileHandle = {}; + + std::array discId{}; + + uint32_t sectorBufSize{}; + + // + uint64_t currentSector{}; + uint64_t totalSectorCount{}; + + // + std::vector skippedSectors; + bool autoSkipOnError = false; + + // We need this to calculate the compression ratio. + int32_t writtenSectors{}; + + void *emptySector = nullptr; +}; \ No newline at end of file diff --git a/source/common/common.h b/source/common/common.h index c400edb..7e80796 100644 --- a/source/common/common.h +++ b/source/common/common.h @@ -2,4 +2,7 @@ #include -extern int32_t gFSAfd; \ No newline at end of file +extern int32_t gFSAfd; + +#define SECTOR_SIZE 0x8000 +#define READ_SECTOR_SIZE SECTOR_SIZE \ No newline at end of file diff --git a/source/fs/CFile.cpp b/source/fs/CFile.cpp index c562f11..f3583a7 100644 --- a/source/fs/CFile.cpp +++ b/source/fs/CFile.cpp @@ -1,11 +1,12 @@ -#include -#include +#include +#include #include +#include #include "CFile.hpp" CFile::CFile() { iFd = -1; - mem_file = NULL; + mem_file = nullptr; filesize = 0; pos = 0; } @@ -21,7 +22,7 @@ CFile::CFile(const uint8_t *mem, int32_t size) { } CFile::~CFile() { - this->close(); + CFile::close(); } int32_t CFile::open(const std::string &filepath, eOpenTypes mode) { @@ -76,7 +77,7 @@ void CFile::close() { ::close(iFd); iFd = -1; - mem_file = NULL; + mem_file = nullptr; filesize = 0; pos = 0; } @@ -89,18 +90,18 @@ int32_t CFile::read(uint8_t *ptr, size_t size) { return ret; } - int32_t readsize = size; + size_t readsize = size; if (readsize > (int64_t) (filesize - pos)) readsize = filesize - pos; if (readsize <= 0) - return readsize; + return (int32_t) readsize; if (mem_file != NULL) { memcpy(ptr, mem_file + pos, readsize); pos += readsize; - return readsize; + return (int32_t) readsize; } return -1; @@ -111,20 +112,23 @@ int32_t CFile::write(const uint8_t *ptr, size_t size) { size_t done = 0; while (done < size) { int32_t ret = ::write(iFd, ptr, size - done); - if (ret <= 0) + if (ret <= 0) { return ret; + } ptr += ret; done += ret; pos += ret; } - return done; + if (pos > filesize) { + filesize = pos; + } + return (int32_t) done; } - return -1; } -int32_t CFile::seek(long int offset, int32_t origin) { +int32_t CFile::seek(int64_t offset, int32_t origin) { int32_t ret = 0; int64_t newPos = pos; @@ -145,7 +149,7 @@ int32_t CFile::seek(long int offset, int32_t origin) { if (iFd >= 0) ret = ::lseek(iFd, pos, SEEK_SET); - if (mem_file != NULL) { + if (mem_file != nullptr) { if (pos > filesize) { pos = filesize; } diff --git a/source/fs/CFile.hpp b/source/fs/CFile.hpp index 6c0421b..a49720e 100644 --- a/source/fs/CFile.hpp +++ b/source/fs/CFile.hpp @@ -1,12 +1,12 @@ -#ifndef CFILE_HPP_ -#define CFILE_HPP_ +#pragma once -#include +#include #include -#include +#include #include #include #include +#include class CFile { public: @@ -29,31 +29,32 @@ public: int32_t open(const uint8_t *memory, int32_t memsize); - BOOL isOpen() const { - if (iFd >= 0) + [[nodiscard]] BOOL isOpen() const { + if (iFd >= 0) { return true; + } - if (mem_file) + if (mem_file) { return true; - + } return false; } - void close(); + virtual void close(); - int32_t read(uint8_t *ptr, size_t size); + virtual int32_t read(uint8_t *ptr, size_t size); - int32_t write(const uint8_t *ptr, size_t size); + virtual int32_t write(const uint8_t *ptr, size_t size); int32_t fwrite(const char *format, ...); - int32_t seek(long int offset, int32_t origin); + virtual int32_t seek(int64_t offset, int32_t origin); - uint64_t tell() { + [[nodiscard]] uint64_t tell() const { return pos; }; - uint64_t size() { + [[nodiscard]] uint64_t size() const { return filesize; }; @@ -63,9 +64,7 @@ public: protected: int32_t iFd; - const uint8_t *mem_file; - uint64_t filesize; - uint64_t pos; -}; - -#endif + const uint8_t *mem_file{}; + uint64_t filesize{}; + uint64_t pos{}; +}; \ No newline at end of file diff --git a/source/fs/WUDFileWriter.cpp b/source/fs/WUDFileWriter.cpp new file mode 100644 index 0000000..4c1cb38 --- /dev/null +++ b/source/fs/WUDFileWriter.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** + * Copyright (C) 2021 Maschell + * + * 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 "WUDFileWriter.h" + +WUDFileWriter::WUDFileWriter(const char *path, eOpenTypes mode, int32_t cacheSize, int32_t pSectorSize) : + WriteOnlyFileWithCache(path, mode, cacheSize), + sectorSize(pSectorSize) { +} + +int32_t WUDFileWriter::writeSector(const uint8_t *buffer, uint32_t numberOfSectors) { + auto result = write(buffer, numberOfSectors * this->sectorSize); + if (result == (int32_t) (numberOfSectors * this->sectorSize)) { + return (int32_t) numberOfSectors; + } + return -1; +} + +WUDFileWriter::~WUDFileWriter() { + WUDFileWriter::close(); +} diff --git a/source/fs/WUDFileWriter.h b/source/fs/WUDFileWriter.h new file mode 100644 index 0000000..12b8048 --- /dev/null +++ b/source/fs/WUDFileWriter.h @@ -0,0 +1,31 @@ +/**************************************************************************** + * Copyright (C) 2021 Maschell + * + * 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 . + ****************************************************************************/ +#pragma once + +#include "WriteOnlyFileWithCache.h" + +class WUDFileWriter : public WriteOnlyFileWithCache { +public: + WUDFileWriter(const char *string, eOpenTypes types, int32_t cacheSize, int32_t sectorSize); + + ~WUDFileWriter() override; + + virtual int32_t writeSector(const uint8_t *buffer, uint32_t numberOfSectors); + +protected: + int32_t sectorSize; +}; diff --git a/source/fs/WUXFileWriter.cpp b/source/fs/WUXFileWriter.cpp new file mode 100644 index 0000000..996600d --- /dev/null +++ b/source/fs/WUXFileWriter.cpp @@ -0,0 +1,113 @@ +/**************************************************************************** + * Copyright (C) 2021 Maschell + * + * 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 "WUXFileWriter.h" +#include "WUDDumperState.h" + +WUXFileWriter::WUXFileWriter(const char *path, CFile::eOpenTypes mode, int32_t cacheSize, int32_t sectorSize) : + WUDFileWriter(path, mode, cacheSize, sectorSize) { + wuxHeader_t wuxHeader = {0}; + wuxHeader.magic0 = WUX_MAGIC_0; + wuxHeader.magic1 = WUX_MAGIC_1; + wuxHeader.sectorSize = swap_uint32(this->sectorSize); + wuxHeader.uncompressedSize = swap_uint64(WUD_FILE_SIZE); + wuxHeader.flags = 0; + + this->write((uint8_t *) &wuxHeader, sizeof(wuxHeader_t)); + this->flush(); + this->sectorTableStart = this->tell(); + this->totalSectorCount = WUD_FILE_SIZE / this->sectorSize; + + this->sectorIndexTable = (void *) malloc(totalSectorCount * 4); + if (sectorIndexTable == nullptr) { + DEBUG_FUNCTION_LINE("Failed to alloc"); + WUDFileWriter::close(); + return; + } + memset(this->sectorIndexTable, 0, totalSectorCount * 4); + + if (this->write((uint8_t *) this->sectorIndexTable, totalSectorCount * 4) < 0) { + WUDFileWriter::close(); + return; + } + this->flush(); + + this->sectorTableEnd = this->tell(); + uint64_t tableEnd = this->sectorTableEnd; + + this->sectorTableEnd += this->sectorSize - 1; + this->sectorTableEnd -= (this->sectorTableEnd % this->sectorSize); + + uint64_t padding = this->sectorTableEnd - tableEnd; + auto *paddingData = (uint8_t *) malloc(padding); + memset(paddingData, 0, padding); + this->write(reinterpret_cast(paddingData), padding); + this->flush(); + free(paddingData); + this->hashMap.clear(); +} + +int32_t WUXFileWriter::writeSector(const uint8_t *buffer, uint32_t numberOfSectors) { + char hashOut[32]; + int32_t curWrittenSectors = 0; + for (uint32_t i = 0; i < numberOfSectors; i++) { + uint32_t addr = ((uint32_t) buffer) + (i * this->sectorSize); + calculateHash256(reinterpret_cast(addr), this->sectorSize, reinterpret_cast(hashOut)); + char tmp[34]; + auto *test = (uint32_t *) hashOut; + snprintf(tmp, 33, "%08X%08X%08X%08X", test[0], test[1], test[2], test[3]); + std::string hash(tmp); + + auto *indexTable = (uint32_t *) this->sectorIndexTable; + + auto it = hashMap.find(hash); + if (it != hashMap.end()) { + indexTable[this->currentSector] = swap_uint32(this->hashMap[hash]); + } else { + indexTable[this->currentSector] = swap_uint32(this->writtenSector); + hashMap[hash] = writtenSector; + if (isOpen()) { + if (!write((uint8_t *) addr, this->sectorSize)) { + DEBUG_FUNCTION_LINE("Write failed"); + return -1; + } + } + this->writtenSector++; + curWrittenSectors++; + } + this->currentSector++; + } + return curWrittenSectors; +} + +void WUXFileWriter::writeSectorIndexTable() { + if (this->isOpen()) { + // We need to make sure to call CFile::seek! + CFile::seek((int64_t) sectorTableStart, SEEK_SET); + CFile::write((uint8_t *) sectorIndexTable, totalSectorCount * 4); + } +} + +void WUXFileWriter::close() { + writeSectorIndexTable(); + CFile::close(); +} + +WUXFileWriter::~WUXFileWriter() { + WUXFileWriter::close(); + free(sectorIndexTable); +} diff --git a/source/fs/WUXFileWriter.h b/source/fs/WUXFileWriter.h new file mode 100644 index 0000000..1880777 --- /dev/null +++ b/source/fs/WUXFileWriter.h @@ -0,0 +1,56 @@ +/**************************************************************************** + * Copyright (C) 2021 Maschell + * + * 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 . + ****************************************************************************/ +#pragma once + +#include "utils/utils.h" +#include "WUDFileWriter.h" +#include + +typedef struct { + unsigned int magic0; + unsigned int magic1; + unsigned int sectorSize; + unsigned long long uncompressedSize; + unsigned int flags; +} wuxHeader_t; + +#define WUX_MAGIC_0 0x57555830 +#define WUX_MAGIC_1 swap_uint32(0x1099d02e) + +class WUXFileWriter : public WUDFileWriter { +public: + WUXFileWriter(const char *string, eOpenTypes types, int32_t cacheSize, int32_t pSectorSize); + + ~WUXFileWriter() override; + + int32_t writeSector(const uint8_t *buffer, uint32_t numberOfSectors) override; + + void close() override; + +private: + void writeSectorIndexTable(); + + uint64_t totalSectorCount; + + uint64_t sectorTableStart; + uint64_t sectorTableEnd; + void *sectorIndexTable = nullptr; + + std::map hashMap; + uint32_t currentSector = 0; + uint32_t writtenSector = 0; +}; \ No newline at end of file diff --git a/source/fs/WriteOnlyFileWithCache.cpp b/source/fs/WriteOnlyFileWithCache.cpp new file mode 100644 index 0000000..b53df06 --- /dev/null +++ b/source/fs/WriteOnlyFileWithCache.cpp @@ -0,0 +1,96 @@ +/**************************************************************************** + * Copyright (C) 2021 Maschell + * + * 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 "WriteOnlyFileWithCache.h" + +WriteOnlyFileWithCache::WriteOnlyFileWithCache(const char *path, CFile::eOpenTypes mode, int32_t cacheSize) : CFile(path, mode) { + if (!this->isOpen()) { + return; + } + this->writeBufferSize = cacheSize; + this->writeBuffer = (void *) memalign(0x1000, this->writeBufferSize); + if (this->writeBuffer == nullptr) { + this->close(); + return; + } + this->writeBufferPos = 0; +} + +WriteOnlyFileWithCache::~WriteOnlyFileWithCache() { + WriteOnlyFileWithCache::close(); + free(writeBuffer); +} + +bool WriteOnlyFileWithCache::flush() { + if (this->writeBufferPos > 0) { + int32_t res = CFile::write(static_cast(this->writeBuffer), this->writeBufferPos); + if (res < 0) { + DEBUG_FUNCTION_LINE("Failed to flush cache, write failed: %d", res); + return false; + } + this->writeBufferPos = 0; + } + return true; +} + +int32_t WriteOnlyFileWithCache::write(const uint8_t *addr, size_t writeSize) { + if (writeSize == this->writeBufferSize) { + if (!this->flush()) { + DEBUG_FUNCTION_LINE("Flush failed"); + return -1; + } + return CFile::write(reinterpret_cast(addr), writeSize); + } + + auto toWrite = (int32_t) writeSize; + if (toWrite == 0) { + return 0; + } + + int32_t written = 0; + + do { + int32_t curWrite = toWrite; + + if (this->writeBufferPos + curWrite > this->writeBufferSize) { + curWrite = this->writeBufferSize - this->writeBufferPos; + } + OSBlockMove((void *) (((uint32_t) this->writeBuffer) + this->writeBufferPos), (void *) (addr + written), curWrite, 1); + this->writeBufferPos += curWrite; + + if (this->writeBufferPos == this->writeBufferSize) { + if (!this->flush()) { + DEBUG_FUNCTION_LINE("Flush failed"); + return -2; + } + } + + toWrite -= curWrite; + written += curWrite; + } while (toWrite > 0); + return written; +} + +int32_t WriteOnlyFileWithCache::seek(int64_t offset, int32_t origin) { + return -1; +} + +int32_t WriteOnlyFileWithCache::read(uint8_t *ptr, size_t size) { + return -1; +} diff --git a/source/fs/WriteOnlyFileWithCache.h b/source/fs/WriteOnlyFileWithCache.h new file mode 100644 index 0000000..b46e4d5 --- /dev/null +++ b/source/fs/WriteOnlyFileWithCache.h @@ -0,0 +1,38 @@ +/**************************************************************************** + * Copyright (C) 2021 Maschell + * + * 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 . + ****************************************************************************/ +#pragma once + +#include + +class WriteOnlyFileWithCache : public CFile { +public: + WriteOnlyFileWithCache(const char *string, eOpenTypes types, int32_t cacheSize); + + ~WriteOnlyFileWithCache() override; + + int32_t write(const uint8_t *data, size_t size) override; + + int32_t seek(int64_t offset, int32_t origin) override; + + int32_t read(uint8_t *ptr, size_t size) override; + + bool flush(); + + void *writeBuffer = nullptr; + size_t writeBufferSize; + size_t writeBufferPos; +}; diff --git a/source/main.cpp b/source/main.cpp index 84d6999..4b0dae7 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -6,13 +6,13 @@ #include #include +#include #include "utils/logger.h" #include "utils/WiiUScreen.h" #include "input/VPADInput.h" -#include "ApplicationState.h" +#include "MainApplicationState.h" #include "common/common.h" -#include "utils/utils.h" void initIOSUHax(); @@ -63,18 +63,19 @@ int main(int argc, char **argv) { void main_loop() { DEBUG_FUNCTION_LINE("Creating state"); - ApplicationState state; + std::unique_ptr state = std::make_unique(); VPadInput input; if (gFSAfd < 0 || !sIosuhaxMount) { - state.setError(ApplicationState::eErrorState::ERROR_IOSUHAX_FAILED); + // state.setError(MainApplicationState::eErrorState::ERROR_IOSUHAX_FAILED); + OSFatal("IOSUHAX Failed"); } DEBUG_FUNCTION_LINE("Entering main loop"); while (WHBProcIsRunning()) { input.update(1280, 720); - state.update(&input); - state.render(); + state->update(&input); + state->render(); } } @@ -89,8 +90,8 @@ void initIOSUHax() { if (gFSAfd < 0) { DEBUG_FUNCTION_LINE("IOSUHAX_FSA_Open failed"); } else { + DEBUG_FUNCTION_LINE("IOSUHAX done"); } - DEBUG_FUNCTION_LINE("IOSUHAX done"); } } diff --git a/source/utils/utils.cpp b/source/utils/utils.cpp index b87f2f3..7dbe340 100644 --- a/source/utils/utils.cpp +++ b/source/utils/utils.cpp @@ -62,3 +62,28 @@ std::string Utils::hashFile(const std::string &path) { free(data); return result; } + +unsigned int swap_uint32(unsigned int val) { + val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF); + return (val << 16) | (val >> 16); +} + +unsigned long long swap_uint64(unsigned long long val) { + val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL); + val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL); + return (val << 32) | (val >> 32); +} + +/* + * Hash function used to create a hash of each sector + * The hashes are then compared to find duplicate sectors + */ +void calculateHash256(unsigned char *data, unsigned int length, unsigned char *hashOut) { + // cheap and simple hash implementation + // you can replace this part with your favorite hash method + memset(hashOut, 0x00, 32); + for (unsigned int i = 0; i < length; i++) { + hashOut[i % 32] ^= data[i]; + hashOut[(i + 7) % 32] += data[i]; + } +} \ No newline at end of file diff --git a/source/utils/utils.h b/source/utils/utils.h index d4223be..e63c4fe 100644 --- a/source/utils/utils.h +++ b/source/utils/utils.h @@ -30,6 +30,12 @@ extern "C" { #define le32(i) ((((uint32_t)le16((i) & 0xFFFF)) << 16) | ((uint32_t)le16(((i) & 0xFFFF0000) >> 16))) #define le64(i) ((((uint64_t)le32((i) & 0xFFFFFFFFLL)) << 32) | ((uint64_t)le32(((i) & 0xFFFFFFFF00000000LL) >> 32))) +unsigned int swap_uint32(unsigned int val); + +unsigned long long swap_uint64(unsigned long long val); + +void calculateHash256(unsigned char *data, unsigned int length, unsigned char *hashOut); + #ifdef __cplusplus } #endif