diff --git a/CHANGES b/CHANGES index f251ce6f3..3c3a730da 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,7 @@ Features: - Ability to set default Game Boy model - Map viewer - Automatic cheat loading and saving + - GameShark and Action Replay button support Bugfixes: - GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749) - GB Serialize: Fix audio state loading diff --git a/include/mgba/core/cheats.h b/include/mgba/core/cheats.h index 362b995d9..98eb4dd91 100644 --- a/include/mgba/core/cheats.h +++ b/include/mgba/core/cheats.h @@ -30,7 +30,8 @@ enum mCheatType { CHEAT_IF_UGT, CHEAT_IF_AND, CHEAT_IF_LAND, - CHEAT_IF_NAND + CHEAT_IF_NAND, + CHEAT_IF_BUTTON, }; struct mCheat { @@ -80,6 +81,7 @@ struct mCheatDevice { struct mCheatSets cheats; bool autosave; + bool buttonDown; }; struct VFile; @@ -102,6 +104,7 @@ bool mCheatSaveFile(struct mCheatDevice*, struct VFile*); void mCheatAutosave(struct mCheatDevice*); void mCheatRefresh(struct mCheatDevice*, struct mCheatSet*); +void mCheatPressButton(struct mCheatDevice*, bool down); CXX_GUARD_END diff --git a/src/core/cheats.c b/src/core/cheats.c index 642230b0b..a9aa253f4 100644 --- a/src/core/cheats.c +++ b/src/core/cheats.c @@ -52,6 +52,7 @@ void mCheatDeviceCreate(struct mCheatDevice* device) { device->d.init = mCheatDeviceInit; device->d.deinit = mCheatDeviceDeinit; device->autosave = false; + device->buttonDown = false; mCheatSetsInit(&device->cheats, 4); } @@ -360,6 +361,12 @@ void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) { negativeConditionRemaining = cheat->negativeRepeat; operationsRemaining = 1; break; + case CHEAT_IF_BUTTON: + condition = device->buttonDown; + conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; + operationsRemaining = 1; + break; } if (performAssignment) { @@ -384,6 +391,10 @@ void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) { } } +void mCheatPressButton(struct mCheatDevice* device, bool down) { + device->buttonDown = down; +} + void mCheatDeviceInit(void* cpu, struct mCPUComponent* component) { UNUSED(cpu); struct mCheatDevice* device = (struct mCheatDevice*) component; diff --git a/src/gba/cheats/gameshark.c b/src/gba/cheats/gameshark.c index 3b2200ee4..d61ffbe6a 100644 --- a/src/gba/cheats/gameshark.c +++ b/src/gba/cheats/gameshark.c @@ -154,9 +154,32 @@ bool GBACheatAddGameSharkRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t cheats->romPatches[0].exists = true; return true; case GSA_BUTTON: - // TODO: Implement button - mLOG(CHEATS, STUB, "GameShark button unimplemented"); - return false; + switch (op1 & 0x00F00000) { + case 0x00100000: + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_IF_BUTTON; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 1; + cheat->address = op1 & 0x0F0FFFFF; + break; + case 0x00200000: + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_IF_BUTTON; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + cheat->address = op1 & 0x0F0FFFFF; + break; + default: + mLOG(CHEATS, STUB, "GameShark button type unimplemented"); + return false; + } + break; case GSA_IF_EQ: if (op1 == 0xDEADFACE) { GBACheatReseedGameShark(cheats->gsaSeeds, op2, _gsa1T1, _gsa1T2); diff --git a/src/gba/cheats/parv3.c b/src/gba/cheats/parv3.c index 8a0b2b547..ee11b28fc 100644 --- a/src/gba/cheats/parv3.c +++ b/src/gba/cheats/parv3.c @@ -144,13 +144,41 @@ static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) { switch (op2 & 0xFF000000) { case PAR3_OTHER_SLOWDOWN: // TODO: Slowdown + mLOG(CHEATS, STUB, "Unimplemented PARv3 slowdown"); return false; case PAR3_OTHER_BUTTON_1: + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_IF_BUTTON; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 1; + cheat->address = _parAddr(op2); + cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); + break; case PAR3_OTHER_BUTTON_2: + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_IF_BUTTON; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + cheat->address = _parAddr(op2); + cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); + break; case PAR3_OTHER_BUTTON_4: - // TODO: Button - mLOG(CHEATS, STUB, "GameShark button unimplemented"); - return false; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_IF_BUTTON; + cheat->repeat = 1; + cheat->negativeRepeat = 0; + cheat = mCheatListAppend(&cheats->d.list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = _parAddr(op2); + cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); + break; // TODO: Fix overriding existing patches case PAR3_OTHER_PATCH_1: cheats->romPatches[0].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); diff --git a/src/gba/test/cheats.c b/src/gba/test/cheats.c index d81f9f67b..08c8b1b82 100644 --- a/src/gba/test/cheats.c +++ b/src/gba/test/cheats.c @@ -1016,6 +1016,34 @@ M_TEST_DEFINE(doPARv3IfXContain1ElseContain1) { assert_int_equal(core->rawRead8(core, 0x03000006, -1), 0x62); assert_int_equal(core->rawRead8(core, 0x03000007, -1), 0x71); assert_int_equal(core->rawRead8(core, 0x03000008, -1), 0x82); + set->deinit(set); +} + +M_TEST_DEFINE(doPARv3IfButton) { + struct mCore* core = *state; + struct mCheatDevice* device = core->cheatDevice(core); + assert_non_null(device); + struct mCheatSet* set = device->createSet(device, NULL); + assert_non_null(set); + GBACheatSetGameSharkVersion((struct GBACheatSet*) set, GBA_GS_PARV3_RAW); + assert_true(set->addLine(set, "00000000 10300000", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00000001 00000000", GBA_CHEAT_PRO_ACTION_REPLAY)); + + core->reset(core); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + + mCheatPressButton(device, true); + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + + mCheatPressButton(device, false); + core->rawWrite8(core, 0x03000000, -1, 0); + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + set->deinit(set); } M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(GBACheats, @@ -1038,4 +1066,5 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(GBACheats, cmocka_unit_test(doPARv3IfXContain1), cmocka_unit_test(doPARv3IfXContain1Else), cmocka_unit_test(doPARv3IfXElseContain1), - cmocka_unit_test(doPARv3IfXContain1ElseContain1)) + cmocka_unit_test(doPARv3IfXContain1ElseContain1), + cmocka_unit_test(doPARv3IfButton)) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 0ffd22ab9..b79f0d2ed 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -52,6 +52,7 @@ #include "VideoView.h" #include +#include #ifdef M_CORE_GB #include #include @@ -1602,6 +1603,16 @@ void Window::setupMenu(QMenuBar* menubar) { exitFullScreen->setShortcut(QKeySequence("Esc")); addHiddenAction(frameMenu, exitFullScreen, "exitFullScreen"); + m_shortcutController->addFunctions(toolsMenu, [this]() { + if (m_controller) { + mCheatPressButton(m_controller->cheatDevice(), true); + } + }, [this]() { + if (m_controller) { + mCheatPressButton(m_controller->cheatDevice(), false); + } + }, QKeySequence(Qt::Key_Apostrophe), tr("GameShark Button (held)"), "holdGSButton"); + QMenu* autofireMenu = new QMenu(tr("Autofire"), this); m_shortcutController->addMenu(autofireMenu);