/*
 *   This file is part of Luma3DS
 *   Copyright (C) 2016-2019 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 <3ds.h>
#include <stdlib.h>
#include "menus/cheats.h"
#include "memory.h"
#include "draw.h"
#include "menu.h"
#include "utils.h"
#include "fmt.h"
#include "ifile.h"

#define MAKE_QWORD(hi,low) \
    ((u64) ((((u64)(hi)) << 32) | (low)))

typedef struct CheatProcessInfo
{
    u32 pid;
    u64 titleId;
} CheatProcessInfo;

typedef struct CheatDescription
{
    struct {
        u8 active : 1;
        u8 valid : 1;
        u8 hasKeyCode : 1;
        u8 activeStorage : 1;
    };
    char name[39];
    u32 codesCount;
    u32 storage1;
    u32 storage2;
    u64 codes[0];
} CheatDescription;

typedef struct BufferedFile
{
    IFile file;
    u64 curPos;
    u64 maxPos;
    char buffer[512];
} BufferedFile;

CheatDescription* cheats[1024] = { 0 };
u8 cheatBuffer[32768] = { 0 };
u8 cheatPage[0x1000] = { 0 };

static CheatProcessInfo cheatinfo[0x40] = { 0 };

static s32 Cheats_FetchProcessInfo(void)
{
    u32 pidList[0x40];
    s32 processAmount;

    s64 sa, textTotalRoundedSize, rodataTotalRoundedSize, dataTotalRoundedSize;

    svcGetProcessList(&processAmount, pidList, 0x40);

    for (s32 i = 0; i < processAmount; i++)
    {
        Handle processHandle;
        Result res = svcOpenProcess(&processHandle, pidList[i]);
        if (R_FAILED(res)) continue;

        cheatinfo[i].pid = pidList[i];
        svcGetProcessInfo((s64 *) &cheatinfo[i].titleId, processHandle, 0x10001);
        svcGetProcessInfo(&textTotalRoundedSize, processHandle, 0x10002);
        svcGetProcessInfo(&rodataTotalRoundedSize, processHandle, 0x10003);
        svcGetProcessInfo(&dataTotalRoundedSize, processHandle, 0x10004);
        svcGetProcessInfo(&sa, processHandle, 0x10005);

        svcCloseHandle(processHandle);
    }

    return processAmount;
}

typedef struct CheatState
{
    u32 index;
    u32 offset1;
    u32 offset2;
    u32 data1;
    u32 data2;
    struct {
        u8 activeOffset : 1;
        u8 activeData : 1;
        u8 conditionalMode : 3;
        u8 data1Mode : 1;
        u8 data2Mode : 1;
        u8 floatMode : 1;
    };
    u8 typeELine;
    u8 typeEIdx;

    s8 loopLine;
    u32 loopCount;

    u32 ifStack;
    u32 storedStack;
    u8 ifCount;
    u8 storedIfCount;

} CheatState;

CheatState cheat_state = { 0 };
u8 cheatCount = 0;
u64 cheatTitleInfo = -1ULL;

static inline u32* activeOffset()
{
    return cheat_state.activeOffset ? &cheat_state.offset2 : &cheat_state.offset1;
}

static inline u32* activeData()
{
    return cheat_state.activeData ? &cheat_state.data2 : &cheat_state.data1;
}

static inline u32* activeStorage(CheatDescription* desc)
{
    return desc->activeStorage ? &desc->storage2 : &desc->storage1;
}

char failureReason[64];

static bool Cheat_IsValidAddress(const Handle processHandle, u32 address, u32 size)
{
    MemInfo info;
    PageInfo out;

    Result res = svcQueryDebugProcessMemory(&info, &out, processHandle, address);
    if (R_SUCCEEDED(res) && info.state != MEMSTATE_FREE && info.base_addr > 0 && info.base_addr <= address && address <= info.base_addr + info.size - size) {
        return true;
    }
    return false;
}

static u32 ReadWriteBuffer32 = 0;
static u16 ReadWriteBuffer16 = 0;
static u8 ReadWriteBuffer8 = 0;

static bool Cheat_Write8(const Handle processHandle, u32 offset, u8 value)
{
    u32 addr = *activeOffset() + offset;
    if (addr >= 0x01E81000 && addr < 0x01E82000)
    {
        cheatPage[addr - 0x01E81000] = value;
        return true;
    }
    if (Cheat_IsValidAddress(processHandle, addr, 1))
    {
        *((u8*) (&ReadWriteBuffer8)) = value;
        return R_SUCCEEDED(svcWriteProcessMemory(processHandle, &ReadWriteBuffer8, addr, 1));
    }
    return false;
}

static bool Cheat_Write16(const Handle processHandle, u32 offset, u16 value)
{
    u32 addr = *activeOffset() + offset;
    if (addr >= 0x01E81000 && addr + 1 < 0x01E82000)
    {
        *(u16*)(cheatPage + addr - 0x01E81000) = value;
        return true;
    }
    if (Cheat_IsValidAddress(processHandle, addr, 2))
    {
        *((u16*) (&ReadWriteBuffer16)) = value;
        return R_SUCCEEDED(svcWriteProcessMemory(processHandle, &ReadWriteBuffer16, addr, 2));
    }
    return false;
}

static bool Cheat_Write32(const Handle processHandle, u32 offset, u32 value)
{
    u32 addr = *activeOffset() + offset;
    if (addr >= 0x01E81000 && addr + 3 < 0x01E82000)
    {
        *(u32*)(cheatPage + addr - 0x01E81000) = value;
        return true;
    }
    if (Cheat_IsValidAddress(processHandle, addr, 4))
    {
        *((u32*) (&ReadWriteBuffer32)) = value;
        return R_SUCCEEDED(svcWriteProcessMemory(processHandle, &ReadWriteBuffer32, addr, 4));
    }
    return false;
}

static bool Cheat_Read8(const Handle processHandle, u32 offset, u8* retValue)
{
    u32 addr = *activeOffset() + offset;
    if (addr >= 0x01E81000 && addr < 0x01E82000)
    {
        *retValue = cheatPage[addr - 0x01E81000];
        return true;
    }
    if (Cheat_IsValidAddress(processHandle, addr, 1))
    {
        Result res = svcReadProcessMemory(&ReadWriteBuffer8, processHandle, addr, 1);
        *retValue = *((u8*) (&ReadWriteBuffer8));
        return R_SUCCEEDED(res);
    }
    return false;
}

static bool Cheat_Read16(const Handle processHandle, u32 offset, u16* retValue)
{
    u32 addr = *activeOffset() + offset;
    if (addr >= 0x01E81000 && addr + 1 < 0x01E82000)
    {
        *retValue = *(u16*)(cheatPage + addr - 0x01E81000);
        return true;
    }
    if (Cheat_IsValidAddress(processHandle, addr, 2))
    {
        Result res = svcReadProcessMemory(&ReadWriteBuffer16, processHandle, addr, 2);
        *retValue = *((u16*) (&ReadWriteBuffer16));
        return R_SUCCEEDED(res);
    }
    return false;
}

static bool Cheat_Read32(const Handle processHandle, u32 offset, u32* retValue)
{
    u32 addr = *activeOffset() + offset;
    if (addr >= 0x01E81000 && addr + 3 < 0x01E82000)
    {
        *retValue = *(u32*)(cheatPage + addr - 0x01E81000);
        return true;
    }
    if (Cheat_IsValidAddress(processHandle, addr, 4))
    {
        Result res = svcReadProcessMemory(&ReadWriteBuffer32, processHandle, addr, 4);
        *retValue = *((u32*) (&ReadWriteBuffer32));
        return R_SUCCEEDED(res);
    }
    return false;
}

static u8 typeEMapping[] = { 4 << 3, 5 << 3, 6 << 3, 7 << 3, 0 << 3, 1 << 3, 2 << 3, 3 << 3 };

static u8 Cheat_GetNextTypeE(const CheatDescription* cheat)
{

    if (cheat_state.typeEIdx == 7)
    {
        cheat_state.typeEIdx = 0;
        cheat_state.typeELine++;
    }
    else
    {
        cheat_state.typeEIdx++;
    }
    return (u8) ((cheat->codes[cheat_state.typeELine] >> (typeEMapping[cheat_state.typeEIdx])) & 0xFF);
}

static u32 Cheat_ApplyCheat(const Handle processHandle, CheatDescription* const cheat)
{
    cheat_state.index = 0;
    cheat_state.offset1 = 0;
    cheat_state.offset2 = 0;
    cheat_state.data1 = 0;
    cheat_state.data2 = 0;
    cheat_state.activeOffset = 0;
    cheat_state.activeData = 0;
    cheat_state.conditionalMode = 0;
    cheat_state.data1Mode = 0;
    cheat_state.data2Mode = 0;
    cheat_state.floatMode = 0;
    cheat_state.loopCount = 0;
    cheat_state.loopLine = -1;
    cheat_state.ifStack = 0;
    cheat_state.storedStack = 0;
    cheat_state.ifCount = 0;
    cheat_state.storedIfCount = 0;

    while (cheat_state.index < cheat->codesCount)
    {
        bool skipExecution = (cheat_state.ifStack & 0x00000001) != 0;
        u32 arg0 = (u32) ((cheat->codes[cheat_state.index] >> 32) & 0x00000000FFFFFFFFULL);
        u32 arg1 = (u32) ((cheat->codes[cheat_state.index]) & 0x00000000FFFFFFFFULL);
        if (arg0 == 0 && arg1 == 0)
        {
            return 0;
        }
        u32 code = ((arg0 >> 28) & 0x0F);
        u32 subcode = ((arg0 >> 24) & 0x0F);
        u32 codeArg = arg0 & 0x0F;

        switch (code)
        {
            case 0x0:
                // 0 Type
                // Format: 0XXXXXXX YYYYYYYY
                // Description: 32bit write of YYYYYYYY to 0XXXXXXX.
                if (!skipExecution)
                {
                    if (!Cheat_Write32(processHandle, (arg0 & 0x0FFFFFFF), arg1)) return 0;
                }
                break;
            case 0x1:
                // 1 Type
                // Format: 1XXXXXXX 0000YYYY
                // Description: 16bit write of YYYY to 0XXXXXXX.
                if (!skipExecution)
                {
                    if (!Cheat_Write16(processHandle, (arg0 & 0x0FFFFFFF), (u16) (arg1 & 0xFFFF))) return 0;
                }
                break;
            case 0x2:
                // 2 Type
                // Format: 2XXXXXXX 000000YY
                // Description: 8bit write of YY to 0XXXXXXX.
                if (!skipExecution)
                {
                    if (!Cheat_Write8(processHandle, (arg0 & 0x0FFFFFFF), (u8) (arg1 & 0xFF))) return 0;
                }
                break;
            case 0x3:
                // 3 Type
                // Format: 3XXXXXXXX YYYYYYYY
                // Description: 32bit if less than.
                // Simple: If the value at address 0XXXXXXX is less than the value YYYYYYYY.
                // Example: 323D6B28 10000000
            {
                u32 value = 0;
                if (!Cheat_Read32(processHandle, arg0 & 0x0FFFFFFF, &value)) return 0;

                cheat_state.ifStack <<= 1;
                if (value < arg1)
                {
                    cheat_state.ifStack |= skipExecution ? 1 : 0;
                }
                else
                {
                    cheat_state.ifStack |= 1;
                }
                cheat_state.ifCount++;
            }
                break;
            case 0x4:
                // 4 Type
                // Format: 4XXXXXXXX YYYYYYYY
                // Description: 32bit if greater than.
                // Simple: If the value at address 0XXXXXXX is greater than the value YYYYYYYY.
                // Example: 423D6B28 10000000
            {
                u32 value = 0;
                if (!Cheat_Read32(processHandle, arg0 & 0x0FFFFFFF, &value)) return 0;
                
                cheat_state.ifStack <<= 1;
                if (value > arg1)
                {
                    cheat_state.ifStack |= skipExecution ? 1 : 0;
                }
                else
                {
                    cheat_state.ifStack |= 1;
                }
                cheat_state.ifCount++;
            }
                break;
            case 0x5:
                // 5 Type
                // Format: 5XXXXXXXX YYYYYYYY
                // Description: 32bit if equal to.
                // Simple: If the value at address 0XXXXXXX is equal to the value YYYYYYYY.
                // Example: 523D6B28 10000000
            {
                u32 value = 0;
                if (!Cheat_Read32(processHandle, arg0 & 0x0FFFFFFF, &value)) return 0;

                cheat_state.ifStack <<= 1;
                if (value == arg1)
                {
                    cheat_state.ifStack |= skipExecution ? 1 : 0;
                }
                else
                {
                    cheat_state.ifStack |= 1;
                }
                cheat_state.ifCount++;
            }
                break;
            case 0x6:
                // 6 Type
                // Format: 3XXXXXXXX YYYYYYYY
                // Description: 32bit if not equal to.
                // Simple: If the value at address 0XXXXXXX is not equal to the value YYYYYYYY.
                // Example: 623D6B28 10000000
            {
                u32 value = 0;
                if (!Cheat_Read32(processHandle, arg0 & 0x0FFFFFFF, &value)) return 0;

                cheat_state.ifStack <<= 1;
                if (value != arg1)
                {
                    cheat_state.ifStack |= skipExecution ? 1 : 0;
                }
                else
                {
                    cheat_state.ifStack |= 1;
                }
                cheat_state.ifCount++;
            }
                break;
            case 0x7:
                // 7 Type
                // Format: 7XXXXXXXX 0000YYYY
                // Description: 16bit if less than.
                // Simple: If the value at address 0XXXXXXX is less than the value YYYY.
                // Example: 723D6B28 00005400
            {
                bool newSkip;
                u16 mask = (u16) ((arg1 >> 16) & 0xFFFF);
                u16 value = 0;
                if (!Cheat_Read16(processHandle, arg0 & 0x0FFFFFFF, &value)) return 0;
                switch (cheat_state.conditionalMode)
                {
                    case 0x0:
                        newSkip = !((value & (~mask)) < (arg1 & 0xFFFF));
                        break;
                    case 0x1:
                        newSkip = !((value & (~mask)) < (*activeData() & (~mask)));
                        break;
                    case 0x2:
                        newSkip = !((*activeData() & (~mask)) < (arg1 & 0xFFFF));
                        break;
                    case 0x3:
                        newSkip = !((*activeStorage(cheat) & (~mask)) < (arg1 & 0xFFFF));
                        break;
                    case 0x4:
                        newSkip = !((*activeData() & (~mask)) < (*activeStorage(cheat) & (~mask)));
                        break;
                    default:
                        return 0;
                }
                cheat_state.ifStack <<= 1;
                cheat_state.ifStack |= (newSkip || skipExecution) ? 1 : 0;
                cheat_state.ifCount++;
            }
                break;
            case 0x8:
                // 8 Type
                // Format: 8XXXXXXXX 0000YYYY
                // Description: 16bit if greater than.
                // Simple: If the value at address 0XXXXXXX is greater than the value YYYY.
                // Example: 823D6B28 00005400
            {
                bool newSkip;
                u16 mask = (u16) ((arg1 >> 16) & 0xFFFF);
                u16 value = 0;
                if (!Cheat_Read16(processHandle, arg0 & 0x0FFFFFFF, &value)) return 0;
                switch (cheat_state.conditionalMode)
                {
                    case 0x0:
                        newSkip = !((value & (~mask)) > (arg1 & 0xFFFF));
                        break;
                    case 0x1:
                        newSkip = !((value & (~mask)) > (*activeData() & (~mask)));
                        break;
                    case 0x2:
                        newSkip = !((*activeData() & (~mask)) > (arg1 & 0xFFFF));
                        break;
                    case 0x3:
                        newSkip = !((*activeStorage(cheat) & (~mask)) > (arg1 & 0xFFFF));
                        break;
                    case 0x4:
                        newSkip = !((*activeData() & (~mask)) > (*activeStorage(cheat) & (~mask)));
                        break;
                    default:
                        return 0;
                }

                cheat_state.ifStack <<= 1;
                cheat_state.ifStack |= (newSkip || skipExecution) ? 1 : 0;
                cheat_state.ifCount++;
            }
                break;
            case 0x9:
                // 9 Type
                // Format: 9XXXXXXXX 0000YYYY
                // Description: 16bit if equal to.
                // Simple: If the value at address 0XXXXXXX is equal to the value YYYY.
                // Example: 923D6B28 00005400
            {
                bool newSkip;
                u16 mask = (u16) ((arg1 >> 16) & 0xFFFF);
                u16 value = 0;
                if (!Cheat_Read16(processHandle, arg0 & 0x0FFFFFFF, &value)) return 0;
                switch (cheat_state.conditionalMode)
                {
                    case 0x0:
                        newSkip = !((value & (~mask)) == (arg1 & 0xFFFF));
                        break;
                    case 0x1:
                        newSkip = !((value & (~mask)) == (*activeData() & (~mask)));
                        break;
                    case 0x2:
                        newSkip = !((*activeData() & (~mask)) == (arg1 & 0xFFFF));
                        break;
                    case 0x3:
                        newSkip = !((*activeStorage(cheat) & (~mask)) == (arg1 & 0xFFFF));
                        break;
                    case 0x4:
                        newSkip = !((*activeData() & (~mask)) == (*activeStorage(cheat) & (~mask)));
                        break;
                    default:
                        return 0;
                }

                cheat_state.ifStack <<= 1;
                cheat_state.ifStack |= (newSkip || skipExecution) ? 1 : 0;
                cheat_state.ifCount++;
            }
                break;
            case 0xA:
                // A Type
                // Format: AXXXXXXXX 0000YYYY
                // Description: 16bit if not equal to.
                // Simple: If the value at address 0XXXXXXX is not equal to the value YYYY.
                // Example: A23D6B28 00005400
            {
                bool newSkip;
                u16 mask = (u16) ((arg1 >> 16) & 0xFFFF);
                u16 value = 0;
                if (!Cheat_Read16(processHandle, arg0 & 0x0FFFFFFF, &value)) return 0;
                switch (cheat_state.conditionalMode)
                {
                    case 0x0:
                        newSkip = !((value & (~mask)) != (arg1 & 0xFFFF));
                        break;
                    case 0x1:
                        newSkip = !((value & (~mask)) != (*activeData() & (~mask)));
                        break;
                    case 0x2:
                        newSkip = !((*activeData() & (~mask)) != (arg1 & 0xFFFF));
                        break;
                    case 0x3:
                        newSkip = !((*activeStorage(cheat) & (~mask)) != (arg1 & 0xFFFF));
                        break;
                    case 0x4:
                        newSkip = !((*activeData() & (~mask)) != (*activeStorage(cheat) & (~mask)));
                        break;
                    default:
                        return 0;
                }

                cheat_state.ifStack <<= 1;
                cheat_state.ifStack |= (newSkip || skipExecution) ? 1 : 0;
                cheat_state.ifCount++;
            }
                break;

            case 0xB:
                // B Type
                // Format: BXXXXXXX 00000000
                // Description: Loads offset register with value at given XXXXXXX
                if (!skipExecution)
                {
                    u32 value;
                    if (!Cheat_Read32(processHandle, arg0 & 0x0FFFFFFF, &value)) return 0;
                    *activeOffset() = value;
                }
                break;
            case 0xC:
                // C Type
                // Format: C0000000 ZZZZZZZZ
                // Description: Repeat following lines at specified offset.
                // Simple: used to write a value to an address, and then continues to write that value Z number of times to all addresses at an offset determined by the (D6, D7, D8, or DC) type following it.
                // Note: used with the D6, D7, D8, and DC types. C types can not be nested.
                // Example:

                // C0000000 00000005
                // 023D6B28 0009896C
                // DC000000 00000010
                // D2000000 00000000
                switch (subcode)
                {
                    case 0x00:
                        cheat_state.loopLine = cheat_state.index;
                        cheat_state.loopCount = arg1;
                        cheat_state.storedStack = cheat_state.ifStack;
                        cheat_state.storedIfCount = cheat_state.ifCount;
                        break;
                    case 0x01:
                        cheat_state.loopLine = cheat_state.index;
                        cheat_state.loopCount = cheat_state.data1;
                        cheat_state.storedStack = cheat_state.ifStack;
                        cheat_state.storedIfCount = cheat_state.ifCount;
                        break;
                    case 0x02:
                        cheat_state.loopLine = cheat_state.index;
                        cheat_state.loopCount = cheat_state.data2;
                        cheat_state.storedStack = cheat_state.ifStack;
                        cheat_state.storedIfCount = cheat_state.ifCount;
                        break;
                }
                break;
            case 0xD:
                switch (subcode)
                {
                    case 0x00:
                        // D0 Type
                        // Format: D0000000 00000000
                        // Description: ends most recent conditional.
                        // Simple: type 3 through A are all "conditionals," the conditional most recently executed before this line will be terminated by it.
                        // Example:

                        // 94000130 FFFB0000
                        // 74000100 FF00000C
                        // 023D6B28 0009896C
                        // D0000000 00000000

                        // The 7 type line would be terminated.
                        if (arg1 == 0)
                        {
                            if (cheat_state.loopLine != -1)
                            {
                                if (cheat_state.ifCount > 0 && cheat_state.ifCount > cheat_state.storedIfCount)
                                {
                                    cheat_state.ifStack >>= 1;
                                    cheat_state.ifCount--;
                                }
                                else
                                {

                                    if (cheat_state.loopCount > 0)
                                    {
                                        cheat_state.loopCount--;
                                        if (cheat_state.loopCount == 0)
                                        {
                                            cheat_state.loopLine = -1;
                                        }
                                        else if (cheat_state.loopLine != -1)
                                        {
                                            cheat_state.index = cheat_state.loopLine;
                                        }
                                    }
                                }
                            }
                            else
                            {
                                if (cheat_state.ifCount > 0)
                                {
                                    cheat_state.ifStack >>= 1;
                                    cheat_state.ifCount--;
                                }
                            }
                        }
                        // D0000000 00000001
                        // Loop break
                        else if (!skipExecution && arg1 == 1)
                        {
                            cheat_state.loopCount = 0;
                            cheat_state.loopLine = -1;
                            cheat_state.index++;
                            while (cheat_state.index < cheat->codesCount)
                            {
                                u64 code = cheat->codes[cheat_state.index++];
                                if (code == 0xD100000000000000ull || code == 0xD200000000000000ull)
                                {
                                    break;
                                }
                            }
                        }
                        break;
                    case 0x01:
                        // D1 Type
                        // Format: D1000000 00000000
                        // Description: ends repeat block.
                        // Simple: will end all conditionals within a C type code, along with the C type itself.
                        // Example:

                        // 94000130 FFFB0000
                        // C0000000 00000010
                        // 8453DA0C 00000200
                        // 023D6B28 0009896C
                        // D6000000 00000005
                        // D1000000 00000000

                        // The C line, 8 line, 0 line, and D6 line would be terminated.
                        if (cheat_state.loopCount > 0)
                        {
                            cheat_state.ifStack = cheat_state.storedStack;
                            cheat_state.ifCount = cheat_state.storedIfCount;
                            cheat_state.loopCount--;
                            if (cheat_state.loopCount == 0)
                            {
                                cheat_state.loopLine = -1;
                            }
                            else
                            {
                                if (cheat_state.loopLine != -1)
                                {
                                    cheat_state.index = cheat_state.loopLine;
                                }
                            }
                        }
                        break;
                    case 0x02:
                        // D2 Type
                        // Format: D2000000 00000000
                        // Description: ends all conditionals/repeats before it and sets offset and stored to zero.
                        // Simple: ends all lines.
                        // Example:

                        // 94000130 FEEF0000
                        // C0000000 00000010
                        // 8453DA0C 00000200
                        // 023D6B28 0009896C
                        // D6000000 00000005
                        // D2000000 00000000

                        // All lines would terminate.
                        if (arg1 == 0)
                        {
                            if (cheat_state.loopCount > 0)
                            {
                                cheat_state.loopCount--;
                                if (cheat_state.loopCount == 0)
                                {
                                    *activeData() = 0;
                                    *activeOffset() = 0;
                                    cheat_state.loopLine = -1;

                                    cheat_state.ifStack = 0;
                                    cheat_state.ifCount = 0;
                                }
                                else
                                {
                                    if (cheat_state.loopLine != -1)
                                    {
                                        cheat_state.index = cheat_state.loopLine;
                                    }
                                }
                            }
                            else
                            {
                                *activeData() = 0;
                                *activeOffset() = 0;
                                cheat_state.ifStack = 0;
                                cheat_state.ifCount = 0;
                            }
                        }
                        // D2000000 00000001
                        // Return
                        else if (!skipExecution && arg1 == 1)
                        {
                            cheat_state.index = cheat->codesCount;
                        }
                        break;
                    case 0x03:
                        // D3 Type
                        // Format: D3000000 XXXXXXXX
                        // Description: sets offset.
                        // Simple: loads the address X so that lines after can modify the value at address X.
                        // Note: used with the D4, D5, D6, D7, D8, and DC types.
                        // Example: D3000000 023D6B28
                        if (!skipExecution)
                        {
                            if (codeArg == 0)
                            {
                                cheat_state.offset1 = arg1;
                            }
                            else if (codeArg == 1)
                            {
                                cheat_state.offset2 = arg1;
                            }
                        }
                        break;
                    case 0x04:
                        // D4 Type
                        // Format: D4000000 YYYYYYYY
                        // Description: adds to the stored address' value.
                        // Simple: adds to the value at the address defined by lines D3, D9, DA, and DB.
                        // Note: used with the D3, D9, DA, DB, DC types.
                        // Example: D4000000 00000025
                        if (!skipExecution)
                        {
                            if (codeArg == 0)
                            {
                                *activeData() += arg1;
                            }
                            else if (codeArg == 1)
                            {
                                cheat_state.data1 += arg1 + cheat_state.data2;
                            }
                            else if (codeArg == 2)
                            {
                                cheat_state.data2 += arg1 + cheat_state.data1;
                            }
                        }
                        break;
                    case 0x05:
                        // D5 Type
                        // Format: D5000000 YYYYYYYY
                        // Description: sets the stored address' value.
                        // Simple: makes the value at the address defined by lines D3, D9, DA, and DB to YYYYYYYY.
                        // Note: used with the D3, D9, DA, DB, and DC types.
                        // Example: D5000000 34540099
                        if (!skipExecution)
                        {
                            if (codeArg == 0)
                            {
                                *activeData() = arg1;
                            }
                            else if (codeArg == 1)
                            {
                                cheat_state.data1 = arg1;
                            }
                            else if (codeArg == 2)
                            {
                                cheat_state.data2 = arg1;
                            }
                        }
                        break;
                    case 0x06:
                        // D6 Type
                        // Format: D6000000 XXXXXXXX
                        // Description: 32bit store and increment by 4.
                        // Simple: stores the value at address XXXXXXXX and to addresses in increments of 4.
                        // Note: used with the C, D3, and D9 types.
                        // Example: D3000000 023D6B28
                        if (!skipExecution)
                        {
                            if (codeArg == 0)
                            {
                                if (!Cheat_Write32(processHandle, arg1, *activeData())) return 0;
                                *activeOffset() += 4;
                            }
                            else if (codeArg == 1)
                            {
                                if (!Cheat_Write32(processHandle, arg1, cheat_state.data1)) return 0;
                                *activeOffset() += 4;
                            }
                            else if (codeArg == 2)
                            {
                                if (!Cheat_Write32(processHandle, arg1, cheat_state.data2)) return 0;
                                *activeOffset() += 4;
                            }
                        }
                        break;
                    case 0x07:
                        // D7 Type
                        // Format: D7000000 XXXXXXXX
                        // Description: 16bit store and increment by 2.
                        // Simple: stores 2 bytes of the value at address XXXXXXXX and to addresses in increments of 2.
                        // Note: used with the C, D3, and DA types.
                        // Example: D7000000 023D6B28
                        if (!skipExecution)
                        {
                            if (codeArg == 0)
                            {
                                if (!Cheat_Write16(processHandle, arg1, (u16) (*activeData() & 0xFFFF))) return 0;
                                *activeOffset() += 2;
                            }
                            else if (codeArg == 1)
                            {
                                if (!Cheat_Write16(processHandle, arg1, (u16) (cheat_state.data1 & 0xFFFF))) return 0;
                                *activeOffset() += 2;
                            }
                            else if (codeArg == 2)
                            {
                                if (!Cheat_Write16(processHandle, arg1, (u16) (cheat_state.data2 & 0xFFFF))) return 0;
                                *activeOffset() += 2;
                            }
                        }
                        break;
                    case 0x08:
                        // D8 Type
                        // Format: D8000000 XXXXXXXX
                        // Description: 8bit store and increment by 1.
                        // Simple: stores 1 byte of the value at address XXXXXXXX and to addresses in increments of 1.
                        // Note: used with the C, D3, and DB types.
                        // Example: D8000000 023D6B28
                        if (!skipExecution)
                        {
                            if (codeArg == 0)
                            {
                                if (!Cheat_Write8(processHandle, arg1, (u8) (*activeData() & 0xFF))) return 0;
                                *activeOffset() += 1;
                            }
                            else if (codeArg == 1)
                            {
                                if (!Cheat_Write8(processHandle, arg1, (u8) (cheat_state.data1 & 0xFF))) return 0;
                                *activeOffset() += 1;
                            }
                            else if (codeArg == 2)
                            {
                                if (!Cheat_Write8(processHandle, arg1, (u8) (cheat_state.data2 & 0xFF))) return 0;
                                *activeOffset() += 1;
                            }
                        }
                        break;
                    case 0x09:
                        // D9 Type
                        // Format: D9000000 XXXXXXXX
                        // Description: 32bit load.
                        // Simple: loads the value from address X.
                        // Note: used with the D5 and D6 types.
                        // Example: D9000000 023D6B28
                        if (!skipExecution)
                        {
                            if (codeArg == 0)
                            {
                                u32 value = 0;
                                if (!Cheat_Read32(processHandle, arg1, &value)) return 0;
                                *activeData() = value;
                            }
                            else if (codeArg == 1)
                            {
                                u32 value = 0;
                                if (!Cheat_Read32(processHandle, arg1, &value)) return 0;
                                cheat_state.data1 = value;
                            }
                            else if (codeArg == 2)
                            {
                                u32 value = 0;
                                if (!Cheat_Read32(processHandle, arg1, &value)) return 0;
                                cheat_state.data2 = value;
                            }
                        }
                        break;
                    case 0x0A:
                        // DA Type
                        // Format: DA000000 XXXXXXXX
                        // Description: 16bit load.
                        // Simple: loads 2 bytes from address X.
                        // Note: used with the D5 and D7 types.
                        // Example: DA000000 023D6B28
                        if (!skipExecution)
                        {
                            if (codeArg == 0)
                            {
                                u16 value = 0;
                                if (!Cheat_Read16(processHandle, arg1, &value)) return 0;
                                *activeData() = value;
                            }
                            else if (codeArg == 1)
                            {
                                u16 value = 0;
                                if (!Cheat_Read16(processHandle, arg1, &value)) return 0;
                                cheat_state.data1 = value;
                            }
                            else if (codeArg == 2)
                            {
                                u16 value = 0;
                                if (!Cheat_Read16(processHandle, arg1, &value)) return 0;
                                cheat_state.data2 = value;
                            }
                        }
                        break;
                    case 0x0B:
                        // DB Type
                        // Format: DB000000 XXXXXXXX
                        // Description: 8bit load.
                        // Simple: loads 1 byte from address X.
                        // Note: used with the D5 and D8 types.
                        // Example: DB000000 023D6B28
                        if (!skipExecution)
                        {
                            if (codeArg == 0)
                            {
                                u8 value = 0;
                                if (!Cheat_Read8(processHandle, arg1, &value)) return 0;
                                *activeData() = value;
                            }
                            else if (codeArg == 1)
                            {
                                u8 value = 0;
                                if (!Cheat_Read8(processHandle, arg1, &value)) return 0;
                                cheat_state.data1 = value;
                            }
                            else if (codeArg == 2)
                            {
                                u8 value = 0;
                                if (!Cheat_Read8(processHandle, arg1, &value)) return 0;
                                cheat_state.data2 = value;
                            }
                        }
                        break;
                    case 0x0C:
                        // DC Type
                        // Format: DC000000 VVVVVVVV
                        // Description: 32bit store and increment by V.
                        // Simple: stores the value at address(es) before it and to addresses in increments of V.
                        // Note: used with the C, D3, D5, D9, D8, DB types.
                        // Example: DC000000 00000100
                        if (!skipExecution)
                        {
                            *activeOffset() += arg1;
                        }
                        break;
                    case 0x0D:
                        // DD Type
                    {
                        bool newSkip = !(arg1 == 0 || (HID_PAD & arg1) == arg1);

                        cheat_state.ifStack <<= 1;
                        cheat_state.ifStack |= (newSkip || skipExecution) ? 1 : 0;;
                        cheat_state.ifCount++;
                    }
                        break;
                    case 0x0E:
                        // Touchpad conditional
                        // DE000000 AAAABBBB: AAAA >= X position >= BBBB
                        // DE000001 AAAABBBB: AAAA >= Y position >= BBBB
                    {
                        bool newSkip;
                        u32 highBound = arg1 >> 16;
                        u32 lowBound = arg1 & 0xFFFF;
                        touchPosition touch;
                        hidTouchRead(&touch);
                        if (codeArg == 0)
                        {
                            newSkip = !(lowBound <= touch.px && highBound >= touch.px);
                        }
                        else if (codeArg == 1)
                        {
                            newSkip = !(lowBound <= touch.py && highBound >= touch.py);
                        }
                        else
                        {
                            return 0;
                        }

                        cheat_state.ifStack <<= 1;
                        cheat_state.ifStack |= (newSkip || skipExecution) ? 1 : 0;
                        cheat_state.ifCount++;
                    }
                        break;
                    case 0x0F:
                    {
                        switch (codeArg)
                        {
                            case 0x00:
                            {
                                if (arg1 & 0x00010000)
                                {
                                    if (arg1 & 0x1)
                                    {
                                        cheat_state.offset2 = cheat_state.offset1;
                                    }
                                    else
                                    {
                                        cheat_state.offset1 = cheat_state.offset2;
                                    }
                                }
                                else if (arg1 & 0x00020000)
                                {
                                    if (arg1 & 0x1)
                                    {
                                        cheat_state.data2 = cheat_state.offset2;
                                    }
                                    else
                                    {
                                        cheat_state.data1 = cheat_state.offset1;
                                    }
                                }
                                else
                                {
                                    cheat_state.activeOffset = arg1 & 0x1;
                                }
                            }
                                break;
                            case 0x01:
                            {
                                if (arg1 & 0x00010000)
                                {
                                    if (arg1 & 0x1)
                                    {
                                        cheat_state.data2 = cheat_state.data1;
                                    }
                                    else
                                    {
                                        cheat_state.data1 = cheat_state.data2;
                                    }
                                }
                                else if (arg1 & 0x00020000)
                                {
                                    if (arg1 & 0x1)
                                    {
                                        cheat_state.offset2 = cheat_state.data2;
                                    }
                                    else
                                    {
                                        cheat_state.offset1 = cheat_state.data1;
                                    }
                                }
                                else
                                {
                                    cheat_state.activeData = arg1 & 0x1;
                                }
                            }
                                break;
                            case 0x02:
                            {
                                if (arg1 & 0x00010000)
                                {
                                    if (arg1 & 0x1)
                                    {
                                        cheat_state.data2 = cheat->storage2;
                                    }
                                    else
                                    {
                                        cheat_state.data1 = cheat->storage1;
                                    }
                                }
                                else if (arg1 & 0x00020000)
                                {
                                    if (arg1 & 0x1)
                                    {
                                        cheat->storage2 = cheat_state.data2;
                                    }
                                    else
                                    {
                                        cheat->storage1 = cheat_state.data1;
                                    }
                                }
                                else
                                {
                                    cheat->activeStorage = arg1 & 0x1;
                                }
                            }
                                break;
                            case 0x0E:
                            {
                                if (cheat_state.activeData)
                                {
                                    switch (arg1)
                                    {
                                        case 0x0:
                                        {
                                            cheat_state.data2Mode = 0;
                                        }
                                            break;
                                        case 0x1:
                                        {
                                            cheat_state.data2Mode = 1;
                                        }
                                            break;
                                        case 0x10:
                                        {
                                            cheat_state.data2Mode = 0;
                                            float val;
                                            memcpy(&val, &cheat_state.data2, sizeof(float));
                                            cheat_state.data2 = val;
                                        }
                                            break;
                                        case 0x11:
                                        {
                                            cheat_state.data2Mode = 1;
                                            float val = cheat_state.data2;
                                            memcpy(&cheat_state.data2, &val, sizeof(float));
                                        }
                                            break;
                                        default:
                                            return 0;
                                    }
                                }
                                else
                                {
                                    switch (arg1)
                                    {
                                        case 0x0:
                                        {
                                            cheat_state.data1Mode = 0;
                                        }
                                            break;
                                        case 0x1:
                                        {
                                            cheat_state.data1Mode = 1;
                                        }
                                            break;
                                        case 0x10:
                                        {
                                            cheat_state.data1Mode = 0;
                                            float val;
                                            memcpy(&val, &cheat_state.data1, sizeof(float));
                                            cheat_state.data1 = val;
                                        }
                                            break;
                                        case 0x11:
                                        {
                                            cheat_state.data1Mode = 1;
                                            float val = cheat_state.data1;
                                            memcpy(&cheat_state.data1, &val, sizeof(float));
                                        }
                                            break;
                                        default:
                                            return 0;
                                    }
                                }
                            }
                                break;
                            case 0x0F:
                            {
                                if (arg1 < 5)
                                {
                                    cheat_state.conditionalMode = (u8)arg1;
                                }
                                else
                                {
                                    return 0;
                                }
                            }
                                break;
                            default:
                                return 0;
                        }
                    }
                        break;
                    default:
                        return 0;
                }
                break;
            case 0xE:
                // E Type
                // Format:
                // EXXXXXXX UUUUUUUU
                // YYYYYYYY YYYYYYYY

                // Description: writes Y to X for U bytes.

            {
                u32 beginOffset = (arg0 & 0x0FFFFFFF);
                u32 count = arg1;
                cheat_state.typeELine = cheat_state.index;
                cheat_state.typeEIdx = 7;
                for (u32 i = 0; i < count; i++)
                {
                    u8 byte = Cheat_GetNextTypeE(cheat);
                    if (!skipExecution)
                    {
                        if (!Cheat_Write8(processHandle, beginOffset + i, byte)) return 0;
                    }
                }
                cheat_state.index = cheat_state.typeELine;
            }
                break;
            case 0xF:
            {
                if (arg0 == 0xF0F00000)
                {
                    // I have no clue how to implement this, or if it's even possible. Needs research.
                    return 0;
                }
                else
                {
                    switch (subcode)
                    {
                        case 0x0:
                        {
                            cheat_state.floatMode = arg1 & 0x1;
                        }
                            break;
                        case 0x1:
                        {
                            if (cheat_state.floatMode)
                            {
                                u32 tmp;
                                if (!Cheat_Read32(processHandle, arg0 & 0x00FFFFFF, &tmp))
                                {
                                    return 0;
                                }
                                float value;
                                memcpy(&value, &tmp, sizeof(float));
                                value += arg1;
                                memcpy(&tmp, &value, sizeof(u32));
                                if (!Cheat_Write32(processHandle, arg0 & 0x00FFFFFF, tmp))
                                {
                                    return 0;
                                }
                            }
                            else
                            {
                                u32 tmp;
                                if (!Cheat_Read32(processHandle, arg0 & 0x00FFFFFF, &tmp))
                                {
                                    return 0;
                                }
                                tmp += arg1;
                                if (!Cheat_Write32(processHandle, arg0 & 0x00FFFFFF, tmp))
                                {
                                    return 0;
                                }
                            }
                        }
                            break;
                        case 0x2:
                        {
                            if (cheat_state.floatMode)
                            {
                                u32 tmp;
                                if (!Cheat_Read32(processHandle, arg0 & 0x00FFFFFF, &tmp))
                                {
                                    return 0;
                                }
                                float value;
                                memcpy(&value, &tmp, sizeof(float));
                                value *= arg1;
                                memcpy(&tmp, &value, sizeof(u32));
                                if (!Cheat_Write32(processHandle, arg0 & 0x00FFFFFF, tmp))
                                {
                                    return 0;
                                }
                            }
                            else
                            {
                                u32 tmp;
                                if (!Cheat_Read32(processHandle, arg0 & 0x00FFFFFF, &tmp))
                                {
                                    return 0;
                                }
                                tmp *= arg1;
                                if (!Cheat_Write32(processHandle, arg0 & 0x00FFFFFF, tmp))
                                {
                                    return 0;
                                }
                            }
                        }
                            break;
                        case 0x3:
                        {
                            if (cheat_state.floatMode)
                            {
                                u32 tmp;
                                if (!Cheat_Read32(processHandle, arg0 & 0x00FFFFFF, &tmp))
                                {
                                    return 0;
                                }
                                float value;
                                memcpy(&value, &tmp, sizeof(float));
                                value /= arg1;
                                memcpy(&tmp, &value, sizeof(u32));
                                if (!Cheat_Write32(processHandle, arg0 & 0x00FFFFFF, tmp))
                                {
                                    return 0;
                                }
                            }
                            else
                            {
                                u32 tmp;
                                if (!Cheat_Read32(processHandle, arg0 & 0x00FFFFFF, &tmp))
                                {
                                    return 0;
                                }
                                tmp /= arg1;
                                if (!Cheat_Write32(processHandle, arg0 & 0x00FFFFFF, tmp))
                                {
                                    return 0;
                                }
                            }
                        }
                            break;
                        case 0x4:
                        {
                            if (cheat_state.data1Mode)
                            {
                                float value;
                                memcpy(&value, activeData(), sizeof(float));
                                value *= arg1;
                                memcpy(activeData(), &value, sizeof(float));
                            }
                            else
                            {
                                *activeData() *= arg1;
                            }
                        }
                            break;
                        case 0x5:
                        {
                            if (cheat_state.data1Mode)
                            {
                                float value;
                                memcpy(&value, activeData(), sizeof(float));
                                value /= arg1;
                                memcpy(activeData(), &value, sizeof(float));
                            }
                            else
                            {
                                *activeData() /= arg1;
                            }
                        }
                            break;
                        case 0x6:
                        {
                            *activeData() &= arg1;
                        }
                            break;
                        case 0x7:
                        {
                            *activeData() |= arg1;
                        }
                            break;
                        case 0x8:
                        {
                            *activeData() ^= arg1;
                        }
                            break;
                        case 0x9:
                        {
                            *activeData() = ~*activeData();
                        }
                            break;
                        case 0xA:
                        {
                            *activeData() <<= arg1;
                        }
                            break;
                        case 0xB:
                        {
                            *activeData() >>= arg1;
                        }
                            break;
                        case 0xC:
                        {
                            u8 origActiveOffset = cheat_state.activeOffset;
                            for (size_t i = 0; i < arg1; i++)
                            {
                                u8 data;
                                cheat_state.activeOffset = 1;
                                if (!Cheat_Read8(processHandle, 0, &data))
                                {
                                    return 0;
                                }
                                cheat_state.activeOffset = 0;
                                if (!Cheat_Write8(processHandle, 0, data))
                                {
                                    return 0;
                                }
                            }
                            cheat_state.activeOffset = origActiveOffset;
                        }
                            break;
                        // Search for pattern
                        case 0xE:
                        {
                            u32 searchSize = arg0 & 0xFFFF;
                            if (searchSize <= arg1 && searchSize + cheat_state.index < cheat->codesCount)
                            {
                                bool newSkip = true;
                                if (!skipExecution) // Don't do an expensive operation if we don't have to
                                {
                                    u8* searchData = (u8*)(cheat->codes + cheat_state.index + 1);
                                    cheat_state.index += searchSize / 8;
                                    if (searchSize & 0x7)
                                    {
                                        cheat_state.index++;
                                    }
                                    for (size_t i = 0; i < arg1 - searchSize; i++)
                                    {
                                        u8 curVal;
                                        newSkip = false;
                                        for (size_t j = 0; j < searchSize; j++)
                                        {
                                            if (!Cheat_Read8(processHandle, i + j, &curVal))
                                            {
                                                return 0;
                                            }
                                            if (curVal != searchData[j])
                                            {
                                                newSkip = 1;
                                                break;
                                            }
                                        }
                                        if (!newSkip)
                                        {
                                            break;
                                        }
                                    }
                                }

                                cheat_state.ifStack <<= 1;
                                cheat_state.ifStack |= (newSkip || skipExecution) ? 1 : 0;
                                cheat_state.ifCount++;
                            }
                            else
                            {
                                return 0;
                            }
                        }
                            break;
                        case 0xF:
                        {
                            u32 range = arg1 - (arg0 & 0xFFFFFF);
                            u32 number = rand() % range;
                            *activeData() = (arg0 & 0xFFFFFF) + number;
                        }
                            break;
                        default:
                            return 0;
                    }
                }
            }
                break;
            // This should now not be possible
            default:
                return 0;
        }
        cheat_state.index++;
    }
    return 1;
}

static void Cheat_EatEvents(Handle debug)
{
    DebugEventInfo info;
    Result r;

    while(true)
    {
        if((r = svcGetProcessDebugEvent(&info, debug)) != 0)
        {
            if(r == (s32)(0xd8402009))
            {
                break;
            }
        }
        svcContinueDebugEvent(debug, 3);
    }
}

static Result Cheat_MapMemoryAndApplyCheat(u32 pid, CheatDescription* const cheat)
{
    Handle processHandle;
    Handle debugHandle;
    Result res;
    res = svcOpenProcess(&processHandle, pid);
    if (R_SUCCEEDED(res))
    {
        res = svcDebugActiveProcess(&debugHandle, pid);
        if (R_SUCCEEDED(res))
        {
            Cheat_EatEvents(debugHandle);
            cheat->valid = Cheat_ApplyCheat(debugHandle, cheat);

            svcCloseHandle(debugHandle);
            svcCloseHandle(processHandle);
            cheat->active = 1;
        }
        else
        {
            sprintf(failureReason, "Debug process failed");
            svcCloseHandle(processHandle);
        }
    }
    else
    {
        sprintf(failureReason, "Open process failed");
    }
    return res;
}

static CheatDescription* Cheat_AllocCheat()
{
    CheatDescription* cheat;
    if (cheatCount == 0)
    {
        cheat = (CheatDescription*) cheatBuffer;
    }
    else
    {
        CheatDescription* prev = cheats[cheatCount - 1];
        cheat = (CheatDescription *) ((u8*) (prev) + sizeof(CheatDescription) + sizeof(u64) * (prev->codesCount));
    }
    cheat->active = 0;
    cheat->valid = 1;
    cheat->codesCount = 0;
    cheat->hasKeyCode = 0;
    cheat->storage1 = 0;
    cheat->storage2 = 0;
    cheat->name[0] = '\0';

    cheats[cheatCount] = cheat;
    cheatCount++;
    return cheat;
}

static void Cheat_AddCode(CheatDescription* cheat, u64 code)
{
    if (cheat)
    {
        cheat->codes[cheat->codesCount] = code;
        (cheat->codesCount)++;
    }
}

static Result BufferedFile_Open(BufferedFile* file, FS_ArchiveID archiveId, FS_Path archivePath, FS_Path filePath, u32 flags)
{
    Result res = 0;
    memset(file->buffer, '\0', sizeof(file->buffer));
    res = IFile_Open(&file->file, archiveId, archivePath, filePath, flags);
    if (R_SUCCEEDED(res))
    {
        file->curPos = 0;
        res = IFile_Read(&file->file, &file->maxPos, file->buffer, sizeof(file->buffer));
    }
    return res;
}

static Result BufferedFile_Read(BufferedFile* file, u64* totalRead, void* buffer, u32 len)
{
    Result res = 0;
    if (len == 0)
    {
        *totalRead = 0;
        return 0;
    }
    else if (file->curPos + len < file->maxPos)
    {
        memcpy(buffer, file->buffer + file->curPos, len);
        file->curPos += len;
        *totalRead = len;
    }
    else
    {
        *totalRead = 0;
        while(R_SUCCEEDED(res) && file->maxPos != 0 && *totalRead < len)
        {
            u32 toRead = file->maxPos - file->curPos < len - *totalRead ? file->maxPos - file->curPos : len - *totalRead;
            memcpy(buffer + *totalRead, file->buffer + file->curPos, toRead);
            *totalRead += toRead;
            file->curPos += toRead;
            if (file->curPos >= file->maxPos)
            {
                res = IFile_Read(&file->file, &file->maxPos, file->buffer, sizeof(file->buffer));
                file->curPos = 0;
            }
        }
    }
    return res;
}

static Result Cheat_ReadLine(BufferedFile* file, char* line, u32 lineSize)
{
    Result res = 0;

    u32 idx = 0;
    u64 total = 0;
    bool lastWasCarriageReturn = false;
    while (R_SUCCEEDED(res) && idx < lineSize)
    {
        res = BufferedFile_Read(file, &total, line + idx, 1);
        if (total == 0)
        {
            line[idx] = '\0';
            return -1;
        }
        if (R_SUCCEEDED(res))
        {
            if (line[idx] == '\r')
            {
                lastWasCarriageReturn = true;
            }
            else if (line[idx] == '\n')
            {
                if (lastWasCarriageReturn)
                {
                    line[--idx] = '\0';
                    return idx;
                }
                else
                {
                    line[idx] = '\0';
                    return idx;
                }
            }
            else if (line[idx] == '\0')
            {
                return -1;
            }
            else
            {
                lastWasCarriageReturn = false;
            }
            idx++;
        }
    }
    return res;
}

static bool Cheat_IsCodeLine(const char *line)
{
    s32 len = strnlen(line, 1023);
    if (len != 17)
    {
        return false;
    }
    if (line[8] != ' ')
    {
        return false;
    }

    int i;
    for (i = 0; i < 8; i++)
    {
        char c = line[i];
        if (!(('0' <= c && c <= '9') || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f')))
        {
            return false;
        }
    }

    for (i = 9; i < 17; i++)
    {
        char c = line[i];
        if (!(('0' <= c && c <= '9') || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f')))
        {
            return false;
        }
    }

    return true;
}

static u64 Cheat_GetCode(const char *line)
{
    u64 tmp = 0;
    int i;
    for (i = 0; i < 8; i++)
    {
        char c = line[i];
        u8 code = 0;
        if ('0' <= c && c <= '9')
        {
            code = c - '0';
        }
        if ('A' <= c && c <= 'F')
        {
            code = c - 'A' + 10;
        }
        if ('a' <= c && c <= 'f')
        {
            code = c - 'a' + 10;
        }
        tmp <<= 4;
        tmp |= (code & 0xF);
    }

    for (i = 9; i < 17; i++)
    {
        char c = line[i];
        u8 code = 0;
        if ('0' <= c && c <= '9')
        {
            code = c - '0';
        }
        if ('A' <= c && c <= 'F')
        {
            code = c - 'A' + 10;
        }
        if ('a' <= c && c <= 'f')
        {
            code = c - 'a' + 10;
        }
        tmp <<= 4;
        tmp |= (code & 0xF);
    }

    return tmp;
}

static char* stripWhitespace(char* in)
{
    char* ret = in;
    while (*ret == ' ' || *ret == '\t')
    {
        ret++;
    }
    int back = strlen(ret) - 1;
    while (back > 0 && (ret[back] == ' ' || ret[back] == '\t'))
    {
        back--;
    }
    ret[back+1] = '\0';
    return ret;
}

static void Cheat_LoadCheatsIntoMemory(u64 titleId)
{
    cheatCount = 0;
    cheatTitleInfo = titleId;

    char path[64] = { 0 };
    sprintf(path, "/luma/titles/%016llX/cheats.txt", titleId);

    BufferedFile file;

    if (R_FAILED(BufferedFile_Open(&file, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, path), FS_OPEN_READ)))
    {
        // OK, let's try another source
        sprintf(path, "/cheats/%016llX.txt", titleId);
        if (R_FAILED(BufferedFile_Open(&file, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, path), FS_OPEN_READ))) return;
    };

    char line[1024] = { 0 };
    Result res = 0;
    CheatDescription* cheat = 0;
    u32 cheatSize = 0;
    do
    {
        res = Cheat_ReadLine(&file, line, 1024);
        if (R_SUCCEEDED(res))
        {
            char* strippedLine = stripWhitespace(line);
            s32 lineLen = strnlen(strippedLine, 1023);
            if (!lineLen)
            {
                continue;
            }
            if (strippedLine[0] == '#')
            {
                continue;
            }
            if (Cheat_IsCodeLine(strippedLine))
            {
                if (cheatSize + sizeof(u64) >= sizeof(cheatBuffer))
                {
                    cheatCount--;
                    break;
                }
                if (cheat)
                {
                    u64 tmp = Cheat_GetCode(strippedLine);
                    Cheat_AddCode(cheat, tmp);
                    cheatSize += sizeof(u64);
                    if (((tmp >> 32) & 0xFFFFFFFF) == 0xDD000000)
                    {
                        cheat->hasKeyCode = 1;
                    }
                }
            }
            else
            {
                if (!cheat || cheat->codesCount > 0)
                {
                    if (cheatSize + sizeof(CheatDescription) >= sizeof(cheatBuffer))
                    {
                        break;
                    }
                    cheat = Cheat_AllocCheat();
                    cheatSize += sizeof(CheatDescription);
                }
                strncpy(cheat->name, line, 38);
                cheat->name[38] = '\0';
            }
        }
    } while (R_SUCCEEDED(res) && cheatSize < sizeof(cheatBuffer));

    IFile_Close(&file.file);

    if ((cheatCount > 0) && (cheats[cheatCount - 1]->codesCount == 0))
    {
        cheatCount--; // Remove last empty cheat
    }

    memset(cheatPage, 0, 0x1000);
}

static u32 Cheat_GetCurrentPID(u64* titleId)
{
    s32 processAmount = Cheats_FetchProcessInfo();

    s32 index = -1;

    for (s32 i = 0; i < processAmount; i++)
    {

        if (((u32) (cheatinfo[i].titleId >> 32)) == 0x00040010 || ((u32) (cheatinfo[i].titleId >> 32) == 0x00040000))
        {
            index = i;

            break;
        }
    }

    if (index != -1)
    {
        *titleId = cheatinfo[index].titleId;
        return cheatinfo[index].pid;
    }
    else
    {
        *titleId = 0;
        return 0xFFFFFFFF;
    }
}

void Cheat_ApplyCheats(void)
{
    if (!cheatCount)
    {
        return;
    }

    u64 titleId = 0;
    u32 pid = Cheat_GetCurrentPID(&titleId);

    if (!titleId)
    {
        cheatCount = 0;
        return;
    }

    if (titleId != cheatTitleInfo)
    {
        cheatCount = 0;
        return;
    }

    for (int i = 0; i < cheatCount; i++)
    {
        if (cheats[i]->active)
        {
            Cheat_MapMemoryAndApplyCheat(pid, cheats[i]);
        } 
    }
}

void RosalinaMenu_Cheats(void)
{
    u64 titleId = 0;
    u32 pid = Cheat_GetCurrentPID(&titleId);

    if (titleId != 0)
    {
        if (cheatTitleInfo != titleId || cheatCount == 0)
        {
            Cheat_LoadCheatsIntoMemory(titleId);
        }
    }

    Draw_Lock();
    Draw_ClearFramebuffer();
    Draw_FlushFramebuffer();
    Draw_Unlock();

    if (titleId == 0 || cheatCount == 0)
    {
        do
        {
            Draw_Lock();
            Draw_DrawString(10, 10, COLOR_TITLE, "Cheats");
            if (titleId == 0)
            {
                Draw_DrawString(10, 30, COLOR_WHITE, "No suitable title found");
            }
            else
            {
                Draw_DrawFormattedString(10, 30, COLOR_WHITE, "No cheats found for title %016llX", titleId);
            }

            Draw_FlushFramebuffer();
            Draw_Unlock();
        } while (!(waitInput() & BUTTON_B) && !terminationRequest);
    }
    else
    {
        s32 selected = 0, page = 0, pagePrev = 0;

        Result r = 0;
        do
        {
            Draw_Lock();
            if (page != pagePrev || R_FAILED(r))
            {
                Draw_ClearFramebuffer();
            }
            if (R_SUCCEEDED(r))
            {
                Draw_DrawFormattedString(10, 10, COLOR_TITLE, "Cheat list");

                for (s32 i = 0; i < CHEATS_PER_MENU_PAGE && page * CHEATS_PER_MENU_PAGE + i < cheatCount; i++)
                {
                    char buf[65] = { 0 };
                    s32 j = page * CHEATS_PER_MENU_PAGE + i;
                    const char * checkbox = (cheats[j]->active ? "(x) " : "( ) ");
                    const char * keyAct = (cheats[j]->hasKeyCode ? "*" : " ");
                    sprintf(buf, "%s%s%s", checkbox, keyAct, cheats[j]->name);

                    Draw_DrawString(30, 30 + i * SPACING_Y, cheats[j]->valid ? COLOR_WHITE : COLOR_RED, buf);
                    Draw_DrawCharacter(10, 30 + i * SPACING_Y, COLOR_TITLE, j == selected ? '>' : ' ');
                }
            }
            else
            {
                Draw_DrawFormattedString(10, 10, COLOR_TITLE, "ERROR: %08lx", r);
                Draw_DrawFormattedString(10, 30, COLOR_RED, failureReason);
            }
            Draw_FlushFramebuffer();
            Draw_Unlock();

            if (terminationRequest) break;

            u32 pressed;
            do
            {
                pressed = waitInputWithTimeout(50);
                if (pressed != 0) break;
            } while (pressed == 0 && !terminationRequest);

            if (pressed & BUTTON_B)
                break;
            else if ((pressed & BUTTON_A) && R_SUCCEEDED(r))
            {
                if (cheats[selected]->active)
                {
                    cheats[selected]->active = 0;
                }
                else
                {
                    r = Cheat_MapMemoryAndApplyCheat(pid, cheats[selected]);
                }
            }
            else if (pressed & BUTTON_DOWN)
                selected++;
            else if (pressed & BUTTON_UP)
                selected--;
            else if (pressed & BUTTON_LEFT)
                selected -= CHEATS_PER_MENU_PAGE;
            else if (pressed & BUTTON_RIGHT)
            {
                if (selected + CHEATS_PER_MENU_PAGE < cheatCount)
                    selected += CHEATS_PER_MENU_PAGE;
                else if ((cheatCount - 1) / CHEATS_PER_MENU_PAGE == page)
                    selected %= CHEATS_PER_MENU_PAGE;
                else selected = cheatCount - 1;
            }

            if (selected < 0)
                selected = cheatCount - 1;
            else if (selected >= cheatCount) selected = 0;

            pagePrev = page;
            page = selected / CHEATS_PER_MENU_PAGE;
        } while (!terminationRequest);
    }

}