diff --git a/CHANGES b/CHANGES index fd24e8eb8..dc155568a 100644 --- a/CHANGES +++ b/CHANGES @@ -48,6 +48,9 @@ Emulation fixes: - GBA Audio: Fix improperly deserializing GB audio registers (fixes mgba.io/i/2793) - GBA Memory: Make VRAM access stalls only apply to BG RAM - GBA SIO: Fix SIOCNT SI pin value after attaching player 2 (fixes mgba.io/i/2805) + - GBA SIO: Fix unconnected normal mode SIOCNT SI bit (fixes mgba.io/i/2810) + - GBA SIO: Normal mode transfers with no clock should not finish (fixes mgba.io/i/2811) + - GBA Timers: Cascading timers don't tick when disabled (fixes mgba.io/i/2812) - GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) Other fixes: - Core: Allow sending thread requests to a crashed core (fixes mgba.io/i/2784) diff --git a/include/mgba-util/common.h b/include/mgba-util/common.h index 87d5f7580..070f1959f 100644 --- a/include/mgba-util/common.h +++ b/include/mgba-util/common.h @@ -110,13 +110,13 @@ typedef intptr_t ssize_t; #define ATOMIC_LOAD_PTR(DST, SRC) DST = InterlockedCompareExchangePointer(&SRC, 0, 0) #else // TODO -#define ATOMIC_STORE(DST, SRC) DST = SRC -#define ATOMIC_LOAD(DST, SRC) DST = SRC -#define ATOMIC_ADD(DST, OP) DST += OP -#define ATOMIC_SUB(DST, OP) DST -= OP -#define ATOMIC_OR(DST, OP) DST |= OP -#define ATOMIC_AND(DST, OP) DST &= OP -#define ATOMIC_CMPXCHG(DST, EXPECTED, OP) ((DST == EXPECTED) ? ((DST = OP), true) : false) +#define ATOMIC_STORE(DST, SRC) ((DST) = (SRC)) +#define ATOMIC_LOAD(DST, SRC) ((DST) = (SRC)) +#define ATOMIC_ADD(DST, OP) ((DST) += (OP)) +#define ATOMIC_SUB(DST, OP) ((DST) -= (OP)) +#define ATOMIC_OR(DST, OP) ((DST) |= (OP)) +#define ATOMIC_AND(DST, OP) ((DST) &= (OP)) +#define ATOMIC_CMPXCHG(DST, EXPECTED, OP) (((DST) == (EXPECTED)) ? (((DST) = (OP)), true) : false) #define ATOMIC_STORE_PTR(DST, SRC) ATOMIC_STORE(DST, SRC) #define ATOMIC_LOAD_PTR(DST, SRC) ATOMIC_LOAD(DST, SRC) #endif diff --git a/include/mgba/script/input.h b/include/mgba/script/input.h index ae1260ad1..fea9f3320 100644 --- a/include/mgba/script/input.h +++ b/include/mgba/script/input.h @@ -249,6 +249,7 @@ mSCRIPT_DECLARE_STRUCT(mScriptGamepad); void mScriptContextAttachInput(struct mScriptContext* context); void mScriptContextFireEvent(struct mScriptContext*, struct mScriptEvent*); +void mScriptContextClearKeys(struct mScriptContext*); int mScriptContextGamepadAttach(struct mScriptContext*, struct mScriptGamepad*); bool mScriptContextGamepadDetach(struct mScriptContext*, int pad); diff --git a/src/gba/sio/lockstep.c b/src/gba/sio/lockstep.c index a03e88e30..3ea1b5cc9 100644 --- a/src/gba/sio/lockstep.c +++ b/src/gba/sio/lockstep.c @@ -127,7 +127,11 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) { break; case SIO_NORMAL_8: case SIO_NORMAL_32: - ATOMIC_ADD(node->p->attachedNormal, 1); + if (ATOMIC_ADD(node->p->attachedNormal, 1) > node->id + 1 && node->id < 3) { + node->d.p->siocnt = GBASIONormalSetSi(node->d.p->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id + 1]->d.p->siocnt)); + } else { + node->d.p->siocnt = GBASIONormalFillSi(node->d.p->siocnt); + } node->d.writeRegister = GBASIOLockstepNodeNormalWriteRegister; break; default: @@ -507,11 +511,28 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive if (address == REG_SIOCNT) { mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04X", node->id, value); + int attached; + ATOMIC_LOAD(attached, node->p->attachedNormal); value &= 0xFF8B; - if (!node->id) { - value = GBASIONormalClearSi(value); + if (node->id < 3 && attached > node->id + 1) { + value = GBASIONormalSetSi(value, GBASIONormalGetIdleSo(node->p->players[node->id + 1]->d.p->siocnt)); + } else { + value = GBASIONormalFillSi(value); } - if (value & 0x0080) { + + enum mLockstepPhase transferActive; + ATOMIC_LOAD(transferActive, node->p->d.transferActive); + if (node->id > 0 && transferActive == TRANSFER_IDLE) { + int try; + for (try = 0; try < 3; ++try) { + GBASIONormal parentSiocnt; + ATOMIC_LOAD(parentSiocnt, node->p->players[node->id - 1]->d.p->siocnt); + if (ATOMIC_CMPXCHG(node->p->players[node->id - 1]->d.p->siocnt, parentSiocnt, GBASIONormalSetSi(parentSiocnt, GBASIONormalGetIdleSo(value)))) { + break; + } + } + } + if ((value & 0x0081) == 0x0081) { if (!node->id) { // Frequency int32_t cycles; @@ -524,9 +545,6 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive cycles *= 4; } - enum mLockstepPhase transferActive; - ATOMIC_LOAD(transferActive, node->p->d.transferActive); - if (transferActive == TRANSFER_IDLE) { mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id); ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING); @@ -541,7 +559,7 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive value &= ~0x0080; } } else { - + // TODO } } } else if (address == REG_SIODATA32_LO) { diff --git a/src/gba/timer.c b/src/gba/timer.c index adfc6b495..14a449b73 100644 --- a/src/gba/timer.c +++ b/src/gba/timer.c @@ -37,7 +37,7 @@ static void GBATimerUpdateAudio(struct GBA* gba, int timerId, uint32_t cyclesLat } bool GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, uint16_t* io, uint32_t cyclesLate) { - if (GBATimerFlagsIsCountUp(nextTimer->flags)) { // TODO: Does this increment while disabled? + if (GBATimerFlagsIsCountUp(nextTimer->flags) && GBATimerFlagsIsEnable(nextTimer->flags)) { ++*io; if (!*io && GBATimerFlagsIsEnable(nextTimer->flags)) { GBATimerUpdate(timing, nextTimer, io, cyclesLate); diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index d8babf06d..b0fbd51ef 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -155,6 +155,10 @@ void ScriptingController::event(QObject* obj, QEvent* event) { } switch (event->type()) { + case QEvent::FocusOut: + case QEvent::WindowDeactivate: + mScriptContextClearKeys(&m_scriptContext); + return; case QEvent::KeyPress: case QEvent::KeyRelease: { struct mScriptKeyEvent ev{mSCRIPT_EV_TYPE_KEY}; diff --git a/src/script/input.c b/src/script/input.c index 23fc498e9..31f1f806a 100644 --- a/src/script/input.c +++ b/src/script/input.c @@ -427,6 +427,44 @@ void mScriptContextFireEvent(struct mScriptContext* context, struct mScriptEvent mScriptListDeinit(&args); } +void mScriptContextClearKeys(struct mScriptContext* context) { + struct mScriptValue* input = mScriptContextGetGlobal(context, "input"); + if (!input) { + return; + } + struct mScriptInputContext* inputContext = input->value.opaque; + size_t keyCount = TableSize(&inputContext->activeKeys); + uint32_t* keys = calloc(keyCount, sizeof(uint32_t)); + struct TableIterator iter; + size_t i = 0; + if (!TableIteratorStart(&inputContext->activeKeys, &iter)) { + free(keys); + return; + } + do { + keys[i] = TableIteratorGetKey(&inputContext->activeKeys, &iter); + ++i; + } while (TableIteratorNext(&inputContext->activeKeys, &iter)); + + struct mScriptKeyEvent event = { + .d = { + .type = mSCRIPT_EV_TYPE_KEY + }, + .state = mSCRIPT_INPUT_STATE_UP, + .modifiers = 0 + }; + for (i = 0; i < keyCount; ++i) { + event.key = keys[i]; + intptr_t value = (intptr_t) TableLookup(&inputContext->activeKeys, event.key); + if (value > 1) { + TableInsert(&inputContext->activeKeys, event.key, (void*) 1); + } + mScriptContextFireEvent(context, &event.d); + } + + free(keys); +} + int mScriptContextGamepadAttach(struct mScriptContext* context, struct mScriptGamepad* pad) { struct mScriptValue* input = mScriptContextGetGlobal(context, "input"); if (!input) { diff --git a/src/script/test/input.c b/src/script/test/input.c index 08b6a3bf8..825e005dd 100644 --- a/src/script/test/input.c +++ b/src/script/test/input.c @@ -131,6 +131,50 @@ M_TEST_DEFINE(activeKeys) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(clearKeys) { + SETUP_LUA; + + TEST_PROGRAM("assert(not input:isKeyActive('a'))"); + TEST_PROGRAM("assert(not input:isKeyActive('b'))"); + TEST_PROGRAM("assert(#input:activeKeys() == 0)"); + + struct mScriptKeyEvent keyEvent = { + .d = { .type = mSCRIPT_EV_TYPE_KEY }, + .state = mSCRIPT_INPUT_STATE_DOWN, + .key = 'a' + }; + mScriptContextFireEvent(&context, &keyEvent.d); + // This changes it to STATE_HELD, but increments the down counter + mScriptContextFireEvent(&context, &keyEvent.d); + keyEvent.state = mSCRIPT_INPUT_STATE_DOWN; + keyEvent.key = 'b'; + mScriptContextFireEvent(&context, &keyEvent.d); + + TEST_PROGRAM("assert(input:isKeyActive('a'))"); + TEST_PROGRAM("assert(input:isKeyActive('b'))"); + TEST_PROGRAM("assert(#input:activeKeys() == 2)"); + + TEST_PROGRAM( + "up = {}\n" + "function cb(ev)\n" + " assert(ev.type == C.EV_TYPE.KEY)\n" + " assert(ev.state == C.INPUT_STATE.UP)\n" + " table.insert(up, ev.key)\n" + "end\n" + "id = callbacks:add('key', cb)\n" + ); + + mScriptContextClearKeys(&context); + + TEST_PROGRAM("assert(not input:isKeyActive('a'))"); + TEST_PROGRAM("assert(not input:isKeyActive('b'))"); + TEST_PROGRAM("assert(#input:activeKeys() == 0)"); + TEST_PROGRAM("assert(#up == 2)"); + + mScriptContextDeinit(&context); +} + + M_TEST_DEFINE(gamepadExport) { SETUP_LUA; @@ -176,5 +220,6 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptInput, cmocka_unit_test(seq), cmocka_unit_test(fireKey), cmocka_unit_test(activeKeys), + cmocka_unit_test(clearKeys), cmocka_unit_test(gamepadExport), )