#include <3ds.h>
#include "memory.h"
#include "patcher.h"
#include "ifile.h"
#include "util.h"
#include "loader.h"
#include "service_manager.h"

u32 config, multiConfig, bootConfig;
bool isN3DS, needToInitSd, isSdMode;

// MAKE SURE fsreg has been init before calling this
static Result fsldrPatchPermissions(void)
{
    u32 pid;
    Result res = 0;
    FS_ProgramInfo info;
    ExHeader_Arm11StorageInfo storageInfo = {
        .fs_access_info = FSACCESS_NANDRW | FSACCESS_NANDRO_RO | FSACCESS_SDMC_RW,
    };

    info.programId = 0x0004013000001302LL; // loader PID
    info.mediaType = MEDIATYPE_NAND;
    TRY(svcGetProcessId(&pid, CUR_PROCESS_HANDLE));
    return FSREG_Register(pid, 0xFFFF000000000000LL, &info, &storageInfo);
}

static inline void loadCFWInfo(void)
{
    s64 out;

    assertSuccess(svcGetSystemInfo(&out, 0x10000, 3));
    config = (u32)out;
    assertSuccess(svcGetSystemInfo(&out, 0x10000, 4));
    multiConfig = (u32)out;
    assertSuccess(svcGetSystemInfo(&out, 0x10000, 5));
    bootConfig = (u32)out;

    assertSuccess(svcGetSystemInfo(&out, 0x10000, 0x201));
    isN3DS = (bool)out;
    assertSuccess(svcGetSystemInfo(&out, 0x10000, 0x202));
    needToInitSd = (bool)out;
    assertSuccess(svcGetSystemInfo(&out, 0x10000, 0x203));
    isSdMode = (bool)out;

    IFile file;
    if(needToInitSd) fileOpen(&file, ARCHIVE_SDMC, "/", FS_OPEN_READ); //Init SD card if SAFE_MODE is being booted
}

// this is called before main
void __appInit()
{
    loadCFWInfo();

    Result res;
    for(res = 0xD88007FA; res == (Result)0xD88007FA; svcSleepThread(500 * 1000LL))
    {
        res = srvInit();
        if(R_FAILED(res) && res != (Result)0xD88007FA)
            panic(res);
    }

    assertSuccess(fsRegInit());
    assertSuccess(fsldrPatchPermissions());

    //fsldrInit();
    assertSuccess(srvGetServiceHandle(fsGetSessionHandle(), "fs:LDR"));

    // Hackjob
    assertSuccess(FSUSER_InitializeWithSdkVersion(*fsGetSessionHandle(), 0x70200C8));
    assertSuccess(FSUSER_SetPriority(0));

    assertSuccess(pxiPmInit());
}

// this is called after main exits
void __appExit()
{
    pxiPmExit();
    //fsldrExit();
    svcCloseHandle(*fsGetSessionHandle());
    fsRegExit();
    srvExit();
}

// stubs for non-needed pre-main functions
void __sync_init();
void __sync_fini();
void __system_initSyscalls();

void __ctru_exit()
{
    void __libc_fini_array(void);

    __libc_fini_array();
    __appExit();
    __sync_fini();
    svcExitProcess();
}

void initSystem()
{
    void __libc_init_array(void);

    __sync_init();
    __system_initSyscalls();
    __appInit();

    __libc_init_array();
}

static const ServiceManagerServiceEntry services[] = {
    { "Loader", 1, loaderHandleCommands, false },
    { NULL },
};

static const ServiceManagerNotificationEntry notifications[] = {
    { 0x000, NULL },
};

int main(void)
{
    loadCFWInfo();
    assertSuccess(ServiceManager_Run(services, notifications, NULL));
    return 0;
}