GBA: Support for unencrypted CodeBreaker codes

This commit is contained in:
Jeffrey Pfau 2015-02-05 03:20:02 -08:00
parent af6ead2c97
commit ef65d185a3
9 changed files with 525 additions and 13 deletions

View File

@ -22,6 +22,7 @@ Features:
- Configurable game overrides - Configurable game overrides
- Support loading 7-Zip files - Support loading 7-Zip files
- Drag and drop game loading - Drag and drop game loading
- Cheat code support
Bugfixes: Bugfixes:
- ARM7: Extend prefetch by one stage - ARM7: Extend prefetch by one stage
- GBA Audio: Support 16-bit writes to FIFO audio - GBA Audio: Support 16-bit writes to FIFO audio

361
src/gba/cheats.c Normal file
View File

@ -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);
}

87
src/gba/cheats.h Normal file
View File

@ -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

View File

@ -6,6 +6,7 @@
#include "gba.h" #include "gba.h"
#include "gba/bios.h" #include "gba/bios.h"
#include "gba/cheats.h"
#include "gba/io.h" #include "gba/io.h"
#include "gba/supervisor/rr.h" #include "gba/supervisor/rr.h"
#include "gba/supervisor/thread.h" #include "gba/supervisor/thread.h"
@ -670,6 +671,15 @@ void GBABreakpoint(struct ARMCore* cpu, int immediate) {
ARMDebuggerEnter(gba->debugger, DEBUGGER_ENTER_BREAKPOINT, &info); ARMDebuggerEnter(gba->debugger, DEBUGGER_ENTER_BREAKPOINT, &info);
} }
break; 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: default:
break; 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) { void GBASetBreakpoint(struct GBA* gba, struct ARMComponent* component, uint32_t address, enum ExecutionMode mode, uint32_t* opcode) {
int immediate = GBA_COMPONENT_DEBUGGER; 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) { if (mode == MODE_ARM) {
int32_t value; int32_t value;
int32_t old; int32_t old;
value = 0xE1200070; value = 0xE1200070;
value |= immediate & 0xF; value |= immediate & 0xF;
value |= (immediate & 0xFFF0) << 4; value |= (immediate & 0xFFF0) << 4;
GBAPatch32(debugger->cpu, address, value, &old); GBAPatch32(gba->cpu, address, value, &old);
*opcode = old; *opcode = old;
} else { } else {
int16_t value; int16_t value;
int16_t old; int16_t old;
value = 0xBE00; value = 0xBE00;
value |= immediate & 0xFF; value |= immediate & 0xFF;
GBAPatch16(debugger->cpu, address, value, &old); GBAPatch16(gba->cpu, address, value, &old);
*opcode = 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; return true;
} }
static bool _clearSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode, uint32_t opcode) { static bool _clearSoftwareBreakpoint(struct ARMDebugger* debugger, uint32_t address, enum ExecutionMode mode, uint32_t opcode) {
if (mode == MODE_ARM) { GBAClearBreakpoint((struct GBA*) debugger->cpu->master, address, mode, opcode);
GBAPatch32(debugger->cpu, address, opcode, 0);
} else {
GBAPatch16(debugger->cpu, address, opcode, 0);
}
return true; return true;
} }

View File

@ -72,6 +72,7 @@ enum GBAKey {
enum GBAComponent { enum GBAComponent {
GBA_COMPONENT_DEBUGGER, GBA_COMPONENT_DEBUGGER,
GBA_COMPONENT_CHEAT_DEVICE,
GBA_COMPONENT_MAX GBA_COMPONENT_MAX
}; };
@ -185,6 +186,9 @@ void GBAHalt(struct GBA* gba);
void GBAAttachDebugger(struct GBA* gba, struct ARMDebugger* debugger); void GBAAttachDebugger(struct GBA* gba, struct ARMDebugger* debugger);
void GBADetachDebugger(struct GBA* gba); 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 GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char* fname);
void GBALoadBIOS(struct GBA* gba, struct VFile* vf); void GBALoadBIOS(struct GBA* gba, struct VFile* vf);
void GBAApplyPatch(struct GBA* gba, struct Patch* patch); void GBAApplyPatch(struct GBA* gba, struct Patch* patch);

View File

@ -7,8 +7,9 @@
#include "arm.h" #include "arm.h"
#include "gba/gba.h" #include "gba/gba.h"
#include "gba/supervisor/config.h" #include "gba/cheats.h"
#include "gba/serialize.h" #include "gba/serialize.h"
#include "gba/supervisor/config.h"
#include "debugger/debugger.h" #include "debugger/debugger.h"
@ -104,6 +105,8 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
struct GBA gba; struct GBA gba;
struct ARMCore cpu; struct ARMCore cpu;
struct Patch patch; struct Patch patch;
struct GBACheatDevice cheatDevice;
struct GBACheatSet cheats;
struct GBAThread* threadContext = context; struct GBAThread* threadContext = context;
struct ARMComponent* components[GBA_COMPONENT_MAX] = {}; struct ARMComponent* components[GBA_COMPONENT_MAX] = {};
int numComponents = GBA_COMPONENT_MAX; int numComponents = GBA_COMPONENT_MAX;
@ -164,6 +167,24 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
GBASkipBIOS(&cpu); 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) { if (threadContext->debugger) {
threadContext->debugger->log = GBADebuggerLogShim; threadContext->debugger->log = GBADebuggerLogShim;
GBAAttachDebugger(&gba, threadContext->debugger); GBAAttachDebugger(&gba, threadContext->debugger);
@ -232,6 +253,7 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
threadContext->gba = 0; threadContext->gba = 0;
ARMDeinit(&cpu); ARMDeinit(&cpu);
GBADestroy(&gba); GBADestroy(&gba);
GBACheatSetDeinit(&cheats);
threadContext->sync.videoFrameOn = false; threadContext->sync.videoFrameOn = false;
ConditionWake(&threadContext->sync.videoFrameAvailableCond); ConditionWake(&threadContext->sync.videoFrameAvailableCond);
@ -285,6 +307,7 @@ void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread*
} }
threadContext->fname = args->fname; threadContext->fname = args->fname;
threadContext->patch = VFileOpen(args->patch, O_RDONLY); threadContext->patch = VFileOpen(args->patch, O_RDONLY);
threadContext->cheatsFile = VFileOpen(args->cheatsFile, O_RDONLY);
} }
bool GBAThreadStart(struct GBAThread* threadContext) { bool GBAThreadStart(struct GBAThread* threadContext) {

View File

@ -16,7 +16,9 @@
struct GBAThread; struct GBAThread;
struct GBAArguments; struct GBAArguments;
struct GBACheatSet;
struct GBAOptions; struct GBAOptions;
typedef void (*ThreadCallback)(struct GBAThread* threadContext); typedef void (*ThreadCallback)(struct GBAThread* threadContext);
typedef void (*LogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args); typedef void (*LogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args);
@ -68,6 +70,7 @@ struct GBAThread {
struct VFile* save; struct VFile* save;
struct VFile* bios; struct VFile* bios;
struct VFile* patch; struct VFile* patch;
struct VFile* cheatsFile;
const char* fname; const char* fname;
int activeKeys; int activeKeys;
struct GBAAVStream* stream; struct GBAAVStream* stream;
@ -81,6 +84,7 @@ struct GBAThread {
int frameskip; int frameskip;
float fpsTarget; float fpsTarget;
size_t audioBuffers; size_t audioBuffers;
bool skipBios;
// Threading state // Threading state
Thread thread; Thread thread;
@ -106,7 +110,7 @@ struct GBAThread {
struct GBASerializedState** rewindBuffer; struct GBASerializedState** rewindBuffer;
int rewindBufferWriteOffset; int rewindBufferWriteOffset;
bool skipBios; struct GBACheatSet* cheats;
}; };
void GBAMapOptionsToContext(const struct GBAOptions*, struct GBAThread*); void GBAMapOptionsToContext(const struct GBAOptions*, struct GBAThread*);

View File

@ -34,6 +34,7 @@
static const struct option _options[] = { static const struct option _options[] = {
{ "bios", required_argument, 0, 'b' }, { "bios", required_argument, 0, 'b' },
{ "cheats", required_argument, 0, 'c' },
{ "dirmode", required_argument, 0, 'D' }, { "dirmode", required_argument, 0, 'D' },
{ "frameskip", required_argument, 0, 's' }, { "frameskip", required_argument, 0, 's' },
#ifdef USE_CLI_DEBUGGER #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) { bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int argc, char* const* argv, struct SubParser* subparser) {
int ch; int ch;
char options[64] = char options[64] =
"b:Dl:p:s:" "b:c:Dl:p:s:"
#ifdef USE_CLI_DEBUGGER #ifdef USE_CLI_DEBUGGER
"d" "d"
#endif #endif
@ -68,6 +69,9 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
case 'b': case 'b':
GBAConfigSetDefaultValue(config, "bios", optarg); GBAConfigSetDefaultValue(config, "bios", optarg);
break; break;
case 'c':
opts->cheatsFile = strdup(optarg);
break;
case 'D': case 'D':
opts->dirmode = true; opts->dirmode = true;
break; break;
@ -199,6 +203,7 @@ void usage(const char* arg0, const char* extraOptions) {
printf("usage: %s [option ...] file\n", arg0); printf("usage: %s [option ...] file\n", arg0);
puts("\nGeneric options:"); puts("\nGeneric options:");
puts(" -b, --bios FILE GBA BIOS file to use"); puts(" -b, --bios FILE GBA BIOS file to use");
puts(" -c, --cheats FILE Apply cheat codes from a file");
#ifdef USE_CLI_DEBUGGER #ifdef USE_CLI_DEBUGGER
puts(" -d, --debug Use command-line debugger"); puts(" -d, --debug Use command-line debugger");
#endif #endif

View File

@ -24,6 +24,7 @@ enum DebuggerType {
struct GBAArguments { struct GBAArguments {
char* fname; char* fname;
char* patch; char* patch;
char* cheatsFile;
bool dirmode; bool dirmode;
enum DebuggerType debuggerType; enum DebuggerType debuggerType;