From ef65d185a3dec0bd89d085e49759c42db210c1ed Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 5 Feb 2015 03:20:02 -0800 Subject: [PATCH] GBA: Support for unencrypted CodeBreaker codes --- CHANGES | 1 + src/gba/cheats.c | 361 ++++++++++++++++++++++++++++++++++++ src/gba/cheats.h | 87 +++++++++ src/gba/gba.c | 46 ++++- src/gba/gba.h | 4 + src/gba/supervisor/thread.c | 25 ++- src/gba/supervisor/thread.h | 6 +- src/platform/commandline.c | 7 +- src/platform/commandline.h | 1 + 9 files changed, 525 insertions(+), 13 deletions(-) create mode 100644 src/gba/cheats.c create mode 100644 src/gba/cheats.h diff --git a/CHANGES b/CHANGES index 719fea128..03316ec6e 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,7 @@ Features: - Configurable game overrides - Support loading 7-Zip files - Drag and drop game loading + - Cheat code support Bugfixes: - ARM7: Extend prefetch by one stage - GBA Audio: Support 16-bit writes to FIFO audio diff --git a/src/gba/cheats.c b/src/gba/cheats.c new file mode 100644 index 000000000..e98453fd1 --- /dev/null +++ b/src/gba/cheats.c @@ -0,0 +1,361 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "cheats.h" + +#include "gba/gba.h" +#include "gba/io.h" + +const uint32_t GBA_CHEAT_DEVICE_ID = 0xABADC0DE; + +DEFINE_VECTOR(GBACheatList, struct GBACheat); + +static int32_t _read(struct ARMCore* cpu, uint32_t address, int width) { + switch (width) { + case 1: + return cpu->memory.load8(cpu, address, 0); + case 2: + return cpu->memory.load16(cpu, address, 0); + case 4: + return cpu->memory.load32(cpu, address, 0); + } + return 0; +} + +static void _write(struct ARMCore* cpu, uint32_t address, int width, int32_t value) { + switch (width) { + case 1: + cpu->memory.store8(cpu, address, value, 0); + break; + case 2: + cpu->memory.store16(cpu, address, value, 0); + break; + case 4: + cpu->memory.store32(cpu, address, value, 0); + break; + } +} + +static int _hexDigit(char digit) { + switch (digit) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return digit - '0'; + + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + return digit - 'a' + 10; + + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + return digit - 'A' + 10; + + default: + return -1; + } +} + +static const char* _hex32(const char* line, uint32_t* out) { + uint32_t value = 0; + int i; + for (i = 0; i < 8; ++i, ++line) { + char digit = *line; + value <<= 4; + int nybble = _hexDigit(digit); + if (nybble < 0) { + return 0; + } + value |= nybble; + } + *out = value; + return line; +} + +static const char* _hex16(const char* line, uint16_t* out) { + uint16_t value = 0; + int i; + for (i = 0; i < 4; ++i, ++line) { + char digit = *line; + value <<= 4; + int nybble = _hexDigit(digit); + if (nybble < 0) { + return 0; + } + value |= nybble; + } + *out = value; + return line; +} + +static void _addBreakpoint(struct GBACheatDevice* device) { + if (!device->cheats || !device->p) { + return; + } + GBASetBreakpoint(device->p, &device->d, device->cheats->hookAddress, device->cheats->hookMode, &device->cheats->patchedOpcode); +} + +static void _removeBreakpoint(struct GBACheatDevice* device) { + if (!device->cheats || !device->p) { + return; + } + GBAClearBreakpoint(device->p, device->cheats->hookAddress, device->cheats->hookMode, device->cheats->patchedOpcode); +} + +static void GBACheatDeviceInit(struct ARMCore*, struct ARMComponent*); +static void GBACheatDeviceDeinit(struct ARMComponent*); + +void GBACheatDeviceCreate(struct GBACheatDevice* device) { + device->d.id = GBA_CHEAT_DEVICE_ID; + device->d.init = GBACheatDeviceInit; + device->d.deinit = GBACheatDeviceDeinit; +} + +void GBACheatSetInit(struct GBACheatSet* set) { + set->hookAddress = 0; + set->hookMode = MODE_THUMB; + GBACheatListInit(&set->list, 4); + set->incompleteCheat = 0; + set->patchedOpcode = 0; +} + +void GBACheatSetDeinit(struct GBACheatSet* set) { + GBACheatListDeinit(&set->list); +} + +void GBACheatAttachDevice(struct GBA* gba, struct GBACheatDevice* device) { + if (gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) { + ARMHotplugDetach(gba->cpu, GBA_COMPONENT_CHEAT_DEVICE); + } + gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE] = &device->d; + ARMHotplugAttach(gba->cpu, GBA_COMPONENT_CHEAT_DEVICE); +} + +void GBACheatInstallSet(struct GBACheatDevice* device, struct GBACheatSet* cheats) { + _removeBreakpoint(device); + device->cheats = cheats; + _addBreakpoint(device); +} + +bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t op2) { + enum GBACodeBreakerType type = op1 >> 28; + struct GBACheat* cheat = 0; + + if (cheats->incompleteCheat) { + cheats->incompleteCheat->repeat = op1 & 0xFFFF; + cheats->incompleteCheat->addressOffset = op2; + cheats->incompleteCheat->operandOffset = 0; + cheats->incompleteCheat = 0; + return true; + } + + switch (type) { + case CB_GAME_ID: + // TODO: Run checksum + return true; + case CB_HOOK: + if (cheats->hookAddress) { + return false; + } + cheats->hookAddress = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); + cheats->hookMode = MODE_THUMB; + return true; + case CB_OR_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_OR; + cheat->width = 2; + break; + case CB_ASSIGN_1: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 1; + break; + case CB_FILL: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + cheats->incompleteCheat = cheat; + break; + case CB_FILL_8: + GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2); + return false; + case CB_AND_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_AND; + cheat->width = 2; + break; + case CB_IF_EQ: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_EQ; + cheat->width = 2; + break; + case CB_ASSIGN_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + break; + case CB_ENCRYPT: + GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker encryption not supported"); + return false; + case CB_IF_NE: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_NE; + cheat->width = 2; + break; + case CB_IF_GT: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_GT; + cheat->width = 2; + break; + case CB_IF_LT: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_LT; + cheat->width = 2; + break; + case CB_IF_SPECIAL: + switch (op1 & 0x0FFFFFFF) { + case 0x20: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_AND; + cheat->width = 2; + cheat->address = BASE_IO | REG_JOYSTAT; + cheat->operand = op2; + cheat->repeat = 1; + return true; + default: + GBALog(0, GBA_LOG_STUB, "[Cheat] CodeBreaker code %08X %04X not supported", op1, op2); + return false; + } + case CB_ADD_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ADD; + cheat->width = 2; + break; + case CB_IF_AND: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_AND; + cheat->width = 2; + break; + } + + cheat->address = op1 & 0x0FFFFFFF; + cheat->operand = op2; + cheat->repeat = 1; + return true; +} + +bool GBACheatAddCodeBreakerLine(struct GBACheatSet* cheats, const char* line) { + uint32_t op1; + uint16_t op2; + line = _hex32(line, &op1); + if (!line) { + return false; + } + while (*line == ' ') { + ++line; + } + line = _hex16(line, &op2); + if (!line) { + return false; + } + return GBACheatAddCodeBreaker(cheats, op1, op2); +} + +void GBACheatRefresh(struct GBACheatDevice* device) { + bool condition = true; + int conditionRemaining = 0; + + size_t nCodes = GBACheatListSize(&device->cheats->list); + size_t i; + for (i = 0; i < nCodes; ++i) { + if (conditionRemaining > 0) { + --conditionRemaining; + if (!condition) { + continue; + } + } else { + condition = true; + } + struct GBACheat* cheat = GBACheatListGetPointer(&device->cheats->list, i); + int32_t value = 0; + int32_t operand = cheat->operand; + uint32_t operationsRemaining = cheat->repeat; + uint32_t address = cheat->address; + bool performAssignment = false; + for (; operationsRemaining; --operationsRemaining) { + switch (cheat->type) { + case CHEAT_ASSIGN: + value = operand; + performAssignment = true; + break; + case CHEAT_AND: + value = _read(device->p->cpu, address, cheat->width) & operand; + performAssignment = true; + break; + case CHEAT_ADD: + value = _read(device->p->cpu, address, cheat->width) + operand; + performAssignment = true; + break; + case CHEAT_OR: + value = _read(device->p->cpu, address, cheat->width) | operand; + performAssignment = true; + break; + case CHEAT_IF_EQ: + condition = _read(device->p->cpu, address, cheat->width) == operand; + conditionRemaining = cheat->repeat; + break; + case CHEAT_IF_NE: + condition = _read(device->p->cpu, address, cheat->width) != operand; + conditionRemaining = cheat->repeat; + break; + case CHEAT_IF_LT: + condition = _read(device->p->cpu, address, cheat->width) < operand; + conditionRemaining = cheat->repeat; + break; + case CHEAT_IF_GT: + condition = _read(device->p->cpu, address, cheat->width) > operand; + conditionRemaining = cheat->repeat; + break; + case CHEAT_IF_AND: + condition = _read(device->p->cpu, address, cheat->width) & operand; + conditionRemaining = cheat->repeat; + break; + } + + if (performAssignment) { + _write(device->p->cpu, address, cheat->width, value); + } + + address += cheat->addressOffset; + operand += cheat->operandOffset; + } + } +} + +void GBACheatDeviceInit(struct ARMCore* cpu, struct ARMComponent* component) { + struct GBACheatDevice* device = (struct GBACheatDevice*) component; + device->p = (struct GBA*) cpu->master; + _addBreakpoint(device); +} + +void GBACheatDeviceDeinit(struct ARMComponent* component) { + struct GBACheatDevice* device = (struct GBACheatDevice*) component; + _removeBreakpoint(device); +} \ No newline at end of file diff --git a/src/gba/cheats.h b/src/gba/cheats.h new file mode 100644 index 000000000..633565d7c --- /dev/null +++ b/src/gba/cheats.h @@ -0,0 +1,87 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef GBA_CHEATS_H +#define GBA_CHEATS_H + +#include "util/common.h" + +#include "arm.h" +#include "util/vector.h" + +enum GBACheatType { + CHEAT_ASSIGN, + CHEAT_AND, + CHEAT_ADD, + CHEAT_OR, + CHEAT_IF_EQ, + CHEAT_IF_NE, + CHEAT_IF_LT, + CHEAT_IF_GT, + CHEAT_IF_AND +}; + +enum GBACodeBreakerType { + CB_GAME_ID = 0x0, + CB_HOOK = 0x1, + CB_OR_2 = 0x2, + CB_ASSIGN_1 = 0x3, + CB_FILL = 0x4, + CB_FILL_8 = 0x5, + CB_AND_2 = 0x6, + CB_IF_EQ = 0x7, + CB_ASSIGN_2 = 0x8, + CB_ENCRYPT = 0x9, + CB_IF_NE = 0xA, + CB_IF_GT = 0xB, + CB_IF_LT = 0xC, + CB_IF_SPECIAL = 0xD, + CB_ADD_2 = 0xE, + CB_IF_AND = 0xF, +}; + +struct GBACheat { + enum GBACheatType type; + int width; + uint32_t address; + uint32_t operand; + uint32_t repeat; + + int32_t addressOffset; + int32_t operandOffset; +}; + +DECLARE_VECTOR(GBACheatList, struct GBACheat); + +struct GBACheatSet { + uint32_t hookAddress; + enum ExecutionMode hookMode; + struct GBACheatList list; + + struct GBACheat* incompleteCheat; + uint32_t patchedOpcode; +}; + +struct GBACheatDevice { + struct ARMComponent d; + struct GBA* p; + + struct GBACheatSet* cheats; +}; + +void GBACheatDeviceCreate(struct GBACheatDevice*); + +void GBACheatSetInit(struct GBACheatSet*); +void GBACheatSetDeinit(struct GBACheatSet*); + +void GBACheatAttachDevice(struct GBA* gba, struct GBACheatDevice*); +void GBACheatInstallSet(struct GBACheatDevice*, struct GBACheatSet*); + +bool GBACheatAddCodeBreaker(struct GBACheatSet*, uint32_t op1, uint16_t op2); +bool GBACheatAddCodeBreakerLine(struct GBACheatSet*, const char* line); + +void GBACheatRefresh(struct GBACheatDevice*); + +#endif \ No newline at end of file diff --git a/src/gba/gba.c b/src/gba/gba.c index c26005a19..642653cd6 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -6,6 +6,7 @@ #include "gba.h" #include "gba/bios.h" +#include "gba/cheats.h" #include "gba/io.h" #include "gba/supervisor/rr.h" #include "gba/supervisor/thread.h" @@ -670,6 +671,15 @@ void GBABreakpoint(struct ARMCore* cpu, int immediate) { ARMDebuggerEnter(gba->debugger, DEBUGGER_ENTER_BREAKPOINT, &info); } break; + case GBA_COMPONENT_CHEAT_DEVICE: + if (gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) { + struct GBACheatDevice* device = (struct GBACheatDevice*) gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]; + if (device->cheats) { + GBACheatRefresh(device); + ARMRunFake(cpu, device->cheats->patchedOpcode); + } + } + break; default: break; } @@ -711,32 +721,48 @@ void GBAFrameEnded(struct GBA* gba) { } } -static bool _setSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode, uint32_t* opcode) { - int immediate = GBA_COMPONENT_DEBUGGER; +void GBASetBreakpoint(struct GBA* gba, struct ARMComponent* component, uint32_t address, enum ExecutionMode mode, uint32_t* opcode) { + size_t immediate; + for (immediate = 0; immediate < gba->cpu->numComponents; ++immediate) { + if (gba->cpu->components[immediate] == component) { + break; + } + } + if (immediate == gba->cpu->numComponents) { + return; + } if (mode == MODE_ARM) { int32_t value; int32_t old; value = 0xE1200070; value |= immediate & 0xF; value |= (immediate & 0xFFF0) << 4; - GBAPatch32(debugger->cpu, address, value, &old); + GBAPatch32(gba->cpu, address, value, &old); *opcode = old; } else { int16_t value; int16_t old; value = 0xBE00; value |= immediate & 0xFF; - GBAPatch16(debugger->cpu, address, value, &old); - *opcode = old; + GBAPatch16(gba->cpu, address, value, &old); + *opcode = (uint16_t) old; } +} + +void GBAClearBreakpoint(struct GBA* gba, uint32_t address, enum ExecutionMode mode, uint32_t opcode) { + if (mode == MODE_ARM) { + GBAPatch32(gba->cpu, address, opcode, 0); + } else { + GBAPatch16(gba->cpu, address, opcode, 0); + } +} + +static bool _setSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode, uint32_t* opcode) { + GBASetBreakpoint((struct GBA*) debugger->cpu->master, &debugger->d, address, mode, opcode); return true; } static bool _clearSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode, uint32_t opcode) { - if (mode == MODE_ARM) { - GBAPatch32(debugger->cpu, address, opcode, 0); - } else { - GBAPatch16(debugger->cpu, address, opcode, 0); - } + GBAClearBreakpoint((struct GBA*) debugger->cpu->master, address, mode, opcode); return true; } diff --git a/src/gba/gba.h b/src/gba/gba.h index 91e33e513..630530ff8 100644 --- a/src/gba/gba.h +++ b/src/gba/gba.h @@ -72,6 +72,7 @@ enum GBAKey { enum GBAComponent { GBA_COMPONENT_DEBUGGER, + GBA_COMPONENT_CHEAT_DEVICE, GBA_COMPONENT_MAX }; @@ -185,6 +186,9 @@ void GBAHalt(struct GBA* gba); void GBAAttachDebugger(struct GBA* gba, struct ARMDebugger* debugger); void GBADetachDebugger(struct GBA* gba); +void GBASetBreakpoint(struct GBA* gba, struct ARMComponent* component, uint32_t address, enum ExecutionMode mode, uint32_t* opcode); +void GBAClearBreakpoint(struct GBA* gba, uint32_t address, enum ExecutionMode mode, uint32_t opcode); + void GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char* fname); void GBALoadBIOS(struct GBA* gba, struct VFile* vf); void GBAApplyPatch(struct GBA* gba, struct Patch* patch); diff --git a/src/gba/supervisor/thread.c b/src/gba/supervisor/thread.c index 4f8c028ea..7fdf4b390 100644 --- a/src/gba/supervisor/thread.c +++ b/src/gba/supervisor/thread.c @@ -7,8 +7,9 @@ #include "arm.h" #include "gba/gba.h" -#include "gba/supervisor/config.h" +#include "gba/cheats.h" #include "gba/serialize.h" +#include "gba/supervisor/config.h" #include "debugger/debugger.h" @@ -104,6 +105,8 @@ static THREAD_ENTRY _GBAThreadRun(void* context) { struct GBA gba; struct ARMCore cpu; struct Patch patch; + struct GBACheatDevice cheatDevice; + struct GBACheatSet cheats; struct GBAThread* threadContext = context; struct ARMComponent* components[GBA_COMPONENT_MAX] = {}; int numComponents = GBA_COMPONENT_MAX; @@ -164,6 +167,24 @@ static THREAD_ENTRY _GBAThreadRun(void* context) { GBASkipBIOS(&cpu); } + GBACheatDeviceCreate(&cheatDevice); + GBACheatSetInit(&cheats); + if (!threadContext->cheats) { + threadContext->cheats = &cheats; + } + if (threadContext->cheatsFile) { + char cheat[32]; + while (true) { + size_t bytesRead = threadContext->cheatsFile->readline(threadContext->cheatsFile, cheat, sizeof(cheat)); + if (!bytesRead) { + break; + } + GBACheatAddCodeBreakerLine(threadContext->cheats, cheat); + } + } + GBACheatInstallSet(&cheatDevice, threadContext->cheats); + GBACheatAttachDevice(&gba, &cheatDevice); + if (threadContext->debugger) { threadContext->debugger->log = GBADebuggerLogShim; GBAAttachDebugger(&gba, threadContext->debugger); @@ -232,6 +253,7 @@ static THREAD_ENTRY _GBAThreadRun(void* context) { threadContext->gba = 0; ARMDeinit(&cpu); GBADestroy(&gba); + GBACheatSetDeinit(&cheats); threadContext->sync.videoFrameOn = false; ConditionWake(&threadContext->sync.videoFrameAvailableCond); @@ -285,6 +307,7 @@ void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread* } threadContext->fname = args->fname; threadContext->patch = VFileOpen(args->patch, O_RDONLY); + threadContext->cheatsFile = VFileOpen(args->cheatsFile, O_RDONLY); } bool GBAThreadStart(struct GBAThread* threadContext) { diff --git a/src/gba/supervisor/thread.h b/src/gba/supervisor/thread.h index 2275459fc..8c4153266 100644 --- a/src/gba/supervisor/thread.h +++ b/src/gba/supervisor/thread.h @@ -16,7 +16,9 @@ struct GBAThread; struct GBAArguments; +struct GBACheatSet; struct GBAOptions; + typedef void (*ThreadCallback)(struct GBAThread* threadContext); typedef void (*LogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args); @@ -68,6 +70,7 @@ struct GBAThread { struct VFile* save; struct VFile* bios; struct VFile* patch; + struct VFile* cheatsFile; const char* fname; int activeKeys; struct GBAAVStream* stream; @@ -81,6 +84,7 @@ struct GBAThread { int frameskip; float fpsTarget; size_t audioBuffers; + bool skipBios; // Threading state Thread thread; @@ -106,7 +110,7 @@ struct GBAThread { struct GBASerializedState** rewindBuffer; int rewindBufferWriteOffset; - bool skipBios; + struct GBACheatSet* cheats; }; void GBAMapOptionsToContext(const struct GBAOptions*, struct GBAThread*); diff --git a/src/platform/commandline.c b/src/platform/commandline.c index 4bd8e21ac..90e00c96e 100644 --- a/src/platform/commandline.c +++ b/src/platform/commandline.c @@ -34,6 +34,7 @@ static const struct option _options[] = { { "bios", required_argument, 0, 'b' }, + { "cheats", required_argument, 0, 'c' }, { "dirmode", required_argument, 0, 'D' }, { "frameskip", required_argument, 0, 's' }, #ifdef USE_CLI_DEBUGGER @@ -51,7 +52,7 @@ bool _parseGraphicsArg(struct SubParser* parser, struct GBAConfig* config, int o bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int argc, char* const* argv, struct SubParser* subparser) { int ch; char options[64] = - "b:Dl:p:s:" + "b:c:Dl:p:s:" #ifdef USE_CLI_DEBUGGER "d" #endif @@ -68,6 +69,9 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg case 'b': GBAConfigSetDefaultValue(config, "bios", optarg); break; + case 'c': + opts->cheatsFile = strdup(optarg); + break; case 'D': opts->dirmode = true; break; @@ -199,6 +203,7 @@ void usage(const char* arg0, const char* extraOptions) { printf("usage: %s [option ...] file\n", arg0); puts("\nGeneric options:"); puts(" -b, --bios FILE GBA BIOS file to use"); + puts(" -c, --cheats FILE Apply cheat codes from a file"); #ifdef USE_CLI_DEBUGGER puts(" -d, --debug Use command-line debugger"); #endif diff --git a/src/platform/commandline.h b/src/platform/commandline.h index ab46ee8b4..a360c335d 100644 --- a/src/platform/commandline.h +++ b/src/platform/commandline.h @@ -24,6 +24,7 @@ enum DebuggerType { struct GBAArguments { char* fname; char* patch; + char* cheatsFile; bool dirmode; enum DebuggerType debuggerType;