/*
*   This file is part of Luma3DS
*   Copyright (C) 2016-2017 Aurora Wright, TuxSH
*
*   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/>.
*
*   Additional Terms 7.b and 7.c of GPLv3 apply to this file:
*       * Requiring preservation of specified reasonable legal notices or
*         author attributions in that material or in the Appropriate Legal
*         Notices displayed by works containing it.
*       * Prohibiting misrepresentation of the origin of that material,
*         or requiring that modified versions of such material be marked in
*         reasonable ways as different from the original version.
*/

#include "config.h"
#include "emunand.h"
#include "fs.h"
#include "firm.h"
#include "utils.h"
#include "exceptions.h"
#include "draw.h"
#include "strings.h"
#include "buttons.h"
#include "pin.h"
#include "crypto.h"
#include "memory.h"
#include "screen.h"
#include "fatfs/sdmmc/sdmmc.h"

extern CfgData configData;
extern ConfigurationStatus needConfig;
extern FirmwareSource firmSource;

bool isSdMode;
u16 launchedPath[41];
BootType bootType;

void main(int argc, char **argv, u32 magicWord)
{
    bool isFirmProtEnabled,
         isSafeMode = false,
         isNoForceFlagSet = false,
         isNtrBoot;
    FirmwareType firmType;
    FirmwareSource nandType;
    const vu8 *bootMediaStatus = (const vu8 *)0x1FFFE00C;
    const vu32 *bootPartitionsStatus = (const vu32 *)0x1FFFE010;

    //Shell closed, no error booting NTRCARD, NAND paritions not even considered
    isNtrBoot = bootMediaStatus[3] == 2 && !bootMediaStatus[1] && !bootPartitionsStatus[0] && !bootPartitionsStatus[1];
 
    if((magicWord & 0xFFFF) == 0xBEEF && argc >= 1) //Normal (B9S) boot
    {
        bootType = isNtrBoot ? B9SNTR : B9S;

        u32 i;
        for(i = 0; i < 40 && argv[0][i] != 0; i++) //Copy and convert the path to UTF-16
            launchedPath[i] = argv[0][i];
        launchedPath[i] = 0;
    }
    else if(magicWord == 0xBABE && argc == 2) //Firmlaunch
    {
        bootType = FIRMLAUNCH;

        u32 i;
        u16 *p = (u16 *)argv[0];
        for(i = 0; i < 40 && p[i] != 0; i++)
            launchedPath[i] = p[i];
        launchedPath[i] = 0;
    }
    else if(magicWord == 0xB002) //FIRM/NTRCARD boot
    {
        if(isNtrBoot) bootType = NTR;
        else
        {
            const char *path;
            if(!((vu8 *)bootPartitionsStatus)[2])
            {
                bootType = FIRM0;
                path = "firm0:";
            }
            else
            {
                bootType = FIRM1;
                path = "firm1:";
            }

            for(u32 i = 0; i < 7; i++) //Copy and convert the path to UTF-16
                launchedPath[i] = path[i];
        }

        setupKeyslots();
    }
    else error("Launched using an unsupported loader.");

    if(memcmp(launchedPath, u"sdmc", 8) == 0)
    {
        if(!mountFs(true, false)) error("Failed to mount SD.");
        isSdMode = true;
    }
    else if(memcmp(launchedPath, u"nand", 8) == 0)
    {
        firmSource = FIRMWARE_SYSNAND;
        if(!mountFs(false, true)) error("Failed to mount CTRNAND.");
        isSdMode = false;
    }
    else if(bootType == NTR || memcmp(launchedPath, u"firm", 8) == 0)
    {
        if(mountFs(true, false)) isSdMode = true;
        else if(mountFs(false, true)) isSdMode = false;
        else error("Failed to mount SD and CTRNAND.");

        if(bootType == NTR)
        {
            while(HID_PAD & NTRBOOT_BUTTONS);
            loadHomebrewFirm(0);
            mcuPowerOff();
        }
    }
    else
    {
        char mountPoint[5];

        u32 i;
        for(i = 0; i < 4 && launchedPath[i] != u':'; i++)
            mountPoint[i] = (char)launchedPath[i];
        mountPoint[i] = 0;

        error("Launched from an unsupported location: %s.", mountPoint);
    }

    //Attempt to read the configuration file
    needConfig = readConfig() ? MODIFY_CONFIGURATION : CREATE_CONFIGURATION;

    //Determine if this is a firmlaunch boot
    if(bootType == FIRMLAUNCH)
    {
        if(needConfig == CREATE_CONFIGURATION) mcuPowerOff();

        switch(argv[1][14])
        {
            case '2':
                firmType = (FirmwareType)(argv[1][10] - '0');
                break;
            case '3':
                firmType = SAFE_FIRM;
                break;
            case '1':
                firmType = SYSUPDATER_FIRM;
                break;
        }

        nandType = (FirmwareSource)BOOTCFG_NAND;
        firmSource = (FirmwareSource)BOOTCFG_FIRM;
        isFirmProtEnabled = !BOOTCFG_NTRCARDBOOT;

        goto boot;
    }

    detectAndProcessExceptionDumps();
    installArm9Handlers();

    firmType = NATIVE_FIRM;
    isFirmProtEnabled = bootType != NTR;

    //Get pressed buttons
    u32 pressed = HID_PAD;

    //If it's a MCU reboot, try to force boot options
    if(CFG_BOOTENV && needConfig != CREATE_CONFIGURATION)
    {
        //Always force a SysNAND boot when quitting AGB_FIRM
        if(CFG_BOOTENV == 7)
        {
            nandType = FIRMWARE_SYSNAND;
            firmSource = (BOOTCFG_NAND != 0) == (BOOTCFG_FIRM != 0) ? FIRMWARE_SYSNAND : (FirmwareSource)BOOTCFG_FIRM;

            //Prevent multiple boot options-forcing
            if(nandType != BOOTCFG_NAND || firmSource != BOOTCFG_FIRM) isNoForceFlagSet = true;

            goto boot;
        }

        /* Else, force the last used boot options unless a button is pressed
           or the no-forcing flag is set */
        if(!pressed && !BOOTCFG_NOFORCEFLAG)
        {
            nandType = (FirmwareSource)BOOTCFG_NAND;
            firmSource = (FirmwareSource)BOOTCFG_FIRM;

            goto boot;
        }
    }

    u32 pinMode = MULTICONFIG(PIN);
    bool pinExists = pinMode != 0 && verifyPin(pinMode);

    //If no configuration file exists or SELECT is held or if booted from NTRCARD, load configuration menu
    bool shouldLoadConfigMenu = needConfig == CREATE_CONFIGURATION || ((pressed & (BUTTON_SELECT | BUTTON_L1)) == BUTTON_SELECT);

    if(shouldLoadConfigMenu)
    {
        configMenu(pinExists, pinMode);

        //Update pressed buttons
        pressed = HID_PAD;
    }

    if(!CFG_BOOTENV && pressed == SAFE_MODE)
    {
        nandType = FIRMWARE_SYSNAND;
        firmSource = FIRMWARE_SYSNAND;

        isSafeMode = true;

        //If the PIN has been verified, wait to make it easier to press the SAFE_MODE combo
        if(pinExists && !shouldLoadConfigMenu)
        {
            while(HID_PAD & PIN_BUTTONS);
            wait(2000ULL);
        }

        goto boot;
    }

    u32 splashMode = MULTICONFIG(SPLASH);

    if(splashMode == 1 && loadSplash()) pressed = HID_PAD;

    bool autoBootEmu = CONFIG(AUTOBOOTEMU);

    if((pressed & (BUTTON_START | BUTTON_L1)) == BUTTON_START)
    {
        loadHomebrewFirm(0);
        pressed = HID_PAD;
    }
    else if((((pressed & SINGLE_PAYLOAD_BUTTONS) || (!autoBootEmu && (pressed & DPAD_BUTTONS))) && !(pressed & (BUTTON_L1 | BUTTON_R1))) ||
            (((pressed & L_PAYLOAD_BUTTONS) || (autoBootEmu && (pressed & DPAD_BUTTONS))) && (pressed & BUTTON_L1))) loadHomebrewFirm(pressed);

    if(splashMode == 2) loadSplash();

    //If booting from CTRNAND, always use SysNAND
    if(!isSdMode) nandType = FIRMWARE_SYSNAND;

    //If R is pressed, boot the non-updated NAND with the FIRM of the opposite one
    else if(pressed & BUTTON_R1)
    {
        if(CONFIG(USEEMUFIRM))
        {
            nandType = FIRMWARE_SYSNAND;
            firmSource = FIRMWARE_EMUNAND;
        }
        else
        {
            nandType = FIRMWARE_EMUNAND;
            firmSource = FIRMWARE_SYSNAND;
        }
    }

    /* Else, boot the NAND the user set to autoboot or the opposite one, depending on L,
       with their own FIRM */
    else firmSource = nandType = (autoBootEmu == ((pressed & BUTTON_L1) == BUTTON_L1)) ? FIRMWARE_SYSNAND : FIRMWARE_EMUNAND;

    //If we're booting EmuNAND or using EmuNAND FIRM, determine which one from the directional pad buttons, or otherwise from the config
    if(nandType == FIRMWARE_EMUNAND || firmSource == FIRMWARE_EMUNAND)
    {
        FirmwareSource tempNand;
        switch(pressed & DPAD_BUTTONS)
        {
            case BUTTON_UP:
                tempNand = FIRMWARE_EMUNAND;
                break;
            case BUTTON_RIGHT:
                tempNand = FIRMWARE_EMUNAND2;
                break;
            case BUTTON_DOWN:
                tempNand = FIRMWARE_EMUNAND3;
                break;
            case BUTTON_LEFT:
                tempNand = FIRMWARE_EMUNAND4;
                break;
            default:
                tempNand = (FirmwareSource)(1 + MULTICONFIG(DEFAULTEMU));
                break;
        }

        if(nandType == FIRMWARE_EMUNAND) nandType = tempNand;
        else firmSource = tempNand;
    }

boot:

    //If we need to boot EmuNAND, make sure it exists
    if(nandType != FIRMWARE_SYSNAND)
    {
        locateEmuNand(&nandType);
        if(nandType == FIRMWARE_SYSNAND) firmSource = FIRMWARE_SYSNAND;
        else if((*(vu16 *)(SDMMC_BASE + REG_SDSTATUS0) & TMIO_STAT0_WRPROTECT) == 0) //Make sure the SD card isn't write protected
            error("The SD card is locked, EmuNAND can not be used.\nPlease turn the write protection switch off.");
    }

    //Same if we're using EmuNAND as the FIRM source
    else if(firmSource != FIRMWARE_SYSNAND)
        locateEmuNand(&firmSource);

    if(bootType != FIRMLAUNCH)
    {
        configData.bootConfig = ((bootType == NTR ? 1 : 0) << 7) | ((u32)isNoForceFlagSet << 6) | ((u32)firmSource << 3) | (u32)nandType;
        writeConfig(false);
    }

    if(isSdMode && !mountFs(false, false)) error("Failed to mount CTRNAND.");

    bool loadFromStorage = CONFIG(LOADEXTFIRMSANDMODULES);
    u32 firmVersion = loadNintendoFirm(&firmType, firmSource, loadFromStorage, isSafeMode);

    bool doUnitinfoPatch = CONFIG(PATCHUNITINFO);
    u32 res;
    switch(firmType)
    {
        case NATIVE_FIRM:
            res = patchNativeFirm(firmVersion, nandType, loadFromStorage, isFirmProtEnabled, isSafeMode, doUnitinfoPatch);
            break;
        case TWL_FIRM:
            res = patchTwlFirm(firmVersion, loadFromStorage, doUnitinfoPatch);
            break;
        case AGB_FIRM:
            res = patchAgbFirm(loadFromStorage, doUnitinfoPatch);
            break;
        case SAFE_FIRM:
        case SYSUPDATER_FIRM:
        case NATIVE_FIRM1X2X:
            res = patch1x2xNativeAndSafeFirm();
            break;
    }

    if(res != 0) error("Failed to apply %u FIRM patch(es).", res);

    if(bootType != FIRMLAUNCH) deinitScreens();
    launchFirm(0, NULL);
}
