diff --git a/CHANGES b/CHANGES index a1bb2a0f3..8fe94d5c3 100644 --- a/CHANGES +++ b/CHANGES @@ -48,6 +48,7 @@ Misc: - GB Video: Add default SGB border - GBA: Automatically skip BIOS if ROM has invalid logo - GBA: Refine multiboot detection (fixes mgba.io/i/2192) + - GBA Cheats: Implement "never" type codes (closes mgba.io/i/915) - GBA DMA: Enhanced logging (closes mgba.io/i/2454) - GBA Video: Implement layer placement for OpenGL renderer (fixes mgba.io/i/1962) - mGUI: Add margin to right-aligned menu text (fixes mgba.io/i/871) diff --git a/include/mgba/core/cheats.h b/include/mgba/core/cheats.h index 4d7bd909c..1da1aa859 100644 --- a/include/mgba/core/cheats.h +++ b/include/mgba/core/cheats.h @@ -31,6 +31,7 @@ enum mCheatType { CHEAT_IF_LAND, CHEAT_IF_NAND, CHEAT_IF_BUTTON, + CHEAT_NEVER, }; struct mCheat { diff --git a/src/core/cheats.c b/src/core/cheats.c index fad5943c0..688b32ebe 100644 --- a/src/core/cheats.c +++ b/src/core/cheats.c @@ -741,6 +741,12 @@ void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) { negativeConditionRemaining = cheat->negativeRepeat; operationsRemaining = 1; break; + case CHEAT_NEVER: + condition = false; + conditionRemaining = cheat->repeat; + negativeConditionRemaining = cheat->negativeRepeat; + operationsRemaining = 1; + break; } if (performAssignment) { diff --git a/src/gba/cheats/parv3.c b/src/gba/cheats/parv3.c index 1a82f084c..30cc5515b 100644 --- a/src/gba/cheats/parv3.c +++ b/src/gba/cheats/parv3.c @@ -72,12 +72,9 @@ static void _parElseBlock(struct GBACheatSet* cheats) { static bool _addPAR3Cond(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { enum GBAActionReplay3Condition condition = op1 & PAR3_COND; int width = 1 << ((op1 & PAR3_WIDTH) >> PAR3_WIDTH_BASE); - if (width > 4) { - // TODO: Always false conditions - return false; - } if ((op1 & PAR3_ACTION) == PAR3_ACTION_DISABLE) { // TODO: Codes that disable + mLOG(CHEATS, STUB, "Disable-type PARv3 codes not yet supported"); return false; } @@ -136,6 +133,11 @@ static bool _addPAR3Cond(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) cheat->type = CHEAT_IF_AND; break; } + + if (width > 4) { + cheat->width = 0; + cheat->type = CHEAT_NEVER; + } return true; } diff --git a/src/gba/test/cheats.c b/src/gba/test/cheats.c index 527c3ccdc..5d76103fc 100644 --- a/src/gba/test/cheats.c +++ b/src/gba/test/cheats.c @@ -1051,6 +1051,133 @@ M_TEST_DEFINE(doPARv3IfButton) { mCheatSetDeinit(set); } +M_TEST_DEFINE(doPARv3Never1) { + 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, "00300001 00000011", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "0E300000 00000000", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00300001 00000012", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00300000 00000001", GBA_CHEAT_PRO_ACTION_REPLAY)); + + core->reset(core); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0); + + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0x1); + assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0x11); + + core->reset(core); + core->rawWrite8(core, 0x03000000, -1, 0x1); + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0x1); + assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0x11); + + mCheatSetDeinit(set); +} + +M_TEST_DEFINE(doPARv3Never2) { + 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, "00300001 00000011", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00300002 00000021", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "4E300000 00000000", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00300001 00000012", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00300002 00000022", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00300000 00000001", GBA_CHEAT_PRO_ACTION_REPLAY)); + + core->reset(core); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0); + + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0x1); + assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0x11); + assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0x21); + + core->reset(core); + core->rawWrite8(core, 0x03000000, -1, 0x1); + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0x1); + assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0x11); + assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0x21); + + mCheatSetDeinit(set); +} + +M_TEST_DEFINE(doPARv3NeverX) { + 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, "00300001 00000011", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "8E300000 00000000", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00300001 00000012", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00000000 40000000", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00300000 00000001", GBA_CHEAT_PRO_ACTION_REPLAY)); + + core->reset(core); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0); + + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0x1); + assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0x11); + + core->reset(core); + core->rawWrite8(core, 0x03000000, -1, 0x1); + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0x1); + assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0x11); + mCheatSetDeinit(set); +} + +M_TEST_DEFINE(doPARv3NeverXElse) { + 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, "00300001 00000011", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00300002 00000021", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "8E300000 00000000", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00300001 00000012", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00000000 60000000", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00300002 00000022", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00000000 40000000", GBA_CHEAT_PRO_ACTION_REPLAY)); + assert_true(set->addLine(set, "00300000 00000001", GBA_CHEAT_PRO_ACTION_REPLAY)); + + core->reset(core); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0); + assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0); + + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0x1); + assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0x11); + assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0x22); + + core->reset(core); + core->rawWrite8(core, 0x03000000, -1, 0x1); + mCheatRefresh(device, set); + assert_int_equal(core->rawRead8(core, 0x03000000, -1), 0x1); + assert_int_equal(core->rawRead8(core, 0x03000001, -1), 0x11); + assert_int_equal(core->rawRead8(core, 0x03000002, -1), 0x22); + mCheatSetDeinit(set); +} + M_TEST_SUITE_DEFINE(GBACheats, cmocka_unit_test_setup_teardown(createSet, cheatsSetup, cheatsTeardown), cmocka_unit_test_setup_teardown(addRawPARv3, cheatsSetup, cheatsTeardown), @@ -1072,4 +1199,8 @@ M_TEST_SUITE_DEFINE(GBACheats, cmocka_unit_test_setup_teardown(doPARv3IfXContain1Else, cheatsSetup, cheatsTeardown), cmocka_unit_test_setup_teardown(doPARv3IfXElseContain1, cheatsSetup, cheatsTeardown), cmocka_unit_test_setup_teardown(doPARv3IfXContain1ElseContain1, cheatsSetup, cheatsTeardown), - cmocka_unit_test_setup_teardown(doPARv3IfButton, cheatsSetup, cheatsTeardown)) + cmocka_unit_test_setup_teardown(doPARv3IfButton, cheatsSetup, cheatsTeardown), + cmocka_unit_test_setup_teardown(doPARv3Never1, cheatsSetup, cheatsTeardown), + cmocka_unit_test_setup_teardown(doPARv3Never2, cheatsSetup, cheatsTeardown), + cmocka_unit_test_setup_teardown(doPARv3NeverX, cheatsSetup, cheatsTeardown), + cmocka_unit_test_setup_teardown(doPARv3NeverXElse, cheatsSetup, cheatsTeardown))