#include "core/gba/gbaCheats.h"

#include <cstdio>
#include <cstring>

#include "core/base/file_util.h"
#include "core/base/message.h"
#include "core/gba/gba.h"
#include "core/gba/gbaInline.h"
#include "core/gba/gbaGlobals.h"

#if __STDC_WANT_SECURE_LIB__
#define snprintf sprintf_s
#define sscanf sscanf_s
#endif

/**
 * Gameshark code types: (based on AR v1.0)
 *
 * NNNNNNNN 001DC0DE - ID code for the game (game 4 character name) from ROM
 * DEADFACE XXXXXXXX - changes decryption seeds // Not supported by VBA.
 * 0AAAAAAA 000000YY - 8-bit constant write
 * 1AAAAAAA 0000YYYY - 16-bit constant write
 * 2AAAAAAA YYYYYYYY - 32-bit constant write
 * 30XXAAAA YYYYYYYY - 32bit Group Write, 8/16/32bit Sub/Add (depending on the XX value).
 * 6AAAAAAA Z000YYYY - 16-bit ROM Patch (address >> 1). Z selects the Rom Patching register.
 *                   - AR v1/2 hardware only supports Z=0.
 *                   - AR v3 hardware should support Z=0,1,2 or 3.
 * 8A1AAAAA 000000YY - 8-bit button write
 * 8A2AAAAA 0000YYYY - 16-bit button write
 * 8A4AAAAA YYYYYYYY - 32-bit button write // BUGGY ! Only writes 00000000 on the AR v1.0.
 * 80F00000 0000YYYY - button slow motion
 * DAAAAAAA 00Z0YYYY - Z = 0 : if 16-bit value at address != YYYY skip next line
 *                   - Z = 1 : if 16-bit value at address == YYYY skip next line
 *                   - Z = 2 : if 16-bit value at address > YYYY (Unsigned) skip next line
 *                   - Z = 3 : if 16-bit value at address < YYYY (Unsigned) skip next line
 * E0CCYYYY ZAAAAAAA - Z = 0 : if 16-bit value at address != YYYY skip CC lines
 *                   - Z = 1 : if 16-bit value at address == YYYY skip CC lines
 *                   - Z = 2 : if 16-bit value at address > YYYY (Unsigned) skip CC lines
 *                   - Z = 3 : if 16-bit value at address < YYYY (Unsigned) skip CC lines
 * FAAAAAAA 0000YYYY - Master code function
 *
 *
 *
 * CodeBreaker codes types: (based on the CBA clone "Cheatcode S" v1.1)
 *
 * 0000AAAA 000Y - Game CRC (Y are flags: 8 - CRC, 2 - DI)
 * 1AAAAAAA YYYY - Master Code function (store address at ((YYYY << 0x16)
 *                 + 0x08000100))
 * 2AAAAAAA YYYY - 16-bit or
 * 3AAAAAAA YYYY - 8-bit constant write
 * 4AAAAAAA YYYY - Slide code
 * XXXXCCCC IIII   (C is count and I is address increment, X is value incr.)
 * 5AAAAAAA CCCC - Super code (Write bytes to address, 2*CCCC is count)
 * BBBBBBBB BBBB
 * 6AAAAAAA YYYY - 16-bit and
 * 7AAAAAAA YYYY - if address contains 16-bit value enable next code
 * 8AAAAAAA YYYY - 16-bit constant write
 * 9AAAAAAA YYYY - change decryption (when first code only?)
 * AAAAAAAA YYYY - if address does not contain 16-bit value enable next code
 * BAAAAAAA YYYY - if 16-bit value at address  <= YYYY skip next code
 * CAAAAAAA YYYY - if 16-bit value at address  >= YYYY skip next code
 * D00000X0 YYYY - if button keys ... enable next code (else skip next code)
 * EAAAAAAA YYYY - increase 16/32bit value stored in address
 * FAAAAAAA YYYY - if 16-bit value at address AND YYYY = 0 then skip next code
 **/

#define UNKNOWN_CODE -1
#define INT_8_BIT_WRITE 0
#define INT_16_BIT_WRITE 1
#define INT_32_BIT_WRITE 2
#define GSA_16_BIT_ROM_PATCH 3
#define GSA_8_BIT_GS_WRITE 4
#define GSA_16_BIT_GS_WRITE 5
#define GSA_32_BIT_GS_WRITE 6
#define CBA_IF_KEYS_PRESSED 7
#define CBA_IF_TRUE 8
#define CBA_SLIDE_CODE 9
#define CBA_IF_FALSE 10
#define CBA_AND 11
#define GSA_8_BIT_GS_WRITE2 12
#define GSA_16_BIT_GS_WRITE2 13
#define GSA_32_BIT_GS_WRITE2 14
#define GSA_16_BIT_ROM_PATCH2C 15
#define GSA_8_BIT_SLIDE 16
#define GSA_16_BIT_SLIDE 17
#define GSA_32_BIT_SLIDE 18
#define GSA_8_BIT_IF_TRUE 19
#define GSA_32_BIT_IF_TRUE 20
#define GSA_8_BIT_IF_FALSE 21
#define GSA_32_BIT_IF_FALSE 22
#define GSA_8_BIT_FILL 23
#define GSA_16_BIT_FILL 24
#define GSA_8_BIT_IF_TRUE2 25
#define GSA_16_BIT_IF_TRUE2 26
#define GSA_32_BIT_IF_TRUE2 27
#define GSA_8_BIT_IF_FALSE2 28
#define GSA_16_BIT_IF_FALSE2 29
#define GSA_32_BIT_IF_FALSE2 30
#define GSA_SLOWDOWN 31
#define CBA_ADD 32
#define CBA_OR 33
#define CBA_LT 34
#define CBA_GT 35
#define CBA_SUPER 36
#define GSA_8_BIT_POINTER 37
#define GSA_16_BIT_POINTER 38
#define GSA_32_BIT_POINTER 39
#define GSA_8_BIT_ADD 40
#define GSA_16_BIT_ADD 41
#define GSA_32_BIT_ADD 42
#define GSA_8_BIT_IF_LOWER_U 43
#define GSA_16_BIT_IF_LOWER_U 44
#define GSA_32_BIT_IF_LOWER_U 45
#define GSA_8_BIT_IF_HIGHER_U 46
#define GSA_16_BIT_IF_HIGHER_U 47
#define GSA_32_BIT_IF_HIGHER_U 48
#define GSA_8_BIT_IF_AND 49
#define GSA_16_BIT_IF_AND 50
#define GSA_32_BIT_IF_AND 51
#define GSA_8_BIT_IF_LOWER_U2 52
#define GSA_16_BIT_IF_LOWER_U2 53
#define GSA_32_BIT_IF_LOWER_U2 54
#define GSA_8_BIT_IF_HIGHER_U2 55
#define GSA_16_BIT_IF_HIGHER_U2 56
#define GSA_32_BIT_IF_HIGHER_U2 57
#define GSA_8_BIT_IF_AND2 58
#define GSA_16_BIT_IF_AND2 59
#define GSA_32_BIT_IF_AND2 60
#define GSA_ALWAYS 61
#define GSA_ALWAYS2 62
#define GSA_8_BIT_IF_LOWER_S 63
#define GSA_16_BIT_IF_LOWER_S 64
#define GSA_32_BIT_IF_LOWER_S 65
#define GSA_8_BIT_IF_HIGHER_S 66
#define GSA_16_BIT_IF_HIGHER_S 67
#define GSA_32_BIT_IF_HIGHER_S 68
#define GSA_8_BIT_IF_LOWER_S2 69
#define GSA_16_BIT_IF_LOWER_S2 70
#define GSA_32_BIT_IF_LOWER_S2 71
#define GSA_8_BIT_IF_HIGHER_S2 72
#define GSA_16_BIT_IF_HIGHER_S2 73
#define GSA_32_BIT_IF_HIGHER_S2 74
#define GSA_16_BIT_WRITE_IOREGS 75
#define GSA_32_BIT_WRITE_IOREGS 76
#define GSA_CODES_ON 77
#define GSA_8_BIT_IF_TRUE3 78
#define GSA_16_BIT_IF_TRUE3 79
#define GSA_32_BIT_IF_TRUE3 80
#define GSA_8_BIT_IF_FALSE3 81
#define GSA_16_BIT_IF_FALSE3 82
#define GSA_32_BIT_IF_FALSE3 83
#define GSA_8_BIT_IF_LOWER_S3 84
#define GSA_16_BIT_IF_LOWER_S3 85
#define GSA_32_BIT_IF_LOWER_S3 86
#define GSA_8_BIT_IF_HIGHER_S3 87
#define GSA_16_BIT_IF_HIGHER_S3 88
#define GSA_32_BIT_IF_HIGHER_S3 89
#define GSA_8_BIT_IF_LOWER_U3 90
#define GSA_16_BIT_IF_LOWER_U3 91
#define GSA_32_BIT_IF_LOWER_U3 92
#define GSA_8_BIT_IF_HIGHER_U3 93
#define GSA_16_BIT_IF_HIGHER_U3 94
#define GSA_32_BIT_IF_HIGHER_U3 95
#define GSA_8_BIT_IF_AND3 96
#define GSA_16_BIT_IF_AND3 97
#define GSA_32_BIT_IF_AND3 98
#define GSA_ALWAYS3 99
#define GSA_16_BIT_ROM_PATCH2D 100
#define GSA_16_BIT_ROM_PATCH2E 101
#define GSA_16_BIT_ROM_PATCH2F 102
#define GSA_GROUP_WRITE 103
#define GSA_32_BIT_ADD2 104
#define GSA_32_BIT_SUB2 105
#define GSA_16_BIT_IF_LOWER_OR_EQ_U 106
#define GSA_16_BIT_IF_HIGHER_OR_EQ_U 107
#define GSA_16_BIT_MIF_TRUE 108
#define GSA_16_BIT_MIF_FALSE 109
#define GSA_16_BIT_MIF_LOWER_OR_EQ_U 110
#define GSA_16_BIT_MIF_HIGHER_OR_EQ_U 111
#define MASTER_CODE 112
#define CHEATS_16_BIT_WRITE 114
#define CHEATS_32_BIT_WRITE 115

CheatsData cheatsList[MAX_CHEATS];
int cheatsNumber = 0;
uint32_t rompatch2addr[4];
uint16_t rompatch2val[4];
uint16_t rompatch2oldval[4];

uint8_t cheatsCBASeedBuffer[0x30];
uint32_t cheatsCBASeed[4];
uint32_t cheatsCBATemporaryValue = 0;
uint16_t cheatsCBATable[256];
bool cheatsCBATableGenerated = false;
uint16_t super = 0;
extern uint32_t mastercode;

uint8_t cheatsCBACurrentSeed[12] = {
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00
};

uint32_t seeds_v1[4];
uint32_t seeds_v3[4];

uint32_t seed_gen(uint8_t upper, uint8_t seed, uint8_t* deadtable1, uint8_t* deadtable2);

//seed tables for AR v1
uint8_t v1_deadtable1[256] = {
    0x31, 0x1C, 0x23, 0xE5, 0x89, 0x8E, 0xA1, 0x37, 0x74, 0x6D, 0x67, 0xFC, 0x1F, 0xC0, 0xB1, 0x94,
    0x3B, 0x05, 0x56, 0x86, 0x00, 0x24, 0xF0, 0x17, 0x72, 0xA2, 0x3D, 0x1B, 0xE3, 0x17, 0xC5, 0x0B,
    0xB9, 0xE2, 0xBD, 0x58, 0x71, 0x1B, 0x2C, 0xFF, 0xE4, 0xC9, 0x4C, 0x5E, 0xC9, 0x55, 0x33, 0x45,
    0x7C, 0x3F, 0xB2, 0x51, 0xFE, 0x10, 0x7E, 0x75, 0x3C, 0x90, 0x8D, 0xDA, 0x94, 0x38, 0xC3, 0xE9,
    0x95, 0xEA, 0xCE, 0xA6, 0x06, 0xE0, 0x4F, 0x3F, 0x2A, 0xE3, 0x3A, 0xE4, 0x43, 0xBD, 0x7F, 0xDA,
    0x55, 0xF0, 0xEA, 0xCB, 0x2C, 0xA8, 0x47, 0x61, 0xA0, 0xEF, 0xCB, 0x13, 0x18, 0x20, 0xAF, 0x3E,
    0x4D, 0x9E, 0x1E, 0x77, 0x51, 0xC5, 0x51, 0x20, 0xCF, 0x21, 0xF9, 0x39, 0x94, 0xDE, 0xDD, 0x79,
    0x4E, 0x80, 0xC4, 0x9D, 0x94, 0xD5, 0x95, 0x01, 0x27, 0x27, 0xBD, 0x6D, 0x78, 0xB5, 0xD1, 0x31,
    0x6A, 0x65, 0x74, 0x74, 0x58, 0xB3, 0x7C, 0xC9, 0x5A, 0xED, 0x50, 0x03, 0xC4, 0xA2, 0x94, 0x4B,
    0xF0, 0x58, 0x09, 0x6F, 0x3E, 0x7D, 0xAE, 0x7D, 0x58, 0xA0, 0x2C, 0x91, 0xBB, 0xE1, 0x70, 0xEB,
    0x73, 0xA6, 0x9A, 0x44, 0x25, 0x90, 0x16, 0x62, 0x53, 0xAE, 0x08, 0xEB, 0xDC, 0xF0, 0xEE, 0x77,
    0xC2, 0xDE, 0x81, 0xE8, 0x30, 0x89, 0xDB, 0xFE, 0xBC, 0xC2, 0xDF, 0x26, 0xE9, 0x8B, 0xD6, 0x93,
    0xF0, 0xCB, 0x56, 0x90, 0xC0, 0x46, 0x68, 0x15, 0x43, 0xCB, 0xE9, 0x98, 0xE3, 0xAF, 0x31, 0x25,
    0x4D, 0x7B, 0xF3, 0xB1, 0x74, 0xE2, 0x64, 0xAC, 0xD9, 0xF6, 0xA0, 0xD5, 0x0B, 0x9B, 0x49, 0x52,
    0x69, 0x3B, 0x71, 0x00, 0x2F, 0xBB, 0xBA, 0x08, 0xB1, 0xAE, 0xBB, 0xB3, 0xE1, 0xC9, 0xA6, 0x7F,
    0x17, 0x97, 0x28, 0x72, 0x12, 0x6E, 0x91, 0xAE, 0x3A, 0xA2, 0x35, 0x46, 0x27, 0xF8, 0x12, 0x50
};
uint8_t v1_deadtable2[256] = {
    0xD8, 0x65, 0x04, 0xC2, 0x65, 0xD5, 0xB0, 0x0C, 0xDF, 0x9D, 0xF0, 0xC3, 0x9A, 0x17, 0xC9, 0xA6,
    0xE1, 0xAC, 0x0D, 0x14, 0x2F, 0x3C, 0x2C, 0x87, 0xA2, 0xBF, 0x4D, 0x5F, 0xAC, 0x2D, 0x9D, 0xE1,
    0x0C, 0x9C, 0xE7, 0x7F, 0xFC, 0xA8, 0x66, 0x59, 0xAC, 0x18, 0xD7, 0x05, 0xF0, 0xBF, 0xD1, 0x8B,
    0x35, 0x9F, 0x59, 0xB4, 0xBA, 0x55, 0xB2, 0x85, 0xFD, 0xB1, 0x72, 0x06, 0x73, 0xA4, 0xDB, 0x48,
    0x7B, 0x5F, 0x67, 0xA5, 0x95, 0xB9, 0xA5, 0x4A, 0xCF, 0xD1, 0x44, 0xF3, 0x81, 0xF5, 0x6D, 0xF6,
    0x3A, 0xC3, 0x57, 0x83, 0xFA, 0x8E, 0x15, 0x2A, 0xA2, 0x04, 0xB2, 0x9D, 0xA8, 0x0D, 0x7F, 0xB8,
    0x0F, 0xF6, 0xAC, 0xBE, 0x97, 0xCE, 0x16, 0xE6, 0x31, 0x10, 0x60, 0x16, 0xB5, 0x83, 0x45, 0xEE,
    0xD7, 0x5F, 0x2C, 0x08, 0x58, 0xB1, 0xFD, 0x7E, 0x79, 0x00, 0x34, 0xAD, 0xB5, 0x31, 0x34, 0x39,
    0xAF, 0xA8, 0xDD, 0x52, 0x6A, 0xB0, 0x60, 0x35, 0xB8, 0x1D, 0x52, 0xF5, 0xF5, 0x30, 0x00, 0x7B,
    0xF4, 0xBA, 0x03, 0xCB, 0x3A, 0x84, 0x14, 0x8A, 0x6A, 0xEF, 0x21, 0xBD, 0x01, 0xD8, 0xA0, 0xD4,
    0x43, 0xBE, 0x23, 0xE7, 0x76, 0x27, 0x2C, 0x3F, 0x4D, 0x3F, 0x43, 0x18, 0xA7, 0xC3, 0x47, 0xA5,
    0x7A, 0x1D, 0x02, 0x55, 0x09, 0xD1, 0xFF, 0x55, 0x5E, 0x17, 0xA0, 0x56, 0xF4, 0xC9, 0x6B, 0x90,
    0xB4, 0x80, 0xA5, 0x07, 0x22, 0xFB, 0x22, 0x0D, 0xD9, 0xC0, 0x5B, 0x08, 0x35, 0x05, 0xC1, 0x75,
    0x4F, 0xD0, 0x51, 0x2D, 0x2E, 0x5E, 0x69, 0xE7, 0x3B, 0xC2, 0xDA, 0xFF, 0xF6, 0xCE, 0x3E, 0x76,
    0xE8, 0x36, 0x8C, 0x39, 0xD8, 0xF3, 0xE9, 0xA6, 0x42, 0xE6, 0xC1, 0x4C, 0x05, 0xBE, 0x17, 0xF2,
    0x5C, 0x1B, 0x19, 0xDB, 0x0F, 0xF3, 0xF8, 0x49, 0xEB, 0x36, 0xF6, 0x40, 0x6F, 0xAD, 0xC1, 0x8C
};

//seed tables for AR v3
uint8_t v3_deadtable1[256] = {
    0xD0, 0xFF, 0xBA, 0xE5, 0xC1, 0xC7, 0xDB, 0x5B, 0x16, 0xE3, 0x6E, 0x26, 0x62, 0x31, 0x2E, 0x2A,
    0xD1, 0xBB, 0x4A, 0xE6, 0xAE, 0x2F, 0x0A, 0x90, 0x29, 0x90, 0xB6, 0x67, 0x58, 0x2A, 0xB4, 0x45,
    0x7B, 0xCB, 0xF0, 0x73, 0x84, 0x30, 0x81, 0xC2, 0xD7, 0xBE, 0x89, 0xD7, 0x4E, 0x73, 0x5C, 0xC7,
    0x80, 0x1B, 0xE5, 0xE4, 0x43, 0xC7, 0x46, 0xD6, 0x6F, 0x7B, 0xBF, 0xED, 0xE5, 0x27, 0xD1, 0xB5,
    0xD0, 0xD8, 0xA3, 0xCB, 0x2B, 0x30, 0xA4, 0xF0, 0x84, 0x14, 0x72, 0x5C, 0xFF, 0xA4, 0xFB, 0x54,
    0x9D, 0x70, 0xE2, 0xFF, 0xBE, 0xE8, 0x24, 0x76, 0xE5, 0x15, 0xFB, 0x1A, 0xBC, 0x87, 0x02, 0x2A,
    0x58, 0x8F, 0x9A, 0x95, 0xBD, 0xAE, 0x8D, 0x0C, 0xA5, 0x4C, 0xF2, 0x5C, 0x7D, 0xAD, 0x51, 0xFB,
    0xB1, 0x22, 0x07, 0xE0, 0x29, 0x7C, 0xEB, 0x98, 0x14, 0xC6, 0x31, 0x97, 0xE4, 0x34, 0x8F, 0xCC,
    0x99, 0x56, 0x9F, 0x78, 0x43, 0x91, 0x85, 0x3F, 0xC2, 0xD0, 0xD1, 0x80, 0xD1, 0x77, 0xA7, 0xE2,
    0x43, 0x99, 0x1D, 0x2F, 0x8B, 0x6A, 0xE4, 0x66, 0x82, 0xF7, 0x2B, 0x0B, 0x65, 0x14, 0xC0, 0xC2,
    0x1D, 0x96, 0x78, 0x1C, 0xC4, 0xC3, 0xD2, 0xB1, 0x64, 0x07, 0xD7, 0x6F, 0x02, 0xE9, 0x44, 0x31,
    0xDB, 0x3C, 0xEB, 0x93, 0xED, 0x9A, 0x57, 0x05, 0xB9, 0x0E, 0xAF, 0x1F, 0x48, 0x11, 0xDC, 0x35,
    0x6C, 0xB8, 0xEE, 0x2A, 0x48, 0x2B, 0xBC, 0x89, 0x12, 0x59, 0xCB, 0xD1, 0x18, 0xEA, 0x72, 0x11,
    0x01, 0x75, 0x3B, 0xB5, 0x56, 0xF4, 0x8B, 0xA0, 0x41, 0x75, 0x86, 0x7B, 0x94, 0x12, 0x2D, 0x4C,
    0x0C, 0x22, 0xC9, 0x4A, 0xD8, 0xB1, 0x8D, 0xF0, 0x55, 0x2E, 0x77, 0x50, 0x1C, 0x64, 0x77, 0xAA,
    0x3E, 0xAC, 0xD3, 0x3D, 0xCE, 0x60, 0xCA, 0x5D, 0xA0, 0x92, 0x78, 0xC6, 0x51, 0xFE, 0xF9, 0x30
};
uint8_t v3_deadtable2[256] = {
    0xAA, 0xAF, 0xF0, 0x72, 0x90, 0xF7, 0x71, 0x27, 0x06, 0x11, 0xEB, 0x9C, 0x37, 0x12, 0x72, 0xAA,
    0x65, 0xBC, 0x0D, 0x4A, 0x76, 0xF6, 0x5C, 0xAA, 0xB0, 0x7A, 0x7D, 0x81, 0xC1, 0xCE, 0x2F, 0x9F,
    0x02, 0x75, 0x38, 0xC8, 0xFC, 0x66, 0x05, 0xC2, 0x2C, 0xBD, 0x91, 0xAD, 0x03, 0xB1, 0x88, 0x93,
    0x31, 0xC6, 0xAB, 0x40, 0x23, 0x43, 0x76, 0x54, 0xCA, 0xE7, 0x00, 0x96, 0x9F, 0xD8, 0x24, 0x8B,
    0xE4, 0xDC, 0xDE, 0x48, 0x2C, 0xCB, 0xF7, 0x84, 0x1D, 0x45, 0xE5, 0xF1, 0x75, 0xA0, 0xED, 0xCD,
    0x4B, 0x24, 0x8A, 0xB3, 0x98, 0x7B, 0x12, 0xB8, 0xF5, 0x63, 0x97, 0xB3, 0xA6, 0xA6, 0x0B, 0xDC,
    0xD8, 0x4C, 0xA8, 0x99, 0x27, 0x0F, 0x8F, 0x94, 0x63, 0x0F, 0xB0, 0x11, 0x94, 0xC7, 0xE9, 0x7F,
    0x3B, 0x40, 0x72, 0x4C, 0xDB, 0x84, 0x78, 0xFE, 0xB8, 0x56, 0x08, 0x80, 0xDF, 0x20, 0x2F, 0xB9,
    0x66, 0x2D, 0x60, 0x63, 0xF5, 0x18, 0x15, 0x1B, 0x86, 0x85, 0xB9, 0xB4, 0x68, 0x0E, 0xC6, 0xD1,
    0x8A, 0x81, 0x2B, 0xB3, 0xF6, 0x48, 0xF0, 0x4F, 0x9C, 0x28, 0x1C, 0xA4, 0x51, 0x2F, 0xD7, 0x4B,
    0x17, 0xE7, 0xCC, 0x50, 0x9F, 0xD0, 0xD1, 0x40, 0x0C, 0x0D, 0xCA, 0x83, 0xFA, 0x5E, 0xCA, 0xEC,
    0xBF, 0x4E, 0x7C, 0x8F, 0xF0, 0xAE, 0xC2, 0xD3, 0x28, 0x41, 0x9B, 0xC8, 0x04, 0xB9, 0x4A, 0xBA,
    0x72, 0xE2, 0xB5, 0x06, 0x2C, 0x1E, 0x0B, 0x2C, 0x7F, 0x11, 0xA9, 0x26, 0x51, 0x9D, 0x3F, 0xF8,
    0x62, 0x11, 0x2E, 0x89, 0xD2, 0x9D, 0x35, 0xB1, 0xE4, 0x0A, 0x4D, 0x93, 0x01, 0xA7, 0xD1, 0x2D,
    0x00, 0x87, 0xE2, 0x2D, 0xA4, 0xE9, 0x0A, 0x06, 0x66, 0xF8, 0x1F, 0x44, 0x75, 0xB5, 0x6B, 0x1C,
    0xFC, 0x31, 0x09, 0x48, 0xA3, 0xFF, 0x92, 0x12, 0x58, 0xE9, 0xFA, 0xAE, 0x4F, 0xE2, 0xB4, 0xCC
};

#define debuggerReadMemory(addr) \
    READ32LE((&map[(addr) >> 24].address[(addr)&map[(addr) >> 24].mask]))

#define debuggerReadHalfWord(addr) \
    READ16LE((&map[(addr) >> 24].address[(addr)&map[(addr) >> 24].mask]))

#define debuggerReadByte(addr) \
    map[(addr) >> 24].address[(addr)&map[(addr) >> 24].mask]

#define debuggerWriteMemory(addr, value) \
    WRITE32LE(&map[(addr) >> 24].address[(addr)&map[(addr) >> 24].mask], value)

#define debuggerWriteHalfWord(addr, value) \
    WRITE16LE(&map[(addr) >> 24].address[(addr)&map[(addr) >> 24].mask], value)

#define debuggerWriteByte(addr, value) \
    map[(addr) >> 24].address[(addr)&map[(addr) >> 24].mask] = (value)

#define CHEAT_IS_HEX(a) (((a) >= 'A' && (a) <= 'F') || ((a) >= '0' && (a) <= '9'))

#define CHEAT_PATCH_ROM_16BIT(a, v) \
    WRITE16LE(((uint16_t*)&g_rom[(a)&0x1ffffff]), v);

#define CHEAT_PATCH_ROM_32BIT(a, v) \
    WRITE32LE(((uint32_t*)&g_rom[(a)&0x1ffffff]), v);

static bool isMultilineWithData(int i)
{
    // we consider it a multiline code if it has more than one line of data
    // otherwise, it can still be considered a single code
    // (Only CBA codes can be true multilines !!!)
    if (i < cheatsNumber && i >= 0)
        switch (cheatsList[i].size) {
        case INT_8_BIT_WRITE:
        case INT_16_BIT_WRITE:
        case INT_32_BIT_WRITE:
        case GSA_16_BIT_ROM_PATCH:
        case GSA_8_BIT_GS_WRITE:
        case GSA_16_BIT_GS_WRITE:
        case GSA_32_BIT_GS_WRITE:
        case CBA_AND:
        case CBA_IF_KEYS_PRESSED:
        case CBA_IF_TRUE:
        case CBA_IF_FALSE:
        case GSA_8_BIT_IF_TRUE:
        case GSA_32_BIT_IF_TRUE:
        case GSA_8_BIT_IF_FALSE:
        case GSA_32_BIT_IF_FALSE:
        case GSA_8_BIT_FILL:
        case GSA_16_BIT_FILL:
        case GSA_8_BIT_IF_TRUE2:
        case GSA_16_BIT_IF_TRUE2:
        case GSA_32_BIT_IF_TRUE2:
        case GSA_8_BIT_IF_FALSE2:
        case GSA_16_BIT_IF_FALSE2:
        case GSA_32_BIT_IF_FALSE2:
        case GSA_SLOWDOWN:
        case CBA_ADD:
        case CBA_OR:
        case CBA_LT:
        case CBA_GT:
        case GSA_8_BIT_POINTER:
        case GSA_16_BIT_POINTER:
        case GSA_32_BIT_POINTER:
        case GSA_8_BIT_ADD:
        case GSA_16_BIT_ADD:
        case GSA_32_BIT_ADD:
        case GSA_8_BIT_IF_LOWER_U:
        case GSA_16_BIT_IF_LOWER_U:
        case GSA_32_BIT_IF_LOWER_U:
        case GSA_8_BIT_IF_HIGHER_U:
        case GSA_16_BIT_IF_HIGHER_U:
        case GSA_32_BIT_IF_HIGHER_U:
        case GSA_8_BIT_IF_AND:
        case GSA_16_BIT_IF_AND:
        case GSA_32_BIT_IF_AND:
        case GSA_8_BIT_IF_LOWER_U2:
        case GSA_16_BIT_IF_LOWER_U2:
        case GSA_32_BIT_IF_LOWER_U2:
        case GSA_8_BIT_IF_HIGHER_U2:
        case GSA_16_BIT_IF_HIGHER_U2:
        case GSA_32_BIT_IF_HIGHER_U2:
        case GSA_8_BIT_IF_AND2:
        case GSA_16_BIT_IF_AND2:
        case GSA_32_BIT_IF_AND2:
        case GSA_ALWAYS:
        case GSA_ALWAYS2:
        case GSA_8_BIT_IF_LOWER_S:
        case GSA_16_BIT_IF_LOWER_S:
        case GSA_32_BIT_IF_LOWER_S:
        case GSA_8_BIT_IF_HIGHER_S:
        case GSA_16_BIT_IF_HIGHER_S:
        case GSA_32_BIT_IF_HIGHER_S:
        case GSA_8_BIT_IF_LOWER_S2:
        case GSA_16_BIT_IF_LOWER_S2:
        case GSA_32_BIT_IF_LOWER_S2:
        case GSA_8_BIT_IF_HIGHER_S2:
        case GSA_16_BIT_IF_HIGHER_S2:
        case GSA_32_BIT_IF_HIGHER_S2:
        case GSA_16_BIT_WRITE_IOREGS:
        case GSA_32_BIT_WRITE_IOREGS:
        case GSA_CODES_ON:
        case GSA_8_BIT_IF_TRUE3:
        case GSA_16_BIT_IF_TRUE3:
        case GSA_32_BIT_IF_TRUE3:
        case GSA_8_BIT_IF_FALSE3:
        case GSA_16_BIT_IF_FALSE3:
        case GSA_32_BIT_IF_FALSE3:
        case GSA_8_BIT_IF_LOWER_S3:
        case GSA_16_BIT_IF_LOWER_S3:
        case GSA_32_BIT_IF_LOWER_S3:
        case GSA_8_BIT_IF_HIGHER_S3:
        case GSA_16_BIT_IF_HIGHER_S3:
        case GSA_32_BIT_IF_HIGHER_S3:
        case GSA_8_BIT_IF_LOWER_U3:
        case GSA_16_BIT_IF_LOWER_U3:
        case GSA_32_BIT_IF_LOWER_U3:
        case GSA_8_BIT_IF_HIGHER_U3:
        case GSA_16_BIT_IF_HIGHER_U3:
        case GSA_32_BIT_IF_HIGHER_U3:
        case GSA_8_BIT_IF_AND3:
        case GSA_16_BIT_IF_AND3:
        case GSA_32_BIT_IF_AND3:
        case GSA_ALWAYS3:
        case GSA_8_BIT_GS_WRITE2:
        case GSA_16_BIT_GS_WRITE2:
        case GSA_32_BIT_GS_WRITE2:
        case GSA_16_BIT_ROM_PATCH2C:
        case GSA_16_BIT_ROM_PATCH2D:
        case GSA_16_BIT_ROM_PATCH2E:
        case GSA_16_BIT_ROM_PATCH2F:
        case GSA_8_BIT_SLIDE:
        case GSA_16_BIT_SLIDE:
        case GSA_32_BIT_SLIDE:
        case GSA_GROUP_WRITE:
        case GSA_32_BIT_ADD2:
        case GSA_32_BIT_SUB2:
        case GSA_16_BIT_IF_LOWER_OR_EQ_U:
        case GSA_16_BIT_IF_HIGHER_OR_EQ_U:
        case GSA_16_BIT_MIF_TRUE:
        case GSA_16_BIT_MIF_FALSE:
        case GSA_16_BIT_MIF_LOWER_OR_EQ_U:
        case GSA_16_BIT_MIF_HIGHER_OR_EQ_U:
        case MASTER_CODE:
        case CHEATS_16_BIT_WRITE:
        case CHEATS_32_BIT_WRITE:
            return false;
        // the codes below have two lines of data
        case CBA_SLIDE_CODE:
        case CBA_SUPER:
            return true;
        }
    return false;
}

static int getCodeLength(int num)
{
    if (num >= cheatsNumber || num < 0)
        return 1;

    // this is for all the codes that are true multiline
    switch (cheatsList[num].size) {
    case INT_8_BIT_WRITE:
    case INT_16_BIT_WRITE:
    case INT_32_BIT_WRITE:
    case GSA_16_BIT_ROM_PATCH:
    case GSA_8_BIT_GS_WRITE:
    case GSA_16_BIT_GS_WRITE:
    case GSA_32_BIT_GS_WRITE:
    case CBA_AND:
    case GSA_8_BIT_FILL:
    case GSA_16_BIT_FILL:
    case GSA_SLOWDOWN:
    case CBA_ADD:
    case CBA_OR:
    case GSA_8_BIT_POINTER:
    case GSA_16_BIT_POINTER:
    case GSA_32_BIT_POINTER:
    case GSA_8_BIT_ADD:
    case GSA_16_BIT_ADD:
    case GSA_32_BIT_ADD:
    case GSA_CODES_ON:
    case GSA_8_BIT_IF_TRUE3:
    case GSA_16_BIT_IF_TRUE3:
    case GSA_32_BIT_IF_TRUE3:
    case GSA_8_BIT_IF_FALSE3:
    case GSA_16_BIT_IF_FALSE3:
    case GSA_32_BIT_IF_FALSE3:
    case GSA_8_BIT_IF_LOWER_S3:
    case GSA_16_BIT_IF_LOWER_S3:
    case GSA_32_BIT_IF_LOWER_S3:
    case GSA_8_BIT_IF_HIGHER_S3:
    case GSA_16_BIT_IF_HIGHER_S3:
    case GSA_32_BIT_IF_HIGHER_S3:
    case GSA_8_BIT_IF_LOWER_U3:
    case GSA_16_BIT_IF_LOWER_U3:
    case GSA_32_BIT_IF_LOWER_U3:
    case GSA_8_BIT_IF_HIGHER_U3:
    case GSA_16_BIT_IF_HIGHER_U3:
    case GSA_32_BIT_IF_HIGHER_U3:
    case GSA_8_BIT_IF_AND3:
    case GSA_16_BIT_IF_AND3:
    case GSA_32_BIT_IF_AND3:
    case GSA_8_BIT_IF_LOWER_U:
    case GSA_16_BIT_IF_LOWER_U:
    case GSA_32_BIT_IF_LOWER_U:
    case GSA_8_BIT_IF_HIGHER_U:
    case GSA_16_BIT_IF_HIGHER_U:
    case GSA_32_BIT_IF_HIGHER_U:
    case GSA_8_BIT_IF_AND:
    case GSA_16_BIT_IF_AND:
    case GSA_32_BIT_IF_AND:
    case GSA_ALWAYS:
    case GSA_8_BIT_IF_LOWER_S:
    case GSA_16_BIT_IF_LOWER_S:
    case GSA_32_BIT_IF_LOWER_S:
    case GSA_8_BIT_IF_HIGHER_S:
    case GSA_16_BIT_IF_HIGHER_S:
    case GSA_32_BIT_IF_HIGHER_S:
    case GSA_16_BIT_WRITE_IOREGS:
    case GSA_32_BIT_WRITE_IOREGS:
    case GSA_8_BIT_GS_WRITE2:
    case GSA_16_BIT_GS_WRITE2:
    case GSA_32_BIT_GS_WRITE2:
    case GSA_16_BIT_ROM_PATCH2C:
    case GSA_16_BIT_ROM_PATCH2D:
    case GSA_16_BIT_ROM_PATCH2E:
    case GSA_16_BIT_ROM_PATCH2F:
    case GSA_8_BIT_SLIDE:
    case GSA_16_BIT_SLIDE:
    case GSA_32_BIT_SLIDE:
    case GSA_8_BIT_IF_TRUE:
    case GSA_32_BIT_IF_TRUE:
    case GSA_8_BIT_IF_FALSE:
    case GSA_32_BIT_IF_FALSE:
    case CBA_LT:
    case CBA_GT:
    case CBA_IF_TRUE:
    case CBA_IF_FALSE:
    case GSA_8_BIT_IF_TRUE2:
    case GSA_16_BIT_IF_TRUE2:
    case GSA_32_BIT_IF_TRUE2:
    case GSA_8_BIT_IF_FALSE2:
    case GSA_16_BIT_IF_FALSE2:
    case GSA_32_BIT_IF_FALSE2:
    case GSA_8_BIT_IF_LOWER_U2:
    case GSA_16_BIT_IF_LOWER_U2:
    case GSA_32_BIT_IF_LOWER_U2:
    case GSA_8_BIT_IF_HIGHER_U2:
    case GSA_16_BIT_IF_HIGHER_U2:
    case GSA_32_BIT_IF_HIGHER_U2:
    case GSA_8_BIT_IF_AND2:
    case GSA_16_BIT_IF_AND2:
    case GSA_32_BIT_IF_AND2:
    case GSA_ALWAYS2:
    case GSA_8_BIT_IF_LOWER_S2:
    case GSA_16_BIT_IF_LOWER_S2:
    case GSA_32_BIT_IF_LOWER_S2:
    case GSA_8_BIT_IF_HIGHER_S2:
    case GSA_16_BIT_IF_HIGHER_S2:
    case GSA_32_BIT_IF_HIGHER_S2:
    case GSA_GROUP_WRITE:
    case GSA_32_BIT_ADD2:
    case GSA_32_BIT_SUB2:
    case GSA_16_BIT_IF_LOWER_OR_EQ_U:
    case GSA_16_BIT_IF_HIGHER_OR_EQ_U:
    case GSA_16_BIT_MIF_TRUE:
    case GSA_16_BIT_MIF_FALSE:
    case GSA_16_BIT_MIF_LOWER_OR_EQ_U:
    case GSA_16_BIT_MIF_HIGHER_OR_EQ_U:
    case MASTER_CODE:
    case CHEATS_16_BIT_WRITE:
    case CHEATS_32_BIT_WRITE:
    case UNKNOWN_CODE:
        return 1;
    case CBA_IF_KEYS_PRESSED:
    case CBA_SLIDE_CODE:
        return 2;
    case CBA_SUPER:
        return ((((cheatsList[num].value - 1) & 0xFFFF) / 3) + 1);
    }
    return 1;
}

int cheatsCheckKeys(uint32_t keys, uint32_t extended)
{
    bool onoff = true;
    int ticks = 0;
    int i;
    mastercode = 0;

    for (i = 0; i < 4; i++)
        if (rompatch2addr[i] != 0) {
            CHEAT_PATCH_ROM_16BIT(rompatch2addr[i], rompatch2oldval[i]);
            rompatch2addr[i] = 0;
        }

    for (i = 0; i < cheatsNumber; i++) {
        if (!cheatsList[i].enabled) {
            // make sure we skip other lines in this code
            i += getCodeLength(i) - 1;
            continue;
        }
        switch (cheatsList[i].size) {
        case GSA_CODES_ON:
            onoff = true;
            break;
        case GSA_SLOWDOWN:
            // check if button was pressed and released, if so toggle our state
            if ((cheatsList[i].status & 4) && !(extended & 4))
                cheatsList[i].status ^= 1;
            if (extended & 4)
                cheatsList[i].status |= 4;
            else
                cheatsList[i].status &= ~4;

            if (cheatsList[i].status & 1)
                ticks += ((cheatsList[i].value & 0xFFFF) * 7);
            break;
        case GSA_8_BIT_SLIDE:
            i++;
            if (i < cheatsNumber) {
                uint32_t addr = cheatsList[i - 1].value;
                uint8_t value = DowncastU8(cheatsList[i].rawaddress);
                const uint8_t vinc = DowncastU8(cheatsList[i].value >> 24);
                uint8_t count = DowncastU8(cheatsList[i].value >> 16);
                const uint16_t ainc = DowncastU16(cheatsList[i].value);
                while (count > 0) {
                    CPUWriteByte(addr, value);
                    value += vinc;
                    addr += ainc;
                    count--;
                }
            }
            break;
        case GSA_16_BIT_SLIDE:
            i++;
            if (i < cheatsNumber) {
                uint32_t addr = cheatsList[i - 1].value;
                uint16_t value = DowncastU16(cheatsList[i].rawaddress);
                const uint16_t vinc = DowncastU16(cheatsList[i].value >> 24);
                uint8_t count = DowncastU8(cheatsList[i].value >> 16);
                const uint16_t ainc = DowncastU16(cheatsList[i].value & 0xffff) * 2;
                while (count > 0) {
                    CPUWriteHalfWord(addr, value);
                    value += vinc;
                    addr += ainc;
                    count--;
                }
            }
            break;
        case GSA_32_BIT_SLIDE:
            i++;
            if (i < cheatsNumber) {
                uint32_t addr = cheatsList[i - 1].value;
                uint32_t value = cheatsList[i].rawaddress;
                int vinc = (cheatsList[i].value >> 24) & 255;
                int count = (cheatsList[i].value >> 16) & 255;
                int ainc = (cheatsList[i].value & 0xffff) * 4;
                while (count > 0) {
                    CPUWriteMemory(addr, value);
                    value += vinc;
                    addr += ainc;
                    count--;
                }
            }
            break;
        case GSA_8_BIT_GS_WRITE2:
            i++;
            if (i < cheatsNumber) {
                if (extended & 4) {
                    CPUWriteByte(cheatsList[i - 1].value, (uint8_t)cheatsList[i].address);
                }
            }
            break;
        case GSA_16_BIT_GS_WRITE2:
            i++;
            if (i < cheatsNumber) {
                if (extended & 4) {
                    CPUWriteHalfWord(cheatsList[i - 1].value, (uint16_t)cheatsList[i].address);
                }
            }
            break;
        case GSA_32_BIT_GS_WRITE2:
            i++;
            if (i < cheatsNumber) {
                if (extended & 4) {
                    CPUWriteMemory(cheatsList[i - 1].value, cheatsList[i].address);
                }
            }
            break;
        case GSA_16_BIT_ROM_PATCH:
            if ((cheatsList[i].status & 1) == 0) {
                if (CPUReadHalfWord(cheatsList[i].address) != cheatsList[i].value) {
                    cheatsList[i].oldValue = CPUReadHalfWord(cheatsList[i].address);
                    cheatsList[i].status |= 1;
                    CHEAT_PATCH_ROM_16BIT(cheatsList[i].address, (uint16_t)cheatsList[i].value);
                }
            }
            break;
        case GSA_16_BIT_ROM_PATCH2C:
            i++;
            if (i < cheatsNumber) {
                rompatch2addr[0] = ((cheatsList[i - 1].value & 0x00FFFFFF) << 1) + 0x8000000;
                rompatch2oldval[0] = (uint16_t)CPUReadHalfWord(rompatch2addr[0]);
                rompatch2val[0] = cheatsList[i].rawaddress & 0xFFFF;
            }
            break;
        case GSA_16_BIT_ROM_PATCH2D:
            i++;
            if (i < cheatsNumber) {
                rompatch2addr[1] = ((cheatsList[i - 1].value & 0x00FFFFFF) << 1) + 0x8000000;
                rompatch2oldval[1] = (uint16_t)CPUReadHalfWord(rompatch2addr[1]);
                rompatch2val[1] = cheatsList[i].rawaddress & 0xFFFF;
            }
            break;
        case GSA_16_BIT_ROM_PATCH2E:
            i++;
            if (i < cheatsNumber) {
                rompatch2addr[2] = ((cheatsList[i - 1].value & 0x00FFFFFF) << 1) + 0x8000000;
                rompatch2oldval[2] = (uint16_t)CPUReadHalfWord(rompatch2addr[2]);
                rompatch2val[2] = cheatsList[i].rawaddress & 0xFFFF;
            }
            break;
        case GSA_16_BIT_ROM_PATCH2F:
            i++;
            if (i < cheatsNumber) {
                rompatch2addr[3] = ((cheatsList[i - 1].value & 0x00FFFFFF) << 1) + 0x8000000;
                rompatch2oldval[3] = (uint16_t)CPUReadHalfWord(rompatch2addr[3]);
                rompatch2val[3] = cheatsList[i].rawaddress & 0xFFFF;
            }
            break;
        case MASTER_CODE:
            mastercode = cheatsList[i].address;
            break;
        }
        if (onoff) {
            switch (cheatsList[i].size) {
            case INT_8_BIT_WRITE:
                CPUWriteByte(cheatsList[i].address, (uint8_t)cheatsList[i].value);
                break;
            case INT_16_BIT_WRITE:
                CPUWriteHalfWord(cheatsList[i].address, (uint16_t)cheatsList[i].value);
                break;
            case INT_32_BIT_WRITE:
                CPUWriteMemory(cheatsList[i].address, cheatsList[i].value);
                break;
            case GSA_8_BIT_GS_WRITE:
                if (extended & 4) {
                    CPUWriteByte(cheatsList[i].address, (uint8_t)cheatsList[i].value);
                }
                break;
            case GSA_16_BIT_GS_WRITE:
                if (extended & 4) {
                    CPUWriteHalfWord(cheatsList[i].address, (uint16_t)cheatsList[i].value);
                }
                break;
            case GSA_32_BIT_GS_WRITE:
                if (extended & 4) {
                    CPUWriteMemory(cheatsList[i].address, cheatsList[i].value);
                }
                break;
            case CBA_IF_KEYS_PRESSED: {
                uint16_t value = (uint16_t)cheatsList[i].value;
                uint32_t addr = cheatsList[i].address;
                if ((addr & 0xF0) == 0x20) {
                    if ((keys & value) == 0) {
                        i++;
                    }
                } else if ((addr & 0xF0) == 0x10) {
                    if ((keys & value) == value) {
                        i++;
                    }
                } else if ((addr & 0xF0) == 0x00) {
                    if (((~keys) & 0x3FF) == value) {
                        i++;
                    }
                }
            } break;
            case CBA_IF_TRUE:
                if (CPUReadHalfWord(cheatsList[i].address) != cheatsList[i].value) {
                    i++;
                }
                break;
            case CBA_SLIDE_CODE: {
                uint32_t address = cheatsList[i].address;
                uint16_t value = (uint16_t)cheatsList[i].value;
                i++;
                if (i < cheatsNumber) {
                    int count = ((cheatsList[i].address - 1) & 0xFFFF);
                    uint16_t vinc = (cheatsList[i].address >> 16) & 0xFFFF;
                    int inc = cheatsList[i].value;
                    for (int x = 0; x <= count; x++) {
                        CPUWriteHalfWord(address, value);
                        address += inc;
                        value += vinc;
                    }
                }
            } break;
            case CBA_IF_FALSE:
                if (CPUReadHalfWord(cheatsList[i].address) == cheatsList[i].value) {
                    i++;
                }
                break;
            case CBA_AND:
                CPUWriteHalfWord(cheatsList[i].address,
                    CPUReadHalfWord(cheatsList[i].address) & (uint16_t)cheatsList[i].value);
                break;
            case GSA_8_BIT_IF_TRUE:
                if (CPUReadByte(cheatsList[i].address) != cheatsList[i].value) {
                    i++;
                }
                break;
            case GSA_32_BIT_IF_TRUE:
                if (CPUReadMemory(cheatsList[i].address) != cheatsList[i].value) {
                    i++;
                }
                break;
            case GSA_8_BIT_IF_FALSE:
                if (CPUReadByte(cheatsList[i].address) == cheatsList[i].value) {
                    i++;
                }
                break;
            case GSA_32_BIT_IF_FALSE:
                if (CPUReadMemory(cheatsList[i].address) == cheatsList[i].value) {
                    i++;
                }
                break;
            case GSA_8_BIT_FILL: {
                uint32_t addr = cheatsList[i].address;
                uint8_t v = cheatsList[i].value & 0xff;
                uint32_t end = addr + (cheatsList[i].value >> 8);
                do {
                    CPUWriteByte(addr, v);
                    addr++;
                } while (addr <= end);
            } break;
            case GSA_16_BIT_FILL: {
                uint32_t addr = cheatsList[i].address;
                uint16_t v = cheatsList[i].value & 0xffff;
                uint32_t end = addr + ((cheatsList[i].value >> 16) << 1);
                do {
                    CPUWriteHalfWord(addr, v);
                    addr += 2;
                } while (addr <= end);
            } break;
            case GSA_8_BIT_IF_TRUE2:
                if (CPUReadByte(cheatsList[i].address) != cheatsList[i].value) {
                    i += 2;
                }
                break;
            case GSA_16_BIT_IF_TRUE2:
                if (CPUReadHalfWord(cheatsList[i].address) != cheatsList[i].value) {
                    i += 2;
                }
                break;
            case GSA_32_BIT_IF_TRUE2:
                if (CPUReadMemory(cheatsList[i].address) != cheatsList[i].value) {
                    i += 2;
                }
                break;
            case GSA_8_BIT_IF_FALSE2:
                if (CPUReadByte(cheatsList[i].address) == cheatsList[i].value) {
                    i += 2;
                }
                break;
            case GSA_16_BIT_IF_FALSE2:
                if (CPUReadHalfWord(cheatsList[i].address) == cheatsList[i].value) {
                    i += 2;
                }
                break;
            case GSA_32_BIT_IF_FALSE2:
                if (CPUReadMemory(cheatsList[i].address) == cheatsList[i].value) {
                    i += 2;
                }
                break;
            case CBA_ADD:
                if ((cheatsList[i].address & 1) == 0) {
                    CPUWriteHalfWord(cheatsList[i].address,
                        (uint16_t)(CPUReadHalfWord(cheatsList[i].address) + cheatsList[i].value));
                } else {
                    CPUWriteMemory(cheatsList[i].address & 0x0FFFFFFE,
                        CPUReadMemory(cheatsList[i].address & 0x0FFFFFFE) + cheatsList[i].value);
                }
                break;
            case CBA_OR:
                CPUWriteHalfWord(cheatsList[i].address,
                    (uint16_t)(CPUReadHalfWord(cheatsList[i].address) | cheatsList[i].value));
                break;
            case CBA_GT:
                if (!(CPUReadHalfWord(cheatsList[i].address) > cheatsList[i].value)) {
                    i++;
                }
                break;
            case CBA_LT:
                if (!(CPUReadHalfWord(cheatsList[i].address) < cheatsList[i].value)) {
                    i++;
                }
                break;
            case CBA_SUPER: {
                int count = 2 * ((cheatsList[i].value - 1) & 0xFFFF) + 1;
                uint32_t address = cheatsList[i].address;
                for (int x = 0; x <= count; x++) {
                    uint8_t b;
                    int res = x % 6;
                    if (res == 0)
                        i++;
                    if (res < 4)
                        b = (cheatsList[i].address >> (24 - 8 * res)) & 0xFF;
                    else
                        b = (cheatsList[i].value >> (8 - 8 * (res - 4))) & 0xFF;
                    CPUWriteByte(address, b);
                    address++;
                }
            } break;
            case GSA_8_BIT_POINTER:
                if (((CPUReadMemory(cheatsList[i].address) >= 0x02000000) && (CPUReadMemory(cheatsList[i].address) < 0x02040000)) || ((CPUReadMemory(cheatsList[i].address) >= 0x03000000) && (CPUReadMemory(cheatsList[i].address) < 0x03008000))) {
                    CPUWriteByte(CPUReadMemory(cheatsList[i].address) + ((cheatsList[i].value & 0xFFFFFF00) >> 8),
                        cheatsList[i].value & 0xFF);
                }
                break;
            case GSA_16_BIT_POINTER:
                if (((CPUReadMemory(cheatsList[i].address) >= 0x02000000) && (CPUReadMemory(cheatsList[i].address) < 0x02040000)) || ((CPUReadMemory(cheatsList[i].address) >= 0x03000000) && (CPUReadMemory(cheatsList[i].address) < 0x03008000))) {
                    CPUWriteHalfWord(CPUReadMemory(cheatsList[i].address) + ((cheatsList[i].value & 0xFFFF0000) >> 15),
                        cheatsList[i].value & 0xFFFF);
                }
                break;
            case GSA_32_BIT_POINTER:
                if (((CPUReadMemory(cheatsList[i].address) >= 0x02000000) && (CPUReadMemory(cheatsList[i].address) < 0x02040000)) || ((CPUReadMemory(cheatsList[i].address) >= 0x03000000) && (CPUReadMemory(cheatsList[i].address) < 0x03008000))) {
                    CPUWriteMemory(CPUReadMemory(cheatsList[i].address),
                        cheatsList[i].value);
                }
                break;
            case GSA_8_BIT_ADD:
                CPUWriteByte(cheatsList[i].address,
                    ((cheatsList[i].value & 0xFF) + CPUReadMemory(cheatsList[i].address)) & 0xFF);
                break;
            case GSA_16_BIT_ADD:
                CPUWriteHalfWord(cheatsList[i].address,
                    ((cheatsList[i].value & 0xFFFF) + CPUReadMemory(cheatsList[i].address)) & 0xFFFF);
                break;
            case GSA_32_BIT_ADD:
                CPUWriteMemory(cheatsList[i].address,
                    (cheatsList[i].value + CPUReadMemory(cheatsList[i].address)) & 0xFFFFFFFF);
                break;
            case GSA_8_BIT_IF_LOWER_U:
                if (!(CPUReadByte(cheatsList[i].address) < (cheatsList[i].value & 0xFF))) {
                    i++;
                }
                break;
            case GSA_16_BIT_IF_LOWER_U:
                if (!(CPUReadHalfWord(cheatsList[i].address) < (cheatsList[i].value & 0xFFFF))) {
                    i++;
                }
                break;
            case GSA_32_BIT_IF_LOWER_U:
                if (!(CPUReadMemory(cheatsList[i].address) < cheatsList[i].value)) {
                    i++;
                }
                break;
            case GSA_8_BIT_IF_HIGHER_U:
                if (!(CPUReadByte(cheatsList[i].address) > (cheatsList[i].value & 0xFF))) {
                    i++;
                }
                break;
            case GSA_16_BIT_IF_HIGHER_U:
                if (!(CPUReadHalfWord(cheatsList[i].address) > (cheatsList[i].value & 0xFFFF))) {
                    i++;
                }
                break;
            case GSA_32_BIT_IF_HIGHER_U:
                if (!(CPUReadMemory(cheatsList[i].address) > cheatsList[i].value)) {
                    i++;
                }
                break;
            case GSA_8_BIT_IF_AND:
                if (!(CPUReadByte(cheatsList[i].address) & (cheatsList[i].value & 0xFF))) {
                    i++;
                }
                break;
            case GSA_16_BIT_IF_AND:
                if (!(CPUReadHalfWord(cheatsList[i].address) & (cheatsList[i].value & 0xFFFF))) {
                    i++;
                }
                break;
            case GSA_32_BIT_IF_AND:
                if (!(CPUReadMemory(cheatsList[i].address) & cheatsList[i].value)) {
                    i++;
                }
                break;
            case GSA_8_BIT_IF_LOWER_U2:
                if (!(CPUReadByte(cheatsList[i].address) < (cheatsList[i].value & 0xFF))) {
                    i += 2;
                }
                break;
            case GSA_16_BIT_IF_LOWER_U2:
                if (!(CPUReadHalfWord(cheatsList[i].address) < (cheatsList[i].value & 0xFFFF))) {
                    i += 2;
                }
                break;
            case GSA_32_BIT_IF_LOWER_U2:
                if (!(CPUReadMemory(cheatsList[i].address) < cheatsList[i].value)) {
                    i += 2;
                }
                break;
            case GSA_8_BIT_IF_HIGHER_U2:
                if (!(CPUReadByte(cheatsList[i].address) > (cheatsList[i].value & 0xFF))) {
                    i += 2;
                }
                break;
            case GSA_16_BIT_IF_HIGHER_U2:
                if (!(CPUReadHalfWord(cheatsList[i].address) > (cheatsList[i].value & 0xFFFF))) {
                    i += 2;
                }
                break;
            case GSA_32_BIT_IF_HIGHER_U2:
                if (!(CPUReadMemory(cheatsList[i].address) > cheatsList[i].value)) {
                    i += 2;
                }
                break;
            case GSA_8_BIT_IF_AND2:
                if (!(CPUReadByte(cheatsList[i].address) & (cheatsList[i].value & 0xFF))) {
                    i += 2;
                }
                break;
            case GSA_16_BIT_IF_AND2:
                if (!(CPUReadHalfWord(cheatsList[i].address) & (cheatsList[i].value & 0xFFFF))) {
                    i += 2;
                }
                break;
            case GSA_32_BIT_IF_AND2:
                if (!(CPUReadMemory(cheatsList[i].address) & cheatsList[i].value)) {
                    i += 2;
                }
                break;
            case GSA_ALWAYS:
                i++;
                break;
            case GSA_ALWAYS2:
                i += 2;
                break;
            case GSA_8_BIT_IF_LOWER_S:
                if (!((int8_t)CPUReadByte(cheatsList[i].address) < ((int8_t)cheatsList[i].value & 0xFF))) {
                    i++;
                }
                break;
            case GSA_16_BIT_IF_LOWER_S:
                if (!((int16_t)CPUReadHalfWord(cheatsList[i].address) < ((int16_t)cheatsList[i].value & 0xFFFF))) {
                    i++;
                }
                break;
            case GSA_32_BIT_IF_LOWER_S:
                if (!((int32_t)CPUReadMemory(cheatsList[i].address) < (int32_t)cheatsList[i].value)) {
                    i++;
                }
                break;
            case GSA_8_BIT_IF_HIGHER_S:
                if (!((int8_t)CPUReadByte(cheatsList[i].address) > ((int8_t)cheatsList[i].value & 0xFF))) {
                    i++;
                }
                break;
            case GSA_16_BIT_IF_HIGHER_S:
                if (!((int16_t)CPUReadHalfWord(cheatsList[i].address) > ((int16_t)cheatsList[i].value & 0xFFFF))) {
                    i++;
                }
                break;
            case GSA_32_BIT_IF_HIGHER_S:
                if (!((int32_t)CPUReadMemory(cheatsList[i].address) > (int32_t)cheatsList[i].value)) {
                    i++;
                }
                break;
            case GSA_8_BIT_IF_LOWER_S2:
                if (!((int8_t)CPUReadByte(cheatsList[i].address) < ((int8_t)cheatsList[i].value & 0xFF))) {
                    i += 2;
                }
                break;
            case GSA_16_BIT_IF_LOWER_S2:
                if (!((int16_t)CPUReadHalfWord(cheatsList[i].address) < ((int16_t)cheatsList[i].value & 0xFFFF))) {
                    i += 2;
                }
                break;
            case GSA_32_BIT_IF_LOWER_S2:
                if (!((int32_t)CPUReadMemory(cheatsList[i].address) < (int32_t)cheatsList[i].value)) {
                    i += 2;
                }
                break;
            case GSA_8_BIT_IF_HIGHER_S2:
                if (!((int8_t)CPUReadByte(cheatsList[i].address) > ((int8_t)cheatsList[i].value & 0xFF))) {
                    i += 2;
                }
                break;
            case GSA_16_BIT_IF_HIGHER_S2:
                if (!((int16_t)CPUReadHalfWord(cheatsList[i].address) > ((int16_t)cheatsList[i].value & 0xFFFF))) {
                    i += 2;
                }
                break;
            case GSA_32_BIT_IF_HIGHER_S2:
                if (!((int32_t)CPUReadMemory(cheatsList[i].address) > (int32_t)cheatsList[i].value)) {
                    i += 2;
                }
                break;
            case GSA_16_BIT_WRITE_IOREGS:
                if ((cheatsList[i].address <= 0x3FF) && (cheatsList[i].address != 0x6) && (cheatsList[i].address != 0x130))
                    g_ioMem[cheatsList[i].address & 0x3FE] = (uint8_t)(cheatsList[i].value & 0xFFFF);
                break;
            case GSA_32_BIT_WRITE_IOREGS:
                if (cheatsList[i].address <= 0x3FF) {
                    uint32_t cheat_addr = cheatsList[i].address & 0x3FC;
                    if ((cheat_addr != 6) && (cheat_addr != 0x130))
                        g_ioMem[cheat_addr] = (uint8_t)(cheatsList[i].value & 0xFFFF);
                    if (((cheat_addr + 2) != 0x6) && (cheat_addr + 2) != 0x130)
                        g_ioMem[cheat_addr + 2] = (uint8_t)((cheatsList[i].value >> 16) & 0xFFFF);
                }
                break;
            case GSA_8_BIT_IF_TRUE3:
                if (CPUReadByte(cheatsList[i].address) != cheatsList[i].value) {
                    onoff = false;
                }
                break;
            case GSA_16_BIT_IF_TRUE3:
                if (CPUReadHalfWord(cheatsList[i].address) != cheatsList[i].value) {
                    onoff = false;
                }
                break;
            case GSA_32_BIT_IF_TRUE3:
                if (CPUReadMemory(cheatsList[i].address) != cheatsList[i].value) {
                    onoff = false;
                }
                break;
            case GSA_8_BIT_IF_FALSE3:
                if (CPUReadByte(cheatsList[i].address) == cheatsList[i].value) {
                    onoff = false;
                }
                break;
            case GSA_16_BIT_IF_FALSE3:
                if (CPUReadHalfWord(cheatsList[i].address) == cheatsList[i].value) {
                    onoff = false;
                }
                break;
            case GSA_32_BIT_IF_FALSE3:
                if (CPUReadMemory(cheatsList[i].address) == cheatsList[i].value) {
                    onoff = false;
                }
                break;
            case GSA_8_BIT_IF_LOWER_S3:
                if (!((int8_t)CPUReadByte(cheatsList[i].address) < ((int8_t)cheatsList[i].value & 0xFF))) {
                    onoff = false;
                }
                break;
            case GSA_16_BIT_IF_LOWER_S3:
                if (!((int16_t)CPUReadHalfWord(cheatsList[i].address) < ((int16_t)cheatsList[i].value & 0xFFFF))) {
                    onoff = false;
                }
                break;
            case GSA_32_BIT_IF_LOWER_S3:
                if (!((int32_t)CPUReadMemory(cheatsList[i].address) < (int32_t)cheatsList[i].value)) {
                    onoff = false;
                }
                break;
            case GSA_8_BIT_IF_HIGHER_S3:
                if (!((int8_t)CPUReadByte(cheatsList[i].address) > ((int8_t)cheatsList[i].value & 0xFF))) {
                    onoff = false;
                }
                break;
            case GSA_16_BIT_IF_HIGHER_S3:
                if (!((int16_t)CPUReadHalfWord(cheatsList[i].address) > ((int16_t)cheatsList[i].value & 0xFFFF))) {
                    onoff = false;
                }
                break;
            case GSA_32_BIT_IF_HIGHER_S3:
                if (!((int32_t)CPUReadMemory(cheatsList[i].address) > (int32_t)cheatsList[i].value)) {
                    onoff = false;
                }
                break;
            case GSA_8_BIT_IF_LOWER_U3:
                if (!(CPUReadByte(cheatsList[i].address) < (cheatsList[i].value & 0xFF))) {
                    onoff = false;
                }
                break;
            case GSA_16_BIT_IF_LOWER_U3:
                if (!(CPUReadHalfWord(cheatsList[i].address) < (cheatsList[i].value & 0xFFFF))) {
                    onoff = false;
                }
                break;
            case GSA_32_BIT_IF_LOWER_U3:
                if (!(CPUReadMemory(cheatsList[i].address) < cheatsList[i].value)) {
                    onoff = false;
                }
                break;
            case GSA_8_BIT_IF_HIGHER_U3:
                if (!(CPUReadByte(cheatsList[i].address) > (cheatsList[i].value & 0xFF))) {
                    onoff = false;
                }
                break;
            case GSA_16_BIT_IF_HIGHER_U3:
                if (!(CPUReadHalfWord(cheatsList[i].address) > (cheatsList[i].value & 0xFFFF))) {
                    onoff = false;
                }
                break;
            case GSA_32_BIT_IF_HIGHER_U3:
                if (!(CPUReadMemory(cheatsList[i].address) > cheatsList[i].value)) {
                    onoff = false;
                }
                break;
            case GSA_8_BIT_IF_AND3:
                if (!(CPUReadByte(cheatsList[i].address) & (cheatsList[i].value & 0xFF))) {
                    onoff = false;
                }
                break;
            case GSA_16_BIT_IF_AND3:
                if (!(CPUReadHalfWord(cheatsList[i].address) & (cheatsList[i].value & 0xFFFF))) {
                    onoff = false;
                }
                break;
            case GSA_32_BIT_IF_AND3:
                if (!(CPUReadMemory(cheatsList[i].address) & cheatsList[i].value)) {
                    onoff = false;
                }
                break;
            case GSA_ALWAYS3:
                if (!(CPUReadMemory(cheatsList[i].address) & cheatsList[i].value)) {
                    onoff = false;
                }
                break;
            case GSA_GROUP_WRITE: {
                int count = ((cheatsList[i].address) & 0xFFFE) + 1;
                uint32_t value = cheatsList[i].value;
                if (count == 0)
                    i++;
                else
                    for (int x = 1; x <= count; x++) {
                        if ((x % 2) == 0) {
                            if (x < count)
                                i++;
                            CPUWriteMemory(cheatsList[i].rawaddress, value);
                        } else
                            CPUWriteMemory(cheatsList[i].value, value);
                    }
            } break;
            case GSA_32_BIT_ADD2:
                CPUWriteMemory(cheatsList[i].value,
                    (CPUReadMemory(cheatsList[i].value) + cheatsList[i + 1].rawaddress) & 0xFFFFFFFF);
                i++;
                break;
            case GSA_32_BIT_SUB2:
                CPUWriteMemory(cheatsList[i].value,
                    (CPUReadMemory(cheatsList[i].value) - cheatsList[i + 1].rawaddress) & 0xFFFFFFFF);
                i++;
                break;
            case GSA_16_BIT_IF_LOWER_OR_EQ_U:
                if (CPUReadHalfWord(cheatsList[i].address) > cheatsList[i].value) {
                    i++;
                }
                break;
            case GSA_16_BIT_IF_HIGHER_OR_EQ_U:
                if (CPUReadHalfWord(cheatsList[i].address) < cheatsList[i].value) {
                    i++;
                }
                break;
            case GSA_16_BIT_MIF_TRUE:
                if (CPUReadHalfWord(cheatsList[i].address) != cheatsList[i].value) {
                    i += ((cheatsList[i].rawaddress >> 0x10) & 0xFF);
                }
                break;
            case GSA_16_BIT_MIF_FALSE:
                if (CPUReadHalfWord(cheatsList[i].address) == cheatsList[i].value) {
                    i += (cheatsList[i].rawaddress >> 0x10) & 0xFF;
                }
                break;
            case GSA_16_BIT_MIF_LOWER_OR_EQ_U:
                if (CPUReadHalfWord(cheatsList[i].address) > cheatsList[i].value) {
                    i += (cheatsList[i].rawaddress >> 0x10) & 0xFF;
                }
                break;
            case GSA_16_BIT_MIF_HIGHER_OR_EQ_U:
                if (CPUReadHalfWord(cheatsList[i].address) < cheatsList[i].value) {
                    i += (cheatsList[i].rawaddress >> 0x10) & 0xFF;
                }
                break;
            case CHEATS_16_BIT_WRITE:
                if ((cheatsList[i].address >> 24) >= 0x08) {
                    CHEAT_PATCH_ROM_16BIT(cheatsList[i].address, (uint16_t)cheatsList[i].value);
                } else {
                    CPUWriteHalfWord(cheatsList[i].address, (uint16_t)cheatsList[i].value);
                }
                break;
            case CHEATS_32_BIT_WRITE:
                if ((cheatsList[i].address >> 24) >= 0x08) {
                    CHEAT_PATCH_ROM_32BIT(cheatsList[i].address, cheatsList[i].value);
                } else {
                    CPUWriteMemory(cheatsList[i].address, cheatsList[i].value);
                }
                break;
            }
        }
    }
    for (i = 0; i < 4; i++)
        if (rompatch2addr[i] != 0)
            CHEAT_PATCH_ROM_16BIT(rompatch2addr[i], rompatch2val[i]);
    return ticks;
}

void cheatsAdd(const char* codeStr,
    const char* desc,
    uint32_t rawaddress,
    uint32_t address,
    uint32_t value,
    int code,
    int size)
{
    if (cheatsNumber < MAX_CHEATS) {
        int x = cheatsNumber;
        cheatsList[x].code = code;
        cheatsList[x].size = size;
        cheatsList[x].rawaddress = rawaddress;
        cheatsList[x].address = address;
        cheatsList[x].value = value;
#if __STDC_WANT_SECURE_LIB__
        strcpy_s(cheatsList[x].codestring, sizeof(cheatsList[x].codestring), codeStr);
        strcpy_s(cheatsList[x].desc, sizeof(cheatsList[x].desc), desc);
#else
        strcpy(cheatsList[x].codestring, codeStr);
        strcpy(cheatsList[x].desc, desc);
#endif
        cheatsList[x].enabled = true;
        cheatsList[x].status = 0;

        // we only store the old value for this simple codes. ROM patching
        // is taken care when it actually patches the ROM
        switch (cheatsList[x].size) {
        case INT_8_BIT_WRITE:
            cheatsList[x].oldValue = CPUReadByte(address);
            break;
        case INT_16_BIT_WRITE:
            cheatsList[x].oldValue = CPUReadHalfWord(address);
            break;
        case INT_32_BIT_WRITE:
            cheatsList[x].oldValue = CPUReadMemory(address);
            break;
        case CHEATS_16_BIT_WRITE:
            cheatsList[x].oldValue = CPUReadHalfWord(address);
            break;
        case CHEATS_32_BIT_WRITE:
            cheatsList[x].oldValue = CPUReadMemory(address);
            break;
        }
        cheatsNumber++;
    }
}

void cheatsDelete(int number, bool restore)
{
    if (number < cheatsNumber && number >= 0) {
        int x = number;

        if (restore) {
            switch (cheatsList[x].size) {
            case INT_8_BIT_WRITE:
                CPUWriteByte(cheatsList[x].address, (uint8_t)cheatsList[x].oldValue);
                break;
            case INT_16_BIT_WRITE:
                CPUWriteHalfWord(cheatsList[x].address, (uint16_t)cheatsList[x].oldValue);
                break;
            case INT_32_BIT_WRITE:
                CPUWriteMemory(cheatsList[x].address, cheatsList[x].oldValue);
                break;
            case CHEATS_16_BIT_WRITE:
                if ((cheatsList[x].address >> 24) >= 0x08) {
                    CHEAT_PATCH_ROM_16BIT(cheatsList[x].address, (uint16_t)cheatsList[x].oldValue);
                } else {
                    CPUWriteHalfWord(cheatsList[x].address, (uint16_t)cheatsList[x].oldValue);
                }
                break;
            case CHEATS_32_BIT_WRITE:
                if ((cheatsList[x].address >> 24) >= 0x08) {
                    CHEAT_PATCH_ROM_32BIT(cheatsList[x].address, cheatsList[x].oldValue);
                } else {
                    CPUWriteMemory(cheatsList[x].address, cheatsList[x].oldValue);
                }
		/* fallthrough */
            case GSA_16_BIT_ROM_PATCH:
                if (cheatsList[x].status & 1) {
                    cheatsList[x].status &= ~1;
                    CHEAT_PATCH_ROM_16BIT(cheatsList[x].address,
                        (uint16_t)cheatsList[x].oldValue);
                }
                break;
            case GSA_16_BIT_ROM_PATCH2C:
            case GSA_16_BIT_ROM_PATCH2D:
            case GSA_16_BIT_ROM_PATCH2E:
            case GSA_16_BIT_ROM_PATCH2F:
                if (cheatsList[x].status & 1) {
                    cheatsList[x].status &= ~1;
                }
                break;
            case MASTER_CODE:
                mastercode = 0;
                break;
            }
        }
        if ((x + 1) < cheatsNumber) {
            memcpy(&cheatsList[x], &cheatsList[x + 1], sizeof(CheatsData) * (cheatsNumber - x - 1));
        }
        cheatsNumber--;
    }
}

void cheatsDeleteAll(bool restore)
{
    for (int i = cheatsNumber - 1; i >= 0; i--) {
        cheatsDelete(i, restore);
    }
}

void cheatsEnable(int i)
{
    if (i >= 0 && i < cheatsNumber) {
        cheatsList[i].enabled = true;
        mastercode = 0;
    }
}

void cheatsDisable(int i)
{
    if (i >= 0 && i < cheatsNumber) {
        switch (cheatsList[i].size) {
        case GSA_16_BIT_ROM_PATCH:
            if (cheatsList[i].status & 1) {
                cheatsList[i].status &= ~1;
                CHEAT_PATCH_ROM_16BIT(cheatsList[i].address,
                    (uint16_t)cheatsList[i].oldValue);
            }
            break;
        case GSA_16_BIT_ROM_PATCH2C:
        case GSA_16_BIT_ROM_PATCH2D:
        case GSA_16_BIT_ROM_PATCH2E:
        case GSA_16_BIT_ROM_PATCH2F:
            if (cheatsList[i].status & 1) {
                cheatsList[i].status &= ~1;
            }
            break;
        case MASTER_CODE:
            mastercode = 0;
            break;
        }
        cheatsList[i].enabled = false;
    }
}

bool cheatsVerifyCheatCode(const char* code, const char* desc)
{
    size_t len = strlen(code);
    if (len != 11 && len != 13 && len != 17) {
        systemMessage(MSG_INVALID_CHEAT_CODE, N_("Invalid cheat code '%s': wrong length"), code);
        return false;
    }

    if (code[8] != ':') {
        systemMessage(MSG_INVALID_CHEAT_CODE, N_("Invalid cheat code '%s': no colon"), code);
        return false;
    }

    size_t i;
    for (i = 0; i < 8; i++) {
        if (!CHEAT_IS_HEX(code[i])) {
            // wrong cheat
            systemMessage(MSG_INVALID_CHEAT_CODE,
                N_("Invalid cheat code '%s': first part is not hex"), code);
            return false;
        }
    }
    for (i = 9; i < len; i++) {
        if (!CHEAT_IS_HEX(code[i])) {
            // wrong cheat
            systemMessage(MSG_INVALID_CHEAT_CODE,
                N_("Invalid cheat code '%s' second part is not hex"), code);
            return false;
        }
    }

    uint32_t address = 0;
    uint32_t value = 0;

    char buffer[10];
#if __STDC_WANT_SECURE_LIB__
    strncpy_s(buffer, sizeof(buffer), code, 8);
#else
    strncpy(buffer, code, 8);
#endif
    buffer[8] = 0;
    sscanf(buffer, "%x", &address);

    switch (address >> 24) {
    case 0x02:
    case 0x03:
    case 0x04:
    case 0x05:
    case 0x06:
    case 0x07:
    case 0x08:
    case 0x09:
    case 0x0A:
    case 0x0B:
    case 0x0C:
    case 0x0D:
        break;
    default:
        systemMessage(MSG_INVALID_CHEAT_CODE_ADDRESS,
            N_("Invalid cheat code address: %08x"),
            address);
        return false;
    }

#if __STDC_WANT_SECURE_LIB__
    strncpy_s(buffer, sizeof(buffer), &code[9], 8);
#else
    strncpy(buffer, &code[9], 8);
#endif

    sscanf(buffer, "%x", &value);
    int type = 0;
    if (len == 13)
        type = 114;
    if (len == 17)
        type = 115;
    cheatsAdd(code, desc, address, address, value, type, type);
    return true;
}

void cheatsAddCheatCode(const char* code, const char* desc)
{
    cheatsVerifyCheatCode(code, desc);
}

uint16_t cheatsGSAGetDeadface(bool v3)
{
    for (int i = cheatsNumber - 1; i >= 0; i--)
        if ((cheatsList[i].address == 0xDEADFACE) && (cheatsList[i].code == (v3 ? 257 : 256)))
            return cheatsList[i].value & 0xFFFF;
    return 0;
}

void cheatsGSAChangeEncryption(uint16_t value, bool v3)
{
    int i;
    uint8_t *deadtable1, *deadtable2;

    if (v3) {
        deadtable1 = (uint8_t*)(&v3_deadtable1);
        deadtable2 = (uint8_t*)(&v3_deadtable2);
        for (i = 0; i < 4; i++)
            seeds_v3[i] = seed_gen((uint8_t)((value & 0xFF00) >> 8), (uint8_t)((value & 0xFF) + i), deadtable1, deadtable2);
    } else {
        deadtable1 = (uint8_t*)(&v1_deadtable1);
        deadtable2 = (uint8_t*)(&v1_deadtable2);
        for (i = 0; i < 4; i++) {
            seeds_v1[i] = seed_gen((uint8_t)((value & 0xFF00) >> 8), (uint8_t)((value & 0xFF) + i), deadtable1, deadtable2);
        }
    }
}

uint32_t seed_gen(uint8_t upper, uint8_t seed, uint8_t* deadtable1, uint8_t* deadtable2)
{
    int i;
    uint32_t newseed = 0;

    for (i = 0; i < 4; i++)
        newseed = ((newseed << 8) | ((deadtable1[(i + upper) & 0xFF] + deadtable2[seed]) & 0xFF));

    return newseed;
}

void cheatsDecryptGSACode(uint32_t& address, uint32_t& value, bool v3)
{
    uint32_t rollingseed = 0xC6EF3720;
    uint32_t* seeds = v3 ? seeds_v3 : seeds_v1;

    int bitsleft = 32;
    while (bitsleft > 0) {
        value -= ((((address << 4) + seeds[2]) ^ (address + rollingseed)) ^ ((address >> 5) + seeds[3]));
        address -= ((((value << 4) + seeds[0]) ^ (value + rollingseed)) ^ ((value >> 5) + seeds[1]));
        rollingseed -= 0x9E3779B9;
        bitsleft--;
    }
}

void cheatsAddGSACode(const char* code, const char* desc, bool v3)
{
    if (strlen(code) != 16) {
        // wrong cheat
        systemMessage(MSG_INVALID_GSA_CODE,
            N_("Invalid GSA code. Format is XXXXXXXXYYYYYYYY"));
        return;
    }

    int i;
    for (i = 0; i < 16; i++) {
        if (!CHEAT_IS_HEX(code[i])) {
            // wrong cheat
            systemMessage(MSG_INVALID_GSA_CODE,
                N_("Invalid GSA code. Format is XXXXXXXXYYYYYYYY"));
            return;
        }
    }

    char buffer[10];
#if __STDC_WANT_SECURE_LIB__
    strncpy_s(buffer, sizeof(buffer), code, 8);
#else
    strncpy(buffer, code, 8);
#endif
    buffer[8] = 0;
    uint32_t address;
    sscanf(buffer, "%x", &address);
#if __STDC_WANT_SECURE_LIB__
    strncpy_s(buffer, sizeof(buffer), &code[8], 8);
#else
    strncpy(buffer, &code[8], 8);
#endif
    buffer[8] = 0;
    uint32_t value;
    sscanf(buffer, "%x", &value);
    cheatsGSAChangeEncryption(cheatsGSAGetDeadface(v3), v3);
    cheatsDecryptGSACode(address, value, v3);

    if (value == 0x1DC0DE) {
        uint32_t gamecode = READ32LE(((uint32_t*)&g_rom[0xac]));
        if (gamecode != address) {
            char buf[5];
            *((uint32_t*)buf) = address;
            buf[4] = 0;
            char buf2[5];
            *((uint32_t*)buf2) = READ32LE(((uint32_t*)&g_rom[0xac]));
            buf2[4] = 0;
            systemMessage(MSG_GBA_CODE_WARNING, N_("Warning: cheats are for game %s. Current game is %s.\nCodes may not work correctly."),
                buf, buf2);
        }
        cheatsAdd(code, desc, address, address & 0x0FFFFFFF, value, v3 ? 257 : 256,
            UNKNOWN_CODE);
        return;
    }
    if (isMultilineWithData(cheatsNumber - 1)) {
        cheatsAdd(code, desc, address, address, value, v3 ? 257 : 256, UNKNOWN_CODE);
        return;
    }
    if (v3) {
        int type = ((address >> 25) & 127) | ((address >> 17) & 0x80);
        uint32_t addr = (address & 0x00F00000) << 4 | (address & 0x0003FFFF);
        uint16_t mcode = (address >> 24 & 0xFF);

        if ((mcode & 0xFE) == 0xC4) {
            cheatsAdd(code, desc, address, (address & 0x1FFFFFF) | (0x08000000),
                value, 257, MASTER_CODE);
            mastercode = (address & 0x1FFFFFF) | (0x08000000);
        } else
            switch (type) {
            case 0x00:
                if (address == 0) {
                    type = (value >> 25) & 127;
                    addr = (value & 0x00F00000) << 4 | (value & 0x0003FFFF);
                    switch (type) {
                    case 0x04:
                        cheatsAdd(code, desc, address, 0, value & 0x00FFFFFF, 257, GSA_SLOWDOWN);
                        break;
                    case 0x08:
                        cheatsAdd(code, desc, address, 0, addr, 257, GSA_8_BIT_GS_WRITE2);
                        break;
                    case 0x09:
                        cheatsAdd(code, desc, address, 0, addr, 257, GSA_16_BIT_GS_WRITE2);
                        break;
                    case 0x0a:
                        cheatsAdd(code, desc, address, 0, addr, 257, GSA_32_BIT_GS_WRITE2);
                        break;
                    case 0x0c:
                        cheatsAdd(code, desc, address, 0, value & 0x00FFFFFF, 257, GSA_16_BIT_ROM_PATCH2C);
                        break;
                    case 0x0d:
                        cheatsAdd(code, desc, address, 0, value & 0x00FFFFFF, 257, GSA_16_BIT_ROM_PATCH2D);
                        break;
                    case 0x0e:
                        cheatsAdd(code, desc, address, 0, value & 0x00FFFFFF, 257, GSA_16_BIT_ROM_PATCH2E);
                        break;
                    case 0x0f:
                        cheatsAdd(code, desc, address, 0, value & 0x00FFFFFF, 257, GSA_16_BIT_ROM_PATCH2F);
                        break;
                    case 0x20:
                        cheatsAdd(code, desc, address, 0, addr, 257, GSA_CODES_ON);
                        break;
                    case 0x40:
                        cheatsAdd(code, desc, address, 0, addr, 257, GSA_8_BIT_SLIDE);
                        break;
                    case 0x41:
                        cheatsAdd(code, desc, address, 0, addr, 257, GSA_16_BIT_SLIDE);
                        break;
                    case 0x42:
                        cheatsAdd(code, desc, address, 0, addr, 257, GSA_32_BIT_SLIDE);
                        break;
                    default:
                        cheatsAdd(code, desc, address, address, value, 257, UNKNOWN_CODE);
                        break;
                    }
                } else
                    cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_FILL);
                break;
            case 0x01:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_FILL);
                break;
            case 0x02:
                cheatsAdd(code, desc, address, addr, value, 257, INT_32_BIT_WRITE);
                break;
            case 0x04:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_TRUE);
                break;
            case 0x05:
                cheatsAdd(code, desc, address, addr, value, 257, CBA_IF_TRUE);
                break;
            case 0x06:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_TRUE);
                break;
            case 0x07:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_ALWAYS);
                break;
            case 0x08:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_FALSE);
                break;
            case 0x09:
                cheatsAdd(code, desc, address, addr, value, 257, CBA_IF_FALSE);
                break;
            case 0x0a:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_FALSE);
                break;
            case 0xc:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_LOWER_S);
                break;
            case 0xd:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_LOWER_S);
                break;
            case 0xe:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_LOWER_S);
                break;
            case 0x10:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_HIGHER_S);
                break;
            case 0x11:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_HIGHER_S);
                break;
            case 0x12:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_HIGHER_S);
                break;
            case 0x14:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_LOWER_U);
                break;
            case 0x15:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_LOWER_U);
                break;
            case 0x16:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_LOWER_U);
                break;
            case 0x18:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_HIGHER_U);
                break;
            case 0x19:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_HIGHER_U);
                break;
            case 0x1A:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_HIGHER_U);
                break;
            case 0x1C:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_AND);
                break;
            case 0x1D:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_AND);
                break;
            case 0x1E:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_AND);
                break;
            case 0x20:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_POINTER);
                break;
            case 0x21:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_POINTER);
                break;
            case 0x22:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_POINTER);
                break;
            case 0x24:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_TRUE2);
                break;
            case 0x25:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_TRUE2);
                break;
            case 0x26:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_TRUE2);
                break;
            case 0x27:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_ALWAYS2);
                break;
            case 0x28:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_FALSE2);
                break;
            case 0x29:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_FALSE2);
                break;
            case 0x2a:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_FALSE2);
                break;
            case 0x2c:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_LOWER_S2);
                break;
            case 0x2d:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_LOWER_S2);
                break;
            case 0x2e:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_LOWER_S2);
                break;
            case 0x30:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_HIGHER_S2);
                break;
            case 0x31:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_HIGHER_S2);
                break;
            case 0x32:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_HIGHER_S2);
                break;
            case 0x34:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_LOWER_U2);
                break;
            case 0x35:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_LOWER_U2);
                break;
            case 0x36:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_LOWER_U2);
                break;
            case 0x38:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_HIGHER_U2);
                break;
            case 0x39:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_HIGHER_U2);
                break;
            case 0x3A:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_HIGHER_U2);
                break;
            case 0x3C:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_AND2);
                break;
            case 0x3D:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_AND2);
                break;
            case 0x3E:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_AND2);
                break;
            case 0x40:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_ADD);
                break;
            case 0x41:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_ADD);
                break;
            case 0x42:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_ADD);
                break;
            case 0x44:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_TRUE3);
                break;
            case 0x45:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_TRUE3);
                break;
            case 0x46:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_TRUE3);
                break;
            case 0x47:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_ALWAYS3);
                break;
            case 0x48:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_FALSE3);
                break;
            case 0x49:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_FALSE3);
                break;
            case 0x4a:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_FALSE3);
                break;
            case 0x4c:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_LOWER_S3);
                break;
            case 0x4d:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_LOWER_S3);
                break;
            case 0x4e:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_LOWER_S3);
                break;
            case 0x50:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_HIGHER_S3);
                break;
            case 0x51:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_HIGHER_S3);
                break;
            case 0x52:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_HIGHER_S3);
                break;
            case 0x54:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_LOWER_U3);
                break;
            case 0x55:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_LOWER_U3);
                break;
            case 0x56:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_LOWER_U3);
                break;
            case 0x58:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_HIGHER_U3);
                break;
            case 0x59:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_HIGHER_U3);
                break;
            case 0x5a:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_HIGHER_U3);
                break;
            case 0x5c:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_8_BIT_IF_AND3);
                break;
            case 0x5d:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_IF_AND3);
                break;
            case 0x5e:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_IF_AND3);
                break;
            case 0x63:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_16_BIT_WRITE_IOREGS);
                break;
            case 0xE3:
                cheatsAdd(code, desc, address, addr, value, 257, GSA_32_BIT_WRITE_IOREGS);
                break;
            default:
                cheatsAdd(code, desc, address, address, value, 257, UNKNOWN_CODE);
                break;
            }
    } else {
        int type = (address >> 28) & 15;
        switch (type) {
        case 0:
        case 1:
        case 2:
            cheatsAdd(code, desc, address, address & 0x0FFFFFFF, value, 256, type);
            break;
        case 3:
            switch ((address >> 0x10) & 0xFF) {
            case 0x00:
                cheatsAdd(code, desc, address, address & 0x0FFFFFFF, value, 256, GSA_GROUP_WRITE);
                break;
            case 0x10:
                cheatsAdd(code, desc, address, value & 0x0FFFFFFF, address & 0xFF, 256, GSA_32_BIT_ADD);
                break;
            case 0x20:
                cheatsAdd(code, desc, address, value & 0x0FFFFFFF, (~(address & 0xFF) + 1), 256, GSA_32_BIT_ADD);
                break;
            case 0x30:
                cheatsAdd(code, desc, address, value & 0x0FFFFFFF, address & 0xFFFF, 256, GSA_32_BIT_ADD);
                break;
            case 0x40:
                cheatsAdd(code, desc, address, value & 0x0FFFFFFF, (~(address & 0xFFFF) + 1), 256, GSA_32_BIT_ADD);
                break;
            case 0x50:
                cheatsAdd(code, desc, address, address & 0x0FFFFFFF, value, 256, GSA_32_BIT_ADD2);
                break;
            case 0x60:
                cheatsAdd(code, desc, address, address & 0x0FFFFFFF, value, 256, GSA_32_BIT_SUB2);
                break;
            default:
                // unsupported code
                cheatsAdd(code, desc, address, address, value, 256,
                    UNKNOWN_CODE);
                break;
            }
            break;
        case 6:
            address <<= 1;
            type = (value >> 24) & 0xFF;
            if (type == 0x00) {
                cheatsAdd(code, desc, address, address & 0x0FFFFFFF, value & 0xFFFF, 256,
                    GSA_16_BIT_ROM_PATCH);
                break;
            }
            // unsupported code
            cheatsAdd(code, desc, address, address, value, 256,
                UNKNOWN_CODE);
            break;
        case 8:
            switch ((address >> 20) & 15) {
            case 1:
                cheatsAdd(code, desc, address, address & 0x0F0FFFFF, value, 256,
                    GSA_8_BIT_GS_WRITE);
                break;
            case 2:
                cheatsAdd(code, desc, address, address & 0x0F0FFFFF, value, 256,
                    GSA_16_BIT_GS_WRITE);
                break;
            case 4:
                // This code is buggy : the value is always set to 0 !
                cheatsAdd(code, desc, address, address & 0x0F0FFFFF, 0, 256,
                    GSA_32_BIT_GS_WRITE);
                break;
            case 15:
                cheatsAdd(code, desc, address, 0, value & 0xFFFF, 256, GSA_SLOWDOWN);
                break;
            default:
                // unsupported code
                cheatsAdd(code, desc, address, address, value, 256,
                    UNKNOWN_CODE);
                break;
            }
            break;
        case 0x0d:
            if (address != 0xDEADFACE) {
                switch ((value >> 20) & 0xF) {
                case 0:
                    cheatsAdd(code, desc, address, address & 0x0FFFFFFF, value & 0xFFFF, 256,
                        CBA_IF_TRUE);
                    break;
                case 1:
                    cheatsAdd(code, desc, address, address & 0x0FFFFFFF, value & 0xFFFF, 256,
                        CBA_IF_FALSE);
                    break;
                case 2:
                    cheatsAdd(code, desc, address, address & 0x0FFFFFFF, value & 0xFFFF, 256,
                        GSA_16_BIT_IF_LOWER_OR_EQ_U);
                    break;
                case 3:
                    cheatsAdd(code, desc, address, address & 0x0FFFFFFF, value & 0xFFFF, 256,
                        GSA_16_BIT_IF_HIGHER_OR_EQ_U);
                    break;
                default:
                    // unsupported code
                    cheatsAdd(code, desc, address, address, value, 256,
                        UNKNOWN_CODE);
                    break;
                }
            } else
                cheatsAdd(code, desc, address, address, value, 256,
                    UNKNOWN_CODE);
            break;
        case 0x0e:
            switch ((value >> 28) & 0xF) {
            case 0:
                cheatsAdd(code, desc, address, value & 0x0FFFFFFF, address & 0xFFFF, 256,
                    GSA_16_BIT_MIF_TRUE);
                break;
            case 1:
                cheatsAdd(code, desc, address, value & 0x0FFFFFFF, address & 0xFFFF, 256,
                    GSA_16_BIT_MIF_FALSE);
                break;
            case 2:
                cheatsAdd(code, desc, address, value & 0x0FFFFFFF, address & 0xFFFF, 256,
                    GSA_16_BIT_MIF_LOWER_OR_EQ_U);
                break;
            case 3:
                cheatsAdd(code, desc, address, value & 0x0FFFFFFF, address & 0xFFFF, 256,
                    GSA_16_BIT_MIF_HIGHER_OR_EQ_U);
                break;
            default:
                // unsupported code
                cheatsAdd(code, desc, address, address, value, 256,
                    UNKNOWN_CODE);
                break;
            }
            break;
        case 0x0f:
            cheatsAdd(code, desc, address, (address & 0xFFFFFFF), value, 256, MASTER_CODE);
            mastercode = (address & 0xFFFFFFF);
            break;
        default:
            // unsupported code
            cheatsAdd(code, desc, address, address, value, 256,
                UNKNOWN_CODE);
            break;
        }
    }
}

bool cheatsImportGSACodeFile(const char* name, int game, bool v3)
{
    FILE* f = utilOpenFile(name, "rb");
    if (!f)
        return false;

    int games = 0;
    fseek(f, 0x1e, SEEK_CUR);
    if(fread(&games, 1, 4, f) == 0)
    {
        fclose(f);
        return false;
    }

    uint32_t len = 0;
    bool found = false;
    int g = 0;
    while (games > 0) {
        if (g == game) {
            found = true;
            break;
        }
        FREAD_UNCHECKED(&len, 1, 4, f);
        fseek(f, len, SEEK_CUR);
        int codes = 0;
        FREAD_UNCHECKED(&codes, 1, 4, f);
        while (codes > 0) {
            FREAD_UNCHECKED(&len, 1, 4, f);
            fseek(f, len, SEEK_CUR);
            fseek(f, 8, SEEK_CUR);
            FREAD_UNCHECKED(&len, 1, 4, f);
            fseek(f, len * 12, SEEK_CUR);
            codes--;
        }
        games--;
        g++;
    }
    if (found) {
        char desc[256];
        char code[17];
        FREAD_UNCHECKED(&len, 1, 4, f);
        fseek(f, len, SEEK_CUR);
        int codes = 0;
        FREAD_UNCHECKED(&codes, 1, 4, f);
        while (codes > 0) {
            FREAD_UNCHECKED(&len, 1, 4, f);
	    if (len > 255)
		    goto evil_gsa_code_file;	//XXX: this functione needs a rewrite in general, so for the short this is better than nothing
            FREAD_UNCHECKED(desc, 1, len, f);
            desc[len] = 0;
            desc[31] = 0;
            FREAD_UNCHECKED(&len, 1, 4, f);
            fseek(f, len, SEEK_CUR);
            fseek(f, 4, SEEK_CUR);
            FREAD_UNCHECKED(&len, 1, 4, f);
            while (len) {
                fseek(f, 4, SEEK_CUR);
                FREAD_UNCHECKED(code, 1, 8, f);
                fseek(f, 4, SEEK_CUR);
                FREAD_UNCHECKED(&code[8], 1, 8, f);
                code[16] = 0;
                cheatsAddGSACode(code, desc, v3);
                len -= 2;
            }
            codes--;
        }
    }
evil_gsa_code_file:
    fclose(f);
    return true;
}

void cheatsCBAReverseArray(uint8_t* array, uint8_t* dest)
{
    dest[0] = array[3];
    dest[1] = array[2];
    dest[2] = array[1];
    dest[3] = array[0];
    dest[4] = array[5];
    dest[5] = array[4];
}

void chatsCBAScramble(uint8_t* array, int count, uint8_t b)
{
    uint8_t* x = array + (count >> 3);
    uint8_t* y = array + (b >> 3);
    uint32_t z = *x & (1 << (count & 7));
    uint32_t x0 = (*x & (~(1 << (count & 7))));
    if (z != 0)
        z = 1;
    if ((*y & (1 << (b & 7))) != 0)
        x0 |= (1 << (count & 7));
    *x = (uint8_t)x0;
    uint32_t temp = *y & (~(1 << (b & 7)));
    if (z != 0)
        temp |= (1 << (b & 7));
    *y = (uint8_t)temp;
}

uint32_t cheatsCBAGetValue(uint8_t* array)
{
    return array[0] | array[1] << 8 | array[2] << 16 | array[3] << 24;
}

uint16_t cheatsCBAGetData(uint8_t* array)
{
    return array[4] | array[5] << 8;
}

void cheatsCBAArrayToValue(uint8_t* array, uint8_t* dest)
{
    dest[0] = array[3];
    dest[1] = array[2];
    dest[2] = array[1];
    dest[3] = array[0];
    dest[4] = array[5];
    dest[5] = array[4];
}

void cheatsCBAParseSeedCode(uint32_t address, uint32_t value, uint32_t* array)
{
    array[0] = 1;
    array[1] = value & 0xFF;
    array[2] = (address >> 0x10) & 0xFF;
    array[3] = (value >> 8) & 0xFF;
    array[4] = (address >> 0x18) & 0x0F;
    array[5] = address & 0xFFFF;
    array[6] = address;
    array[7] = value;
}

uint32_t cheatsCBAEncWorker()
{
    uint32_t x = (cheatsCBATemporaryValue * 0x41c64e6d) + 0x3039;
    uint32_t y = (x * 0x41c64e6d) + 0x3039;
    uint32_t z = x >> 0x10;
    x = ((y >> 0x10) & 0x7fff) << 0x0f;
    z = (z << 0x1e) | x;
    x = (y * 0x41c64e6d) + 0x3039;
    cheatsCBATemporaryValue = x;
    return z | ((x >> 0x10) & 0x7fff);
}

#define ROR(v, s) \
    (((v) >> (s)) | (((v) & ((1 << (s)) - 1)) << (32 - (s))))

uint32_t cheatsCBACalcIndex(uint32_t x, uint32_t y)
{
    if (y != 0) {
        if (y == 1)
            x = 0;
        else if (x == y)
            x = 0;
        if (y < 1)
            return x;
        else if (x < y)
            return x;
        uint32_t x0 = 1;

        while (y < 0x10000000) {
            if (y < x) {
                y = y << 4;
                x0 = x0 << 4;
            } else
                break;
        }

        while (y < 0x80000000) {
            if (y < x) {
                y = y << 1;
                x0 = x0 << 1;
            } else
                break;
        }

    loop:
        uint32_t z = 0;
        if (x >= y)
            x -= y;
        if (x >= (y >> 1)) {
            x -= (y >> 1);
            z |= ROR(x0, 1);
        }
        if (x >= (y >> 2)) {
            x -= (y >> 2);
            z |= ROR(x0, 2);
        }
        if (x >= (y >> 3)) {
            x -= (y >> 3);
            z |= ROR(x0, 3);
        }

        uint32_t temp = x0;

        if (x != 0) {
            x0 = x0 >> 4;
            if (x0 != 0) {
                y = y >> 4;
                goto loop;
            }
        }

        z = z & 0xe0000000;

        if (z != 0) {
            if ((temp & 7) == 0)
                return x;
        } else
            return x;

        if ((z & ROR(temp, 3)) != 0)
            x += y >> 3;
        if ((z & ROR(temp, 2)) != 0)
            x += y >> 2;
        if ((z & ROR(temp, 1)) != 0)
            x += y >> 1;
        return x;
    } else {
    }
    // should not happen in the current code
    return 0;
}

void cheatsCBAUpdateSeedBuffer(uint32_t address, uint8_t* buffer, int count)
{
    int i;
    for (i = 0; i < count; i++)
        buffer[i] = (uint8_t)i;
    for (i = 0; (uint32_t)i < address; i++) {
        uint32_t a = cheatsCBACalcIndex(cheatsCBAEncWorker(), count);
        uint32_t b = cheatsCBACalcIndex(cheatsCBAEncWorker(), count);
        uint32_t t = buffer[a];
        buffer[a] = buffer[b];
        buffer[b] = (uint8_t)t;
    }
}

void cheatsCBAChangeEncryption(uint32_t* seed)
{
    int i;

    cheatsCBATemporaryValue = (seed[1] ^ 0x1111);
    cheatsCBAUpdateSeedBuffer(0x50, cheatsCBASeedBuffer, 0x30);
    cheatsCBATemporaryValue = 0x4efad1c3;

    for (i = 0; (uint32_t)i < seed[4]; i++) {
        cheatsCBATemporaryValue = cheatsCBAEncWorker();
    }
    cheatsCBASeed[2] = cheatsCBAEncWorker();
    cheatsCBASeed[3] = cheatsCBAEncWorker();

    cheatsCBATemporaryValue = seed[3] ^ 0xf254;

    for (i = 0; (uint32_t)i < seed[3]; i++) {
        cheatsCBATemporaryValue = cheatsCBAEncWorker();
    }

    cheatsCBASeed[0] = cheatsCBAEncWorker();
    cheatsCBASeed[1] = cheatsCBAEncWorker();

    *((uint32_t*)&cheatsCBACurrentSeed[0]) = seed[6];
    *((uint32_t*)&cheatsCBACurrentSeed[4]) = seed[7];
    *((uint32_t*)&cheatsCBACurrentSeed[8]) = 0;
}

uint16_t cheatsCBAGenValue(uint32_t x, uint32_t y, uint32_t z)
{
    y <<= 0x10;
    z <<= 0x10;
    x <<= 0x18;
    uint32_t x0 = (int)y >> 0x10;
    z = (int)z >> 0x10;
    x = (int)x >> 0x10;
    for (int i = 0; i < 8; i++) {
        uint32_t temp = z ^ x;
        if ((int)temp >= 0) {
            temp = z << 0x11;
        } else {
            temp = z << 0x01;
            temp ^= x0;
            temp = temp << 0x10;
        }
        z = (int)temp >> 0x10;
        temp = x << 0x11;
        x = (int)temp >> 0x10;
    }
    return z & 0xffff;
}

void cheatsCBAGenTable()
{
    for (int i = 0; i < 0x100; i++) {
        cheatsCBATable[i] = cheatsCBAGenValue(i, 0x1021, 0);
    }
    cheatsCBATableGenerated = true;
}

uint16_t cheatsCBACalcCRC(uint8_t* rom, int count)
{
    uint32_t crc = 0xffffffff;

    if (count & 3) {
        // 0x08000EAE
    } else {
        count = (count >> 2) - 1;
        if (count != -1) {
            while (count != -1) {
                crc = (((crc << 0x08) ^ cheatsCBATable[(((uint32_t)crc << 0x10) >> 0x18)
                                            ^ *rom++])
                          << 0x10)
                    >> 0x10;
                crc = (((crc << 0x08) ^ cheatsCBATable[(((uint32_t)crc << 0x10) >> 0x18)
                                            ^ *rom++])
                          << 0x10)
                    >> 0x10;
                crc = (((crc << 0x08) ^ cheatsCBATable[(((uint32_t)crc << 0x10) >> 0x18)
                                            ^ *rom++])
                          << 0x10)
                    >> 0x10;
                crc = (((crc << 0x08) ^ cheatsCBATable[(((uint32_t)crc << 0x10) >> 0x18)
                                            ^ *rom++])
                          << 0x10)
                    >> 0x10;
                count--;
            }
        }
    }
    return crc & 0xffff;
}

void cheatsCBADecrypt(uint8_t* decrypt)
{
    uint8_t buffer[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
    uint8_t* array = &buffer[1];

    cheatsCBAReverseArray(decrypt, array);

    for (int count = 0x2f; count >= 0; count--) {
        chatsCBAScramble(array, count, cheatsCBASeedBuffer[count]);
    }
    cheatsCBAArrayToValue(array, decrypt);
    *((uint32_t*)decrypt) = cheatsCBAGetValue(decrypt) ^ cheatsCBASeed[0];
    *((uint16_t*)(decrypt + 4)) = (cheatsCBAGetData(decrypt) ^ cheatsCBASeed[1]) & 0xffff;

    cheatsCBAReverseArray(decrypt, array);

    uint32_t cs = cheatsCBAGetValue(cheatsCBACurrentSeed);
    for (int i = 0; i <= 4; i++) {
        array[i] = (uint8_t)(((cs >> 8) ^ array[i + 1]) ^ array[i]);
    }

    array[5] = (uint8_t)((cs >> 8) ^ array[5]);

    for (int j = 5; j >= 0; j--) {
        array[j] = (uint8_t)((cs ^ array[j - 1]) ^ array[j]);
    }

    cheatsCBAArrayToValue(array, decrypt);

    *((uint32_t*)decrypt) = cheatsCBAGetValue(decrypt)
        ^ cheatsCBASeed[2];
    *((uint16_t*)(decrypt + 4)) = (cheatsCBAGetData(decrypt)
                                 ^ cheatsCBASeed[3])
        & 0xffff;
}

int cheatsCBAGetCount()
{
    int count = 0;
    for (int i = 0; i < cheatsNumber; i++) {
        if (cheatsList[i].code == 512)
            count++;
    }
    return count;
}

bool cheatsCBAShouldDecrypt()
{
    for (int i = 0; i < cheatsNumber; i++) {
        if (cheatsList[i].code == 512) {
            return (cheatsList[i].codestring[0] == '9');
        }
    }
    return false;
}

void cheatsAddCBACode(const char* code, const char* desc)
{
    if (strlen(code) != 13) {
        // wrong cheat
        systemMessage(MSG_INVALID_CBA_CODE,
            N_("Invalid CBA code. Format is XXXXXXXX YYYY."));
        return;
    }

    int i;
    for (i = 0; i < 8; i++) {
        if (!CHEAT_IS_HEX(code[i])) {
            // wrong cheat
            systemMessage(MSG_INVALID_CBA_CODE,
                N_("Invalid CBA code. Format is XXXXXXXX YYYY."));
            return;
        }
    }

    if (code[8] != ' ') {
        systemMessage(MSG_INVALID_CBA_CODE,
            N_("Invalid CBA code. Format is XXXXXXXX YYYY."));
        return;
    }

    for (i = 9; i < 13; i++) {
        if (!CHEAT_IS_HEX(code[i])) {
            // wrong cheat
            systemMessage(MSG_INVALID_CBA_CODE,
                N_("Invalid CBA code. Format is XXXXXXXX YYYY."));
            return;
        }
    }

    char buffer[10];
#if __STDC_WANT_SECURE_LIB__
    strncpy_s(buffer, sizeof(buffer), code, 8);
#else
    strncpy(buffer, code, 8);
#endif
    buffer[8] = 0;
    uint32_t address;
    sscanf(buffer, "%x", &address);
#if __STDC_WANT_SECURE_LIB__
    strncpy_s(buffer, sizeof(buffer), &code[9], 4);
#else
    strncpy(buffer, &code[9], 4);
#endif
    buffer[4] = 0;
    uint32_t value;
    sscanf(buffer, "%x", &value);

    uint8_t array[8] = {
        (uint8_t)(address & 255),
        (uint8_t)((address >> 8) & 255),
        (uint8_t)((address >> 16) & 255),
        (uint8_t)((address >> 24) & 255),
        (uint8_t)(value & 255),
        (uint8_t)((value >> 8) & 255),
        0,
        0
    };

    if (cheatsCBAGetCount() == 0 && (address >> 28) == 9) {
        uint32_t seed[8];
        cheatsCBAParseSeedCode(address, value, seed);
        cheatsCBAChangeEncryption(seed);
        cheatsAdd(code, desc, address, address & 0x0FFFFFFF, value, 512, UNKNOWN_CODE);
    } else {
        if (cheatsCBAShouldDecrypt())
            cheatsCBADecrypt(array);

        address = READ32LE(((uint32_t*)array));
        value = READ16LE(((uint16_t*)&array[4]));

        int type = (address >> 28) & 15;

        if (isMultilineWithData(cheatsNumber - 1) || (super > 0)) {
            cheatsAdd(code, desc, address, address, value, 512, UNKNOWN_CODE);
            if (super > 0)
                super -= 1;
            return;
        }

        switch (type) {
        case 0x00: {
            if (!cheatsCBATableGenerated)
                cheatsCBAGenTable();
            uint32_t crc = cheatsCBACalcCRC(g_rom, 0x10000);
            if (crc != address) {
                systemMessage(MSG_CBA_CODE_WARNING,
                    N_("Warning: Codes seem to be for a different game.\nCodes may not work correctly."));
            }
            cheatsAdd(code, desc, address, address & 0x0FFFFFFF, value, 512,
                UNKNOWN_CODE);
        } break;
        case 0x01:
            cheatsAdd(code, desc, address, (address & 0x1FFFFFF) | 0x08000000, value, 512, MASTER_CODE);
            mastercode = (address & 0x1FFFFFF) | 0x08000000;
            break;
        case 0x02:
            cheatsAdd(code, desc, address, address & 0x0FFFFFFE, value, 512,
                CBA_OR);
            break;
        case 0x03:
            cheatsAdd(code, desc, address, address & 0x0FFFFFFF, value, 512,
                INT_8_BIT_WRITE);
            break;
        case 0x04:
            cheatsAdd(code, desc, address, address & 0x0FFFFFFE, value, 512,
                CBA_SLIDE_CODE);
            break;
        case 0x05:
            cheatsAdd(code, desc, address, address & 0x0FFFFFFE, value, 512,
                CBA_SUPER);
            super = (uint16_t)getCodeLength(cheatsNumber - 1);
            break;
        case 0x06:
            cheatsAdd(code, desc, address, address & 0x0FFFFFFE, value, 512,
                CBA_AND);
            break;
        case 0x07:
            cheatsAdd(code, desc, address, address & 0x0FFFFFFE, value, 512,
                CBA_IF_TRUE);
            break;
        case 0x08:
            cheatsAdd(code, desc, address, address & 0x0FFFFFFE, value, 512,
                INT_16_BIT_WRITE);
            break;
        case 0x0a:
            cheatsAdd(code, desc, address, address & 0x0FFFFFFE, value, 512,
                CBA_IF_FALSE);
            break;
        case 0x0b:
            cheatsAdd(code, desc, address, address & 0x0FFFFFFE, value, 512,
                CBA_GT);
            break;
        case 0x0c:
            cheatsAdd(code, desc, address, address & 0x0FFFFFFE, value, 512,
                CBA_LT);
            break;
        case 0x0d:
            if ((address & 0xF0) < 0x30)
                cheatsAdd(code, desc, address, address & 0xF0, value, 512,
                    CBA_IF_KEYS_PRESSED);
            break;
        case 0x0e:
            cheatsAdd(code, desc, address, address & 0x0FFFFFFF, value & 0x8000 ? value | 0xFFFF0000 : value, 512,
                CBA_ADD);
            break;
        case 0x0f:
            cheatsAdd(code, desc, address, address & 0x0FFFFFFE, value, 512,
                GSA_16_BIT_IF_AND);
            break;
        default:
            // unsupported code
            cheatsAdd(code, desc, address, address & 0xFFFFFFFF, value, 512,
                UNKNOWN_CODE);
            break;
        }
    }
}

#ifndef __LIBRETRO__
void cheatsSaveGame(gzFile file)
{
    utilWriteInt(file, cheatsNumber);

    utilGzWrite(file, cheatsList, sizeof(cheatsList));
}

void cheatsReadGame(gzFile file, int version)
{
    cheatsNumber = 0;

    cheatsNumber = utilReadInt(file);

    if (cheatsNumber > MAX_CHEATS)
        cheatsNumber = MAX_CHEATS;

    if (version > 8)
        utilGzRead(file, cheatsList, sizeof(cheatsList));

    bool firstCodeBreaker = true;

    for (int i = 0; i < cheatsNumber; i++) {
        if (version < 9) {
            cheatsList[i].code = utilReadInt(file);
            cheatsList[i].size = utilReadInt(file);
            cheatsList[i].status = utilReadInt(file);
            cheatsList[i].enabled = utilReadInt(file) ? true : false;
            utilGzRead(file, &cheatsList[i].address, sizeof(uint32_t));
            cheatsList[i].rawaddress = cheatsList[i].address;
            utilGzRead(file, &cheatsList[i].value, sizeof(uint32_t));
            utilGzRead(file, &cheatsList[i].oldValue, sizeof(uint32_t));
            utilGzRead(file, &cheatsList[i].codestring, 20 * sizeof(char));
            utilGzRead(file, &cheatsList[i].desc, 32 * sizeof(char));
        }

        cheatsList[i].status = 0;
        if (!cheatsList[i].codestring[0]) {
            switch (cheatsList[i].size) {
            case 0:
                snprintf(cheatsList[i].codestring, sizeof(cheatsList[i].codestring), "%08x:%02x", cheatsList[i].address,
                    cheatsList[i].value);
                break;
            case 1:
                snprintf(cheatsList[i].codestring, sizeof(cheatsList[i].codestring), "%08x:%04x", cheatsList[i].address,
                    cheatsList[i].value);
                break;
            case 2:
                snprintf(cheatsList[i].codestring, sizeof(cheatsList[i].codestring), "%08x:%08x", cheatsList[i].address,
                    cheatsList[i].value);
                break;
            }
        }

        if (cheatsList[i].enabled) {
            cheatsEnable(i);
        }

        if (cheatsList[i].code == 512 && firstCodeBreaker) {
            firstCodeBreaker = false;
            char buffer[10];
#if __STDC_WANT_SECURE_LIB__
            strncpy_s(buffer, sizeof(buffer), cheatsList[i].codestring, 8);
#else
            strncpy(buffer, cheatsList[i].codestring, 8);
#endif
            buffer[8] = 0;
            uint32_t address;
            sscanf(buffer, "%x", &address);
            if ((address >> 28) == 9) {
#if __STDC_WANT_SECURE_LIB__
                strncpy_s(buffer, sizeof(buffer), &cheatsList[i].codestring[9], 4);
#else
                strncpy(buffer, &cheatsList[i].codestring[9], 4);
#endif
                buffer[4] = 0;
                uint32_t value;
                sscanf(buffer, "%x", &value);

                uint32_t seed[8];
                cheatsCBAParseSeedCode(address, value, seed);
                cheatsCBAChangeEncryption(seed);
            }
        }
    }
}

// skip the cheat list data
void cheatsReadGameSkip(gzFile file, int version)
{
    int nCheats = 0;
    nCheats = utilReadInt(file);

    if (version >= 9) {
        utilGzSeek(file, sizeof(cheatsList), SEEK_CUR);
    }

    for (int i = 0; i < nCheats; i++) {
        if (version < 9) {
            utilGzSeek(file, (7 * sizeof(int)) + (52 * sizeof(char)), SEEK_CUR);
        }
    }
}

void cheatsSaveCheatList(const char* file)
{
    if (cheatsNumber == 0)
        return;
    FILE* f = utilOpenFile(file, "wb");
    if (f == NULL)
        return;
    int version = 1;
    fwrite(&version, 1, sizeof(version), f);
    int type = 1;
    fwrite(&type, 1, sizeof(type), f);
    fwrite(&cheatsNumber, 1, sizeof(cheatsNumber), f);
    fwrite(cheatsList, 1, sizeof(CheatsData) * cheatsNumber, f);
    fclose(f);
}

bool cheatsLoadCheatList(const char* file)
{

    int count = 0;

    FILE* f = utilOpenFile(file, "rb");

    if (f == NULL)
        return false;

    int version = 0;

    if (fread(&version, 1, sizeof(version), f) != sizeof(version)) {
        fclose(f);
        return false;
    }

    if (version != 1) {
        systemMessage(MSG_UNSUPPORTED_CHEAT_LIST_VERSION,
            N_("Unsupported cheat list version %d"), version);
        fclose(f);
        return false;
    }

    int type = 0;
    if (fread(&type, 1, sizeof(type), f) != sizeof(type)) {
        fclose(f);
        return false;
    }

    if ((type != 0) && (type != 1)) {
        systemMessage(MSG_UNSUPPORTED_CHEAT_LIST_TYPE,
            N_("Unsupported cheat list type %d"), type);
        fclose(f);
        return false;
    }

    if (fread(&count, 1, sizeof(count), f) != sizeof(count)) {
        fclose(f);
        return false;
    }
    if (type == 1) {
        if (fread(cheatsList, 1, sizeof(cheatsList), f) > sizeof(cheatsList)) {
            fclose(f);
            return false;
        }
    } else if (type == 0) {
        for (int i = 0; i < count; i++) {
            FREAD_UNCHECKED(&cheatsList[i].code, 1, sizeof(int), f);
            FREAD_UNCHECKED(&cheatsList[i].size, 1, sizeof(int), f);
            FREAD_UNCHECKED(&cheatsList[i].status, 1, sizeof(int), f);
            FREAD_UNCHECKED(&cheatsList[i].enabled, 1, sizeof(int), f);
            cheatsList[i].enabled = cheatsList[i].enabled ? true : false;
            FREAD_UNCHECKED(&cheatsList[i].address, 1, sizeof(uint32_t), f);
            cheatsList[i].rawaddress = cheatsList[i].address;
            FREAD_UNCHECKED(&cheatsList[i].value, 1, sizeof(uint32_t), f);
            FREAD_UNCHECKED(&cheatsList[i].oldValue, 1, sizeof(uint32_t), f);
            FREAD_UNCHECKED(&cheatsList[i].codestring, 1, 20 * sizeof(char), f);
            if (fread(&cheatsList[i].desc, 1, 32 * sizeof(char), f) != 32 * sizeof(char)) {
                fclose(f);
                return false;
            }
        }
    }

    bool firstCodeBreaker = true;

    for (int i = 0; i < count; i++) {
        cheatsList[i].status = 0; // remove old status as it is not used
        if (!cheatsList[i].codestring[0]) {
            switch (cheatsList[i].size) {
            case 0:
                snprintf(cheatsList[i].codestring, sizeof(cheatsList[i].codestring), "%08x:%02x", cheatsList[i].address,
                    cheatsList[i].value);
                break;
            case 1:
                snprintf(cheatsList[i].codestring, sizeof(cheatsList[i].codestring), "%08x:%04x", cheatsList[i].address,
                    cheatsList[i].value);
                break;
            case 2:
                snprintf(cheatsList[i].codestring, sizeof(cheatsList[i].codestring), "%08x:%08x", cheatsList[i].address,
                    cheatsList[i].value);
                break;
            }
        }

        if (cheatsList[i].code == 512 && firstCodeBreaker) {
            firstCodeBreaker = false;
            char buffer[10];
#if __STDC_WANT_SECURE_LIB__
            strncpy_s(buffer, sizeof(buffer), cheatsList[i].codestring, 8);
#else
            strncpy(buffer, cheatsList[i].codestring, 8);
#endif
            buffer[8] = 0;
            uint32_t address;
            sscanf(buffer, "%x", &address);
            if ((address >> 28) == 9) {
#if __STDC_WANT_SECURE_LIB__
                strncpy_s(buffer, sizeof(buffer), &cheatsList[i].codestring[9], 4);
#else
                strncpy(buffer, &cheatsList[i].codestring[9], 4);
#endif
                buffer[4] = 0;
                uint32_t value;
                sscanf(buffer, "%x", &value);

                uint32_t seed[8];
                cheatsCBAParseSeedCode(address, value, seed);
                cheatsCBAChangeEncryption(seed);
            }
        }
    }
    cheatsNumber = count;
    fclose(f);
    return true;
}
#endif

extern int cpuNextEvent;

extern void debuggerBreakOnWrite(uint32_t, uint32_t, uint32_t, int, int);

#ifdef VBAM_ENABLE_DEBUGGER
static uint8_t cheatsGetType(uint32_t address)
{
    switch (address >> 24) {
    case 2:
        return freezeWorkRAM[address & 0x3FFFF];
    case 3:
        return freezeInternalRAM[address & 0x7FFF];
    case 5:
        return freezePRAM[address & 0x3FC];
    case 6:
        if (address > 0x06010000)
            return freezeVRAM[address & 0x17FFF];
        else
            return freezeVRAM[address & 0x1FFFF];
    case 7:
        return freezeOAM[address & 0x3FC];
    }
    return 0;
}
#endif

#ifdef VBAM_ENABLE_DEBUGGER
void cheatsWriteMemory(uint32_t address, uint32_t value)
{
    if (cheatsNumber == 0) {
        int type = cheatsGetType(address);
        uint32_t oldValue = debuggerReadMemory(address);
        if (type == 1 || (type == 2 && oldValue != value)) {
            debuggerBreakOnWrite(address, oldValue, value, 2, type);
            cpuNextEvent = 0;
        }
        debuggerWriteMemory(address, value);
    }
}

void cheatsWriteHalfWord(uint32_t address, uint16_t value)
{
    if (cheatsNumber == 0) {
        int type = cheatsGetType(address);
        uint16_t oldValue = debuggerReadHalfWord(address);
        if (type == 1 || (type == 2 && oldValue != value)) {
            debuggerBreakOnWrite(address, oldValue, value, 1, type);
            cpuNextEvent = 0;
        }
        debuggerWriteHalfWord(address, value);
    }
}

void cheatsWriteByte(uint32_t address, uint8_t value)
{
    if (cheatsNumber == 0) {
        int type = cheatsGetType(address);
        uint8_t oldValue = debuggerReadByte(address);
        if (type == 1 || (type == 2 && oldValue != value)) {
            debuggerBreakOnWrite(address, oldValue, value, 0, type);
            cpuNextEvent = 0;
        }
        debuggerWriteByte(address, value);
    }
}
#endif
