/*
* This file is part of fastboot 3DS
* Copyright (C) 2017 derrek, profi200
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include "types.h"
#include "mem_map.h"
#include "arm9/console.h"
#include "arm9/dev.h"
#include "fatfs/ff.h"
#include "arm9/hardware/hid.h"
#include "util.h"
#include "arm9/main.h"
#include "arm9/gui/menu.h"
#include "arm9/hardware/timer.h"
#include "arm9/partitions.h"
#include "arm9/firmwriter.h"
#include "arm9/nandimage.h"
#include "arm9/fsutils.h"
bool menuDumpNand(const char *filePath)
{
const u32 sectorCount = dev_rawnand->get_sector_count();
const u32 sectorBlkSize = 0x400; // 512 KB in sectors
FIL file;
FRESULT fres;
UINT bytesWritten;
u64 bytesFree;
u8 *buf = (u8*)malloc(sectorBlkSize<<9);
if(!buf)
{ // this should never happen.
uiPrintIfVerbose("Out of memory!");
return false;
}
uiClearConsoles();
consoleSelect(&con_top);
uiPrintCenteredInLine(1, "NAND Backup");
uiPrintTextAt(0, 3, "Checking free space on SD card...\n");
if(!fsGetFreeSpaceOnDrive("sdmc:", &bytesFree))
uiPrintError("Failed to get free space!\n");
if(bytesFree < sectorCount<<9)
{
uiPrintError("Not enough space on the SD card!\n");
goto fail;
}
uiPrintTextAt(0, 4, "Creating image file...\n");
if((fres = f_open(&file, filePath, FA_CREATE_ALWAYS | FA_WRITE)) != FR_OK)
{
uiPrintError("Failed to create '%s'! Error: %X\n", filePath, fres);
goto fail;
}
// Allocate space to make sure the nand image is a contiguous file
if((fres = f_expand(&file, sectorCount<<9, 0)) != FR_OK)
{
uiPrintError("Failed to expand file! Error: %X\n", fres);
f_sync(&file);
f_close(&file);
f_unlink(filePath);
goto fail;
}
uiPrintTextAt(0, 4, "Unmounting NAND fs...\n");
fsUnmountNandFilesystems();
uiPrintTextAt(0, 5, "Dumping NAND...\n");
uiPrintTextAt(0, 22, "Press B to cancel.");
/* Main loop */
u32 curSector = 0;
u32 curSectorBlkSize;
while(curSector < sectorCount)
{
if(curSector + sectorBlkSize < sectorCount) curSectorBlkSize = sectorBlkSize;
else curSectorBlkSize = sectorCount - curSector;
if(!dev_rawnand->read_sector(curSector, curSectorBlkSize, buf))
{
uiPrintError("\nFailed to read sector 0x%"PRIx32"!\n", curSector);
f_sync(&file);
f_close(&file);
f_unlink(filePath);
goto fail;
}
if((f_write(&file, buf, curSectorBlkSize<<9, &bytesWritten) != FR_OK) || (bytesWritten != curSectorBlkSize<<9))
{
uiPrintError("\nFailed to write to file!\n");
f_sync(&file);
f_close(&file);
f_unlink(filePath);
goto fail;
}
hidScanInput();
if(hidKeysDown() & KEY_B)
{
uiPrintTextAt(0, 22, "... canceled. ");
f_sync(&file);
f_close(&file);
f_unlink(filePath);
goto fail;
}
curSector += curSectorBlkSize;
// print current progress information
uiPrintTextAt(1, 20, "\r%"PRId32"/%"PRId32" MB (Sector 0x%"PRIX32"/0x%"PRIX32")", curSector>>11, sectorCount>>11,
curSector, sectorCount);
uiPrintProgressBar(10, 80, 380, 20, curSector, sectorCount);
switch(menuUpdateGlobalState())
{
case MENU_EVENT_HOME_PRESSED:
case MENU_EVENT_POWER_PRESSED:
f_sync(&file);
f_close(&file);
// falls through
case MENU_EVENT_SD_CARD_REMOVED:
menuActState();
goto fail;
default:
break;
}
menuActState();
}
f_sync(&file);
f_close(&file);
free(buf);
fsRemountNandFilesystems();
uiDialog("Finished!\nPress any key to return.", NULL, HID_KEY_MASK_ALL, 1, 0, 0, true);
menuSetReturnToState(STATE_PREVIOUS);
return true;
fail:
free(buf);
fsRemountNandFilesystems();
uiPrintTextAt(0, 24, "Press any key to return.");
menuWaitForAnyPadkey();
menuSetReturnToState(STATE_PREVIOUS);
return false;
}
bool menuRestoreNand(const char *filePath)
{
const u32 sectorBlkSize = 0x400; // 512 KB in sectors
FIL file;
UINT bytesRead;
u8 *buf = (u8*)malloc(sectorBlkSize<<9);
if(!buf)
{ // this should never happen.
uiPrintIfVerbose("Out of memory!");
return false;
}
uiClearConsoles();
consoleSelect(&con_top);
uiPrintCenteredInLine(1, "NAND Restore");
uiPrintTextAt(0, 3, "Checking backup file...\n");
FILINFO fileStat;
if(f_stat(filePath, &fileStat) != FR_OK)
{
uiPrintError("Failed to get file status!\n");
goto fail;
}
if(fileStat.fsize > dev_rawnand->get_sector_count()<<9)
{
uiPrintError("NAND file is bigger than NAND!\n");
goto fail;
}
if(fileStat.fsize < 0x200)
{
uiPrintError("NAND file is too small!\n");
goto fail;
}
if(f_open(&file, filePath, FA_READ) != FR_OK)
{
uiPrintError("Failed to open '%s'!\n", filePath);
f_close(&file);
goto fail;
}
if(!isNandImageCompatible(&file))
{
uiPrintError("NAND file is not compatible with this console!\n");
f_close(&file);
goto fail;
}
uiPrintTextAt(0, 4, "Unmounting NAND fs...\n");
fsUnmountNandFilesystems();
uiPrintTextAt(0, 5, "Restoring...\n");
uiPrintTextAt(0, 22, "Press B to cancel.");
/* Main loop */
const u32 sectorCount = fileStat.fsize>>9;
u32 curSector = 0;
u32 curSectorBlkSize;
while(curSector < sectorCount)
{
if(curSector + sectorBlkSize < sectorCount) curSectorBlkSize = sectorBlkSize;
else curSectorBlkSize = sectorCount - curSector;
if(f_read(&file, buf, curSectorBlkSize<<9, &bytesRead) != FR_OK || bytesRead != curSectorBlkSize<<9)
{
uiPrintError("\nFailed to read from file!\n");
f_close(&file);
goto fail;
}
if(!dev_rawnand->write_sector(curSector, curSectorBlkSize, buf))
{
uiPrintError("\nFailed to write sector 0x%"PRIX32"!\n", curSector);
f_close(&file);
goto fail;
}
hidScanInput();
if(hidKeysDown() & KEY_B)
{
uiPrintTextAt(0, 22, "... canceled. ");
f_close(&file);
goto fail;
}
curSector += curSectorBlkSize;
uiPrintTextAt(1, 20, "\r%"PRId32"/%"PRId32" MB (Sector 0x%"PRIX32"/0x%"PRIX32")", curSector>>11, sectorCount>>11,
curSector, sectorCount);
uiPrintProgressBar(10, 80, 380, 20, curSector, sectorCount);
switch(menuUpdateGlobalState())
{
case MENU_EVENT_HOME_PRESSED:
case MENU_EVENT_POWER_PRESSED:
f_close(&file);
// falls through
case MENU_EVENT_SD_CARD_REMOVED:
menuActState();
goto fail;
default:
break;
}
menuActState();
}
f_close(&file);
free(buf);
fsRemountNandFilesystems();
uiPrintTextAt(0, 24, "Finished! Press any key to return.");
menuWaitForAnyPadkey();
menuSetReturnToState(STATE_PREVIOUS);
return true;
fail:
free(buf);
fsRemountNandFilesystems();
uiPrintTextAt(0, 24, "Press any key to return.");
menuWaitForAnyPadkey();
menuSetReturnToState(STATE_PREVIOUS);
return false;
}
bool menuFlashFirmware(const char *filepath)
{
const char *partName = "firm1"; // TODO let the user decide
FILINFO fileStat;
size_t fwSize;
size_t index;
size_t sector;
size_t numSectors;
uiClearConsoles();
consoleSelect(&con_top);
uiPrintCenteredInLine(1, "Flash firmware");
uiPrintTextAt(0, 3, "Verifying file...\n");
if(f_stat(filepath, &fileStat) != FR_OK)
{
uiPrintError("Could not retrieve file status!\n");
goto fail;
}
if(!tryLoadFirmware(filepath, false, false) || !firm_size(&fwSize))
{
uiPrintError("Firmware is invalid or corrupted!\n");
goto fail;
}
if(!partitionGetIndex(partName, &index))
{
uiPrintError("Could not find partition %s!", partName);
goto fail;
}
partitionGetSectorOffset(index, §or);
if(!uiDialogYesNo(1, "Proceed", "Cancel", "Flash firmware to %s?", partName))
{
goto fail;
}
uiPrintTextAt(0, 4, "Writing...\n");
if(!firmwriterInit(sector, fwSize / 0x200, false))
{
uiPrintError("Failed to start flashing process!");
goto fail;
}
for(size_t i=0; i<fwSize / 0x200; )
{
if(!firmwriterIsDone())
{
/* Write one block in each iteration and update ui */
numSectors = firmwriterWriteBlock();
if(!numSectors)
{
uiPrintError("Writing block failed!");
goto fail;
}
}
else
{
uiPrintTextAt(0, 21, "Finalizing...");
numSectors = firmwriterFinish();
if(!numSectors)
{
uiPrintError("Failed writing block!");
goto fail;
}
}
i+= numSectors;
uiPrintTextAt(1, 20, "\r%"PRId32"/%"PRId32, i, fwSize / 0x200);
uiPrintProgressBar(10, 80, 380, 20, i, fwSize / 0x200);
}
uiPrintTextAt(0, 24, "Success! Press any key to return.");
menuWaitForAnyPadkey();
menuSetReturnToState(STATE_PREVIOUS);
return true;
fail:
uiPrintTextAt(0, 24, "Press any key to return...");
menuWaitForAnyPadkey();
menuSetReturnToState(STATE_PREVIOUS);
return false;
}