mirror of https://github.com/mgba-emu/mgba.git
GBA: Support for unencrypted CodeBreaker codes
This commit is contained in:
parent
af6ead2c97
commit
ef65d185a3
1
CHANGES
1
CHANGES
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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*);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue