From d1f589e002b8bb8cbdb3df98c4308eefc2894252 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 30 Jan 2023 00:21:18 -0800 Subject: [PATCH 001/290] GBA Audio: Fix improperly deserializing GB audio registers (fixes #2793) --- CHANGES | 1 + src/gb/audio.c | 2 ++ src/gba/audio.c | 10 ++++++++++ src/gba/io.c | 4 ++-- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 369036477..38cf9a0bd 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ Features: - New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81 - Debugger: Add range watchpoints 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 Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) Other fixes: diff --git a/src/gb/audio.c b/src/gb/audio.c index d2d6a9260..078f93358 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -1089,6 +1089,8 @@ void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGSt audio->ch4.lastEvent = currentTime + (when & (cycles - 1)) - cycles; } } + audio->ch4.nSamples = 0; + audio->ch4.samples = 0; } void GBAudioSerialize(const struct GBAudio* audio, struct GBSerializedState* state) { diff --git a/src/gba/audio.c b/src/gba/audio.c index f957ef191..2c61aa1ce 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -506,6 +506,16 @@ void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state) { GBAudioPSGDeserialize(&audio->psg, &state->audio.psg, &state->audio.flags); + uint16_t reg; + LOAD_16(reg, REG_SOUND1CNT_X, state->io); + GBAIOWrite(audio->p, REG_SOUND1CNT_X, reg & 0x7FFF); + LOAD_16(reg, REG_SOUND2CNT_HI, state->io); + GBAIOWrite(audio->p, REG_SOUND2CNT_HI, reg & 0x7FFF); + LOAD_16(reg, REG_SOUND3CNT_X, state->io); + GBAIOWrite(audio->p, REG_SOUND3CNT_X, reg & 0x7FFF); + LOAD_16(reg, REG_SOUND4CNT_HI, state->io); + GBAIOWrite(audio->p, REG_SOUND4CNT_HI, reg & 0x7FFF); + LOAD_32(audio->chA.internalSample, 0, &state->audio.internalA); LOAD_32(audio->chB.internalSample, 0, &state->audio.internalB); memcpy(audio->chA.samples, state->samples.chA, sizeof(audio->chA.samples)); diff --git a/src/gba/io.c b/src/gba/io.c index 4e3763536..0b08772ed 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -296,8 +296,8 @@ static const int _isWSpecialRegister[REG_INTERNAL_MAX >> 1] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Audio - 1, 1, 1, 0, 1, 0, 1, 0, - 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, From d08b307be0091211486a7941ae1331b318fed0db Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 30 Jan 2023 00:23:01 -0800 Subject: [PATCH 002/290] CHANGES: Add input API --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 38cf9a0bd..36e2cefa3 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,6 @@ 0.11.0: (Future) Features: + - Scripting: New `input` API for getting raw keyboard/mouse/controller state - New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81 - Debugger: Add range watchpoints Emulation fixes: From 92d86af9552b48d4ce05b13823807131b99b1cce Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 30 Jan 2023 20:48:39 -0800 Subject: [PATCH 003/290] GBA SIO: Fix SIOCNT SI pin value after attaching player 2 (fixes #2805) --- CHANGES | 1 + src/gba/sio/lockstep.c | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGES b/CHANGES index 36e2cefa3..7045e46b6 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,7 @@ Features: 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 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/src/gba/sio/lockstep.c b/src/gba/sio/lockstep.c index bc5a5fd33..a03e88e30 100644 --- a/src/gba/sio/lockstep.c +++ b/src/gba/sio/lockstep.c @@ -111,6 +111,18 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) { if (node->id) { node->d.p->rcnt |= 4; node->d.p->siocnt = GBASIOMultiplayerFillSlave(node->d.p->siocnt); + + int try; + for (try = 0; try < 3; ++try) { + uint16_t masterSiocnt; + ATOMIC_LOAD(masterSiocnt, node->p->players[0]->d.p->siocnt); + if (ATOMIC_CMPXCHG(node->p->players[0]->d.p->siocnt, masterSiocnt, GBASIOMultiplayerClearSlave(masterSiocnt))) { + break; + } + } + } else { + node->d.p->rcnt &= ~4; + node->d.p->siocnt = GBASIOMultiplayerClearSlave(node->d.p->siocnt); } break; case SIO_NORMAL_8: From 5a5adc1b154e9cde3b428ca36b0d63498468ff3c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 31 Jan 2023 17:07:03 -0800 Subject: [PATCH 004/290] Script: Fix leaking tables passed from Lua --- src/script/engines/lua.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index db7a70075..fc7c94511 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -591,6 +591,7 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext) size_t len = mScriptTableSize(table); if (!isList || !len) { + mScriptContextFillPool(luaContext->d.context, table); return table; } @@ -600,6 +601,7 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext) struct mScriptValue* value = mScriptTableLookup(table, &mSCRIPT_MAKE_S64(i)); if (!value) { mScriptValueDeref(list); + mScriptContextFillPool(luaContext->d.context, table); return table; } mScriptValueWrap(value, mScriptListAppend(list->value.list)); From e445baaf14e5872e10945a3dca4159524e3b858a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 31 Jan 2023 17:19:01 -0800 Subject: [PATCH 005/290] Script: Fix table string key UAF --- src/script/engines/lua.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index fc7c94511..bac841f1b 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -584,7 +584,11 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext) return false; } mScriptTableInsert(table, key, value); - mScriptValueDeref(key); + if (key->type != mSCRIPT_TYPE_MS_STR) { + // Strings are added to the ref pool, so we need to keep it + // ref'd to prevent it from being collected prematurely + mScriptValueDeref(key); + } mScriptValueDeref(value); } lua_pop(luaContext->lua, 1); From bef88a4e1380f10670508fc2d7e0f77e409ac5bf Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 31 Jan 2023 20:58:38 -0800 Subject: [PATCH 006/290] Qt: Hide ? button on forwarder view --- src/platform/qt/ForwarderView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/ForwarderView.cpp b/src/platform/qt/ForwarderView.cpp index 0546e7180..062a0bf45 100644 --- a/src/platform/qt/ForwarderView.cpp +++ b/src/platform/qt/ForwarderView.cpp @@ -15,7 +15,7 @@ using namespace QGBA; ForwarderView::ForwarderView(QWidget* parent) - : QDialog(parent) + : QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) { m_ui.setupUi(this); From 86327de14fb2efe86a01d0a115a2c89e3781754e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 31 Jan 2023 21:05:00 -0800 Subject: [PATCH 007/290] Qt: Add filter for selecting forwarder images --- src/platform/qt/ForwarderView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/ForwarderView.cpp b/src/platform/qt/ForwarderView.cpp index 062a0bf45..922ef7c16 100644 --- a/src/platform/qt/ForwarderView.cpp +++ b/src/platform/qt/ForwarderView.cpp @@ -189,7 +189,7 @@ void ForwarderView::connectBrowseButton(QAbstractButton* button, QLineEdit* line } void ForwarderView::selectImage() { - QString filename = GBAApp::app()->getOpenFileName(this, tr("Select an image"), {}); + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select an image"), tr("Image files (*.png *.jpg *.bmp)")); if (filename.isEmpty()) { return; } From 4afacfa0679de08812a8fc8eac59909f1dea4196 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 31 Jan 2023 21:11:02 -0800 Subject: [PATCH 008/290] Qt: Link QJpegPlugin on static Windows --- src/platform/qt/CMakeLists.txt | 2 +- src/platform/qt/main.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 7bd8e095b..44a208b81 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -222,7 +222,7 @@ if(${QT}Multimedia_FOUND) list(APPEND SOURCE_FILES VideoDumper.cpp) if (WIN32 AND QT_STATIC) - list(APPEND QT_LIBRARIES ${QT}::QWindowsAudioPlugin ${QT}::DSServicePlugin ${QT}::QWindowsVistaStylePlugin + list(APPEND QT_LIBRARIES ${QT}::QWindowsAudioPlugin ${QT}::DSServicePlugin ${QT}::QWindowsVistaStylePlugin ${QT}::QJpegPlugin strmiids mfuuid mfplat mf ksguid dxva2 evr d3d9) endif() list(APPEND QT_LIBRARIES ${QT}::Multimedia) diff --git a/src/platform/qt/main.cpp b/src/platform/qt/main.cpp index c53408e8e..4343168a7 100644 --- a/src/platform/qt/main.cpp +++ b/src/platform/qt/main.cpp @@ -28,6 +28,7 @@ #ifdef QT_STATIC #include #ifdef Q_OS_WIN +Q_IMPORT_PLUGIN(QJpegPlugin); Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); Q_IMPORT_PLUGIN(QWindowsVistaStylePlugin); #ifdef BUILD_QT_MULTIMEDIA From f27ce8d82e00b1840caaa78cf90159aad9e67106 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 31 Jan 2023 21:34:19 -0800 Subject: [PATCH 009/290] Scripting: Add input:activeKeys to get currently active keyboard keys --- src/script/input.c | 19 ++++++++++++++++++- src/script/test/input.c | 23 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/script/input.c b/src/script/input.c index fdb6593cb..23fc498e9 100644 --- a/src/script/input.c +++ b/src/script/input.c @@ -36,17 +36,21 @@ struct mScriptInputContext { static void _mScriptInputDeinit(struct mScriptInputContext*); static bool _mScriptInputIsKeyActive(const struct mScriptInputContext*, struct mScriptValue*); +static struct mScriptValue* _mScriptInputActiveKeys(const struct mScriptInputContext*); mSCRIPT_DECLARE_STRUCT(mScriptInputContext); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptInputContext, _deinit, _mScriptInputDeinit, 0); mSCRIPT_DECLARE_STRUCT_C_METHOD(mScriptInputContext, BOOL, isKeyActive, _mScriptInputIsKeyActive, 1, WRAPPER, key); +mSCRIPT_DECLARE_STRUCT_C_METHOD(mScriptInputContext, WLIST, activeKeys, _mScriptInputActiveKeys, 0); mSCRIPT_DEFINE_STRUCT(mScriptInputContext) mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptInputContext) mSCRIPT_DEFINE_DOCSTRING("Sequence number of the next event to be emitted") mSCRIPT_DEFINE_STRUCT_MEMBER(mScriptInputContext, U64, seq) - mSCRIPT_DEFINE_DOCSTRING("Check if a given keyboard key is currently held. The input can be either the printable character for a key, or the numerical Unicode codepoint") + mSCRIPT_DEFINE_DOCSTRING("Check if a given keyboard key is currently held. The input can be either the printable character for a key, the numerical Unicode codepoint, or a special value from C.KEY") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptInputContext, isKeyActive) + mSCRIPT_DEFINE_DOCSTRING("Get a list of the currently active keys. The values are Unicode codepoints or special key values from C.KEY, not strings, so make sure to convert as needed") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptInputContext, activeKeys) mSCRIPT_DEFINE_DOCSTRING("The currently active gamepad, if any") mSCRIPT_DEFINE_STRUCT_MEMBER(mScriptInputContext, PCS(mScriptGamepad), activeGamepad) mSCRIPT_DEFINE_END; @@ -346,6 +350,19 @@ bool _mScriptInputIsKeyActive(const struct mScriptInputContext* context, struct return down != NULL; } +static struct mScriptValue* _mScriptInputActiveKeys(const struct mScriptInputContext* context) { + struct mScriptValue* list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST); + struct TableIterator iter; + if (!TableIteratorStart(&context->activeKeys, &iter)) { + return list; + } + do { + uint32_t key = TableIteratorGetKey(&context->activeKeys, &iter); + *mScriptListAppend(list->value.list) = mSCRIPT_MAKE_U32(key); + } while (TableIteratorNext(&context->activeKeys, &iter)); + return list; +} + static bool _updateKeys(struct mScriptContext* context, struct mScriptKeyEvent* event) { int offset = 0; switch (event->state) { diff --git a/src/script/test/input.c b/src/script/test/input.c index de3ae0e0c..08b6a3bf8 100644 --- a/src/script/test/input.c +++ b/src/script/test/input.c @@ -109,6 +109,28 @@ M_TEST_DEFINE(fireKey) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(activeKeys) { + SETUP_LUA; + + 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); + TEST_PROGRAM("assert(#input:activeKeys() == 1)"); + TEST_PROGRAM("assert(input:activeKeys()[1] == string.byte('a'))"); + + keyEvent.state = mSCRIPT_INPUT_STATE_UP; + mScriptContextFireEvent(&context, &keyEvent.d); + + TEST_PROGRAM("assert(#input:activeKeys() == 0)"); + + mScriptContextDeinit(&context); +} + M_TEST_DEFINE(gamepadExport) { SETUP_LUA; @@ -153,5 +175,6 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptInput, cmocka_unit_test(members), cmocka_unit_test(seq), cmocka_unit_test(fireKey), + cmocka_unit_test(activeKeys), cmocka_unit_test(gamepadExport), ) From e95bd06321d485369987acf868f3c2066992d97c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 2 Feb 2023 21:34:14 -0800 Subject: [PATCH 010/290] Scripting: Clear down keys when the window is deactivated --- include/mgba/script/input.h | 1 + .../qt/scripting/ScriptingController.cpp | 4 ++ src/script/input.c | 38 ++++++++++++++++ src/script/test/input.c | 45 +++++++++++++++++++ 4 files changed, 88 insertions(+) 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/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), ) From e4707952586292d85724ebcb67b47bf0c9bc8a49 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 3 Feb 2023 01:43:34 -0800 Subject: [PATCH 011/290] GBA Timers: Cascading timers don't tick when disabled (fixes #2812) --- CHANGES | 1 + src/gba/timer.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 7045e46b6..556830b6e 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,7 @@ 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 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/src/gba/timer.c b/src/gba/timer.c index 8a3a3e8f6..da054e12a 100644 --- a/src/gba/timer.c +++ b/src/gba/timer.c @@ -34,7 +34,7 @@ static void GBATimerUpdate(struct GBA* gba, int timerId, uint32_t cyclesLate) { if (timerId < 3) { struct GBATimer* nextTimer = &gba->timers[timerId + 1]; - if (GBATimerFlagsIsCountUp(nextTimer->flags)) { // TODO: Does this increment while disabled? + if (GBATimerFlagsIsCountUp(nextTimer->flags) && GBATimerFlagsIsEnable(nextTimer->flags)) { ++gba->memory.io[REG_TMCNT_LO(timerId + 1) >> 1]; if (!gba->memory.io[REG_TMCNT_LO(timerId + 1) >> 1] && GBATimerFlagsIsEnable(nextTimer->flags)) { GBATimerUpdate(gba, timerId + 1, cyclesLate); From 4e85de3a42a96f6a1265dcf2d02563d086dfb41e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 3 Feb 2023 01:54:30 -0800 Subject: [PATCH 012/290] Util: Fix some macro best practices --- include/mgba-util/common.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/include/mgba-util/common.h b/include/mgba-util/common.h index 4583ac0ed..ae86c8b3c 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 From f046596ca7c80dfc52ec163555fbd0a4794fc91d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 3 Feb 2023 02:06:20 -0800 Subject: [PATCH 013/290] GBA SIO: Fix unconnected normal mode SIOCNT SI bit (fixes #2810) --- CHANGES | 1 + src/gba/sio/lockstep.c | 30 ++++++++++++++++++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 556830b6e..1ee88d5fd 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,7 @@ 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 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: diff --git a/src/gba/sio/lockstep.c b/src/gba/sio/lockstep.c index a03e88e30..db92c50a5 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,9 +511,26 @@ 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); + } + + 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 & 0x0080) { if (!node->id) { @@ -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); From 527313bafc2e6be15c250f64490f08e3c01eb63b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 3 Feb 2023 02:08:14 -0800 Subject: [PATCH 014/290] GBA SIO: Normal mode transfers with no clock should not finish (fixes #2811) --- CHANGES | 1 + src/gba/sio/lockstep.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 1ee88d5fd..028537466 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,7 @@ Emulation fixes: - 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: diff --git a/src/gba/sio/lockstep.c b/src/gba/sio/lockstep.c index db92c50a5..3ea1b5cc9 100644 --- a/src/gba/sio/lockstep.c +++ b/src/gba/sio/lockstep.c @@ -532,7 +532,7 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive } } } - if (value & 0x0080) { + if ((value & 0x0081) == 0x0081) { if (!node->id) { // Frequency int32_t cycles; @@ -559,7 +559,7 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive value &= ~0x0080; } } else { - + // TODO } } } else if (address == REG_SIODATA32_LO) { From f37d0687337e31dcfb96974810a572acba571416 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 3 Feb 2023 02:20:47 -0800 Subject: [PATCH 015/290] GBA SIO: Minor code modernization --- src/gba/sio.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gba/sio.c b/src/gba/sio.c index d4df7e8da..9c47335dc 100644 --- a/src/gba/sio.c +++ b/src/gba/sio.c @@ -184,13 +184,13 @@ void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) { switch (sio->mode) { case SIO_NORMAL_8: case SIO_NORMAL_32: - value |= 0x0004; + value = GBASIONormalFillSi(value); if ((value & 0x0081) == 0x0081) { - if (value & 0x4000) { + if (GBASIONormalIsIrq(value)) { // TODO: Test this on hardware to see if this is correct GBARaiseIRQ(sio->p, GBA_IRQ_SIO, 0); } - value &= ~0x0080; + value = GBASIONormalClearStart(value); } break; case SIO_MULTI: From 5164b888d895f2f91bb40290f6b2ef0e35b89571 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 31 Jan 2023 17:22:29 -0800 Subject: [PATCH 016/290] Scripting: Allow Lua to pass nested tables to the scripting subsystem --- include/mgba/script/types.h | 3 ++- src/script/engines/lua.c | 25 +++++++++++++++---- src/script/test/lua.c | 50 ++++++++++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index ed8ae02e3..0be1c41e3 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -35,7 +35,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_C_PTR void* #define mSCRIPT_TYPE_C_CPTR const void* #define mSCRIPT_TYPE_C_LIST struct mScriptList* -#define mSCRIPT_TYPE_C_TABLE Table* +#define mSCRIPT_TYPE_C_TABLE struct Table* #define mSCRIPT_TYPE_C_WRAPPER struct mScriptValue* #define mSCRIPT_TYPE_C_WEAKREF uint32_t #define mSCRIPT_TYPE_C_S(STRUCT) struct STRUCT* @@ -120,6 +120,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_CMP_STR(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) #define mSCRIPT_TYPE_CMP_CHARP(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE) #define mSCRIPT_TYPE_CMP_LIST(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE) +#define mSCRIPT_TYPE_CMP_TABLE(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_TABLE, TYPE) #define mSCRIPT_TYPE_CMP_PTR(TYPE) ((TYPE)->base >= mSCRIPT_TYPE_OPAQUE) #define mSCRIPT_TYPE_CMP_WRAPPER(TYPE) (true) #define mSCRIPT_TYPE_CMP_S(STRUCT) mSCRIPT_TYPE_MS_S(STRUCT)->name == _mSCRIPT_FIELD_NAME diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index bac841f1b..95ac70eba 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -538,11 +538,13 @@ struct mScriptValue* _luaCoerceFunction(struct mScriptEngineContextLua* luaConte return value; } -struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext) { +struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext, struct Table* markedObjects) { struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); bool isList = true; lua_pushnil(luaContext->lua); + + void* tablePointer; while (lua_next(luaContext->lua, -2) != 0) { struct mScriptValue* value = NULL; int type = lua_type(luaContext->lua, -1); @@ -553,14 +555,23 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext) case LUA_TFUNCTION: value = _luaCoerce(luaContext, true); break; + case LUA_TTABLE: + tablePointer = lua_topointer(luaContext->lua, -1); + // Ensure this table doesn't contain any cycles + if (!HashTableLookupBinary(markedObjects, &tablePointer, sizeof(tablePointer))) { + HashTableInsertBinary(markedObjects, &tablePointer, sizeof(tablePointer), tablePointer); + value = _luaCoerceTable(luaContext, markedObjects); + if (value) { + mScriptValueRef(value); + } + } default: - // Don't let values be something that could contain themselves break; } if (!value) { - lua_pop(luaContext->lua, 3); + lua_pop(luaContext->lua, type == LUA_TTABLE ? 2 : 3); mScriptValueDeref(table); - return false; + return NULL; } struct mScriptValue* key = NULL; @@ -628,6 +639,7 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool size_t size; const void* buffer; + struct Table markedObjects; struct mScriptValue* value = NULL; switch (lua_type(luaContext->lua, -1)) { case LUA_TNIL: @@ -664,7 +676,10 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool if (!pop) { break; } - return _luaCoerceTable(luaContext); + HashTableInit(&markedObjects, 0, NULL); + value = _luaCoerceTable(luaContext, &markedObjects); + HashTableDeinit(&markedObjects); + return value; case LUA_TUSERDATA: if (!lua_getmetatable(luaContext->lua, -1)) { break; diff --git a/src/script/test/lua.c b/src/script/test/lua.c index 5731b0705..eab8550dc 100644 --- a/src/script/test/lua.c +++ b/src/script/test/lua.c @@ -66,9 +66,14 @@ static int32_t sum(struct mScriptList* list) { return sum; } +static unsigned tableSize(struct Table* table) { + return TableSize(table); +} + mSCRIPT_BIND_FUNCTION(boundIdentityInt, S32, identityInt, 1, S32, a); mSCRIPT_BIND_FUNCTION(boundAddInts, S32, addInts, 2, S32, a, S32, b); mSCRIPT_BIND_FUNCTION(boundSum, S32, sum, 1, LIST, list); +mSCRIPT_BIND_FUNCTION(boundTableSize, U32, tableSize, 1, TABLE, table); mSCRIPT_DECLARE_STRUCT(Test); mSCRIPT_DECLARE_STRUCT_D_METHOD(Test, S32, ifn0, 0); @@ -371,8 +376,50 @@ M_TEST_DEFINE(callCFunc) { assert_true(a.type->equal(&a, val)); mScriptValueDeref(val); + LOAD_PROGRAM("b('a')"); + assert_false(lua->run(lua)); + mScriptContextDeinit(&context); } + +M_TEST_DEFINE(callCTable) { + SETUP_LUA; + + struct mScriptValue a = mSCRIPT_MAKE_S32(1); + struct mScriptValue* val; + + assert_true(lua->setGlobal(lua, "b", &boundTableSize)); + + TEST_PROGRAM("assert(b({}) == 0)"); + assert_null(lua->getError(lua)); + + TEST_PROGRAM("assert(b({[2]=1}) == 1)"); + assert_null(lua->getError(lua)); + + TEST_PROGRAM("assert(b({a=1}) == 1)"); + assert_null(lua->getError(lua)); + + TEST_PROGRAM("assert(b({a={}}) == 1)"); + assert_null(lua->getError(lua)); + + LOAD_PROGRAM( + "a = {}\n" + "a.b = a\n" + "assert(b(a) == 1)\n" + ); + assert_false(lua->run(lua)); + + LOAD_PROGRAM( + "a = {}\n" + "a.b = {}\n" + "a.b.c = a\n" + "assert(b(a) == 1)\n" + ); + assert_false(lua->run(lua)); + + mScriptContextDeinit(&context); +} + M_TEST_DEFINE(globalNull) { SETUP_LUA; @@ -774,6 +821,7 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua, cmocka_unit_test(rootScope), cmocka_unit_test(callLuaFunc), cmocka_unit_test(callCFunc), + cmocka_unit_test(callCTable), cmocka_unit_test(globalNull), cmocka_unit_test(globalStructFieldGet), cmocka_unit_test(globalStructFieldSet), @@ -782,5 +830,5 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua, cmocka_unit_test(tableLookup), cmocka_unit_test(tableIterate), cmocka_unit_test(callList), - cmocka_unit_test(linkedList) + cmocka_unit_test(linkedList), ) From 0193bc3a837a564ece40ea7eab2f0cf30a4d33f3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Feb 2023 18:14:37 -0800 Subject: [PATCH 017/290] Scripting: Fix table unwrapping --- src/script/engines/lua.c | 17 +++++++++++++---- src/script/types.c | 7 ++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 95ac70eba..1e09bcde1 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -1202,8 +1202,11 @@ int _luaGetTable(lua_State* lua) { } lua_pop(lua, 2); - obj = mScriptContextAccessWeakref(luaContext->d.context, obj); - if (!obj) { + obj = mScriptContextAccessWeakref(luaContext->d.context, obj); + if (obj->type->base == mSCRIPT_TYPE_WRAPPER) { + obj = mScriptValueUnwrap(obj); + } + if (!obj || obj->type != mSCRIPT_TYPE_MS_TABLE) { luaL_traceback(lua, lua, "Invalid table", 1); return lua_error(lua); } @@ -1235,7 +1238,10 @@ int _luaLenTable(lua_State* lua) { lua_pop(lua, 1); obj = mScriptContextAccessWeakref(luaContext->d.context, obj); - if (!obj) { + if (obj->type->base == mSCRIPT_TYPE_WRAPPER) { + obj = mScriptValueUnwrap(obj); + } + if (!obj || obj->type != mSCRIPT_TYPE_MS_TABLE) { luaL_traceback(lua, lua, "Invalid table", 1); return lua_error(lua); } @@ -1271,7 +1277,10 @@ static int _luaNextTable(lua_State* lua) { lua_pop(lua, 2); table = mScriptContextAccessWeakref(luaContext->d.context, table); - if (!table) { + if (table->type->base == mSCRIPT_TYPE_WRAPPER) { + table = mScriptValueUnwrap(table); + } + if (!table || table->type != mSCRIPT_TYPE_MS_TABLE) { luaL_traceback(lua, lua, "Invalid table", 1); return lua_error(lua); } diff --git a/src/script/types.c b/src/script/types.c index 0237cd4cb..3f04647a7 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -944,14 +944,11 @@ bool mScriptTableRemove(struct mScriptValue* table, struct mScriptValue* key) { } struct mScriptValue* mScriptTableLookup(struct mScriptValue* table, struct mScriptValue* key) { - if (table->type->base == mSCRIPT_TYPE_WRAPPER) { - table = mScriptValueUnwrap(table); - } if (table->type != mSCRIPT_TYPE_MS_TABLE) { - return false; + return NULL; } if (!key->type->hash) { - return false; + return NULL; } return HashTableLookupCustom(table->value.table, key); } From f2e9ea6a6b2abae476df2d5a9ecf41599ec10841 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Feb 2023 19:07:22 -0800 Subject: [PATCH 018/290] Scripting: Remove unused type macros --- include/mgba/script/types.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index 0be1c41e3..c9e3bed42 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -40,7 +40,6 @@ CXX_GUARD_START #define mSCRIPT_TYPE_C_WEAKREF uint32_t #define mSCRIPT_TYPE_C_S(STRUCT) struct STRUCT* #define mSCRIPT_TYPE_C_CS(STRUCT) const struct STRUCT* -#define mSCRIPT_TYPE_C_S_METHOD(STRUCT, NAME) _mSTStructFunctionType_ ## STRUCT ## _ ## NAME #define mSCRIPT_TYPE_C_PS(X) void #define mSCRIPT_TYPE_C_PCS(X) void #define mSCRIPT_TYPE_C_WSTR struct mScriptValue* @@ -68,7 +67,6 @@ CXX_GUARD_START #define mSCRIPT_TYPE_FIELD_WEAKREF u32 #define mSCRIPT_TYPE_FIELD_S(STRUCT) opaque #define mSCRIPT_TYPE_FIELD_CS(STRUCT) copaque -#define mSCRIPT_TYPE_FIELD_S_METHOD(STRUCT, NAME) copaque #define mSCRIPT_TYPE_FIELD_PS(STRUCT) opaque #define mSCRIPT_TYPE_FIELD_PCS(STRUCT) copaque #define mSCRIPT_TYPE_FIELD_WSTR opaque @@ -96,7 +94,6 @@ CXX_GUARD_START #define mSCRIPT_TYPE_MS_WEAKREF (&mSTWeakref) #define mSCRIPT_TYPE_MS_S(STRUCT) (&mSTStruct_ ## STRUCT) #define mSCRIPT_TYPE_MS_CS(STRUCT) (&mSTStructConst_ ## STRUCT) -#define mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME) (&_mSTStructBindingType_ ## STRUCT ## _ ## NAME) #define mSCRIPT_TYPE_MS_PS(STRUCT) (&mSTStructPtr_ ## STRUCT) #define mSCRIPT_TYPE_MS_PCS(STRUCT) (&mSTStructPtrConst_ ## STRUCT) #define mSCRIPT_TYPE_MS_WSTR (&mSTStringWrapper) @@ -125,10 +122,9 @@ CXX_GUARD_START #define mSCRIPT_TYPE_CMP_WRAPPER(TYPE) (true) #define mSCRIPT_TYPE_CMP_S(STRUCT) mSCRIPT_TYPE_MS_S(STRUCT)->name == _mSCRIPT_FIELD_NAME #define mSCRIPT_TYPE_CMP_CS(STRUCT) mSCRIPT_TYPE_MS_CS(STRUCT)->name == _mSCRIPT_FIELD_NAME -#define mSCRIPT_TYPE_CMP_S_METHOD(STRUCT, NAME) mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME)->name == _mSCRIPT_FIELD_NAME -#define mSCRIPT_TYPE_CMP(TYPE0, TYPE1) mSCRIPT_TYPE_CMP_ ## TYPE0(TYPE1) #define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) || mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE)) #define mSCRIPT_TYPE_CMP_WLIST(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE)) +#define mSCRIPT_TYPE_CMP(TYPE0, TYPE1) mSCRIPT_TYPE_CMP_ ## TYPE0(TYPE1) enum mScriptTypeBase { mSCRIPT_TYPE_VOID = 0, From c2bcf0df0758c4cff59f051b0bbfe255b3b72937 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Feb 2023 18:15:03 -0800 Subject: [PATCH 019/290] Scripting: Fix object get thunking --- src/script/types.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/types.c b/src/script/types.c index 3f04647a7..c2b367210 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -1262,7 +1262,7 @@ bool mScriptObjectGet(struct mScriptValue* obj, const char* member, struct mScri this->type = obj->type; this->refs = mSCRIPT_VALUE_UNREF; this->flags = 0; - this->value.opaque = obj; + this->value.opaque = obj->value.opaque; mSCRIPT_PUSH(&frame.arguments, CHARP, member); if (!mScriptInvoke(&getMember, &frame) || mScriptListSize(&frame.returnValues) != 1) { mScriptFrameDeinit(&frame); From 39e3b5181a0290735a639a528a79a72dc9c980a4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 3 Feb 2023 23:39:12 -0800 Subject: [PATCH 020/290] Scripting: Add WTABLE --- include/mgba/script/types.h | 5 +++++ src/script/types.c | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index c9e3bed42..9cc2e94b1 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -44,6 +44,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_C_PCS(X) void #define mSCRIPT_TYPE_C_WSTR struct mScriptValue* #define mSCRIPT_TYPE_C_WLIST struct mScriptValue* +#define mSCRIPT_TYPE_C_WTABLE struct mScriptValue* #define mSCRIPT_TYPE_C_W(X) struct mScriptValue* #define mSCRIPT_TYPE_C_CW(X) const struct mScriptValue* @@ -71,6 +72,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_FIELD_PCS(STRUCT) copaque #define mSCRIPT_TYPE_FIELD_WSTR opaque #define mSCRIPT_TYPE_FIELD_WLIST opaque +#define mSCRIPT_TYPE_FIELD_WTABLE opaque #define mSCRIPT_TYPE_FIELD_W(TYPE) opaque #define mSCRIPT_TYPE_FIELD_CW(TYPE) opaque @@ -98,6 +100,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_MS_PCS(STRUCT) (&mSTStructPtrConst_ ## STRUCT) #define mSCRIPT_TYPE_MS_WSTR (&mSTStringWrapper) #define mSCRIPT_TYPE_MS_WLIST (&mSTListWrapper) +#define mSCRIPT_TYPE_MS_WTABLE (&mSTTableWrapper) #define mSCRIPT_TYPE_MS_W(TYPE) (&mSTWrapper_ ## TYPE) #define mSCRIPT_TYPE_MS_CW(TYPE) (&mSTWrapperConst_ ## TYPE) #define mSCRIPT_TYPE_MS_DS(STRUCT) (&mSTStruct_doc_ ## STRUCT) @@ -124,6 +127,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_CMP_CS(STRUCT) mSCRIPT_TYPE_MS_CS(STRUCT)->name == _mSCRIPT_FIELD_NAME #define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) || mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE)) #define mSCRIPT_TYPE_CMP_WLIST(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE)) +#define mSCRIPT_TYPE_CMP_WTABLE(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_TABLE, TYPE)) #define mSCRIPT_TYPE_CMP(TYPE0, TYPE1) mSCRIPT_TYPE_CMP_ ## TYPE0(TYPE1) enum mScriptTypeBase { @@ -180,6 +184,7 @@ extern const struct mScriptType mSTWrapper; extern const struct mScriptType mSTWeakref; extern const struct mScriptType mSTStringWrapper; extern const struct mScriptType mSTListWrapper; +extern const struct mScriptType mSTTableWrapper; extern struct mScriptValue mScriptValueNull; diff --git a/src/script/types.c b/src/script/types.c index c2b367210..601d57339 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -243,6 +243,15 @@ const struct mScriptType mSTListWrapper = { .hash = NULL, }; +const struct mScriptType mSTTableWrapper = { + .base = mSCRIPT_TYPE_WRAPPER, + .size = sizeof(struct mScriptValue), + .name = "wrapper table", + .alloc = NULL, + .free = NULL, + .hash = NULL, +}; + const struct mScriptType mSTWeakref = { .base = mSCRIPT_TYPE_WEAKREF, .size = sizeof(uint32_t), From bfab9dc9f20a200f8a1065b0946a51b831a409bd Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 4 Feb 2023 01:12:04 -0800 Subject: [PATCH 021/290] Scripting: Specific wrapper types shouldn't compare equal with wrapped type --- include/mgba/script/types.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index 9cc2e94b1..17b3da70e 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -125,9 +125,9 @@ CXX_GUARD_START #define mSCRIPT_TYPE_CMP_WRAPPER(TYPE) (true) #define mSCRIPT_TYPE_CMP_S(STRUCT) mSCRIPT_TYPE_MS_S(STRUCT)->name == _mSCRIPT_FIELD_NAME #define mSCRIPT_TYPE_CMP_CS(STRUCT) mSCRIPT_TYPE_MS_CS(STRUCT)->name == _mSCRIPT_FIELD_NAME -#define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) || mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE)) -#define mSCRIPT_TYPE_CMP_WLIST(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE)) -#define mSCRIPT_TYPE_CMP_WTABLE(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_TABLE, TYPE)) +#define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_WSTR, TYPE)) +#define mSCRIPT_TYPE_CMP_WLIST(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_WLIST, TYPE)) +#define mSCRIPT_TYPE_CMP_WTABLE(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_WTABLE, TYPE)) #define mSCRIPT_TYPE_CMP(TYPE0, TYPE1) mSCRIPT_TYPE_CMP_ ## TYPE0(TYPE1) enum mScriptTypeBase { From f74db92ccd24aa4b1fefb5d9fff41c8c4f20bae0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 4 Feb 2023 01:13:16 -0800 Subject: [PATCH 022/290] Scripting: Add wrapper drill-down casts --- src/script/types.c | 48 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/script/types.c b/src/script/types.c index 601d57339..4ee851812 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -27,6 +27,10 @@ static bool _stringCast(const struct mScriptValue*, const struct mScriptType*, s static bool _castScalar(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*); static uint32_t _hashScalar(const struct mScriptValue*); +static bool _wstrCast(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*); +static bool _wlistCast(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*); +static bool _wtableCast(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*); + static uint32_t _valHash(const void* val, size_t len, uint32_t seed); static bool _valEqual(const void* a, const void* b); static void* _valRef(void*); @@ -232,6 +236,7 @@ const struct mScriptType mSTStringWrapper = { .alloc = NULL, .free = NULL, .hash = NULL, + .cast = _wstrCast, }; const struct mScriptType mSTListWrapper = { @@ -241,6 +246,7 @@ const struct mScriptType mSTListWrapper = { .alloc = NULL, .free = NULL, .hash = NULL, + .cast = _wlistCast, }; const struct mScriptType mSTTableWrapper = { @@ -250,6 +256,7 @@ const struct mScriptType mSTTableWrapper = { .alloc = NULL, .free = NULL, .hash = NULL, + .cast = _wtableCast, }; const struct mScriptType mSTWeakref = { @@ -375,6 +382,45 @@ uint32_t _hashScalar(const struct mScriptValue* val) { return x; } +bool _wstrCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) { + if (input->type->base != mSCRIPT_TYPE_WRAPPER) { + return false; + } + const struct mScriptValue* unwrapped = mScriptValueUnwrapConst(input); + if (unwrapped->type != mSCRIPT_TYPE_MS_STR) { + return false; + } + memcpy(output, input, sizeof(*output)); + output->type = type; + return true; +} + +bool _wlistCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) { + if (input->type->base != mSCRIPT_TYPE_WRAPPER) { + return false; + } + const struct mScriptValue* unwrapped = mScriptValueUnwrapConst(input); + if (unwrapped->type != mSCRIPT_TYPE_MS_LIST) { + return false; + } + memcpy(output, input, sizeof(*output)); + output->type = type; + return true; +} + +bool _wtableCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) { + if (input->type->base != mSCRIPT_TYPE_WRAPPER) { + return false; + } + const struct mScriptValue* unwrapped = mScriptValueUnwrapConst(input); + if (unwrapped->type != mSCRIPT_TYPE_MS_TABLE) { + return false; + } + memcpy(output, input, sizeof(*output)); + output->type = type; + return true; +} + #define AS(NAME, TYPE) \ bool _as ## NAME(const struct mScriptValue* input, mSCRIPT_TYPE_C_ ## TYPE * T) { \ switch (input->type->base) { \ @@ -1484,7 +1530,7 @@ bool mScriptPopPointer(struct mScriptList* list, void** out) { } bool mScriptCast(const struct mScriptType* type, const struct mScriptValue* input, struct mScriptValue* output) { - if (input->type->base == mSCRIPT_TYPE_WRAPPER) { + if (input->type->base == mSCRIPT_TYPE_WRAPPER && type->base != mSCRIPT_TYPE_WRAPPER) { input = mScriptValueUnwrapConst(input); } if (type->cast && type->cast(input, type, output)) { From 004f68496f5071d68517042019234cf21403cc74 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 3 Feb 2023 23:08:07 -0800 Subject: [PATCH 023/290] Scripting: Add type-overloadable setters --- include/mgba/script/macros.h | 2 +- include/mgba/script/types.h | 2 +- src/script/test/classes.c | 103 ++++++++++++++++++++++++++++ src/script/types.c | 129 +++++++++++++++++++++++++++++++++-- 4 files changed, 229 insertions(+), 7 deletions(-) diff --git a/include/mgba/script/macros.h b/include/mgba/script/macros.h index 42710f9e8..bbde17b07 100644 --- a/include/mgba/script/macros.h +++ b/include/mgba/script/macros.h @@ -423,7 +423,7 @@ CXX_GUARD_START #define mSCRIPT_DEFINE_STRUCT_DEINIT(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(DEINIT, TYPE, _deinit, _deinit) #define mSCRIPT_DEFINE_STRUCT_DEINIT_NAMED(TYPE, NAME) _mSCRIPT_DEFINE_STRUCT_BINDING(DEINIT, TYPE, _deinit, NAME) #define mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(GET, TYPE, _get, _get) -#define mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(SET, TYPE, _set, _set) +#define mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TYPE, SETTER) _mSCRIPT_DEFINE_STRUCT_BINDING(SET, TYPE, SETTER, SETTER) #define mSCRIPT_DEFINE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME) mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(doc_ ## TYPE, NAME, NAME) diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index 17b3da70e..76d5c1579 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -252,10 +252,10 @@ struct mScriptTypeClass { bool internal; struct Table instanceMembers; struct Table castToMembers; + struct Table setters; struct mScriptClassMember* alloc; // TODO struct mScriptClassMember* free; struct mScriptClassMember* get; - struct mScriptClassMember* set; // TODO }; struct mScriptType { diff --git a/src/script/test/classes.c b/src/script/test/classes.c index 85afb627c..f4dec7722 100644 --- a/src/script/test/classes.c +++ b/src/script/test/classes.c @@ -46,6 +46,14 @@ struct TestF { int* ref; }; +struct TestG { + const char* name; + int64_t s; + uint64_t u; + double f; + const char* c; +}; + static int32_t testAi0(struct TestA* a) { return a->i; } @@ -83,6 +91,26 @@ static void testDeinit(struct TestF* f) { ++*f->ref; } +static void testSetS(struct TestG* g, const char* name, int64_t val) { + g->name = name; + g->s = val; +} + +static void testSetU(struct TestG* g, const char* name, uint64_t val) { + g->name = name; + g->u = val; +} + +static void testSetF(struct TestG* g, const char* name, double val) { + g->name = name; + g->f = val; +} + +static void testSetC(struct TestG* g, const char* name, const char* val) { + g->name = name; + g->c = val; +} + #define MEMBER_A_DOCSTRING "Member a" mSCRIPT_DECLARE_STRUCT(TestA); @@ -157,6 +185,19 @@ mSCRIPT_DEFINE_STRUCT(TestF) mSCRIPT_DEFINE_STRUCT_DEINIT(TestF) mSCRIPT_DEFINE_END; +mSCRIPT_DECLARE_STRUCT(TestG); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestG, setS, testSetS, 2, CHARP, name, S64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestG, setU, testSetU, 2, CHARP, name, U64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestG, setF, testSetF, 2, CHARP, name, F64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestG, setC, testSetC, 2, CHARP, name, CHARP, value); + +mSCRIPT_DEFINE_STRUCT(TestG) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setS) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setU) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setF) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setC) +mSCRIPT_DEFINE_END; + M_TEST_DEFINE(testALayout) { struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestA)->details.cls; assert_false(cls->init); @@ -1032,6 +1073,67 @@ M_TEST_DEFINE(testFDeinit) { assert_false(cls->init); } +M_TEST_DEFINE(testGSet) { + struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestG)->details.cls; + + struct TestG s = { + }; + + assert_int_equal(s.s, 0); + assert_int_equal(s.u, 0); + assert_float_equal(s.f, 0, 0); + assert_null(s.c); + + struct mScriptValue sval = mSCRIPT_MAKE_S(TestG, &s); + struct mScriptValue val; + struct mScriptValue* pval; + + val = mSCRIPT_MAKE_S64(1); + assert_true(mScriptObjectSet(&sval, "a", &val)); + assert_int_equal(s.s, 1); + assert_string_equal(s.name, "a"); + + val = mSCRIPT_MAKE_U64(2); + assert_true(mScriptObjectSet(&sval, "b", &val)); + assert_int_equal(s.u, 2); + assert_string_equal(s.name, "b"); + + val = mSCRIPT_MAKE_F64(1.5); + assert_true(mScriptObjectSet(&sval, "c", &val)); + assert_float_equal(s.f, 1.5, 0); + assert_string_equal(s.name, "c"); + + val = mSCRIPT_MAKE_CHARP("hello"); + assert_true(mScriptObjectSet(&sval, "d", &val)); + assert_string_equal(s.c, "hello"); + assert_string_equal(s.name, "d"); + + val = mSCRIPT_MAKE_S32(3); + assert_true(mScriptObjectSet(&sval, "a", &val)); + assert_int_equal(s.s, 3); + + val = mSCRIPT_MAKE_S16(4); + assert_true(mScriptObjectSet(&sval, "a", &val)); + assert_int_equal(s.s, 4); + + val = mSCRIPT_MAKE_S8(5); + assert_true(mScriptObjectSet(&sval, "a", &val)); + assert_int_equal(s.s, 5); + + val = mSCRIPT_MAKE_BOOL(false); + assert_true(mScriptObjectSet(&sval, "a", &val)); + assert_int_equal(s.u, 0); + + pval = mScriptStringCreateFromASCII("goodbye"); + assert_true(mScriptObjectSet(&sval, "a", pval)); + assert_string_equal(s.c, "goodbye"); + mScriptValueDeref(pval); + + assert_true(cls->init); + mScriptClassDeinit(cls); + assert_false(cls->init); +} + M_TEST_SUITE_DEFINE(mScriptClasses, cmocka_unit_test(testALayout), cmocka_unit_test(testASignatures), @@ -1047,4 +1149,5 @@ M_TEST_SUITE_DEFINE(mScriptClasses, cmocka_unit_test(testDSet), cmocka_unit_test(testEGet), cmocka_unit_test(testFDeinit), + cmocka_unit_test(testGSet), ) diff --git a/src/script/types.c b/src/script/types.c index 4ee851812..a05f6d252 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -1149,12 +1149,16 @@ static void _mScriptClassInit(struct mScriptTypeClass* cls, const struct mScript } break; case mSCRIPT_CLASS_INIT_SET: - cls->set = calloc(1, sizeof(*member)); - memcpy(cls->set, &detail->info.member, sizeof(*member)); + member = calloc(1, sizeof(*member)); + memcpy(member, &detail->info.member, sizeof(*member)); if (docstring) { - cls->set->docstring = docstring; + member->docstring = docstring; docstring = NULL; } + if (detail->info.member.type->details.function.parameters.count != 3) { + abort(); + } + HashTableInsert(&cls->setters, detail->info.member.type->details.function.parameters.entries[2]->name, member); break; case mSCRIPT_CLASS_INIT_INTERNAL: cls->internal = true; @@ -1169,11 +1173,11 @@ void mScriptClassInit(struct mScriptTypeClass* cls) { } HashTableInit(&cls->instanceMembers, 0, free); HashTableInit(&cls->castToMembers, 0, NULL); + HashTableInit(&cls->setters, 0, free); cls->alloc = NULL; cls->free = NULL; cls->get = NULL; - cls->set = NULL; _mScriptClassInit(cls, cls->details, false); cls->init = true; @@ -1185,6 +1189,7 @@ void mScriptClassDeinit(struct mScriptTypeClass* cls) { } HashTableDeinit(&cls->instanceMembers); HashTableDeinit(&cls->castToMembers); + HashTableDeinit(&cls->setters); cls->init = false; } @@ -1352,6 +1357,91 @@ bool mScriptObjectGetConst(const struct mScriptValue* obj, const char* member, s return _accessRawMember(m, obj->value.opaque, true, val); } +static struct mScriptClassMember* _findSetter(const struct mScriptTypeClass* cls, const struct mScriptType* type) { + struct mScriptClassMember* m = HashTableLookup(&cls->setters, type->name); + if (m) { + return m; + } + + switch (type->base) { + case mSCRIPT_TYPE_SINT: + if (type->size < 2) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_S16->name); + if (m) { + return m; + } + } + if (type->size < 4) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_S32->name); + if (m) { + return m; + } + } + if (type->size < 8) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_S64->name); + if (m) { + return m; + } + } + break; + case mSCRIPT_TYPE_UINT: + if (type->size < 2) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_U16->name); + if (m) { + return m; + } + } + if (type->size < 4) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_U32->name); + if (m) { + return m; + } + } + if (type->size < 8) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_U64->name); + if (m) { + return m; + } + } + break; + case mSCRIPT_TYPE_FLOAT: + if (type->size < 8) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_F64->name); + if (m) { + return m; + } + } + break; + case mSCRIPT_TYPE_STRING: + if (type == mSCRIPT_TYPE_MS_STR) { + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_CHARP->name); + if (m) { + return m; + } + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_WSTR->name); + if (m) { + return m; + } + } + break; + case mSCRIPT_TYPE_LIST: + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_WLIST->name); + if (m) { + return m; + } + break; + case mSCRIPT_TYPE_TABLE: + m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_WTABLE->name); + if (m) { + return m; + } + break; + default: + break; + } + return NULL; +} + bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScriptValue* val) { if (obj->type->base != mSCRIPT_TYPE_OBJECT || obj->type->isConst) { return false; @@ -1366,7 +1456,36 @@ bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScri struct mScriptClassMember* m = HashTableLookup(&cls->instanceMembers, member); if (!m) { - return false; + if (val->type->base == mSCRIPT_TYPE_WRAPPER) { + val = mScriptValueUnwrap(val); + } + struct mScriptValue setMember; + m = _findSetter(cls, val->type); + if (!m || !_accessRawMember(m, obj->value.opaque, obj->type->isConst, &setMember)) { + return false; + } + struct mScriptFrame frame; + mScriptFrameInit(&frame); + struct mScriptValue* this = mScriptListAppend(&frame.arguments); + this->type = obj->type; + this->refs = mSCRIPT_VALUE_UNREF; + this->flags = 0; + this->value.opaque = obj->value.opaque; + mSCRIPT_PUSH(&frame.arguments, CHARP, member); + mScriptValueWrap(val, mScriptListAppend(&frame.arguments)); + bool needsDeref = mScriptListGetPointer(&frame.arguments, 2)->type->base == mSCRIPT_TYPE_WRAPPER; + if (!mScriptInvoke(&setMember, &frame) || mScriptListSize(&frame.returnValues) != 0) { + mScriptFrameDeinit(&frame); + if (needsDeref) { + mScriptValueDeref(val); + } + return false; + } + mScriptFrameDeinit(&frame); + if (needsDeref) { + mScriptValueDeref(val); + } + return true; } void* rawMember = (void *)((uintptr_t) obj->value.opaque + m->offset); From 5c0bd1b2454cd64930b05806086b09844f6a4016 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 4 Feb 2023 23:16:33 -0800 Subject: [PATCH 024/290] Scripting: Add faux "NUL" type for type matching --- include/mgba/script/types.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index 76d5c1579..da13883d2 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -38,6 +38,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_C_TABLE struct Table* #define mSCRIPT_TYPE_C_WRAPPER struct mScriptValue* #define mSCRIPT_TYPE_C_WEAKREF uint32_t +#define mSCRIPT_TYPE_C_NUL void* #define mSCRIPT_TYPE_C_S(STRUCT) struct STRUCT* #define mSCRIPT_TYPE_C_CS(STRUCT) const struct STRUCT* #define mSCRIPT_TYPE_C_PS(X) void @@ -66,6 +67,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_FIELD_TABLE table #define mSCRIPT_TYPE_FIELD_WRAPPER opaque #define mSCRIPT_TYPE_FIELD_WEAKREF u32 +#define mSCRIPT_TYPE_FIELD_NUL opaque #define mSCRIPT_TYPE_FIELD_S(STRUCT) opaque #define mSCRIPT_TYPE_FIELD_CS(STRUCT) copaque #define mSCRIPT_TYPE_FIELD_PS(STRUCT) opaque @@ -94,6 +96,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_MS_TABLE (&mSTTable) #define mSCRIPT_TYPE_MS_WRAPPER (&mSTWrapper) #define mSCRIPT_TYPE_MS_WEAKREF (&mSTWeakref) +#define mSCRIPT_TYPE_MS_NUL mSCRIPT_TYPE_MS_VOID #define mSCRIPT_TYPE_MS_S(STRUCT) (&mSTStruct_ ## STRUCT) #define mSCRIPT_TYPE_MS_CS(STRUCT) (&mSTStructConst_ ## STRUCT) #define mSCRIPT_TYPE_MS_PS(STRUCT) (&mSTStructPtr_ ## STRUCT) @@ -123,6 +126,7 @@ CXX_GUARD_START #define mSCRIPT_TYPE_CMP_TABLE(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_TABLE, TYPE) #define mSCRIPT_TYPE_CMP_PTR(TYPE) ((TYPE)->base >= mSCRIPT_TYPE_OPAQUE) #define mSCRIPT_TYPE_CMP_WRAPPER(TYPE) (true) +#define mSCRIPT_TYPE_CMP_NUL(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_VOID, TYPE) #define mSCRIPT_TYPE_CMP_S(STRUCT) mSCRIPT_TYPE_MS_S(STRUCT)->name == _mSCRIPT_FIELD_NAME #define mSCRIPT_TYPE_CMP_CS(STRUCT) mSCRIPT_TYPE_MS_CS(STRUCT)->name == _mSCRIPT_FIELD_NAME #define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_WSTR, TYPE)) From 282a033df265596335800f34a2e9e5111ff5ad00 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 00:39:02 -0800 Subject: [PATCH 025/290] Scripting: Clean up refcounting --- src/script/context.c | 1 + src/script/engines/lua.c | 97 +++++++++++++++++++++++++--------------- src/script/stdlib.c | 1 - src/script/types.c | 8 ---- 4 files changed, 63 insertions(+), 44 deletions(-) diff --git a/src/script/context.c b/src/script/context.c index bf5fcfa54..418050e74 100644 --- a/src/script/context.c +++ b/src/script/context.c @@ -250,6 +250,7 @@ uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* c HashTableIteratorLookup(&context->callbacks, &iter, callback); info->callback = HashTableIteratorGetKey(&context->callbacks, &iter); info->id = mScriptListSize(list->value.list); + mScriptValueRef(fn); mScriptValueWrap(fn, mScriptListAppend(list->value.list)); while (true) { uint32_t id = context->nextCallbackId; diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 1e09bcde1..9d652eb4f 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -36,8 +36,11 @@ static const char* _luaGetError(struct mScriptEngineContext*); static bool _luaCall(struct mScriptFrame*, void* context); +static void _freeFrame(struct mScriptList* frame); +static void _autofreeFrame(struct mScriptContext* context, struct mScriptList* frame); + struct mScriptEngineContextLua; -static bool _luaPushFrame(struct mScriptEngineContextLua*, struct mScriptList*, bool internal); +static bool _luaPushFrame(struct mScriptEngineContextLua*, struct mScriptList*); static bool _luaPopFrame(struct mScriptEngineContextLua*, struct mScriptList*); static bool _luaInvoke(struct mScriptEngineContextLua*, struct mScriptFrame*); @@ -520,6 +523,8 @@ struct mScriptValue* _luaRootScope(struct mScriptEngineContext* ctx) { lua_pop(luaContext->lua, 1); key = _luaCoerce(luaContext, false); mScriptValueWrap(key, mScriptListAppend(list->value.list)); + mScriptValueRef(key); + mScriptContextFillPool(luaContext->d.context, key); } lua_pop(luaContext->lua, 1); @@ -544,7 +549,7 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext, lua_pushnil(luaContext->lua); - void* tablePointer; + const void* tablePointer; while (lua_next(luaContext->lua, -2) != 0) { struct mScriptValue* value = NULL; int type = lua_type(luaContext->lua, -1); @@ -559,11 +564,8 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext, tablePointer = lua_topointer(luaContext->lua, -1); // Ensure this table doesn't contain any cycles if (!HashTableLookupBinary(markedObjects, &tablePointer, sizeof(tablePointer))) { - HashTableInsertBinary(markedObjects, &tablePointer, sizeof(tablePointer), tablePointer); + HashTableInsertBinary(markedObjects, &tablePointer, sizeof(tablePointer), (void*) tablePointer); value = _luaCoerceTable(luaContext, markedObjects); - if (value) { - mScriptValueRef(value); - } } default: break; @@ -595,18 +597,13 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext, return false; } mScriptTableInsert(table, key, value); - if (key->type != mSCRIPT_TYPE_MS_STR) { - // Strings are added to the ref pool, so we need to keep it - // ref'd to prevent it from being collected prematurely - mScriptValueDeref(key); - } + mScriptValueDeref(key); mScriptValueDeref(value); } lua_pop(luaContext->lua, 1); size_t len = mScriptTableSize(table); if (!isList || !len) { - mScriptContextFillPool(luaContext->d.context, table); return table; } @@ -616,18 +613,15 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext, struct mScriptValue* value = mScriptTableLookup(table, &mSCRIPT_MAKE_S64(i)); if (!value) { mScriptValueDeref(list); - mScriptContextFillPool(luaContext->d.context, table); return table; } mScriptValueWrap(value, mScriptListAppend(list->value.list)); } if (i != len + 1) { mScriptValueDeref(list); - mScriptContextFillPool(luaContext->d.context, table); return table; } mScriptValueDeref(table); - mScriptContextFillPool(luaContext->d.context, list); return list; } @@ -663,7 +657,6 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool case LUA_TSTRING: buffer = lua_tolstring(luaContext->lua, -1, &size); value = mScriptStringCreateFromBytes(buffer, size); - mScriptContextFillPool(luaContext->d.context, value); break; case LUA_TFUNCTION: // This function pops the value internally via luaL_ref @@ -692,6 +685,12 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool lua_pop(luaContext->lua, 2); value = lua_touserdata(luaContext->lua, -1); value = mScriptContextAccessWeakref(luaContext->d.context, value); + if (value->type->base == mSCRIPT_TYPE_WRAPPER) { + value = mScriptValueUnwrap(value); + } + if (value) { + mScriptValueRef(value); + } break; } if (pop) { @@ -713,6 +712,7 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v lua_pushnil(luaContext->lua); return true; } + mScriptContextFillPool(luaContext->d.context, value); } struct mScriptValue derefPtr; if (value->type->base == mSCRIPT_TYPE_OPAQUE) { @@ -803,6 +803,7 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v if (needsWeakref) { *newValue = mSCRIPT_MAKE(WEAKREF, weakref); } else { + mScriptValueRef(value); mScriptValueWrap(value, newValue); } lua_getfield(luaContext->lua, LUA_REGISTRYINDEX, "mSTList"); @@ -813,6 +814,7 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v if (needsWeakref) { *newValue = mSCRIPT_MAKE(WEAKREF, weakref); } else { + mScriptValueRef(value); mScriptValueWrap(value, newValue); } lua_getfield(luaContext->lua, LUA_REGISTRYINDEX, "mSTTable"); @@ -824,7 +826,6 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v newValue->refs = mSCRIPT_VALUE_UNREF; newValue->type->alloc(newValue); lua_pushcclosure(luaContext->lua, _luaThunk, 1); - mScriptValueDeref(value); break; case mSCRIPT_TYPE_OBJECT: if (!value->value.opaque) { @@ -835,6 +836,7 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v if (needsWeakref) { *newValue = mSCRIPT_MAKE(WEAKREF, weakref); } else { + mScriptValueRef(value); mScriptValueWrap(value, newValue); } lua_getfield(luaContext->lua, LUA_REGISTRYINDEX, "mSTStruct"); @@ -935,16 +937,12 @@ const char* _luaGetError(struct mScriptEngineContext* context) { return luaContext->lastError; } -bool _luaPushFrame(struct mScriptEngineContextLua* luaContext, struct mScriptList* frame, bool internal) { +bool _luaPushFrame(struct mScriptEngineContextLua* luaContext, struct mScriptList* frame) { bool ok = true; if (frame) { size_t i; for (i = 0; i < mScriptListSize(frame); ++i) { struct mScriptValue* value = mScriptListGetPointer(frame, i); - if (internal && value->type->base == mSCRIPT_TYPE_WRAPPER) { - value = mScriptValueUnwrap(value); - mScriptContextFillPool(luaContext->d.context, value); - } if (!_luaWrap(luaContext, value)) { ok = false; break; @@ -968,8 +966,11 @@ bool _luaPopFrame(struct mScriptEngineContextLua* luaContext, struct mScriptList ok = false; break; } - mScriptValueWrap(value, mScriptListAppend(frame)); - mScriptValueDeref(value); + struct mScriptValue* tail = mScriptListAppend(frame); + mScriptValueWrap(value, tail); + if (tail->type == value->type) { + mScriptValueDeref(value); + } } if (count > i) { lua_pop(luaContext->lua, count - i); @@ -996,6 +997,26 @@ bool _luaCall(struct mScriptFrame* frame, void* context) { return true; } +void _freeFrame(struct mScriptList* frame) { + size_t i; + for (i = 0; i < mScriptListSize(frame); ++i) { + struct mScriptValue* val = mScriptValueUnwrap(mScriptListGetPointer(frame, i)); + if (val) { + mScriptValueDeref(val); + } + } +} + +void _autofreeFrame(struct mScriptContext* context, struct mScriptList* frame) { + size_t i; + for (i = 0; i < mScriptListSize(frame); ++i) { + struct mScriptValue* val = mScriptValueUnwrap(mScriptListGetPointer(frame, i)); + if (val) { + mScriptContextFillPool(context, val); + } + } +} + bool _luaInvoke(struct mScriptEngineContextLua* luaContext, struct mScriptFrame* frame) { int nargs = 0; if (frame) { @@ -1007,7 +1028,7 @@ bool _luaInvoke(struct mScriptEngineContextLua* luaContext, struct mScriptFrame* luaContext->lastError = NULL; } - if (frame && !_luaPushFrame(luaContext, &frame->arguments, false)) { + if (frame && !_luaPushFrame(luaContext, &frame->arguments)) { return false; } @@ -1072,6 +1093,7 @@ int _luaThunk(lua_State* lua) { struct mScriptFrame frame; mScriptFrameInit(&frame); if (!_luaPopFrame(luaContext, &frame.arguments)) { + _freeFrame(&frame.arguments); mScriptContextDrainPool(luaContext->d.context); mScriptFrameDeinit(&frame); luaL_traceback(lua, lua, "Error calling function (translating arguments into runtime)", 1); @@ -1079,19 +1101,21 @@ int _luaThunk(lua_State* lua) { } struct mScriptValue* fn = lua_touserdata(lua, lua_upvalueindex(1)); + _autofreeFrame(luaContext->d.context, &frame.arguments); if (!fn || !mScriptInvoke(fn, &frame)) { + mScriptContextDrainPool(luaContext->d.context); mScriptFrameDeinit(&frame); luaL_traceback(lua, lua, "Error calling function (invoking failed)", 1); return lua_error(lua); } - if (!_luaPushFrame(luaContext, &frame.returnValues, true)) { - mScriptFrameDeinit(&frame); + bool ok = _luaPushFrame(luaContext, &frame.returnValues); + mScriptContextDrainPool(luaContext->d.context); + mScriptFrameDeinit(&frame); + if (!ok) { luaL_traceback(lua, lua, "Error calling function (translating return values from runtime)", 1); return lua_error(lua); } - mScriptContextDrainPool(luaContext->d.context); - mScriptFrameDeinit(&frame); return lua_gettop(luaContext->lua); } @@ -1146,19 +1170,22 @@ int _luaSetObject(lua_State* lua) { strlcpy(key, keyPtr, sizeof(key)); lua_pop(lua, 2); - obj = mScriptContextAccessWeakref(luaContext->d.context, obj); - if (!obj) { - luaL_traceback(lua, lua, "Invalid object", 1); - return lua_error(lua); - } - if (!val) { luaL_traceback(lua, lua, "Error translating value to runtime", 1); return lua_error(lua); } + obj = mScriptContextAccessWeakref(luaContext->d.context, obj); + if (!obj) { + mScriptValueDeref(val); + mScriptContextDrainPool(luaContext->d.context); + luaL_traceback(lua, lua, "Invalid object", 1); + return lua_error(lua); + } + if (!mScriptObjectSet(obj, key, val)) { mScriptValueDeref(val); + mScriptContextDrainPool(luaContext->d.context); char error[MAX_KEY_SIZE + 16]; snprintf(error, sizeof(error), "Invalid key '%s'", key); luaL_traceback(lua, lua, "Invalid key", 1); diff --git a/src/script/stdlib.c b/src/script/stdlib.c index 839673cdb..3163a0d98 100644 --- a/src/script/stdlib.c +++ b/src/script/stdlib.c @@ -24,7 +24,6 @@ static uint32_t _mScriptCallbackAdd(struct mScriptCallbackManager* adapter, stru fn = mScriptValueUnwrap(fn); } uint32_t id = mScriptContextAddCallback(adapter->context, name->buffer, fn); - mScriptValueDeref(fn); return id; } diff --git a/src/script/types.c b/src/script/types.c index a05f6d252..dd50f7968 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -892,7 +892,6 @@ void mScriptValueWrap(struct mScriptValue* value, struct mScriptValue* out) { out->type = mSCRIPT_TYPE_MS_WRAPPER; out->value.opaque = value; - mScriptValueRef(value); } struct mScriptValue* mScriptValueUnwrap(struct mScriptValue* value) { @@ -1473,18 +1472,11 @@ bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScri this->value.opaque = obj->value.opaque; mSCRIPT_PUSH(&frame.arguments, CHARP, member); mScriptValueWrap(val, mScriptListAppend(&frame.arguments)); - bool needsDeref = mScriptListGetPointer(&frame.arguments, 2)->type->base == mSCRIPT_TYPE_WRAPPER; if (!mScriptInvoke(&setMember, &frame) || mScriptListSize(&frame.returnValues) != 0) { mScriptFrameDeinit(&frame); - if (needsDeref) { - mScriptValueDeref(val); - } return false; } mScriptFrameDeinit(&frame); - if (needsDeref) { - mScriptValueDeref(val); - } return true; } From 045a2c96dce4aa287612e1c06a5e5b961555a740 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 00:41:33 -0800 Subject: [PATCH 026/290] Scripting: Fix passing mSTList/Table from Lua back into the runtime --- src/script/context.c | 8 -------- src/script/engines/lua.c | 12 +++++++++-- src/script/test/lua.c | 44 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/script/context.c b/src/script/context.c index 418050e74..65932fd79 100644 --- a/src/script/context.c +++ b/src/script/context.c @@ -84,14 +84,6 @@ void mScriptContextFillPool(struct mScriptContext* context, struct mScriptValue* if (value->refs == mSCRIPT_VALUE_UNREF) { return; } - switch (value->type->base) { - case mSCRIPT_TYPE_SINT: - case mSCRIPT_TYPE_UINT: - case mSCRIPT_TYPE_FLOAT: - return; - default: - break; - } struct mScriptValue* poolEntry = mScriptListAppend(&context->refPool); poolEntry->type = mSCRIPT_TYPE_MS_WRAPPER; diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 9d652eb4f..3dfe8d0ac 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -679,8 +679,16 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool } luaL_getmetatable(luaContext->lua, "mSTStruct"); if (!lua_rawequal(luaContext->lua, -1, -2)) { - lua_pop(luaContext->lua, 2); - break; + lua_pop(luaContext->lua, 1); + luaL_getmetatable(luaContext->lua, "mSTList"); + if (!lua_rawequal(luaContext->lua, -1, -2)) { + lua_pop(luaContext->lua, 1); + luaL_getmetatable(luaContext->lua, "mSTTable"); + if (!lua_rawequal(luaContext->lua, -1, -2)) { + lua_pop(luaContext->lua, 2); + break; + } + } } lua_pop(luaContext->lua, 2); value = lua_touserdata(luaContext->lua, -1); diff --git a/src/script/test/lua.c b/src/script/test/lua.c index eab8550dc..16ecc9987 100644 --- a/src/script/test/lua.c +++ b/src/script/test/lua.c @@ -811,6 +811,48 @@ M_TEST_DEFINE(linkedList) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(listConvert) { + SETUP_LUA; + + struct mScriptValue* list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST); + + assert_true(lua->setGlobal(lua, "l", list)); + TEST_PROGRAM("assert(l)"); + + struct mScriptValue* val = lua->getGlobal(lua, "l"); + assert_non_null(val); + if (val->type->base == mSCRIPT_TYPE_WRAPPER) { + val = mScriptValueUnwrap(val); + } + assert_ptr_equal(val->type, mSCRIPT_TYPE_MS_LIST); + assert_ptr_equal(val->value.list, list->value.list); + mScriptValueDeref(val); + mScriptValueDeref(list); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(tableConvert) { + SETUP_LUA; + + struct mScriptValue* list = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + + assert_true(lua->setGlobal(lua, "l", list)); + TEST_PROGRAM("assert(l)"); + + struct mScriptValue* val = lua->getGlobal(lua, "l"); + assert_non_null(val); + if (val->type->base == mSCRIPT_TYPE_WRAPPER) { + val = mScriptValueUnwrap(val); + } + assert_ptr_equal(val->type, mSCRIPT_TYPE_MS_TABLE); + assert_ptr_equal(val->value.table, list->value.table); + mScriptValueDeref(val); + mScriptValueDeref(list); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua, cmocka_unit_test(create), cmocka_unit_test(loadGood), @@ -831,4 +873,6 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua, cmocka_unit_test(tableIterate), cmocka_unit_test(callList), cmocka_unit_test(linkedList), + cmocka_unit_test(listConvert), + cmocka_unit_test(tableConvert), ) From aefcd174a822ed9f2929eb968631df03f69aede3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 7 Feb 2023 21:09:29 -0800 Subject: [PATCH 027/290] Scripting: Warning cleanup --- src/script/test/lua.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/script/test/lua.c b/src/script/test/lua.c index 16ecc9987..49c5521d7 100644 --- a/src/script/test/lua.c +++ b/src/script/test/lua.c @@ -385,9 +385,6 @@ M_TEST_DEFINE(callCFunc) { M_TEST_DEFINE(callCTable) { SETUP_LUA; - struct mScriptValue a = mSCRIPT_MAKE_S32(1); - struct mScriptValue* val; - assert_true(lua->setGlobal(lua, "b", &boundTableSize)); TEST_PROGRAM("assert(b({}) == 0)"); @@ -424,7 +421,6 @@ M_TEST_DEFINE(globalNull) { SETUP_LUA; struct Test s = {}; - struct mScriptValue* val; struct mScriptValue a; LOAD_PROGRAM("assert(a)"); From 00a34e0d0767f6a1f3f79f52ab24367034ee7204 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 4 Feb 2023 01:18:01 -0800 Subject: [PATCH 028/290] Scripting: Add skeleton of storage API --- include/mgba/script/storage.h | 21 +++++ src/script/CMakeLists.txt | 9 ++ src/script/storage.c | 155 +++++++++++++++++++++++++++++++ src/script/test/storage.c | 169 ++++++++++++++++++++++++++++++++++ 4 files changed, 354 insertions(+) create mode 100644 include/mgba/script/storage.h create mode 100644 src/script/storage.c create mode 100644 src/script/test/storage.c diff --git a/include/mgba/script/storage.h b/include/mgba/script/storage.h new file mode 100644 index 000000000..5b6490e82 --- /dev/null +++ b/include/mgba/script/storage.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2013-2023 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 M_SCRIPT_STORAGE_H +#define M_SCRIPT_STORAGE_H + +#include + +CXX_GUARD_START + +#include +#include + +struct VFile; +void mScriptContextAttachStorage(struct mScriptContext* context); + +CXX_GUARD_END + +#endif diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index d84659453..f79b66d08 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -4,12 +4,17 @@ set(SOURCE_FILES input.c socket.c stdlib.c + storage.c types.c) set(TEST_FILES test/classes.c test/types.c) +if(USE_JSON_C) + list(APPEND SOURCE_FILES storage.c) +endif() + if(USE_LUA) list(APPEND SOURCE_FILES engines/lua.c) list(APPEND TEST_FILES @@ -17,6 +22,10 @@ if(USE_LUA) test/input.c test/lua.c test/stdlib.c) + + if(USE_JSON_C) + list(APPEND TEST_FILES test/storage.c) + endif() endif() source_group("Scripting" FILES ${SOURCE_FILES}) diff --git a/src/script/storage.c b/src/script/storage.c new file mode 100644 index 000000000..9bbbbfa5b --- /dev/null +++ b/src/script/storage.c @@ -0,0 +1,155 @@ +/* Copyright (c) 2013-2023 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 + +#define STORAGE_LEN_MAX 64 + +struct mScriptStorageBucket { + char* name; + struct mScriptValue* root; + bool dirty; +}; + +struct mScriptStorageContext { + struct Table buckets; +}; + +void mScriptStorageBucketDeinit(void*); +struct mScriptValue* mScriptStorageBucketGet(struct mScriptStorageBucket* adapter, const char* key); +static void mScriptStorageBucketSet(struct mScriptStorageBucket* adapter, const char* key, struct mScriptValue* value); +static void mScriptStorageBucketSetVoid(struct mScriptStorageBucket* adapter, const char* key, struct mScriptValue* value); +static void mScriptStorageBucketSetSInt(struct mScriptStorageBucket* adapter, const char* key, int64_t value); +static void mScriptStorageBucketSetUInt(struct mScriptStorageBucket* adapter, const char* key, uint64_t value); +static void mScriptStorageBucketSetFloat(struct mScriptStorageBucket* adapter, const char* key, double value); +static void mScriptStorageBucketSetBool(struct mScriptStorageBucket* adapter, const char* key, bool value); + +void mScriptStorageContextDeinit(struct mScriptStorageContext*); +struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext*, const char* name); + +mSCRIPT_DECLARE_STRUCT(mScriptStorageBucket); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, WRAPPER, _get, mScriptStorageBucketGet, 1, CHARP, key); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setSInt, mScriptStorageBucketSetSInt, 2, CHARP, key, S64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setUInt, mScriptStorageBucketSetUInt, 2, CHARP, key, U64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setFloat, mScriptStorageBucketSetFloat, 2, CHARP, key, F64, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setBool, mScriptStorageBucketSetBool, 2, CHARP, key, BOOL, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setStr, mScriptStorageBucketSet, 2, CHARP, key, WSTR, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setList, mScriptStorageBucketSet, 2, CHARP, key, WLIST, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setTable, mScriptStorageBucketSet, 2, CHARP, key, WTABLE, value); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setVoid, mScriptStorageBucketSetVoid, 2, CHARP, key, NUL, value); + +mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setSInt) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setUInt) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setFloat) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setBool) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setStr) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setList) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setTable) + mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setVoid) + mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(mScriptStorageBucket) +mSCRIPT_DEFINE_END; + +mSCRIPT_DECLARE_STRUCT(mScriptStorageContext); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageContext, _deinit, mScriptStorageContextDeinit, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageContext, S(mScriptStorageBucket), getBucket, mScriptStorageGetBucket, 1, CHARP, key); + +mSCRIPT_DEFINE_STRUCT(mScriptStorageContext) + mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptStorageContext) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageContext, getBucket) +mSCRIPT_DEFINE_END; + +struct mScriptValue* mScriptStorageBucketGet(struct mScriptStorageBucket* bucket, const char* key) { + struct mScriptValue* val = mScriptTableLookup(bucket->root, &mSCRIPT_MAKE_CHARP(key)); + if (val) { + mScriptValueRef(val); + } + return val; +} + +void mScriptStorageBucketSet(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value) { + struct mScriptValue* vkey = mScriptStringCreateFromUTF8(key); + if (value->type->base == mSCRIPT_TYPE_WRAPPER) { + value = mScriptValueUnwrap(value); + } + mScriptTableInsert(bucket->root, vkey, value); + mScriptValueDeref(vkey); + bucket->dirty = true; +} + +void mScriptStorageBucketSetVoid(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value) { + UNUSED(value); + struct mScriptValue* vkey = mScriptStringCreateFromUTF8(key); + mScriptTableInsert(bucket->root, vkey, &mScriptValueNull); + mScriptValueDeref(vkey); + bucket->dirty = true; +} + +#define MAKE_SCALAR_SETTER(NAME, TYPE) \ + void mScriptStorageBucketSet ## NAME (struct mScriptStorageBucket* bucket, const char* key, mSCRIPT_TYPE_C_ ## TYPE value) { \ + struct mScriptValue* vkey = mScriptStringCreateFromUTF8(key); \ + struct mScriptValue* vval = mScriptValueAlloc(mSCRIPT_TYPE_MS_ ## TYPE); \ + vval->value.mSCRIPT_TYPE_FIELD_ ## TYPE = value; \ + mScriptTableInsert(bucket->root, vkey, vval); \ + mScriptValueDeref(vkey); \ + mScriptValueDeref(vval); \ + bucket->dirty = true; \ + } + +MAKE_SCALAR_SETTER(SInt, S64) +MAKE_SCALAR_SETTER(UInt, U64) +MAKE_SCALAR_SETTER(Float, F64) +MAKE_SCALAR_SETTER(Bool, BOOL) + +void mScriptContextAttachStorage(struct mScriptContext* context) { + struct mScriptStorageContext* storage = calloc(1, sizeof(*storage)); + struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptStorageContext)); + value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER; + value->value.opaque = storage; + + HashTableInit(&storage->buckets, 0, mScriptStorageBucketDeinit); + + mScriptContextSetGlobal(context, "storage", value); + mScriptContextSetDocstring(context, "storage", "Singleton instance of struct::mScriptStorageContext"); +} + +void mScriptStorageContextDeinit(struct mScriptStorageContext* storage) { + HashTableDeinit(&storage->buckets); +} + +struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext* storage, const char* name) { + if (!name) { + return NULL; + } + + // Check if name is allowed + // Currently only names matching /[0-9A-Za-z_.]+/ are allowed + size_t i; + for (i = 0; name[i]; ++i) { + if (i >= STORAGE_LEN_MAX) { + return NULL; + } + if (!isalnum(name[i]) && name[i] != '_' && name[i] != '.') { + return NULL; + } + } + struct mScriptStorageBucket* bucket = HashTableLookup(&storage->buckets, name); + if (bucket) { + return bucket; + } + + bucket = calloc(1, sizeof(*bucket)); + bucket->root = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + bucket->name = strdup(name); + HashTableInsert(&storage->buckets, name, bucket); + return bucket; +} + +void mScriptStorageBucketDeinit(void* data) { + struct mScriptStorageBucket* bucket = data; + mScriptValueDeref(bucket->root); + free(bucket->name); + free(bucket); +} diff --git a/src/script/test/storage.c b/src/script/test/storage.c new file mode 100644 index 000000000..9777c6280 --- /dev/null +++ b/src/script/test/storage.c @@ -0,0 +1,169 @@ +/* Copyright (c) 2013-2023 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 "util/test/suite.h" + +#include +#include +#include + +#include "script/test.h" + +#define SETUP_LUA \ + struct mScriptContext context; \ + mScriptContextInit(&context); \ + struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA); \ + mScriptContextAttachStdlib(&context); \ + mScriptContextAttachStorage(&context) + +M_TEST_SUITE_SETUP(mScriptStorage) { + if (mSCRIPT_ENGINE_LUA->init) { + mSCRIPT_ENGINE_LUA->init(mSCRIPT_ENGINE_LUA); + } + return 0; +} + +M_TEST_SUITE_TEARDOWN(mScriptStorage) { + if (mSCRIPT_ENGINE_LUA->deinit) { + mSCRIPT_ENGINE_LUA->deinit(mSCRIPT_ENGINE_LUA); + } + return 0; +} + +M_TEST_DEFINE(basicInt) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = 1"); + TEST_PROGRAM("assert(bucket.a == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicFloat) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = 0.5"); + TEST_PROGRAM("assert(bucket.a == 0.5)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicBool) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = true"); + TEST_PROGRAM("assert(bucket.a == true)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicNil) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = nil"); + TEST_PROGRAM("assert(bucket.a == nil)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = 'hello'"); + TEST_PROGRAM("assert(bucket.a == 'hello')"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicList) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = {1}"); + TEST_PROGRAM("assert(#bucket.a == 1)"); + TEST_PROGRAM("assert(bucket.a[1] == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(basicTable) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = {['a']=1}"); + TEST_PROGRAM("assert(#bucket.a == 1)"); + TEST_PROGRAM("assert(bucket.a.a == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(nullByteString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM("bucket.a = 'a\\x00b'"); + TEST_PROGRAM("assert(bucket.a == 'a\\x00b')"); + TEST_PROGRAM("assert(#bucket.a == 3)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(structured) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM( + "bucket.a = {\n" + " ['a'] = 1,\n" + " ['b'] = {1},\n" + " ['c'] = {\n" + " ['d'] = 1\n" + " }\n" + "}" + ); + TEST_PROGRAM("assert(bucket.a)"); + TEST_PROGRAM("assert(bucket.a.a == 1)"); + TEST_PROGRAM("assert(#bucket.a.b == 1)"); + TEST_PROGRAM("assert(bucket.a.b[1] == 1)"); + TEST_PROGRAM("assert(#bucket.a.c == 1)"); + TEST_PROGRAM("assert(bucket.a.c.d == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, + cmocka_unit_test(basicInt), + cmocka_unit_test(basicFloat), + cmocka_unit_test(basicBool), + cmocka_unit_test(basicNil), + cmocka_unit_test(basicString), + cmocka_unit_test(basicList), + cmocka_unit_test(basicTable), + cmocka_unit_test(nullByteString), + cmocka_unit_test(structured), +) From c1e1843e5e1e94e7e9137c40cecd866fb0818096 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 29 Jan 2023 21:56:02 -0800 Subject: [PATCH 029/290] CMake: Add json-c optional dependency --- CMakeLists.txt | 22 ++++++++++++++++++++++ src/core/flags.h.in | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 42cff1ebc..57cb36029 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ if(NOT LIBMGBA_ONLY) set(USE_SQLITE3 ON CACHE BOOL "Whether or not to enable SQLite3 support") set(USE_ELF ON CACHE BOOL "Whether or not to enable ELF support") set(USE_LUA ON CACHE BOOL "Whether or not to enable Lua scripting support") + set(USE_JSON_C ON CACHE BOOL "Whether or not to enable JSON-C support") set(M_CORE_GBA ON CACHE BOOL "Build Game Boy Advance core") set(M_CORE_GB ON CACHE BOOL "Build Game Boy core") set(USE_LZMA ON CACHE BOOL "Whether or not to enable 7-Zip support") @@ -477,6 +478,7 @@ endif() if(DISABLE_DEPS) set(USE_GDB_STUB OFF) set(USE_DISCORD_RPC OFF) + set(USE_JSON_C OFF) set(USE_SQLITE3 OFF) set(USE_PNG OFF) set(USE_ZLIB OFF) @@ -765,11 +767,30 @@ endif() if(ENABLE_SCRIPTING) list(APPEND ENABLES SCRIPTING) + find_feature(USE_JSON_C "json-c") if(NOT USE_LUA VERSION_LESS 5.1) find_feature(USE_LUA "Lua" ${USE_LUA}) else() find_feature(USE_LUA "Lua") endif() + if(USE_JSON_C) + list(APPEND FEATURES JSON_C) + if(TARGET json-c::json-c) + list(APPEND DEPENDENCY_LIB json-c::json-c) + get_target_property(JSON_C_SONAME json-c::json-c IMPORTED_SONAME_NONE) + string(SUBSTRING "${JSON_C_SONAME}" 13 -1 JSON_C_SOVER) + else() + if(${json-c_VERSION} VERSION_LESS 0.13.0) + set(JSON_C_SOVER 3) + elseif(${json-c_VERSION} VERSION_LESS 0.15.0) + set(JSON_C_SOVER 4) + endif() + list(APPEND DEPENDENCY_LIB ${json-c_LIBRARIES}) + include_directories(AFTER ${json-c_INCLUDE_DIRS}) + link_directories(${json-c_LIBDIRS}) + endif() + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libjson-c${JSON_C_SOVER}") + endif() if(USE_LUA) list(APPEND FEATURE_DEFINES USE_LUA) include_directories(AFTER ${LUA_INCLUDE_DIR}) @@ -1289,6 +1310,7 @@ if(NOT QUIET AND NOT LIBMGBA_ONLY) else() message(STATUS " Lua: ${USE_LUA}") endif() + message(STATUS " storage API: ${USE_JSON_C}") endif() message(STATUS "Frontends:") message(STATUS " Qt: ${BUILD_QT}") diff --git a/src/core/flags.h.in b/src/core/flags.h.in index 71ea562ef..ec9db8de5 100644 --- a/src/core/flags.h.in +++ b/src/core/flags.h.in @@ -79,6 +79,10 @@ #cmakedefine USE_GDB_STUB #endif +#ifndef USE_JSON_C +#cmakedefine USE_JSON_C +#endif + #ifndef USE_LIBAV #cmakedefine USE_LIBAV #endif From 0c6b443065bc6c6d1a1b258dc448b68a24f1cf02 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 4 Feb 2023 23:41:20 -0800 Subject: [PATCH 030/290] Scripting: Initial serialization work --- include/mgba/script/storage.h | 4 + src/script/storage.c | 149 ++++++++++++++++++++++++++++ src/script/test/storage.c | 176 ++++++++++++++++++++++++++++++++++ 3 files changed, 329 insertions(+) diff --git a/include/mgba/script/storage.h b/include/mgba/script/storage.h index 5b6490e82..f08efd87c 100644 --- a/include/mgba/script/storage.h +++ b/include/mgba/script/storage.h @@ -16,6 +16,10 @@ CXX_GUARD_START struct VFile; void mScriptContextAttachStorage(struct mScriptContext* context); +bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucket); +bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucket, struct VFile* vf); +void mScriptStorageGetBucketPath(const char* bucket, char* out); + CXX_GUARD_END #endif diff --git a/src/script/storage.c b/src/script/storage.c index 9bbbbfa5b..a8e785743 100644 --- a/src/script/storage.c +++ b/src/script/storage.c @@ -5,6 +5,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include +#include + +#include +#include + #define STORAGE_LEN_MAX 64 struct mScriptStorageBucket { @@ -29,6 +35,8 @@ static void mScriptStorageBucketSetBool(struct mScriptStorageBucket* adapter, co void mScriptStorageContextDeinit(struct mScriptStorageContext*); struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext*, const char* name); +static bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out); + mSCRIPT_DECLARE_STRUCT(mScriptStorageBucket); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, WRAPPER, _get, mScriptStorageBucketGet, 1, CHARP, key); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setSInt, mScriptStorageBucketSetSInt, 2, CHARP, key, S64, value); @@ -103,6 +111,147 @@ MAKE_SCALAR_SETTER(UInt, U64) MAKE_SCALAR_SETTER(Float, F64) MAKE_SCALAR_SETTER(Bool, BOOL) +void mScriptStorageGetBucketPath(const char* bucket, char* out) { + mCoreConfigDirectory(out, PATH_MAX); + + strncat(out, PATH_SEP "storage" PATH_SEP, PATH_MAX); + mkdir(out, 0755); + + char suffix[STORAGE_LEN_MAX + 6]; + snprintf(suffix, sizeof(suffix), "%s.json", bucket); + strncat(out, suffix, PATH_MAX); +} + +static struct json_object* _tableToJson(struct mScriptValue* rootVal) { + bool ok = true; + + struct TableIterator iter; + struct json_object* rootObj = json_object_new_object(); + if (mScriptTableIteratorStart(rootVal, &iter)) { + do { + struct mScriptValue* key = mScriptTableIteratorGetKey(rootVal, &iter); + struct mScriptValue* value = mScriptTableIteratorGetValue(rootVal, &iter); + const char* ckey; + if (key->type == mSCRIPT_TYPE_MS_CHARP) { + ckey = key->value.copaque; + } else if (key->type == mSCRIPT_TYPE_MS_STR) { + ckey = key->value.string->buffer; + } else { + ok = false; + break; + } + + struct json_object* obj; + ok = mScriptStorageToJson(value, &obj); + + if (!ok || json_object_object_add(rootObj, ckey, obj) < 0) { + ok = false; + } + } while (mScriptTableIteratorNext(rootVal, &iter) && ok); + } + if (!ok) { + json_object_put(rootObj); + return NULL; + } + return rootObj; +} + +bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out) { + if (value->type->base == mSCRIPT_TYPE_WRAPPER) { + value = mScriptValueUnwrap(value); + } + + size_t i; + bool ok = true; + struct json_object* obj = NULL; + switch (value->type->base) { + case mSCRIPT_TYPE_SINT: + obj = json_object_new_int64(value->value.s64); + break; + case mSCRIPT_TYPE_UINT: + if (value->type == mSCRIPT_TYPE_MS_BOOL) { + obj = json_object_new_boolean(value->value.u32); + break; + } + obj = json_object_new_uint64(value->value.u64); + break; + case mSCRIPT_TYPE_FLOAT: + obj = json_object_new_double(value->value.f64); + break; + case mSCRIPT_TYPE_STRING: + obj = json_object_new_string_len(value->value.string->buffer, value->value.string->size); + break; + case mSCRIPT_TYPE_LIST: + obj = json_object_new_array_ext(mScriptListSize(value->value.list)); + for (i = 0; i < mScriptListSize(value->value.list); ++i) { + struct json_object* listObj; + ok = mScriptStorageToJson(mScriptListGetPointer(value->value.list, i), &listObj); + if (!ok) { + break; + } + json_object_array_add(obj, listObj); + } + break; + case mSCRIPT_TYPE_TABLE: + obj = _tableToJson(value); + if (!obj) { + ok = false; + } + break; + case mSCRIPT_TYPE_VOID: + obj = NULL; + break; + default: + ok = false; + break; + } + + if (!ok) { + if (obj) { + json_object_put(obj); + } + *out = NULL; + } else { + *out = obj; + } + return ok; +} + +bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); + if (!value) { + return false; + } + struct mScriptStorageContext* storage = value->value.opaque; + struct mScriptStorageBucket* bucket = mScriptStorageGetBucket(storage, bucketName); + struct json_object* rootObj; + bool ok = mScriptStorageToJson(bucket->root, &rootObj); + if (!ok) { + return false; + } + + const char* json = json_object_to_json_string_ext(rootObj, JSON_C_TO_STRING_PRETTY_TAB); + if (!json) { + json_object_put(rootObj); + return false; + } + + vf->write(vf, json, strlen(json)); + vf->close(vf); + + bucket->dirty = false; + + json_object_put(rootObj); + return true; +} + +bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucketName) { + char path[PATH_MAX]; + mScriptStorageGetBucketPath(bucketName, path); + struct VFile* vf = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); + return mScriptStorageSaveBucketVF(context, bucketName, vf); +} + void mScriptContextAttachStorage(struct mScriptContext* context) { struct mScriptStorageContext* storage = calloc(1, sizeof(*storage)); struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptStorageContext)); diff --git a/src/script/test/storage.c b/src/script/test/storage.c index 9777c6280..d6c491b25 100644 --- a/src/script/test/storage.c +++ b/src/script/test/storage.c @@ -156,6 +156,174 @@ M_TEST_DEFINE(structured) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(serializeInt) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = 1"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\"a\":1}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeFloat) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = 0.5"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\"a\":0.5}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeBool) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = true"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\"a\":true}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeNil) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = nil"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\"a\":null}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = 'hello'"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\"a\":\"hello\"}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeList) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = {1, 2}"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\"a\":[1,2]}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeTable) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = {['b']=1}"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\"a\":{\"b\":1}}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(serializeNullByteString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("bucket.a = 'a\\x00b'"); + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + ssize_t size = vf->size(vf); + char* buf = calloc(1, size + 1); + assert_int_equal(vf->read(vf, buf, size), size); + assert_string_equal(buf, "{\"a\":\"a\\u0000b\"}"); + free(buf); + vf->close(vf); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(basicInt), cmocka_unit_test(basicFloat), @@ -166,4 +334,12 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(basicTable), cmocka_unit_test(nullByteString), cmocka_unit_test(structured), + cmocka_unit_test(serializeInt), + cmocka_unit_test(serializeFloat), + cmocka_unit_test(serializeBool), + cmocka_unit_test(serializeNil), + cmocka_unit_test(serializeString), + cmocka_unit_test(serializeList), + cmocka_unit_test(serializeTable), + cmocka_unit_test(serializeNullByteString), ) From 8b65f3772c5363da3a50bbe1114b880c96665d04 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Feb 2023 01:19:54 -0800 Subject: [PATCH 031/290] Scripting: Initial deserialization work --- include/mgba/script/storage.h | 2 + src/script/storage.c | 111 +++++++++++++++++++++++++++ src/script/test/storage.c | 139 ++++++++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+) diff --git a/include/mgba/script/storage.h b/include/mgba/script/storage.h index f08efd87c..4766ae8e8 100644 --- a/include/mgba/script/storage.h +++ b/include/mgba/script/storage.h @@ -18,6 +18,8 @@ void mScriptContextAttachStorage(struct mScriptContext* context); bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucket); bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucket, struct VFile* vf); +bool mScriptStorageLoadBucket(struct mScriptContext* context, const char* bucket); +bool mScriptStorageLoadBucketVF(struct mScriptContext* context, const char* bucket, struct VFile* vf); void mScriptStorageGetBucketPath(const char* bucket, char* out); CXX_GUARD_END diff --git a/src/script/storage.c b/src/script/storage.c index a8e785743..bf0e73547 100644 --- a/src/script/storage.c +++ b/src/script/storage.c @@ -36,6 +36,7 @@ void mScriptStorageContextDeinit(struct mScriptStorageContext*); struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext*, const char* name); static bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out); +static struct mScriptValue* mScriptStorageFromJson(struct json_object* json); mSCRIPT_DECLARE_STRUCT(mScriptStorageBucket); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, WRAPPER, _get, mScriptStorageBucketGet, 1, CHARP, key); @@ -220,6 +221,7 @@ bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out) bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) { struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); if (!value) { + vf->close(vf); return false; } struct mScriptStorageContext* storage = value->value.opaque; @@ -227,12 +229,14 @@ bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* buck struct json_object* rootObj; bool ok = mScriptStorageToJson(bucket->root, &rootObj); if (!ok) { + vf->close(vf); return false; } const char* json = json_object_to_json_string_ext(rootObj, JSON_C_TO_STRING_PRETTY_TAB); if (!json) { json_object_put(rootObj); + vf->close(vf); return false; } @@ -252,6 +256,113 @@ bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucket return mScriptStorageSaveBucketVF(context, bucketName, vf); } +struct mScriptValue* mScriptStorageFromJson(struct json_object* json) { + enum json_type type = json_object_get_type(json); + struct mScriptValue* value = NULL; + switch (type) { + case json_type_null: + return &mScriptValueNull; + case json_type_int: + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S64); + value->value.s64 = json_object_get_int64(json); + break; + case json_type_double: + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_F64); + value->value.f64 = json_object_get_double(json); + break; + case json_type_boolean: + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_BOOL); + value->value.u32 = json_object_get_boolean(json); + break; + case json_type_string: + value = mScriptStringCreateFromBytes(json_object_get_string(json), json_object_get_string_len(json)); + break; + case json_type_array: + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST); + { + size_t i; + for (i = 0; i < json_object_array_length(json); ++i) { + struct mScriptValue* vval = mScriptStorageFromJson(json_object_array_get_idx(json, i)); + if (!vval) { + mScriptValueDeref(value); + value = NULL; + break; + } + mScriptValueWrap(vval, mScriptListAppend(value->value.list)); + mScriptValueDeref(vval); + } + } + break; + case json_type_object: + value = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + { + json_object_object_foreach(json, jkey, jval) { + struct mScriptValue* vval = mScriptStorageFromJson(jval); + if (!vval) { + mScriptValueDeref(value); + value = NULL; + break; + } + struct mScriptValue* vkey = mScriptStringCreateFromUTF8(jkey); + mScriptTableInsert(value, vkey, vval); + mScriptValueDeref(vkey); + mScriptValueDeref(vval); + } + } + break; + } + return value; +} + +bool mScriptStorageLoadBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); + if (!value) { + vf->close(vf); + return false; + } + struct mScriptStorageContext* storage = value->value.opaque; + + ssize_t size = vf->size(vf); + if (size < 2) { + vf->close(vf); + return false; + } + char* json = calloc(1, size + 1); + if (vf->read(vf, json, size) != size) { + vf->close(vf); + return false; + } + vf->close(vf); + + struct json_object* obj = json_tokener_parse(json); + free(json); + if (!obj) { + return false; + } + + struct mScriptValue* root = mScriptStorageFromJson(obj); + json_object_put(obj); + if (!root) { + return false; + } + + struct mScriptStorageBucket* bucket = mScriptStorageGetBucket(storage, bucketName); + mScriptValueDeref(bucket->root); + bucket->root = root; + + return true; +} + +bool mScriptStorageLoadBucket(struct mScriptContext* context, const char* bucketName) { + char path[PATH_MAX]; + mScriptStorageGetBucketPath(bucketName, path); + struct VFile* vf = VFileOpen(path, O_RDONLY); + if (!vf) { + return false; + } + return mScriptStorageLoadBucketVF(context, bucketName, vf); +} + void mScriptContextAttachStorage(struct mScriptContext* context) { struct mScriptStorageContext* storage = calloc(1, sizeof(*storage)); struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptStorageContext)); diff --git a/src/script/test/storage.c b/src/script/test/storage.c index d6c491b25..41aeff8df 100644 --- a/src/script/test/storage.c +++ b/src/script/test/storage.c @@ -324,6 +324,137 @@ M_TEST_DEFINE(serializeNullByteString) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(deserializeInt) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":1}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeFloat) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":0.5}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == 0.5)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeBool) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":true}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == true)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeNil) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":null}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == nil)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":\"hello\"}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == 'hello')"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeList) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":[1,2]}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(#bucket.a == 2)"); + TEST_PROGRAM("assert(bucket.a[1] == 1)"); + TEST_PROGRAM("assert(bucket.a[2] == 2)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeTable) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":{\"b\":1}}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a.b == 1)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(deserializeNullByteString) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{\"a\":\"a\\u0000b\"}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(bucket.a == 'a\\x00b')"); + TEST_PROGRAM("assert(bucket.a ~= 'a\\x00c')"); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(basicInt), cmocka_unit_test(basicFloat), @@ -342,4 +473,12 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(serializeList), cmocka_unit_test(serializeTable), cmocka_unit_test(serializeNullByteString), + cmocka_unit_test(deserializeInt), + cmocka_unit_test(deserializeFloat), + cmocka_unit_test(deserializeBool), + cmocka_unit_test(deserializeNil), + cmocka_unit_test(deserializeString), + cmocka_unit_test(deserializeList), + cmocka_unit_test(deserializeTable), + cmocka_unit_test(deserializeNullByteString), ) From 91474e179ca82c8a0574bf5e70bb086e4f74a975 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Feb 2023 15:34:37 -0800 Subject: [PATCH 032/290] Scripting: More storage tests --- src/script/test/storage.c | 67 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/script/test/storage.c b/src/script/test/storage.c index 41aeff8df..a64c5b899 100644 --- a/src/script/test/storage.c +++ b/src/script/test/storage.c @@ -156,6 +156,18 @@ M_TEST_DEFINE(structured) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(invalidObject) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + LOAD_PROGRAM("bucket.a = bucket"); + assert_false(lua->run(lua)); + + mScriptContextDeinit(&context); +} + M_TEST_DEFINE(serializeInt) { SETUP_LUA; @@ -455,6 +467,58 @@ M_TEST_DEFINE(deserializeNullByteString) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(deserializeError) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + + TEST_PROGRAM("assert(not bucket.a)"); + + static const char* json = "{a:1}"; + struct VFile* vf = VFileFromConstMemory(json, strlen(json)); + assert_false(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + TEST_PROGRAM("assert(not bucket.a)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(structuredRoundTrip) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + TEST_PROGRAM( + "bucket.a = {\n" + " ['a'] = 1,\n" + " ['b'] = {1},\n" + " ['c'] = {\n" + " ['d'] = 1\n" + " }\n" + "}" + ); + + struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY); + assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf)); + + TEST_PROGRAM("bucket.a = nil") + TEST_PROGRAM("assert(not bucket.a)"); + + vf = VFileOpen("test.json", O_RDONLY); + assert_non_null(vf); + assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf)); + + TEST_PROGRAM("assert(bucket.a)"); + TEST_PROGRAM("assert(bucket.a.a == 1)"); + TEST_PROGRAM("assert(#bucket.a.b == 1)"); + TEST_PROGRAM("assert(bucket.a.b[1] == 1)"); + TEST_PROGRAM("assert(#bucket.a.c == 1)"); + TEST_PROGRAM("assert(bucket.a.c.d == 1)"); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(basicInt), cmocka_unit_test(basicFloat), @@ -464,6 +528,7 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(basicList), cmocka_unit_test(basicTable), cmocka_unit_test(nullByteString), + cmocka_unit_test(invalidObject), cmocka_unit_test(structured), cmocka_unit_test(serializeInt), cmocka_unit_test(serializeFloat), @@ -481,4 +546,6 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(deserializeList), cmocka_unit_test(deserializeTable), cmocka_unit_test(deserializeNullByteString), + cmocka_unit_test(deserializeError), + cmocka_unit_test(structuredRoundTrip), ) From 63d96ab7129000799fb49ed24b6f66442ce31d97 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Feb 2023 21:01:11 -0800 Subject: [PATCH 033/290] Scripting: Add flushing/reloading --- include/mgba/script/storage.h | 1 + src/script/storage.c | 148 +++++++++++++++++++++++++--------- src/script/test/storage.c | 5 +- 3 files changed, 114 insertions(+), 40 deletions(-) diff --git a/include/mgba/script/storage.h b/include/mgba/script/storage.h index 4766ae8e8..629158e83 100644 --- a/include/mgba/script/storage.h +++ b/include/mgba/script/storage.h @@ -15,6 +15,7 @@ CXX_GUARD_START struct VFile; void mScriptContextAttachStorage(struct mScriptContext* context); +void mScriptStorageFlushAll(struct mScriptContext* context); bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucket); bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucket, struct VFile* vf); diff --git a/src/script/storage.c b/src/script/storage.c index bf0e73547..d845db708 100644 --- a/src/script/storage.c +++ b/src/script/storage.c @@ -24,15 +24,18 @@ struct mScriptStorageContext { }; void mScriptStorageBucketDeinit(void*); -struct mScriptValue* mScriptStorageBucketGet(struct mScriptStorageBucket* adapter, const char* key); -static void mScriptStorageBucketSet(struct mScriptStorageBucket* adapter, const char* key, struct mScriptValue* value); -static void mScriptStorageBucketSetVoid(struct mScriptStorageBucket* adapter, const char* key, struct mScriptValue* value); -static void mScriptStorageBucketSetSInt(struct mScriptStorageBucket* adapter, const char* key, int64_t value); -static void mScriptStorageBucketSetUInt(struct mScriptStorageBucket* adapter, const char* key, uint64_t value); -static void mScriptStorageBucketSetFloat(struct mScriptStorageBucket* adapter, const char* key, double value); -static void mScriptStorageBucketSetBool(struct mScriptStorageBucket* adapter, const char* key, bool value); +struct mScriptValue* mScriptStorageBucketGet(struct mScriptStorageBucket* bucket, const char* key); +static void mScriptStorageBucketSet(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value); +static void mScriptStorageBucketSetVoid(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value); +static void mScriptStorageBucketSetSInt(struct mScriptStorageBucket* bucket, const char* key, int64_t value); +static void mScriptStorageBucketSetUInt(struct mScriptStorageBucket* bucket, const char* key, uint64_t value); +static void mScriptStorageBucketSetFloat(struct mScriptStorageBucket* bucket, const char* key, double value); +static void mScriptStorageBucketSetBool(struct mScriptStorageBucket* bucket, const char* key, bool value); +static bool mScriptStorageBucketReload(struct mScriptStorageBucket* bucket); +static bool mScriptStorageBucketFlush(struct mScriptStorageBucket* bucket); -void mScriptStorageContextDeinit(struct mScriptStorageContext*); +static void mScriptStorageContextDeinit(struct mScriptStorageContext*); +static void mScriptStorageContextFlushAll(struct mScriptStorageContext*); struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext*, const char* name); static bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out); @@ -48,6 +51,8 @@ mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setStr, mScriptStorageB mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setList, mScriptStorageBucketSet, 2, CHARP, key, WLIST, value); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setTable, mScriptStorageBucketSet, 2, CHARP, key, WTABLE, value); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setVoid, mScriptStorageBucketSetVoid, 2, CHARP, key, NUL, value); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, BOOL, reload, mScriptStorageBucketReload, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, BOOL, flush, mScriptStorageBucketFlush, 0); mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setSInt) @@ -59,15 +64,19 @@ mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setTable) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setVoid) mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(mScriptStorageBucket) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, reload) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, flush) mSCRIPT_DEFINE_END; mSCRIPT_DECLARE_STRUCT(mScriptStorageContext); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageContext, _deinit, mScriptStorageContextDeinit, 0); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageContext, S(mScriptStorageBucket), getBucket, mScriptStorageGetBucket, 1, CHARP, key); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageContext, flushAll, mScriptStorageContextFlushAll, 0); mSCRIPT_DEFINE_STRUCT(mScriptStorageContext) mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptStorageContext) mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageContext, getBucket) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageContext, flushAll) mSCRIPT_DEFINE_END; struct mScriptValue* mScriptStorageBucketGet(struct mScriptStorageBucket* bucket, const char* key) { @@ -218,14 +227,7 @@ bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out) return ok; } -bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) { - struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); - if (!value) { - vf->close(vf); - return false; - } - struct mScriptStorageContext* storage = value->value.opaque; - struct mScriptStorageBucket* bucket = mScriptStorageGetBucket(storage, bucketName); +static bool _mScriptStorageBucketFlushVF(struct mScriptStorageBucket* bucket, struct VFile* vf) { struct json_object* rootObj; bool ok = mScriptStorageToJson(bucket->root, &rootObj); if (!ok) { @@ -249,6 +251,24 @@ bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* buck return true; } +bool mScriptStorageBucketFlush(struct mScriptStorageBucket* bucket) { + char path[PATH_MAX]; + mScriptStorageGetBucketPath(bucket->name, path); + struct VFile* vf = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); + return _mScriptStorageBucketFlushVF(bucket, vf); +} + +bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); + if (!value) { + vf->close(vf); + return false; + } + struct mScriptStorageContext* storage = value->value.opaque; + struct mScriptStorageBucket* bucket = mScriptStorageGetBucket(storage, bucketName); + return _mScriptStorageBucketFlushVF(bucket, vf); +} + bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucketName) { char path[PATH_MAX]; mScriptStorageGetBucketPath(bucketName, path); @@ -314,6 +334,51 @@ struct mScriptValue* mScriptStorageFromJson(struct json_object* json) { return value; } +static struct mScriptValue* _mScriptStorageLoadJson(struct VFile* vf) { + ssize_t size = vf->size(vf); + if (size < 2) { + vf->close(vf); + return NULL; + } + char* json = calloc(1, size + 1); + if (vf->read(vf, json, size) != size) { + vf->close(vf); + return NULL; + } + vf->close(vf); + + struct json_object* obj = json_tokener_parse(json); + free(json); + if (!obj) { + return NULL; + } + + struct mScriptValue* root = mScriptStorageFromJson(obj); + json_object_put(obj); + return root; +} + +bool mScriptStorageBucketReload(struct mScriptStorageBucket* bucket) { + char path[PATH_MAX]; + mScriptStorageGetBucketPath(bucket->name, path); + struct VFile* vf = VFileOpen(path, O_RDONLY); + if (!vf) { + return false; + } + struct mScriptValue* root = _mScriptStorageLoadJson(vf); + if (!root) { + return false; + } + if (bucket->root) { + mScriptValueDeref(bucket->root); + } + bucket->root = root; + + bucket->dirty = false; + + return true; +} + bool mScriptStorageLoadBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) { struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); if (!value) { @@ -321,35 +386,16 @@ bool mScriptStorageLoadBucketVF(struct mScriptContext* context, const char* buck return false; } struct mScriptStorageContext* storage = value->value.opaque; - - ssize_t size = vf->size(vf); - if (size < 2) { - vf->close(vf); - return false; - } - char* json = calloc(1, size + 1); - if (vf->read(vf, json, size) != size) { - vf->close(vf); - return false; - } - vf->close(vf); - - struct json_object* obj = json_tokener_parse(json); - free(json); - if (!obj) { - return false; - } - - struct mScriptValue* root = mScriptStorageFromJson(obj); - json_object_put(obj); + struct mScriptValue* root = _mScriptStorageLoadJson(vf); if (!root) { return false; } - struct mScriptStorageBucket* bucket = mScriptStorageGetBucket(storage, bucketName); mScriptValueDeref(bucket->root); bucket->root = root; + bucket->dirty = false; + return true; } @@ -375,10 +421,29 @@ void mScriptContextAttachStorage(struct mScriptContext* context) { mScriptContextSetDocstring(context, "storage", "Singleton instance of struct::mScriptStorageContext"); } +void mScriptStorageFlushAll(struct mScriptContext* context) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); + if (!value) { + return; + } + struct mScriptStorageContext* storage = value->value.opaque; + mScriptStorageContextFlushAll(storage); +} + void mScriptStorageContextDeinit(struct mScriptStorageContext* storage) { HashTableDeinit(&storage->buckets); } +void mScriptStorageContextFlushAll(struct mScriptStorageContext* storage) { + struct TableIterator iter; + if (HashTableIteratorStart(&storage->buckets, &iter)) { + do { + struct mScriptStorageBucket* bucket = HashTableIteratorGetValue(&storage->buckets, &iter); + mScriptStorageBucketFlush(bucket); + } while (HashTableIteratorNext(&storage->buckets, &iter)); + } +} + struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext* storage, const char* name) { if (!name) { return NULL; @@ -401,14 +466,19 @@ struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContex } bucket = calloc(1, sizeof(*bucket)); - bucket->root = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); bucket->name = strdup(name); + if (!mScriptStorageBucketReload(bucket)) { + bucket->root = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + } HashTableInsert(&storage->buckets, name, bucket); return bucket; } void mScriptStorageBucketDeinit(void* data) { struct mScriptStorageBucket* bucket = data; + if (bucket->dirty) { + mScriptStorageBucketFlush(bucket); + } mScriptValueDeref(bucket->root); free(bucket->name); free(bucket); diff --git a/src/script/test/storage.c b/src/script/test/storage.c index a64c5b899..9978776b0 100644 --- a/src/script/test/storage.c +++ b/src/script/test/storage.c @@ -16,7 +16,10 @@ mScriptContextInit(&context); \ struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA); \ mScriptContextAttachStdlib(&context); \ - mScriptContextAttachStorage(&context) + mScriptContextAttachStorage(&context); \ + char bucketPath[PATH_MAX]; \ + mScriptStorageGetBucketPath("xtest", bucketPath); \ + remove(bucketPath) M_TEST_SUITE_SETUP(mScriptStorage) { if (mSCRIPT_ENGINE_LUA->init) { From f3d49527b70d35be464c630c0b5c1c8981e12385 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Feb 2023 21:20:27 -0800 Subject: [PATCH 034/290] Qt: Add scripting storage integration --- src/platform/qt/scripting/ScriptingController.cpp | 11 +++++++++++ src/platform/qt/scripting/ScriptingController.h | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index b0fbd51ef..836e379f1 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -20,6 +20,7 @@ #include "scripting/ScriptingTextBufferModel.h" #include +#include #include #include @@ -51,6 +52,9 @@ ScriptingController::ScriptingController(QObject* parent) m_bufferModel = new ScriptingTextBufferModel(this); QObject::connect(m_bufferModel, &ScriptingTextBufferModel::textBufferCreated, this, &ScriptingController::textBufferCreated); + connect(&m_storageFlush, &QTimer::timeout, this, &ScriptingController::flushStorage); + m_storageFlush.setInterval(5); + mScriptGamepadInit(&m_gamepad); init(); @@ -144,6 +148,10 @@ void ScriptingController::runCode(const QString& code) { load(vf, "*prompt"); } +void ScriptingController::flushStorage() { + mScriptStorageFlushAll(&m_scriptContext); +} + bool ScriptingController::eventFilter(QObject* obj, QEvent* ev) { event(obj, ev); return false; @@ -293,6 +301,7 @@ void ScriptingController::detachGamepad() { void ScriptingController::init() { mScriptContextInit(&m_scriptContext); mScriptContextAttachStdlib(&m_scriptContext); + mScriptContextAttachStorage(&m_scriptContext); mScriptContextAttachSocket(&m_scriptContext); mScriptContextAttachInput(&m_scriptContext); mScriptContextRegisterEngines(&m_scriptContext); @@ -308,6 +317,8 @@ void ScriptingController::init() { if (m_engines.count() == 1) { m_activeEngine = *m_engines.begin(); } + + m_storageFlush.start(); } uint32_t ScriptingController::qtToScriptingKey(const QKeyEvent* event) { diff --git a/src/platform/qt/scripting/ScriptingController.h b/src/platform/qt/scripting/ScriptingController.h index 0e275561d..e34b34542 100644 --- a/src/platform/qt/scripting/ScriptingController.h +++ b/src/platform/qt/scripting/ScriptingController.h @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -55,6 +56,8 @@ public slots: void reset(); void runCode(const QString& code); + void flushStorage(); + protected: bool eventFilter(QObject*, QEvent*) override; @@ -84,6 +87,8 @@ private: std::shared_ptr m_controller; InputController* m_inputController = nullptr; + + QTimer m_storageFlush; }; } From dca1e49c9f3011ca34177a28dc0a0eb7f41609cd Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Feb 2023 22:32:19 -0800 Subject: [PATCH 035/290] Scripting: Add documentation for storage and buckets --- src/script/docgen.c | 2 ++ src/script/storage.c | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/script/docgen.c b/src/script/docgen.c index 65fcc87ce..502baa705 100644 --- a/src/script/docgen.c +++ b/src/script/docgen.c @@ -9,6 +9,7 @@ #include #include #include +#include #include struct mScriptContext context; @@ -469,6 +470,7 @@ int main(int argc, char* argv[]) { mScriptContextInit(&context); mScriptContextAttachStdlib(&context); mScriptContextAttachSocket(&context); + mScriptContextAttachStorage(&context); mScriptContextAttachInput(&context); mScriptContextSetTextBufferFactory(&context, NULL, NULL); diff --git a/src/script/storage.c b/src/script/storage.c index d845db708..822a85b4f 100644 --- a/src/script/storage.c +++ b/src/script/storage.c @@ -55,6 +55,14 @@ mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, BOOL, reload, mScriptStorage mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, BOOL, flush, mScriptStorageBucketFlush, 0); mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket) + mSCRIPT_DEFINE_CLASS_DOCSTRING( + "A single 'bucket' of stored data, appropriate for a single script to store its data. " + "Fields can be set directly on the bucket objct, e.g. if you want to store a value called " + "`foo` on a bucket named `bucket`, you can directly assign to it as `bucket.foo = value`, " + "and retrieve it in the same way later. Primitive types (numbers, strings, lists and tables) " + "can be stored in buckets, but complex data types (e.g. a bucket itself) cannot. Data " + "stored in a bucket is periodically flushed to disk and persists between sessions." + ) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setSInt) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setUInt) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setFloat) @@ -64,7 +72,9 @@ mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setTable) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setVoid) mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(mScriptStorageBucket) + mSCRIPT_DEFINE_DOCSTRING("Reload the state of the bucket from disk") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, reload) + mSCRIPT_DEFINE_DOCSTRING("Flush the bucket to disk manually") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, flush) mSCRIPT_DEFINE_END; @@ -75,7 +85,12 @@ mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageContext, flushAll, mScriptStora mSCRIPT_DEFINE_STRUCT(mScriptStorageContext) mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptStorageContext) + mSCRIPT_DEFINE_DOCSTRING( + "Get a bucket with the given name. Names can contain letters, numbers, " + "underscores and periods. If a given bucket doesn't exist, it is created." + ) mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageContext, getBucket) + mSCRIPT_DEFINE_DOCSTRING("Flush all buckets to disk manually") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageContext, flushAll) mSCRIPT_DEFINE_END; From e3e0957f1456b3ea5b38270d7b1b8c566fa81e9e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 02:37:35 -0800 Subject: [PATCH 036/290] Scripting: A slew of buildfixes --- CMakeLists.txt | 6 ++++++ src/script/storage.c | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 57cb36029..32551c602 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -779,6 +779,12 @@ if(ENABLE_SCRIPTING) list(APPEND DEPENDENCY_LIB json-c::json-c) get_target_property(JSON_C_SONAME json-c::json-c IMPORTED_SONAME_NONE) string(SUBSTRING "${JSON_C_SONAME}" 13 -1 JSON_C_SOVER) + + # This is only needed on 0.15, but the target unhelpfully does not contain version info + get_target_property(JSON_C_INCLUDE_DIR json-c::json-c INTERFACE_INCLUDE_DIRECTORIES) + if(NOT JSON_C_INCLUDE_DIR MATCHES json-c$) + include_directories(AFTER "${JSON_C_INCLUDE_DIR}/json-c") + endif() else() if(${json-c_VERSION} VERSION_LESS 0.13.0) set(JSON_C_SOVER 3) diff --git a/src/script/storage.c b/src/script/storage.c index 822a85b4f..f9562a488 100644 --- a/src/script/storage.c +++ b/src/script/storage.c @@ -140,7 +140,14 @@ void mScriptStorageGetBucketPath(const char* bucket, char* out) { mCoreConfigDirectory(out, PATH_MAX); strncat(out, PATH_SEP "storage" PATH_SEP, PATH_MAX); +#ifdef _WIN32 + // TODO: Move this to vfs somewhere + WCHAR wout[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, out, -1, wout, MAX_PATH); + CreateDirectoryW(wout, NULL); +#else mkdir(out, 0755); +#endif char suffix[STORAGE_LEN_MAX + 6]; snprintf(suffix, sizeof(suffix), "%s.json", bucket); @@ -169,8 +176,12 @@ static struct json_object* _tableToJson(struct mScriptValue* rootVal) { struct json_object* obj; ok = mScriptStorageToJson(value, &obj); - if (!ok || json_object_object_add(rootObj, ckey, obj) < 0) { - ok = false; + if (ok) { +#if JSON_C_VERSION_NUM >= (13 << 8) + ok = json_object_object_add(rootObj, ckey, obj) >= 0; +#else + json_object_object_add(rootObj, ckey, obj); +#endif } } while (mScriptTableIteratorNext(rootVal, &iter) && ok); } @@ -198,7 +209,15 @@ bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out) obj = json_object_new_boolean(value->value.u32); break; } +#if JSON_C_VERSION_NUM >= (14 << 8) obj = json_object_new_uint64(value->value.u64); +#else + if (value->value.u64 < (uint64_t) INT64_MAX) { + obj = json_object_new_int64(value->value.u64); + } else { + obj = json_object_new_double(value->value.u64); + } +#endif break; case mSCRIPT_TYPE_FLOAT: obj = json_object_new_double(value->value.f64); @@ -207,7 +226,11 @@ bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out) obj = json_object_new_string_len(value->value.string->buffer, value->value.string->size); break; case mSCRIPT_TYPE_LIST: +#if JSON_C_VERSION_NUM >= (15 << 8) obj = json_object_new_array_ext(mScriptListSize(value->value.list)); +#else + obj = json_object_new_array(); +#endif for (i = 0; i < mScriptListSize(value->value.list); ++i) { struct json_object* listObj; ok = mScriptStorageToJson(mScriptListGetPointer(value->value.list, i), &listObj); @@ -242,6 +265,10 @@ bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out) return ok; } +#ifndef JSON_C_TO_STRING_PRETTY_TAB +#define JSON_C_TO_STRING_PRETTY_TAB 0 +#endif + static bool _mScriptStorageBucketFlushVF(struct mScriptStorageBucket* bucket, struct VFile* vf) { struct json_object* rootObj; bool ok = mScriptStorageToJson(bucket->root, &rootObj); @@ -250,7 +277,7 @@ static bool _mScriptStorageBucketFlushVF(struct mScriptStorageBucket* bucket, st return false; } - const char* json = json_object_to_json_string_ext(rootObj, JSON_C_TO_STRING_PRETTY_TAB); + const char* json = json_object_to_json_string_ext(rootObj, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_PRETTY_TAB); if (!json) { json_object_put(rootObj); vf->close(vf); From 0cc66867ca623d1979ed6b1b16addf83f17acad9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 02:38:37 -0800 Subject: [PATCH 037/290] CHANGES: Add storage API mention --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 028537466..5334fd617 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ 0.11.0: (Future) Features: - Scripting: New `input` API for getting raw keyboard/mouse/controller state + - Scripting: New `storage` API for saving data for a script, e.g. settings - New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81 - Debugger: Add range watchpoints Emulation fixes: From 1268aaee1c12e8cb45c46d5b116db067efd1649b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 02:45:54 -0800 Subject: [PATCH 038/290] Scripting: Fix tests --- src/script/test/storage.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/script/test/storage.c b/src/script/test/storage.c index 9978776b0..d735a1f87 100644 --- a/src/script/test/storage.c +++ b/src/script/test/storage.c @@ -185,7 +185,7 @@ M_TEST_DEFINE(serializeInt) { ssize_t size = vf->size(vf); char* buf = calloc(1, size + 1); assert_int_equal(vf->read(vf, buf, size), size); - assert_string_equal(buf, "{\"a\":1}"); + assert_string_equal(buf, "{\n\t\"a\":1\n}"); free(buf); vf->close(vf); @@ -206,7 +206,7 @@ M_TEST_DEFINE(serializeFloat) { ssize_t size = vf->size(vf); char* buf = calloc(1, size + 1); assert_int_equal(vf->read(vf, buf, size), size); - assert_string_equal(buf, "{\"a\":0.5}"); + assert_string_equal(buf, "{\n\t\"a\":0.5\n}"); free(buf); vf->close(vf); @@ -227,7 +227,7 @@ M_TEST_DEFINE(serializeBool) { ssize_t size = vf->size(vf); char* buf = calloc(1, size + 1); assert_int_equal(vf->read(vf, buf, size), size); - assert_string_equal(buf, "{\"a\":true}"); + assert_string_equal(buf, "{\n\t\"a\":true\n}"); free(buf); vf->close(vf); @@ -248,7 +248,7 @@ M_TEST_DEFINE(serializeNil) { ssize_t size = vf->size(vf); char* buf = calloc(1, size + 1); assert_int_equal(vf->read(vf, buf, size), size); - assert_string_equal(buf, "{\"a\":null}"); + assert_string_equal(buf, "{\n\t\"a\":null\n}"); free(buf); vf->close(vf); @@ -269,7 +269,7 @@ M_TEST_DEFINE(serializeString) { ssize_t size = vf->size(vf); char* buf = calloc(1, size + 1); assert_int_equal(vf->read(vf, buf, size), size); - assert_string_equal(buf, "{\"a\":\"hello\"}"); + assert_string_equal(buf, "{\n\t\"a\":\"hello\"\n}"); free(buf); vf->close(vf); @@ -290,7 +290,7 @@ M_TEST_DEFINE(serializeList) { ssize_t size = vf->size(vf); char* buf = calloc(1, size + 1); assert_int_equal(vf->read(vf, buf, size), size); - assert_string_equal(buf, "{\"a\":[1,2]}"); + assert_string_equal(buf, "{\n\t\"a\":[\n\t\t1,\n\t\t2\n\t]\n}"); free(buf); vf->close(vf); @@ -311,7 +311,7 @@ M_TEST_DEFINE(serializeTable) { ssize_t size = vf->size(vf); char* buf = calloc(1, size + 1); assert_int_equal(vf->read(vf, buf, size), size); - assert_string_equal(buf, "{\"a\":{\"b\":1}}"); + assert_string_equal(buf, "{\n\t\"a\":{\n\t\t\"b\":1\n\t}\n}"); free(buf); vf->close(vf); @@ -332,7 +332,7 @@ M_TEST_DEFINE(serializeNullByteString) { ssize_t size = vf->size(vf); char* buf = calloc(1, size + 1); assert_int_equal(vf->read(vf, buf, size), size); - assert_string_equal(buf, "{\"a\":\"a\\u0000b\"}"); + assert_string_equal(buf, "{\n\t\"a\":\"a\\u0000b\"\n}"); free(buf); vf->close(vf); From ff449dc66c77f47ffc2fa8cb7ba5c84b8a556e4b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 17:57:23 -0800 Subject: [PATCH 039/290] Scripting: Fix non-json-c build --- src/script/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index f79b66d08..f741b9c05 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -4,7 +4,6 @@ set(SOURCE_FILES input.c socket.c stdlib.c - storage.c types.c) set(TEST_FILES From 123532ed6e80b0ceec0de40cb5b27d4c2db8c9cb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 19:14:36 -0800 Subject: [PATCH 040/290] Scripting: Add `callbacks:oneshot` for single-call callbacks --- CHANGES | 1 + include/mgba/script/context.h | 1 + src/script/context.c | 99 ++++++++++++++++++++++++----------- src/script/stdlib.c | 11 ++++ src/script/test/stdlib.c | 33 ++++++++++++ 5 files changed, 115 insertions(+), 30 deletions(-) diff --git a/CHANGES b/CHANGES index 5334fd617..40ca1e7a5 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,7 @@ Misc: - GBA: Improve detection of valid ELF ROMs - Qt: Include wayland QPA in AppImage (fixes mgba.io/i/2796) - Qt: Stop eating boolean action key events (fixes mgba.io/i/2636) + - Scripting: Add `callbacks:oneshot` for single-call callbacks 0.10.1: (2023-01-10) Emulation fixes: diff --git a/include/mgba/script/context.h b/include/mgba/script/context.h index dcec138e1..9e8e5ec23 100644 --- a/include/mgba/script/context.h +++ b/include/mgba/script/context.h @@ -98,6 +98,7 @@ void mScriptContextExportNamespace(struct mScriptContext* context, const char* n void mScriptContextTriggerCallback(struct mScriptContext*, const char* callback, struct mScriptList* args); uint32_t mScriptContextAddCallback(struct mScriptContext*, const char* callback, struct mScriptValue* value); +uint32_t mScriptContextAddOneshot(struct mScriptContext*, const char* callback, struct mScriptValue* value); void mScriptContextRemoveCallback(struct mScriptContext*, uint32_t cbid); void mScriptContextSetDocstring(struct mScriptContext*, const char* key, const char* docstring); diff --git a/src/script/context.c b/src/script/context.c index 65932fd79..e49d2527b 100644 --- a/src/script/context.c +++ b/src/script/context.c @@ -17,8 +17,10 @@ struct mScriptFileInfo { }; struct mScriptCallbackInfo { + struct mScriptValue* fn; const char* callback; - size_t id; + uint32_t id; + bool oneshot; }; static void _engineContextDestroy(void* ctx) { @@ -56,13 +58,28 @@ static void _contextFindForFile(const char* key, void* value, void* user) { } } +static void _freeTable(void* data) { + struct Table* table = data; + + struct TableIterator iter; + if (TableIteratorStart(table, &iter)) { + do { + struct mScriptCallbackInfo* info = TableIteratorGetValue(table, &iter); + mScriptValueDeref(info->fn); + } while (TableIteratorNext(table, &iter)); + } + + TableDeinit(table); + free(table); +} + void mScriptContextInit(struct mScriptContext* context) { HashTableInit(&context->rootScope, 0, (void (*)(void*)) mScriptValueDeref); HashTableInit(&context->engines, 0, _engineContextDestroy); mScriptListInit(&context->refPool, 0); TableInit(&context->weakrefs, 0, (void (*)(void*)) mScriptValueDeref); context->nextWeakref = 1; - HashTableInit(&context->callbacks, 0, (void (*)(void*)) mScriptValueDeref); + HashTableInit(&context->callbacks, 0, _freeTable); TableInit(&context->callbackId, 0, free); context->nextCallbackId = 1; context->constants = NULL; @@ -204,46 +221,60 @@ void mScriptContextDisownWeakref(struct mScriptContext* context, uint32_t weakre } void mScriptContextTriggerCallback(struct mScriptContext* context, const char* callback, struct mScriptList* args) { - struct mScriptValue* list = HashTableLookup(&context->callbacks, callback); - if (!list) { + struct Table* table = HashTableLookup(&context->callbacks, callback); + if (!table) { return; } - size_t i; - for (i = 0; i < mScriptListSize(list->value.list); ++i) { + struct TableIterator iter; + if (!TableIteratorStart(table, &iter)) { + return; + } + + struct UInt32List oneshots; + UInt32ListInit(&oneshots, 0); + do { struct mScriptFrame frame; - struct mScriptValue* fn = mScriptListGetPointer(list->value.list, i); - if (!fn->type) { - continue; - } + struct mScriptCallbackInfo* info = TableIteratorGetValue(table, &iter); mScriptFrameInit(&frame); if (args) { mScriptListCopy(&frame.arguments, args); } - if (fn->type->base == mSCRIPT_TYPE_WRAPPER) { - fn = mScriptValueUnwrap(fn); - } - mScriptInvoke(fn, &frame); + mScriptInvoke(info->fn, &frame); mScriptFrameDeinit(&frame); + + if (info->oneshot) { + *UInt32ListAppend(&oneshots) = info->id; + } + } while (TableIteratorNext(table, &iter)); + + size_t i; + for (i = 0; i < UInt32ListSize(&oneshots); ++i) { + mScriptContextRemoveCallback(context, *UInt32ListGetPointer(&oneshots, i)); } + UInt32ListDeinit(&oneshots); } -uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) { +static uint32_t mScriptContextAddCallbackInternal(struct mScriptContext* context, const char* callback, struct mScriptValue* fn, bool oneshot) { if (fn->type->base != mSCRIPT_TYPE_FUNCTION) { return 0; } - struct mScriptValue* list = HashTableLookup(&context->callbacks, callback); - if (!list) { - list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST); - HashTableInsert(&context->callbacks, callback, list); + struct Table* table = HashTableLookup(&context->callbacks, callback); + if (!table) { + table = calloc(1, sizeof(*table)); + TableInit(table, 0, NULL); + HashTableInsert(&context->callbacks, callback, table); } struct mScriptCallbackInfo* info = malloc(sizeof(*info)); // Steal the string from the table key, since it's guaranteed to outlive this struct struct TableIterator iter; HashTableIteratorLookup(&context->callbacks, &iter, callback); info->callback = HashTableIteratorGetKey(&context->callbacks, &iter); - info->id = mScriptListSize(list->value.list); + info->oneshot = oneshot; + if (fn->type->base == mSCRIPT_TYPE_WRAPPER) { + fn = mScriptValueUnwrap(fn); + } + info->fn = fn; mScriptValueRef(fn); - mScriptValueWrap(fn, mScriptListAppend(list->value.list)); while (true) { uint32_t id = context->nextCallbackId; ++context->nextCallbackId; @@ -251,8 +282,19 @@ uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* c continue; } TableInsert(&context->callbackId, id, info); - return id; + info->id = id; + break; } + TableInsert(table, info->id, info); + return info->id; +} + +uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) { + return mScriptContextAddCallbackInternal(context, callback, fn, false); +} + +uint32_t mScriptContextAddOneshot(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) { + return mScriptContextAddCallbackInternal(context, callback, fn, true); } void mScriptContextRemoveCallback(struct mScriptContext* context, uint32_t cbid) { @@ -260,16 +302,13 @@ void mScriptContextRemoveCallback(struct mScriptContext* context, uint32_t cbid) if (!info) { return; } - struct mScriptValue* list = HashTableLookup(&context->callbacks, info->callback); - if (!list) { + struct Table* table = HashTableLookup(&context->callbacks, info->callback); + if (!table) { return; } - if (info->id >= mScriptListSize(list->value.list)) { - return; - } - struct mScriptValue* fn = mScriptValueUnwrap(mScriptListGetPointer(list->value.list, info->id)); - mScriptValueDeref(fn); - mScriptListGetPointer(list->value.list, info->id)->type = NULL; + mScriptValueDeref(info->fn); + TableRemove(table, cbid); + TableRemove(&context->callbackId, cbid); } void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants) { diff --git a/src/script/stdlib.c b/src/script/stdlib.c index 3163a0d98..7397bb09e 100644 --- a/src/script/stdlib.c +++ b/src/script/stdlib.c @@ -27,12 +27,21 @@ static uint32_t _mScriptCallbackAdd(struct mScriptCallbackManager* adapter, stru return id; } +static uint32_t _mScriptCallbackOneshot(struct mScriptCallbackManager* adapter, struct mScriptString* name, struct mScriptValue* fn) { + if (fn->type->base == mSCRIPT_TYPE_WRAPPER) { + fn = mScriptValueUnwrap(fn); + } + uint32_t id = mScriptContextAddOneshot(adapter->context, name->buffer, fn); + return id; +} + static void _mScriptCallbackRemove(struct mScriptCallbackManager* adapter, uint32_t id) { mScriptContextRemoveCallback(adapter->context, id); } mSCRIPT_DECLARE_STRUCT(mScriptCallbackManager); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCallbackManager, U32, add, _mScriptCallbackAdd, 2, STR, callback, WRAPPER, function); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCallbackManager, U32, oneshot, _mScriptCallbackOneshot, 2, STR, callback, WRAPPER, function); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCallbackManager, remove, _mScriptCallbackRemove, 1, U32, cbid); static uint64_t mScriptMakeBitmask(struct mScriptList* list) { @@ -83,6 +92,8 @@ mSCRIPT_DEFINE_STRUCT(mScriptCallbackManager) ) mSCRIPT_DEFINE_DOCSTRING("Add a callback of the named type. The returned id can be used to remove it later") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, add) + mSCRIPT_DEFINE_DOCSTRING("Add a one-shot callback of the named type that will be automatically removed after called. The returned id can be used to remove it early") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, oneshot) mSCRIPT_DEFINE_DOCSTRING("Remove a callback with the previously retuned id") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, remove) mSCRIPT_DEFINE_END; diff --git a/src/script/test/stdlib.c b/src/script/test/stdlib.c index 82a4c425b..0f821dbb4 100644 --- a/src/script/test/stdlib.c +++ b/src/script/test/stdlib.c @@ -96,8 +96,41 @@ M_TEST_DEFINE(callbacks) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(oneshot) { + SETUP_LUA; + + TEST_PROGRAM( + "val = 0\n" + "function cb()\n" + " val = val + 1\n" + "end\n" + "id = callbacks:oneshot('test', cb)\n" + "assert(id)" + ); + + TEST_VALUE(S32, "val", 0); + + mScriptContextTriggerCallback(&context, "test", NULL); + TEST_VALUE(S32, "val", 1); + + mScriptContextTriggerCallback(&context, "test", NULL); + TEST_VALUE(S32, "val", 1); + + TEST_PROGRAM( + "id = callbacks:oneshot('test', cb)\n" + "assert(id)\n" + "callbacks:remove(id)" + ); + + mScriptContextTriggerCallback(&context, "test", NULL); + TEST_VALUE(S32, "val", 1); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStdlib, cmocka_unit_test(bitMask), cmocka_unit_test(bitUnmask), cmocka_unit_test(callbacks), + cmocka_unit_test(oneshot), ) From 466639ee31d7736f48032fd62fe99a69d6c79acc Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 19:17:28 -0800 Subject: [PATCH 041/290] Qt: Fix build without json-c --- src/platform/qt/scripting/ScriptingController.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index 836e379f1..e92c3ee7f 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -301,7 +301,9 @@ void ScriptingController::detachGamepad() { void ScriptingController::init() { mScriptContextInit(&m_scriptContext); mScriptContextAttachStdlib(&m_scriptContext); +#ifdef USE_JSON_C mScriptContextAttachStorage(&m_scriptContext); +#endif mScriptContextAttachSocket(&m_scriptContext); mScriptContextAttachInput(&m_scriptContext); mScriptContextRegisterEngines(&m_scriptContext); From 3cbfaa010dafe1c3af10e12c7e78cd628662205d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 20:37:19 -0800 Subject: [PATCH 042/290] Scripting: Add method to enable/disable storage bucket autoflushing --- src/script/storage.c | 19 ++++++++++++++++++- src/script/test/storage.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/script/storage.c b/src/script/storage.c index f9562a488..f17da67de 100644 --- a/src/script/storage.c +++ b/src/script/storage.c @@ -16,6 +16,7 @@ struct mScriptStorageBucket { char* name; struct mScriptValue* root; + bool autoflush; bool dirty; }; @@ -33,6 +34,7 @@ static void mScriptStorageBucketSetFloat(struct mScriptStorageBucket* bucket, co static void mScriptStorageBucketSetBool(struct mScriptStorageBucket* bucket, const char* key, bool value); static bool mScriptStorageBucketReload(struct mScriptStorageBucket* bucket); static bool mScriptStorageBucketFlush(struct mScriptStorageBucket* bucket); +static void mScriptStorageBucketEnableAutoFlush(struct mScriptStorageBucket* bucket, bool enable); static void mScriptStorageContextDeinit(struct mScriptStorageContext*); static void mScriptStorageContextFlushAll(struct mScriptStorageContext*); @@ -53,6 +55,7 @@ mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setTable, mScriptStorag mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setVoid, mScriptStorageBucketSetVoid, 2, CHARP, key, NUL, value); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, BOOL, reload, mScriptStorageBucketReload, 0); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, BOOL, flush, mScriptStorageBucketFlush, 0); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, enableAutoFlush, mScriptStorageBucketEnableAutoFlush, 1, BOOL, enable); mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket) mSCRIPT_DEFINE_CLASS_DOCSTRING( @@ -76,6 +79,13 @@ mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket) mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, reload) mSCRIPT_DEFINE_DOCSTRING("Flush the bucket to disk manually") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, flush) + mSCRIPT_DEFINE_DOCSTRING( + "Enable or disable the automatic flushing of this bucket. This is good for ensuring buckets " + "don't get flushed in an inconsistent state. It will also disable flushing to disk when the " + "emulator is shut down, so make sure to either manually flush the bucket or re-enable " + "automatic flushing whenever you're done updating it if you do disable it prior." + ) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, enableAutoFlush) mSCRIPT_DEFINE_END; mSCRIPT_DECLARE_STRUCT(mScriptStorageContext); @@ -300,6 +310,10 @@ bool mScriptStorageBucketFlush(struct mScriptStorageBucket* bucket) { return _mScriptStorageBucketFlushVF(bucket, vf); } +void mScriptStorageBucketEnableAutoFlush(struct mScriptStorageBucket* bucket, bool enable) { + bucket->autoflush = enable; +} + bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) { struct mScriptValue* value = mScriptContextGetGlobal(context, "storage"); if (!value) { @@ -481,7 +495,9 @@ void mScriptStorageContextFlushAll(struct mScriptStorageContext* storage) { if (HashTableIteratorStart(&storage->buckets, &iter)) { do { struct mScriptStorageBucket* bucket = HashTableIteratorGetValue(&storage->buckets, &iter); - mScriptStorageBucketFlush(bucket); + if (bucket->autoflush) { + mScriptStorageBucketFlush(bucket); + } } while (HashTableIteratorNext(&storage->buckets, &iter)); } } @@ -509,6 +525,7 @@ struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContex bucket = calloc(1, sizeof(*bucket)); bucket->name = strdup(name); + bucket->autoflush = true; if (!mScriptStorageBucketReload(bucket)) { bucket->root = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); } diff --git a/src/script/test/storage.c b/src/script/test/storage.c index d735a1f87..e9bb535ba 100644 --- a/src/script/test/storage.c +++ b/src/script/test/storage.c @@ -522,6 +522,36 @@ M_TEST_DEFINE(structuredRoundTrip) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(autoflush) { + SETUP_LUA; + + TEST_PROGRAM("bucket = storage:getBucket('xtest')"); + TEST_PROGRAM("assert(bucket)"); + TEST_PROGRAM("assert(not bucket.a)"); + + TEST_PROGRAM("bucket:enableAutoFlush(true)") + TEST_PROGRAM("bucket.a = 1"); + TEST_PROGRAM("storage:flushAll()"); + TEST_PROGRAM("assert(bucket:reload())") + TEST_PROGRAM("assert(bucket.a == 1)"); + + TEST_PROGRAM("bucket:enableAutoFlush(false)") + TEST_PROGRAM("bucket.a = 2"); + TEST_PROGRAM("storage:flushAll()"); + TEST_PROGRAM("assert(bucket:reload())") + TEST_PROGRAM("assert(bucket.a == 1)"); + + TEST_PROGRAM("bucket:enableAutoFlush(false)") + TEST_PROGRAM("bucket.a = 3"); + TEST_PROGRAM("storage:flushAll()"); + TEST_PROGRAM("bucket:enableAutoFlush(true)") + TEST_PROGRAM("storage:flushAll()"); + TEST_PROGRAM("assert(bucket:reload())") + TEST_PROGRAM("assert(bucket.a == 3)"); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(basicInt), cmocka_unit_test(basicFloat), @@ -551,4 +581,5 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage, cmocka_unit_test(deserializeNullByteString), cmocka_unit_test(deserializeError), cmocka_unit_test(structuredRoundTrip), + cmocka_unit_test(autoflush), ) From c709aee0f3895285bfb6743c1d56005c84a79fa5 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 8 Feb 2023 21:15:51 -0800 Subject: [PATCH 043/290] Qt: Getting tired of pushing commits to fix the build without json-c --- src/platform/qt/scripting/ScriptingController.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index e92c3ee7f..8b0200956 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -149,7 +149,9 @@ void ScriptingController::runCode(const QString& code) { } void ScriptingController::flushStorage() { +#ifdef USE_JSON_C mScriptStorageFlushAll(&m_scriptContext); +#endif } bool ScriptingController::eventFilter(QObject* obj, QEvent* ev) { @@ -320,7 +322,9 @@ void ScriptingController::init() { m_activeEngine = *m_engines.begin(); } +#ifdef USE_JSON_C m_storageFlush.start(); +#endif } uint32_t ScriptingController::qtToScriptingKey(const QKeyEvent* event) { From cade5eebde3a39e0c39e3d9cefd9caa233e43d29 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 9 Feb 2023 00:08:45 -0800 Subject: [PATCH 044/290] Qt: Properly cap number of attached players by platform (fixes #2807) --- CHANGES | 1 + src/platform/qt/MultiplayerController.cpp | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 40ca1e7a5..398934294 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,7 @@ Other fixes: - Qt: Fix crash when attempting to use OpenGL 2.1 to 3.1 (fixes mgba.io/i/2794) - Qt: Disable sync while running scripts from main thread (fixes mgba.io/i/2738) - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) + - Qt: Properly cap number of attached players by platform (fixes mgba.io/i/2807) Misc: - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs diff --git a/src/platform/qt/MultiplayerController.cpp b/src/platform/qt/MultiplayerController.cpp index f44323603..a45f287a9 100644 --- a/src/platform/qt/MultiplayerController.cpp +++ b/src/platform/qt/MultiplayerController.cpp @@ -214,10 +214,6 @@ MultiplayerController::~MultiplayerController() { } bool MultiplayerController::attachGame(CoreController* controller) { - if (m_lockstep.attached == MAX_GBAS) { - return false; - } - if (m_lockstep.attached == 0) { switch (controller->platform()) { #ifdef M_CORE_GBA @@ -243,6 +239,10 @@ bool MultiplayerController::attachGame(CoreController* controller) { switch (controller->platform()) { #ifdef M_CORE_GBA case mPLATFORM_GBA: { + if (m_lockstep.attached >= MAX_GBAS) { + return false; + } + GBA* gba = static_cast(thread->core->board); GBASIOLockstepNode* node = new GBASIOLockstepNode; @@ -259,6 +259,10 @@ bool MultiplayerController::attachGame(CoreController* controller) { #endif #ifdef M_CORE_GB case mPLATFORM_GB: { + if (m_lockstep.attached >= 2) { + return false; + } + GB* gb = static_cast(thread->core->board); GBSIOLockstepNode* node = new GBSIOLockstepNode; From 3bacc33ebe48ea6178f4dc7e5b12256794948511 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 9 Feb 2023 00:17:55 -0800 Subject: [PATCH 045/290] Qt: Disable attempted linking betwen incompatible platforms (fixes #2702) --- CHANGES | 1 + src/platform/qt/MultiplayerController.cpp | 3 +++ src/platform/qt/MultiplayerController.h | 3 +++ 3 files changed, 7 insertions(+) diff --git a/CHANGES b/CHANGES index 398934294..8f69806c9 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,7 @@ Other fixes: - Qt: Disable sync while running scripts from main thread (fixes mgba.io/i/2738) - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) - Qt: Properly cap number of attached players by platform (fixes mgba.io/i/2807) + - Qt: Disable attempted linking betwen incompatible platforms (fixes mgba.io/i/2702) Misc: - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs diff --git a/src/platform/qt/MultiplayerController.cpp b/src/platform/qt/MultiplayerController.cpp index a45f287a9..618efd1f6 100644 --- a/src/platform/qt/MultiplayerController.cpp +++ b/src/platform/qt/MultiplayerController.cpp @@ -229,6 +229,9 @@ bool MultiplayerController::attachGame(CoreController* controller) { default: return false; } + m_platform = controller->platform(); + } else if (controller->platform() != m_platform) { + return false; } mCoreThread* thread = controller->thread(); diff --git a/src/platform/qt/MultiplayerController.h b/src/platform/qt/MultiplayerController.h index 5ad6124db..35cae13bb 100644 --- a/src/platform/qt/MultiplayerController.h +++ b/src/platform/qt/MultiplayerController.h @@ -9,6 +9,7 @@ #include #include +#include #include #ifdef M_CORE_GBA #include @@ -77,6 +78,8 @@ private: GBASIOLockstep m_gbaLockstep; #endif }; + + mPlatform m_platform = mPLATFORM_NONE; QList m_players; QMutex m_lock; }; From 1acaa45ea56e7dddd71d3dae5a1205dc669b11a0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 9 Feb 2023 00:25:50 -0800 Subject: [PATCH 046/290] README: Minor updates --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 20507604e..78b69f255 100644 --- a/README.md +++ b/README.md @@ -125,9 +125,9 @@ Compiling requires using CMake 3.1 or newer. GCC, Clang, and Visual Studio 2019 #### Docker building -The recommended way to build for most platforms is to use Docker. Several Docker images are provided that contain the requisite toolchain and dependencies for building mGBA across several platforms. +The recommended way to build for most platforms is to use Docker. Several Docker images are provided that contain the requisite toolchain and dependencies for building mGBA across several platforms. -Note: If you are on an older Windows system before Windows 10, you may need to configure your Docker to use VirtualBox shared folders to correctly map your current `mgba` checkout directory to the Docker image's working directory. (See issue [#1985](https://mgba.io/i/1985) for details.) +Note: If you are on an older Windows system before Windows 10, you may need to configure your Docker to use VirtualBox shared folders to correctly map your current `mgba` checkout directory to the Docker image's working directory. (See issue [#1985](https://mgba.io/i/1985) for details.) To use a Docker image to build mGBA, simply run the following command while in the root of an mGBA checkout: @@ -234,6 +234,7 @@ mGBA has no hard dependencies, however, the following optional dependencies are - SQLite3: for game databases. - libelf: for ELF loading. - Lua: for scripting. +- json-c: for the scripting `storage` API. SQLite3, libpng, and zlib are included with the emulator, so they do not need to be externally compiled first. @@ -254,7 +255,7 @@ Footnotes Copyright --------- -mGBA is Copyright © 2013 – 2022 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file. +mGBA is Copyright © 2013 – 2023 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file. mGBA contains the following third-party libraries: From 1722fe4530e71a6174eb46496fcdfaa681d02314 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 9 Feb 2023 19:59:55 -0800 Subject: [PATCH 047/290] Qt: Fix modifier key names in shortcut editor (fixes #2817) --- CHANGES | 1 + src/platform/qt/KeyEditor.cpp | 31 ++----------------------------- src/platform/qt/ShortcutModel.cpp | 5 +++-- src/platform/qt/utils.cpp | 24 ++++++++++++++++++++++++ src/platform/qt/utils.h | 2 ++ 5 files changed, 32 insertions(+), 31 deletions(-) diff --git a/CHANGES b/CHANGES index 8f69806c9..700a9d6e0 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,7 @@ Other fixes: - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) - Qt: Properly cap number of attached players by platform (fixes mgba.io/i/2807) - Qt: Disable attempted linking betwen incompatible platforms (fixes mgba.io/i/2702) + - Qt: Fix modifier key names in shortcut editor (fixes mgba.io/i/2817) Misc: - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs diff --git a/src/platform/qt/KeyEditor.cpp b/src/platform/qt/KeyEditor.cpp index e8da70970..7d3ba1990 100644 --- a/src/platform/qt/KeyEditor.cpp +++ b/src/platform/qt/KeyEditor.cpp @@ -8,6 +8,7 @@ #include "input/GamepadAxisEvent.h" #include "input/GamepadButtonEvent.h" #include "ShortcutController.h" +#include "utils.h" #include #include @@ -33,35 +34,7 @@ void KeyEditor::setValue(int key) { if (key < 0) { setText(tr("---")); } else { - QKeySequence seq(key); - switch (key) { -#ifndef Q_OS_MAC - case Qt::Key_Shift: - setText(QCoreApplication::translate("QShortcut", "Shift")); - break; - case Qt::Key_Control: - setText(QCoreApplication::translate("QShortcut", "Control")); - break; - case Qt::Key_Alt: - setText(QCoreApplication::translate("QShortcut", "Alt")); - break; - case Qt::Key_Meta: - setText(QCoreApplication::translate("QShortcut", "Meta")); - break; -#endif - case Qt::Key_Super_L: - setText(tr("Super (L)")); - break; - case Qt::Key_Super_R: - setText(tr("Super (R)")); - break; - case Qt::Key_Menu: - setText(tr("Menu")); - break; - default: - setText(QKeySequence(key).toString(QKeySequence::NativeText)); - break; - } + setText(keyName(key)); } } emit valueChanged(key); diff --git a/src/platform/qt/ShortcutModel.cpp b/src/platform/qt/ShortcutModel.cpp index 54fa83fcf..b73982cad 100644 --- a/src/platform/qt/ShortcutModel.cpp +++ b/src/platform/qt/ShortcutModel.cpp @@ -6,6 +6,7 @@ #include "ShortcutModel.h" #include "ShortcutController.h" +#include "utils.h" using namespace QGBA; @@ -33,7 +34,7 @@ QVariant ShortcutModel::data(const QModelIndex& index, int role) const { case 0: return m_controller->visibleName(item->name); case 1: - return shortcut ? QKeySequence(shortcut->shortcut()).toString(QKeySequence::NativeText) : QVariant(); + return shortcut ? keyName(shortcut->shortcut()) : QVariant(); case 2: if (!shortcut) { return QVariant(); @@ -134,4 +135,4 @@ void ShortcutModel::clearMenu(const QString&) { // TODO beginResetModel(); endResetModel(); -} \ No newline at end of file +} diff --git a/src/platform/qt/utils.cpp b/src/platform/qt/utils.cpp index d6da29781..7a9cd3bd7 100644 --- a/src/platform/qt/utils.cpp +++ b/src/platform/qt/utils.cpp @@ -6,6 +6,7 @@ #include "utils.h" #include +#include #include #include "VFileDevice.h" @@ -129,4 +130,27 @@ bool extractMatchingFile(VDir* dir, std::function filter) return false; } +QString keyName(int key) { + switch (key) { +#ifndef Q_OS_MAC + case Qt::Key_Shift: + return QCoreApplication::translate("QShortcut", "Shift"); + case Qt::Key_Control: + return QCoreApplication::translate("QShortcut", "Control"); + case Qt::Key_Alt: + return QCoreApplication::translate("QShortcut", "Alt"); + case Qt::Key_Meta: + return QCoreApplication::translate("QShortcut", "Meta"); +#endif + case Qt::Key_Super_L: + return QObject::tr("Super (L)"); + case Qt::Key_Super_R: + return QObject::tr("Super (R)"); + case Qt::Key_Menu: + return QObject::tr("Menu"); + default: + return QKeySequence(key).toString(QKeySequence::NativeText); + } +} + } diff --git a/src/platform/qt/utils.h b/src/platform/qt/utils.h index 72ce74c30..0ce974bc5 100644 --- a/src/platform/qt/utils.h +++ b/src/platform/qt/utils.h @@ -75,4 +75,6 @@ constexpr const T& clamp(const T& v, const T& lo, const T& hi) { QString romFilters(bool includeMvl = false); bool extractMatchingFile(VDir* dir, std::function filter); +QString keyName(int key); + } From 30fa0a384345a281a3728f0c1752fa54f4ed24ff Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 11 Feb 2023 21:08:31 -0800 Subject: [PATCH 048/290] OpenGL: Fix null calloc/memcpy --- src/platform/opengl/gles2.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 4277b7b76..2a8d91297 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -1010,8 +1010,11 @@ bool mGLES2ShaderLoad(struct VideoShader* shader, struct VDir* dir) { } } u = mGLES2UniformListSize(&uniformVector); - struct mGLES2Uniform* uniformBlock = calloc(u, sizeof(*uniformBlock)); - memcpy(uniformBlock, mGLES2UniformListGetPointer(&uniformVector, 0), sizeof(*uniformBlock) * u); + struct mGLES2Uniform* uniformBlock; + if (u) { + uniformBlock = calloc(u, sizeof(*uniformBlock)); + memcpy(uniformBlock, mGLES2UniformListGetPointer(&uniformVector, 0), sizeof(*uniformBlock) * u); + } mGLES2UniformListDeinit(&uniformVector); mGLES2ShaderInit(&shaderBlock[n], vssrc, fssrc, width, height, scaling, uniformBlock, u); From 7b979a679ef86b7ab944bc26aa974fcaf382ca54 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 11 Feb 2023 21:09:29 -0800 Subject: [PATCH 049/290] Res: Port hq2x from SameBoy --- res/shaders/hq2x.shader/hq2x.fs | 143 +++++++++++++++++++++++++++ res/shaders/hq2x.shader/manifest.ini | 11 +++ 2 files changed, 154 insertions(+) create mode 100644 res/shaders/hq2x.shader/hq2x.fs create mode 100644 res/shaders/hq2x.shader/manifest.ini diff --git a/res/shaders/hq2x.shader/hq2x.fs b/res/shaders/hq2x.shader/hq2x.fs new file mode 100644 index 000000000..97019bd3c --- /dev/null +++ b/res/shaders/hq2x.shader/hq2x.fs @@ -0,0 +1,143 @@ +/* MIT License +* +* Copyright (c) 2015-2023 Lior Halphon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ +/* Based on this (really good) article: http://blog.pkh.me/p/19-butchering-hqx-scaling-filters.html */ + +/* The colorspace used by the HQnx filters is not really YUV, despite the algorithm description claims it is. It is + also not normalized. Therefore, we shall call the colorspace used by HQnx "HQ Colorspace" to avoid confusion. */ +varying vec2 texCoord; +uniform sampler2D tex; +uniform vec2 texSize; + +vec3 rgb_to_hq_colospace(vec4 rgb) +{ + return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b, + 0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b, + -0.125 * rgb.r + 0.250 * rgb.g - 0.125 * rgb.b); +} + +bool is_different(vec4 a, vec4 b) +{ + vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b)); + return diff.x > 0.018 || diff.y > 0.002 || diff.z > 0.005; +} + +#define P(m, r) ((pattern & (m)) == (r)) + +vec4 interp_2px(vec4 c1, float w1, vec4 c2, float w2) +{ + return (c1 * w1 + c2 * w2) / (w1 + w2); +} + +vec4 interp_3px(vec4 c1, float w1, vec4 c2, float w2, vec4 c3, float w3) +{ + return (c1 * w1 + c2 * w2 + c3 * w3) / (w1 + w2 + w3); +} + +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution) +{ + // o = offset, the width of a pixel + vec2 o = vec2(1, 1) / input_resolution; + + /* We always calculate the top left pixel. If we need a different pixel, we flip the image */ + + // p = the position within a pixel [0...1] + vec2 p = fract(position * input_resolution); + + if (p.x > 0.5) o.x = -o.x; + if (p.y > 0.5) o.y = -o.y; + + vec4 w0 = texture2D(image, position + vec2( -o.x, -o.y)); + vec4 w1 = texture2D(image, position + vec2( 0, -o.y)); + vec4 w2 = texture2D(image, position + vec2( o.x, -o.y)); + vec4 w3 = texture2D(image, position + vec2( -o.x, 0)); + vec4 w4 = texture2D(image, position + vec2( 0, 0)); + vec4 w5 = texture2D(image, position + vec2( o.x, 0)); + vec4 w6 = texture2D(image, position + vec2( -o.x, o.y)); + vec4 w7 = texture2D(image, position + vec2( 0, o.y)); + vec4 w8 = texture2D(image, position + vec2( o.x, o.y)); + + int pattern = 0; + if (is_different(w0, w4)) pattern |= 1; + if (is_different(w1, w4)) pattern |= 2; + if (is_different(w2, w4)) pattern |= 4; + if (is_different(w3, w4)) pattern |= 8; + if (is_different(w5, w4)) pattern |= 16; + if (is_different(w6, w4)) pattern |= 32; + if (is_different(w7, w4)) pattern |= 64; + if (is_different(w8, w4)) pattern |= 128; + + if ((P(0xBF,0x37) || P(0xDB,0x13)) && is_different(w1, w5)) { + return interp_2px(w4, 3.0, w3, 1.0); + } + if ((P(0xDB,0x49) || P(0xEF,0x6D)) && is_different(w7, w3)) { + return interp_2px(w4, 3.0, w1, 1.0); + } + if ((P(0x0B,0x0B) || P(0xFE,0x4A) || P(0xFE,0x1A)) && is_different(w3, w1)) { + return w4; + } + if ((P(0x6F,0x2A) || P(0x5B,0x0A) || P(0xBF,0x3A) || P(0xDF,0x5A) || + P(0x9F,0x8A) || P(0xCF,0x8A) || P(0xEF,0x4E) || P(0x3F,0x0E) || + P(0xFB,0x5A) || P(0xBB,0x8A) || P(0x7F,0x5A) || P(0xAF,0x8A) || + P(0xEB,0x8A)) && is_different(w3, w1)) { + return interp_2px(w4, 3.0, w0, 1.0); + } + if (P(0x0B,0x08)) { + return interp_3px(w4, 2.0, w0, 1.0, w1, 1.0); + } + if (P(0x0B,0x02)) { + return interp_3px(w4, 2.0, w0, 1.0, w3, 1.0); + } + if (P(0x2F,0x2F)) { + return interp_3px(w4, 4.0, w3, 1.0, w1, 1.0); + } + if (P(0xBF,0x37) || P(0xDB,0x13)) { + return interp_3px(w4, 5.0, w1, 2.0, w3, 1.0); + } + if (P(0xDB,0x49) || P(0xEF,0x6D)) { + return interp_3px(w4, 5.0, w3, 2.0, w1, 1.0); + } + if (P(0x1B,0x03) || P(0x4F,0x43) || P(0x8B,0x83) || P(0x6B,0x43)) { + return interp_2px(w4, 3.0, w3, 1.0); + } + if (P(0x4B,0x09) || P(0x8B,0x89) || P(0x1F,0x19) || P(0x3B,0x19)) { + return interp_2px(w4, 3.0, w1, 1.0); + } + if (P(0x7E,0x2A) || P(0xEF,0xAB) || P(0xBF,0x8F) || P(0x7E,0x0E)) { + return interp_3px(w4, 2.0, w3, 3.0, w1, 3.0); + } + if (P(0xFB,0x6A) || P(0x6F,0x6E) || P(0x3F,0x3E) || P(0xFB,0xFA) || + P(0xDF,0xDE) || P(0xDF,0x1E)) { + return interp_2px(w4, 3.0, w0, 1.0); + } + if (P(0x0A,0x00) || P(0x4F,0x4B) || P(0x9F,0x1B) || P(0x2F,0x0B) || + P(0xBE,0x0A) || P(0xEE,0x0A) || P(0x7E,0x0A) || P(0xEB,0x4B) || + P(0x3B,0x1B)) { + return interp_3px(w4, 2.0, w3, 1.0, w1, 1.0); + } + + return interp_3px(w4, 6.0, w3, 1.0, w1, 1.0); +} + +void main() { + gl_FragColor = scale(tex, texCoord, texSize); +} diff --git a/res/shaders/hq2x.shader/manifest.ini b/res/shaders/hq2x.shader/manifest.ini new file mode 100644 index 000000000..06c457e81 --- /dev/null +++ b/res/shaders/hq2x.shader/manifest.ini @@ -0,0 +1,11 @@ +[shader] +name=hq2x +author=Lior Halphson +description="High Quality" 2x scaling +passes=1 + +[pass.0] +fragmentShader=hq2x.fs +blend=0 +width=-2 +height=-2 From 422439f0a63356c3cf123e2f79669af0bf183e34 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 11 Feb 2023 22:03:31 -0800 Subject: [PATCH 050/290] OpenGL: Export output buffer size to shader --- src/platform/opengl/gles2.c | 4 +++- src/platform/opengl/gles2.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 2a8d91297..88ac000a5 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -301,7 +301,8 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glUseProgram(shader->program); glUniform1i(shader->texLocation, 0); - glUniform2f(shader->texSizeLocation, context->d.width - padW, context->d.height - padH); + glUniform2f(shader->texSizeLocation, context->d.width, context->d.height); + glUniform2f(shader->outputSizeLocation, drawW, drawH); #ifdef BUILD_GLES3 if (shader->vao != (GLuint) -1) { glBindVertexArray(shader->vao); @@ -532,6 +533,7 @@ void mGLES2ShaderInit(struct mGLES2Shader* shader, const char* vs, const char* f shader->texLocation = glGetUniformLocation(shader->program, "tex"); shader->texSizeLocation = glGetUniformLocation(shader->program, "texSize"); shader->positionLocation = glGetAttribLocation(shader->program, "position"); + shader->outputSizeLocation = glGetUniformLocation(shader->program, "outputSize"); size_t i; for (i = 0; i < shader->nUniforms; ++i) { shader->uniforms[i].location = glGetUniformLocation(shader->program, shader->uniforms[i].name); diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h index 144a64a13..b497b4bfa 100644 --- a/src/platform/opengl/gles2.h +++ b/src/platform/opengl/gles2.h @@ -70,6 +70,7 @@ struct mGLES2Shader { GLuint texLocation; GLuint texSizeLocation; GLuint positionLocation; + GLuint outputSizeLocation; struct mGLES2Uniform* uniforms; size_t nUniforms; From 3139ac7d582ab83c5d9b6b9e545f4b15b63c29b8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 11 Feb 2023 22:07:41 -0800 Subject: [PATCH 051/290] Res: Port OmniScale from SameBoy --- res/shaders/omniscale.shader/manifest.ini | 9 + res/shaders/omniscale.shader/omniscale.fs | 292 ++++++++++++++++++++++ 2 files changed, 301 insertions(+) create mode 100644 res/shaders/omniscale.shader/manifest.ini create mode 100644 res/shaders/omniscale.shader/omniscale.fs diff --git a/res/shaders/omniscale.shader/manifest.ini b/res/shaders/omniscale.shader/manifest.ini new file mode 100644 index 000000000..562abcb90 --- /dev/null +++ b/res/shaders/omniscale.shader/manifest.ini @@ -0,0 +1,9 @@ +[shader] +name=OmniScale +author=Lior Halphson +description=Resolution-indepedent scaler inspired by the hqx family scalers +passes=1 + +[pass.0] +fragmentShader=omniscale.fs +blend=0 diff --git a/res/shaders/omniscale.shader/omniscale.fs b/res/shaders/omniscale.shader/omniscale.fs new file mode 100644 index 000000000..d6b71475b --- /dev/null +++ b/res/shaders/omniscale.shader/omniscale.fs @@ -0,0 +1,292 @@ +/* MIT License +* +* Copyright (c) 2015-2023 Lior Halphon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ +/* OmniScale is derived from the pattern based design of HQnx, but with the following general differences: + - The actual output calculating was completely redesigned as resolution independent graphic generator. This allows + scaling to any factor. + - HQnx approximations that were good enough for a 2x/3x/4x factor were refined, creating smoother gradients. + - "Quarters" can be interpolated in more ways than in the HQnx filters + - If a pattern does not provide enough information to determine the suitable scaling interpolation, up to 16 pixels + per quarter are sampled (in contrast to the usual 9) in order to determine the best interpolation. + */ +varying vec2 texCoord; +uniform sampler2D tex; +uniform vec2 texSize; +uniform vec2 outputSize; + +/* We use the same colorspace as the HQ algorithms. */ +vec3 rgb_to_hq_colospace(vec4 rgb) +{ + return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b, + 0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b, + -0.125 * rgb.r + 0.250 * rgb.g - 0.125 * rgb.b); +} + + +bool is_different(vec4 a, vec4 b) +{ + vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b)); + return diff.x > 0.018 || diff.y > 0.002 || diff.z > 0.005; +} + +#define P(m, r) ((pattern & (m)) == (r)) + +vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + // o = offset, the width of a pixel + vec2 o = vec2(1, 1) / input_resolution; + + /* We always calculate the top left quarter. If we need a different quarter, we flip our co-ordinates */ + + // p = the position within a pixel [0...1] + vec2 p = fract(position * input_resolution); + + if (p.x > 0.5) { + o.x = -o.x; + p.x = 1.0 - p.x; + } + if (p.y > 0.5) { + o.y = -o.y; + p.y = 1.0 - p.y; + } + + vec4 w0 = texture2D(image, position + vec2( -o.x, -o.y)); + vec4 w1 = texture2D(image, position + vec2( 0, -o.y)); + vec4 w2 = texture2D(image, position + vec2( o.x, -o.y)); + vec4 w3 = texture2D(image, position + vec2( -o.x, 0)); + vec4 w4 = texture2D(image, position + vec2( 0, 0)); + vec4 w5 = texture2D(image, position + vec2( o.x, 0)); + vec4 w6 = texture2D(image, position + vec2( -o.x, o.y)); + vec4 w7 = texture2D(image, position + vec2( 0, o.y)); + vec4 w8 = texture2D(image, position + vec2( o.x, o.y)); + + int pattern = 0; + if (is_different(w0, w4)) pattern |= 1 << 0; + if (is_different(w1, w4)) pattern |= 1 << 1; + if (is_different(w2, w4)) pattern |= 1 << 2; + if (is_different(w3, w4)) pattern |= 1 << 3; + if (is_different(w5, w4)) pattern |= 1 << 4; + if (is_different(w6, w4)) pattern |= 1 << 5; + if (is_different(w7, w4)) pattern |= 1 << 6; + if (is_different(w8, w4)) pattern |= 1 << 7; + + if ((P(0xBF,0x37) || P(0xDB,0x13)) && is_different(w1, w5)) { + return mix(w4, w3, 0.5 - p.x); + } + if ((P(0xDB,0x49) || P(0xEF,0x6D)) && is_different(w7, w3)) { + return mix(w4, w1, 0.5 - p.y); + } + if ((P(0x0B,0x0B) || P(0xFE,0x4A) || P(0xFE,0x1A)) && is_different(w3, w1)) { + return w4; + } + if ((P(0x6F,0x2A) || P(0x5B,0x0A) || P(0xBF,0x3A) || P(0xDF,0x5A) || + P(0x9F,0x8A) || P(0xCF,0x8A) || P(0xEF,0x4E) || P(0x3F,0x0E) || + P(0xFB,0x5A) || P(0xBB,0x8A) || P(0x7F,0x5A) || P(0xAF,0x8A) || + P(0xEB,0x8A)) && is_different(w3, w1)) { + return mix(w4, mix(w4, w0, 0.5 - p.x), 0.5 - p.y); + } + if (P(0x0B,0x08)) { + return mix(mix(w0 * 0.375 + w1 * 0.25 + w4 * 0.375, w4 * 0.5 + w1 * 0.5, p.x * 2.0), w4, p.y * 2.0); + } + if (P(0x0B,0x02)) { + return mix(mix(w0 * 0.375 + w3 * 0.25 + w4 * 0.375, w4 * 0.5 + w3 * 0.5, p.y * 2.0), w4, p.x * 2.0); + } + if (P(0x2F,0x2F)) { + float dist = length(p - vec2(0.5)); + float pixel_size = length(1.0 / (output_resolution / input_resolution)); + if (dist < 0.5 - pixel_size / 2) { + return w4; + } + vec4 r; + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist > 0.5 + pixel_size / 2) { + return r; + } + return mix(w4, r, (dist - 0.5 + pixel_size / 2) / pixel_size); + } + if (P(0xBF,0x37) || P(0xDB,0x13)) { + float dist = p.x - 2.0 * p.y; + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); + if (dist > pixel_size / 2) { + return w1; + } + vec4 r = mix(w3, w4, p.x + 0.5); + if (dist < -pixel_size / 2) { + return r; + } + return mix(r, w1, (dist + pixel_size / 2) / pixel_size); + } + if (P(0xDB,0x49) || P(0xEF,0x6D)) { + float dist = p.y - 2.0 * p.x; + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); + if (p.y - 2.0 * p.x > pixel_size / 2) { + return w3; + } + vec4 r = mix(w1, w4, p.x + 0.5); + if (dist < -pixel_size / 2) { + return r; + } + return mix(r, w3, (dist + pixel_size / 2) / pixel_size); + } + if (P(0xBF,0x8F) || P(0x7E,0x0E)) { + float dist = p.x + 2.0 * p.y; + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); + + if (dist > 1.0 + pixel_size / 2) { + return w4; + } + + vec4 r; + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist < 1.0 - pixel_size / 2) { + return r; + } + + return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size); + } + + if (P(0x7E,0x2A) || P(0xEF,0xAB)) { + float dist = p.y + 2.0 * p.x; + float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); + + if (p.y + 2.0 * p.x > 1.0 + pixel_size / 2) { + return w4; + } + + vec4 r; + + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist < 1.0 - pixel_size / 2) { + return r; + } + + return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size); + } + + if (P(0x1B,0x03) || P(0x4F,0x43) || P(0x8B,0x83) || P(0x6B,0x43)) { + return mix(w4, w3, 0.5 - p.x); + } + + if (P(0x4B,0x09) || P(0x8B,0x89) || P(0x1F,0x19) || P(0x3B,0x19)) { + return mix(w4, w1, 0.5 - p.y); + } + + if (P(0xFB,0x6A) || P(0x6F,0x6E) || P(0x3F,0x3E) || P(0xFB,0xFA) || + P(0xDF,0xDE) || P(0xDF,0x1E)) { + return mix(w4, w0, (1.0 - p.x - p.y) / 2.0); + } + + if (P(0x4F,0x4B) || P(0x9F,0x1B) || P(0x2F,0x0B) || + P(0xBE,0x0A) || P(0xEE,0x0A) || P(0x7E,0x0A) || P(0xEB,0x4B) || + P(0x3B,0x1B)) { + float dist = p.x + p.y; + float pixel_size = length(1.0 / (output_resolution / input_resolution)); + + if (dist > 0.5 + pixel_size / 2) { + return w4; + } + + vec4 r; + if (is_different(w0, w1) || is_different(w0, w3)) { + r = mix(w1, w3, p.y - p.x + 0.5); + } + else { + r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); + } + + if (dist < 0.5 - pixel_size / 2) { + return r; + } + + return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size); + } + + if (P(0x0B,0x01)) { + return mix(mix(w4, w3, 0.5 - p.x), mix(w1, (w1 + w3) / 2.0, 0.5 - p.x), 0.5 - p.y); + } + + if (P(0x0B,0x00)) { + return mix(mix(w4, w3, 0.5 - p.x), mix(w1, w0, 0.5 - p.x), 0.5 - p.y); + } + + float dist = p.x + p.y; + float pixel_size = length(1.0 / (output_resolution / input_resolution)); + + if (dist > 0.5 + pixel_size / 2) { + return w4; + } + + /* We need more samples to "solve" this diagonal */ + vec4 x0 = texture2D(image, position + vec2( -o.x * 2.0, -o.y * 2.0)); + vec4 x1 = texture2D(image, position + vec2( -o.x , -o.y * 2.0)); + vec4 x2 = texture2D(image, position + vec2( 0.0 , -o.y * 2.0)); + vec4 x3 = texture2D(image, position + vec2( o.x , -o.y * 2.0)); + vec4 x4 = texture2D(image, position + vec2( -o.x * 2.0, -o.y )); + vec4 x5 = texture2D(image, position + vec2( -o.x * 2.0, 0.0 )); + vec4 x6 = texture2D(image, position + vec2( -o.x * 2.0, o.y )); + + if (is_different(x0, w4)) pattern |= 1 << 8; + if (is_different(x1, w4)) pattern |= 1 << 9; + if (is_different(x2, w4)) pattern |= 1 << 10; + if (is_different(x3, w4)) pattern |= 1 << 11; + if (is_different(x4, w4)) pattern |= 1 << 12; + if (is_different(x5, w4)) pattern |= 1 << 13; + if (is_different(x6, w4)) pattern |= 1 << 14; + + int diagonal_bias = -7; + while (pattern != 0) { + diagonal_bias += pattern & 1; + pattern >>= 1; + } + + if (diagonal_bias <= 0) { + vec4 r = mix(w1, w3, p.y - p.x + 0.5); + if (dist < 0.5 - pixel_size / 2) { + return r; + } + return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size); + } + + return w4; +} + +void main() { + gl_FragColor = scale(tex, texCoord, texSize, outputSize); +} From b1faf674388d69db64e10045cd0c57d50ecdc1f8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 12 Feb 2023 01:46:05 -0800 Subject: [PATCH 052/290] Scripting: Bucket names can't start with . --- src/script/storage.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/script/storage.c b/src/script/storage.c index f17da67de..b1b740162 100644 --- a/src/script/storage.c +++ b/src/script/storage.c @@ -508,15 +508,22 @@ struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContex } // Check if name is allowed - // Currently only names matching /[0-9A-Za-z_.]+/ are allowed + // Currently only names matching /[0-9A-Za-z][0-9A-Za-z_.]*/ are allowed size_t i; for (i = 0; name[i]; ++i) { if (i >= STORAGE_LEN_MAX) { return NULL; } - if (!isalnum(name[i]) && name[i] != '_' && name[i] != '.') { - return NULL; + if (isalnum(name[i])) { + continue; } + if (name[i] == '_') { + continue; + } + if (i > 0 && name[i] == '.') { + continue; + } + return NULL; } struct mScriptStorageBucket* bucket = HashTableLookup(&storage->buckets, name); if (bucket) { From e10f5997be76615eb611fe8928372cac045d26f0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 12 Feb 2023 12:44:14 -0800 Subject: [PATCH 053/290] Res: Fix name spelling --- res/shaders/hq2x.shader/manifest.ini | 2 +- res/shaders/omniscale.shader/manifest.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/res/shaders/hq2x.shader/manifest.ini b/res/shaders/hq2x.shader/manifest.ini index 06c457e81..c0ab0ef8a 100644 --- a/res/shaders/hq2x.shader/manifest.ini +++ b/res/shaders/hq2x.shader/manifest.ini @@ -1,6 +1,6 @@ [shader] name=hq2x -author=Lior Halphson +author=Lior Halphon description="High Quality" 2x scaling passes=1 diff --git a/res/shaders/omniscale.shader/manifest.ini b/res/shaders/omniscale.shader/manifest.ini index 562abcb90..a98e34ce2 100644 --- a/res/shaders/omniscale.shader/manifest.ini +++ b/res/shaders/omniscale.shader/manifest.ini @@ -1,6 +1,6 @@ [shader] name=OmniScale -author=Lior Halphson +author=Lior Halphon description=Resolution-indepedent scaler inspired by the hqx family scalers passes=1 From 033efff86e52621968ebbf6523b7ab8c2269e07c Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Sun, 12 Feb 2023 14:57:29 -0600 Subject: [PATCH 054/290] hook frame callback in socket connect --- CHANGES | 1 + src/script/engines/lua.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 700a9d6e0..e84a651af 100644 --- a/CHANGES +++ b/CHANGES @@ -49,6 +49,7 @@ Other fixes: - Qt: Fix initializing update revision info - Qt: Redo stable branch detection heuristic (fixes mgba.io/i/2679) - Res: Fix species name location in Ruby/Sapphire revs 1/2 (fixes mgba.io/i/2685) + - Scripting: Fix receiving packets for client sockets - VFS: Fix minizip write returning 0 on success instead of size Misc: - macOS: Add category to plist (closes mgba.io/i/2691) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 3dfe8d0ac..58c13d96f 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -134,7 +134,7 @@ static const char* _socketLuaSource = " end,\n" " connect = function(self, address, port)\n" " local status = self._s:connect(address, port)\n" - " return socket._wrap(status)\n" + " return self:_hook(status)\n" " end,\n" " listen = function(self, backlog)\n" " local status = self._s:listen(backlog or 1)\n" From 0b17a40d6be12a6f3327d18505304f0611007698 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 14 Feb 2023 23:13:04 -0800 Subject: [PATCH 055/290] Qt: Fix a handful of edge cases with graphics viewers (fixes #2827) --- CHANGES | 1 + src/platform/qt/MapView.cpp | 12 +++++++++++- src/platform/qt/TileView.cpp | 4 ++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index e84a651af..a34b063f9 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,7 @@ Other fixes: - Qt: Properly cap number of attached players by platform (fixes mgba.io/i/2807) - Qt: Disable attempted linking betwen incompatible platforms (fixes mgba.io/i/2702) - Qt: Fix modifier key names in shortcut editor (fixes mgba.io/i/2817) + - Qt: Fix a handful of edge cases with graphics viewers (fixes mgba.io/i/2827) Misc: - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs diff --git a/src/platform/qt/MapView.cpp b/src/platform/qt/MapView.cpp index da65abf9d..d565f2fea 100644 --- a/src/platform/qt/MapView.cpp +++ b/src/platform/qt/MapView.cpp @@ -42,7 +42,7 @@ MapView::MapView(std::shared_ptr controller, QWidget* parent) #ifdef M_CORE_GBA case mPLATFORM_GBA: m_boundary = 2048; - m_ui.tile->setMaxTile(3096); + m_ui.tile->setMaxTile(3072); m_addressBase = GBA_BASE_VRAM; m_addressWidth = 8; m_ui.bgInfo->addCustomProperty("priority", tr("Priority")); @@ -119,6 +119,9 @@ void MapView::selectMap(int map) { } m_map = map; m_mapStatus.fill({}); + // Different maps can have different max palette counts; set it to + // 0 immediately to avoid tile lookups with state palette IDs break + m_ui.tile->setPalette(0); updateTiles(true); } @@ -184,11 +187,18 @@ void MapView::updateTilesGBA(bool) { frame = GBARegisterDISPCNTGetFrameSelect(io[REG_DISPCNT >> 1]); } } + m_boundary = 1024; + m_ui.tile->setMaxTile(1536); priority = GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + m_map]); if (mode == 0 || (mode == 1 && m_map != 2)) { offset = QString("%1, %2") .arg(io[(REG_BG0HOFS >> 1) + (m_map << 1)]) .arg(io[(REG_BG0VOFS >> 1) + (m_map << 1)]); + + if (!GBARegisterBGCNTIs256Color(io[(REG_BG0CNT >> 1) + m_map])) { + m_boundary = 2048; + m_ui.tile->setMaxTile(3072); + } } else if ((mode > 0 && m_map == 2) || (mode == 2 && m_map == 3)) { int32_t refX = io[(REG_BG2X_LO >> 1) + ((m_map - 2) << 2)]; refX |= io[(REG_BG2X_HI >> 1) + ((m_map - 2) << 2)] << 16; diff --git a/src/platform/qt/TileView.cpp b/src/platform/qt/TileView.cpp index 116935e29..7d8d1a71e 100644 --- a/src/platform/qt/TileView.cpp +++ b/src/platform/qt/TileView.cpp @@ -51,7 +51,7 @@ TileView::TileView(std::shared_ptr controller, QWidget* parent) #ifdef M_CORE_GBA case mPLATFORM_GBA: m_ui.tile->setBoundary(2048, 0, 2); - m_ui.tile->setMaxTile(3096); + m_ui.tile->setMaxTile(3072); break; #endif #ifdef M_CORE_GB @@ -76,7 +76,7 @@ TileView::TileView(std::shared_ptr controller, QWidget* parent) #ifdef M_CORE_GBA case mPLATFORM_GBA: m_ui.tile->setBoundary(2048 >> selected, selected, selected + 2); - m_ui.tile->setMaxTile(3096 >> selected); + m_ui.tile->setMaxTile(3072 >> selected); break; #endif #ifdef M_CORE_GB From 6f14732e0d03748242bdcf208a26c8deb833ad3c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 15 Feb 2023 02:29:57 -0800 Subject: [PATCH 056/290] Qt: Fix loading a script leaving sync disabled --- src/platform/qt/scripting/ScriptingController.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index 8b0200956..990cbca9b 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -111,9 +111,11 @@ bool ScriptingController::load(VFileDevice& vf, const QString& name) { emit error(QString::fromUtf8(m_activeEngine->getError(m_activeEngine))); ok = false; } - if (m_controller && m_controller->isPaused()) { + if (m_controller) { m_controller->setSync(true); - m_controller->paused(); + if (m_controller->isPaused()) { + m_controller->paused(); + } } return ok; } From ea345ca8158d7339ab5d7c4dee4910edd3a01a36 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 15 Feb 2023 16:36:00 -0800 Subject: [PATCH 057/290] Res: Add SGB platform icons --- res/sgb-icon-128.png | Bin 0 -> 2808 bytes res/sgb-icon-16.png | Bin 0 -> 1775 bytes res/sgb-icon-24.png | Bin 0 -> 1987 bytes res/sgb-icon-256.png | Bin 0 -> 3209 bytes res/sgb-icon-32.png | Bin 0 -> 1948 bytes res/sgb-icon-48.png | Bin 0 -> 2180 bytes res/sgb-icon-64.png | Bin 0 -> 2234 bytes res/sgb-icon.svg | 1 + 8 files changed, 1 insertion(+) create mode 100644 res/sgb-icon-128.png create mode 100644 res/sgb-icon-16.png create mode 100644 res/sgb-icon-24.png create mode 100644 res/sgb-icon-256.png create mode 100644 res/sgb-icon-32.png create mode 100644 res/sgb-icon-48.png create mode 100644 res/sgb-icon-64.png create mode 100644 res/sgb-icon.svg diff --git a/res/sgb-icon-128.png b/res/sgb-icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..473adac4cccf1df8b208f1fcba19addd31eb1392 GIT binary patch literal 2808 zcmah}2~<;O7Jdl{JA{g?VR?iAVaW=CBqkvtA%G--0xDQhWAX^mkQbAO2~cZnXL`on zS}oRU)v8rdanNd2RNRq8K&^~hJ?c2eT3bM!9%v8M`4bdm)R}kQcK>_t_kaK0mK3UU z{Jg`xAqeuz%as>_ufzFxx`Fq_z?6`l@x-SR{0ETh(N|!i*mA{#;%A*K=3l> zmRTXlC(!xepbZ;CA&6LDC@IBCRZ4*ttzu|&=p2M$uQCH`2oh%6%^K}I1cT=wdV@(s z>2GeKzy_U&GL5TZsmwB@(vVwgL5geDCED6~TE32wDfSlH1puH5!8EYF%4o6*>>`Q_ zR{;K<%S;OFGQsAFD5a`GScX~-LDU6|ie)bVfqR*RahzUlU@fUJ zBg`ViidI{+NcI8{y;xV^oN*Bp8tjO%RBix4wSsJjC~OYzzqDoV(#lXHYAFU~hyXX2 zlQVXLyef?znP$*om1AgQ3MFFFzoBumGgdpxVT#EB&chi)D;?8`C>b13h|VH$;g!8r zHJS^7i=PVB0eYz}uQ64gOjc+?D-1@UbG9Lzmn}Yk z!{VgUSsXf>Tf#~eq;dq@3_6P^V2$u`S%d!3X)w+Ivv+m@EYxWQm;o~)E@`AIq(*E+ z8WD{KXDbMd8k1f`vD0-(g{InwQN&jL93j)$HAcfrXP5NKTgV&-Ed&%9TVz!{VYAvF3#Sicv(OS_8%%-s*Qaz|95k*>2VE}on zfk9sz^d`gtvl%?bNX$olY?TPWdv_R_uOH?WIU(~6`Xgt5ZGr&5ku@-xz=&gx&Nv{9 zPD#WBMxzDHz{)>E>L3W8SEww>&M_-p!_@hD@#4kx-piiO%R^n1LnIRGYHdS9L#p=` z9v&XOKXhNa+O1G1e0+RF5}ucr7dI`{*Vo6_*O$X#OESgJpFeMJZ)dYvZEbDw@o`KB zox^1i2yRhPkrJ7JlSa=~W-(Za$@GMzl=!p^Wg^J89etbggByoB*HlIi&_>=rQ+b^6vek3w70yWL^@$t6IH&Lk3%|{R6 z@%ZfQ?A&}Ql}Zr^({gha@*Ht=bd+44C6h_ZXH2(MTk{L#lhxpsO)r}!l4OL1h2$!w z)VP>fYD{o&5MP)U6f{wuE9Ubv()sKO69Ra=bS@9jjg!a)v?OXoWO!kLDpMv%NQ@;% zgpo-8YE`~S%ClNf20O`&8oj(c8H^MUo*Rp2yiho?|xpuYdN>4}krHd<9ELAG=bXtw4muE~&bWBW?yN7#SjZLADM@Ev# zF96oUD=y$D0NjE_axnV`>V(6LG_wm$tP-9(ra%qYE=C8MYLvKkYdJLp5 z`TTm5oByG_W1Q5+UvD;k(AqhAExK_)zAX5>df~%|aaZpQwhpQLV}DHSSAS7l#9F_7 zIb#ax>yk*Ekk0nk2TNZNCAw)p1rmEY_S!4I zb9Y>)cuF9AfWtw>hi=>E2Osui;P3%kLI;}aNLQX_o;=`y7S`SJm~eKmH+b+N>VSHU z!MAQ5=!j~0blm>zYGnV9v&3IsN=bo3pWI&|YVJBwOPw`bcB#qj%x{ybx856Y%$`fS zbG*Vb*w=jI+XjpE!IN+4Tx^|w`IoKlpDjH#v*+ShJ2=~?wQPJ8s92XTen7_6-~F)oPmSRd<Kf59K)gHJu|JA<`)4Tdv4J*%f z-}tF^G5u2C!O)V{`v-TH?f$}Xa3@)^#`JI(^wDM<4aW>RrCE$GjVOV(;ZhU?7{ZmD zfFdL}eas~A5At*OvrgGHpHrkA+fP7!rFW#Z6w*v3J|N_CiO0Xr|E0+?bJZVvzHIF% zd$M4-0IxiCI!@v^Hb`VaLHz(?c)w$AJv`XbTYS0To@ZmyWXI!^Q*PAV8|tsGBF$I} zoxUJ}Lib3#SZtP~Cze2+BsmZwar;{xt`A3`rfymG&E_=1Y9h|FXG=&S4t6a&SwS9D s+Vqd^<~@CH7ek8wrh7I!^wQJ7;rmnF7{TouozpTeTP<(Qn*H&A03h|pS^xk5 literal 0 HcmV?d00001 diff --git a/res/sgb-icon-16.png b/res/sgb-icon-16.png new file mode 100644 index 0000000000000000000000000000000000000000..6a76a156fc15b9f1fdf1b3483821b7f0236faf5b GIT binary patch literal 1775 zcmah~Yi!$86!z3rKu5Q+{a~8}lXm2>i_jd7(032h zGMmlXv$b|bOOlSp#zvB&Nt(7HhE4C0A!pcRz1)<@X?TIoYoZE8MaE4{u2acC7eOE# zUl14gytbOrBuiP~Ng#nVkfBaG><)4{9~k0NG_t;sE|fUZL=c@SphGdS2Hiwf(AAUz z6+NY>`Jf7x$ipifQMy-mF*tQKGRyuqJ)JJth7Nt*C~*siP!QAN0(z`R1!ODGm5jy% zUpLBLxs^CGFHA%j=Gqmo}nAi&qg&c zR4yKije1AmAoqWKvjcFaz%x*U60odXN4O=JvvQ^+nr*;HoSbwKhD`toE+avrNl$h< zNwZ@lalzTL7QLNhfwU8a#%mb^i%A)1xWnFH&*ePtlT87nw{{xI<(pXy&PlEa zKezW~(*ybC-l3_4#v7TRZ^+0`NFbxJsG<4yyMKEa&CUanP|O#LK2lx1v*XctWo1Pz z?eMFiUAw9&5{(JFtp+{rY<54WYf4K?=-Tv^`T6p$pF zK6>=kFV3Gm@$NgFd`GCofA#XkvD2qce{>RR>eSTa^z_ukg|9vwIX*r4-3RX<9~?Y< zW^8n3>hjRgz$YJ%zB2k_9|~q8Yz;@>n;0JG!!8U44ptqxbmL6#GjAj0xuDk_+xES0 zpnXd}wYj*w;^(`Is;V9s+dp6Q)Wp5B$KHf{XI`Q!rmsBl0+bHzxq9gJ^}{vnkbkP8 z`nRF#J-v_J`g?BpRD}5D@s64FUt74XKas3{^PBr_-g6>wq^u^8sJ!tcefSP49G`nu z*w=KiG(Ok&$BwV#TpRTodu_+$f$^fgx4zs|c5CCef9{_}A14e8`oi9k=6(JD0*;7C A^#A|> literal 0 HcmV?d00001 diff --git a/res/sgb-icon-24.png b/res/sgb-icon-24.png new file mode 100644 index 0000000000000000000000000000000000000000..5b8bd7a0d13062bb76735d69c106df77b896a3e3 GIT binary patch literal 1987 zcmah}Yfuwc6kfGdj8Luh2epo_OUtM=*(3;sH9XMi5|%mI=VMtk_L0tETd4Y-WK%7bT#9AdpBj8e~Hv z5!GXHy9C_J$2f+@DN?l_^Vl7zkrM!#qsmdKu>u9E6KEgdG|lc)hLRo&ND@!rcq9^0 zMY2_#;Kw!j`T4k7i)*z?$WV$kA&HDCLt>_EB4NV>L`q(nC{1`L`nW}WV`*p>A~QjFht3`6eh0k5eE76-~!QI!vow2L@q2) zz`PV@FEgGvIWL5Ri2~MRVqj85_!@dlJN+TB{0X3uV>!VEixB8BO{0{@NXKYw5h0+iMW6}28^7LbR>=i96Lb*WV=E@%nCEtOFduQip=v;PO zs~@r&jShkHF)YO77DTOPW3DDYH&>}uB?5tuMnGN0S7Rb2$kM8{*-EumsmXC`atUo7 zq0uVUd4xLQPn->VhbATR|M+qTpgNi&Bt~LETuO9=ft32HfTXO9tN-Z#)!@mlHu!@@wcZ zUf}331?YJGpx+5pE#fNE5EszlC{I*|a7OY4bg z#0LiRz&RcMi2I4y2TTvBmpFq{363{BIp3g=oRA;{$D#n|UrW33Q#d6$-{@fYHvH<)zw9L ztE_h8#S0x(-iiR@{e0KKvd+Pc4C z$F{Sv*!FE(Det0+3eWrRts<)yE_`(Xzhp7TFLt_Z&Qg1A?TXskm4^=OKlt@Nr_0v7 zx2eQ6w|?E4`ua8RX5B1;j%G;CV%O+*>bj?OA*J{4rr)_+Ij-mB?(sKo3|sN@flXr$W8)~y|5 z8hSJ`$_(AKrFtgG?*Rl0fox}Dcnl%%b$X_|Agbkfb; z8SeI-*ILs5_$$3`N=DC>3zsI$n{oZh?U5%s&oOtxJsrR5hVQ>O;>z^18?%;o&n`bd zv}*Cal62+pj&VO-sylk?Nb@Jhst(^eA(Rb0y`jo5W2sSM0=*+9!mz~i9 P14pdpV$+_&syF@x4l1WD literal 0 HcmV?d00001 diff --git a/res/sgb-icon-256.png b/res/sgb-icon-256.png new file mode 100644 index 0000000000000000000000000000000000000000..6e3132c5755cadc66a55c7ce13a50604956c469c GIT binary patch literal 3209 zcmai030M=?7QP7~KmrnRX)91-+^uAlEha1ivL#em1fMJk$s|k(nV1YFfJoF@rS(~N zD}tqnRiN?#MX)Rq7fP`TE`=_DP_6K^Mclv*6^bMHAbS<6EM zY)CUm005hyK))~ma99@yEX}Yc>AUC%tRcvKggyY2eMue?n;TJ0Sin+H((G^x8(66V zS7`tsPci;*AUo%M0L(vzBcjo0p@1tvlIdb8k^s?l$tuhm0G^jlC6**XC^Z3+!%9A_ zzowo>g{6F2l#7rlRQW=QaA2Am3Qr4-kfbF^Jft))PZCeZ#R!riR7}++E0h|pj!!f3 za}7py~Esy zykV|NL{LPNh^WRzy)wl({DvbI?zg+>#HzO$3x)sNJURK5WoS_UwOHWBHsKXbFDB4L zq^clB7^Fc`)Dp;lEf&35rob8F!d(vQAVstvj0IJL?FOI5c5!_TUG*;17f~SUaO@aD zd>YH-WQ-j*C|N9rqF^bS_>%1|37=SPl8evD3t- z`N(82wr{CaEU#g?5>iuHba(n_%*TDSiIDMly)%rAiHmt5j>mXI{^;5#R1nN>^chx} zuoA}@uW{I5yh=h!tTd{z8W_0I7QyQ0gXMw<{{WT1R7}VIMn*<{YrAY|X&D+8Kq8Uw zW_TMLYfDQDD=SM23kxEVNH8bh@i;P>JZaJ-GlH45HJL)O#V+d14;>xt1t0s*n?L7r z%g;=fb9w1^t{yCo3thM(K&R7wv}pdc=~L|NCa0ilynUAh3YPM`+_+r##)i|swqEjE z=Dl~%P9BdpYc@3`IHXlTg8-A%hz^KC&M(Sq2~ z(IJYDo!_&Gh%J|ZhJ}WMjo0vb`>9RXo2DYj&nH6nM_*^s>U@C%@oohR?|;`iU)Oel zmqK?g?hHNH=h-!Km8XY(Se?})@a=fij@})qk>;44`(wsNs-^M1(3D0EJbNV=1Gqu$EUCE1)e0yc&W z-t8jlF?r;nb9IaRY=GO|v8{PW(~al_b9V=R+E)-(sN>N;FJDAnI@>O5*|dfb;MDrf zF{eA$4GFt)rR>6@qI|!@mjnGMgjLh)PH-Nscg)`!{u--+gqu3BLpZ#p=*%4bK;w0* zluc*V>#rSs%mwL9)Q_)77Me>yL&Y{r+LJe#wiNj`6WvQp`3zb7iG z^!s#0l+!JjkT##$8{+j@r;)!mr`<}>%BMAq@D^4jt=x3KAniGBF0r{f{5aq9>5~K3 zk^b#fx%i6CH)#LYmOnfD*bP-&`Lw3E&f)>mYK}17%+S2@o2V~-cll4d z+l^*rbL`FD0I%)eKdH#5khUhuM&Pz3U~T!u-~o7Mc8+IE>yn;7`wkxUFLBWoc09kG z(_??c?O*|sS(8>$)Ku_&g~JyOF`dkNu7 zN{hQ$g7Ykzl`Bgp)#hGlTUm3tgVFP#VOVy!Zf|UN-OwP899g!mJO?k`TJ+vAdnvg( zm>q*hE024ARorHHFWlhrS$q8CGgozC@V4e&r(83A)&@>V&bAHMrO|TN=9H>oI$DZh#y2j&NI$ssKWQt|qCyS#KBR6~m zu!;-%xe_nwU`TUe{{s@d~ca5D~kHE7ej#q7bo5)QY}cFVx-6}@-XAeYV` z&xaAf)7mbc-cm;>HGVUit^h#pBY!oJ6x&UEn$jsf#n`b#vwfDDB0s-wpQMIST0FU| zR!g}~vq*<_Ce|!^rq5Ycow?NEW_d`3gRh=*Wq8{4h4s~c9dgtMo%5R6tX?o>_Xpm& zdwe^N2!6Z(N}4{*1nC7W*530hhHws)+z<5BE$-VEm6}mf36yaWFT{X|N9K<$@@g3d zFK{yh_wY@FF-v_5I9VGQEV8(p%-F4|VHid>@7ja;)<^8Se+p9)a_x5gRLMW|eS^a{ z?>E~_iOH~t?qdG=Y_;rUHGKYwmhi}<=aEhJ4NpeZ#ev4T${fNdaVem<+X9vG<9h7M zeuf`1Ti5i!T!(+WtCyT-`vNe`13!E-lTjNpC#X2RRUno8q(hi;Nuj%6u+q^o!M{qWq&8jhAkzDD4MJXyJzS#TDPB`>b+9?82{S}>9^o#K9o%} zJdAz8kF_DVlU?t1JL;dF0{!5ZN9LC-CV*I%T+kKYS=RKsoh?BgS8>eAn)2B5@p!G*V9tC) literal 0 HcmV?d00001 diff --git a/res/sgb-icon-32.png b/res/sgb-icon-32.png new file mode 100644 index 0000000000000000000000000000000000000000..ea28b979db798f2ecf9f76fc521d2a3f123280af GIT binary patch literal 1948 zcmah}eQXnD7{AF_Mj507<`y&K2MH>2V48uPx&ks#DaJBvw!RP?snJwA5T`wqOneYM0p%;iu*gIYW!YBC0A8I3AD3 z&G8bmD2H)td3iZ*vEeqG2{KGdtDrIolb{r7AyP3Ypaf-3QaMpTwU|tk7*k0MgE%@A zoDHU9OEH<(BeOvq@E{5VRl%)hD?XAB65OyFK^aOHOdPHukZu`8SGmwr=o<1EU5bdR zs6<35Eo#&jY4|ZmnC>ULBpB&QWSahOdNexf7>Zii0uwiM38OSU+&~GmN&xo*MU2To zP}u^rSEMIS%M0P-5`eF#IG9ug-Uf-4*d75Jo&vf=UX*KL5dspk>ONZL2zQhTgF22? zBk5Sf>mDG49|bgJd`v5a#R3P7F;QFo=Q%vxSnX0n-62Z5JHKwmD@N--*A*(|malf`DTmIkb4gl#r4 ztK4LnLs(LJbT;fAmQk7i<7*v&+SwqXaw-pWE7cKBUQJmkljpQHAb3UylUTyUf)Eqq zRm`D;o9wvOF+4ZqtXmJg?f9tBb|{UeiE2(!MY(mvZndC!^lp0Np{QO?1QXP5gTy3R zWMe_V>iL7ZQ>Y?_)HovpXBbuzi8(_d4&GZU3VWIh3qVG#<~in6&eJ~e2!QdPo<@A+ zVIGRJF zg8cm4Tw{%w&NUkI@}|JW{QPGN3gBYyq)C$?Iyg9Z?aJ3XcWmj|x%K$*W2L3DX3l)^ zqy2mPjvVRTx995Riyt5CZEoflcq-i<%ImLcTwVvo_!rYxE?ro==Jmse4s7Y(bmZ{C z{!_?w#wG zi~o7=`rU!w?JNF($SpVJ49q#Ze?xfkDof64XTVt1ZV)p5`mycS;NYSw?xJSt=X=|) zP9M0zC3mCur!Ma7k>0J{H1^U`@A_{YRQC2azPVv+8rP6Zz3_dp<@B^)CJ!>%kaC^WFC8Yo5srA#2+#cN+T)$1>iTwi?OGdF`dYLwyga9w>r}2ZIORw$;+=w6@|{kLlrbMoa%q5X7U+%x3rf?|a|>{_p+o z73s9fAu(fPAP5?wR>}3?&10Xaf#84M_tE>oYmiN%kw8#=>ENg4aA1dd^hzmI+Y#3V zjA)lC*8@R=-(sIIXyc|a5EPzG8cmEzlY?1k2iJ_#c?8$zZ~-&~2{U{yvn8Kk;5@=c zIz`CCrlSZ<;v!^rvWBN|$p|~C3b+YFKx?!F@-68&k|B-}`Y=G?AQ&_3b5KqX<`W@7 zUJQJ*%P0Z|AxypqF==$LjCK=nGB=saL&Pz#(2ZL$y*#TY7+502&M+FMbxPk;&p9Dv|>{7%N~<2XI>EJcVzPIxSC(#4Rp6J|Nhd9;@iAqeopJ>v;w4veOLh zvD2>Jr21{q>wYB=$o={dpV{>~utxLWaEGJcF+7Z70T8aI2>o(;zJbT+cM+(b@X%hj zg-|R2>ct0zV|BrFq>rFXauSH@0o4#8)6!mY=Dxuxqbb^L0GE&uA^czrs~o0wm~F&t z5@+naTz%^~gwyu2gUyWcvRC1KCkf6X=;Jc=`9w&n09-=$f&}SuU+e0v0~X9qL*pR1 zq+nE^OD&V>+_aUXfRAlKSgnwy@Y7RLI0A0yAfRX%h$XFlxtRed0Z)*`;R!hWWFtQX z6Qp8E0uCYQJBA{Af`iucXd&8Xp@7HW#i~`s~@W&d$#5HCswbN?MLI zmVQ#K(`lOy?K|A~^{T>UJVD~h!lhi^yID$U!0%PhP^i=jrAoSPZK)zls?3&1Yov0Oc=2NYnf8<7Oe|A|$>bTC^6APMnbSonsvN0QE|$tga-|p(rATCA zaRxS1pOc?&oj>2IH)`wa_Ec4VQL(wa?bPwpCy!n1XusHTX7~Qboj{ABj9zPi?!kup z+egzpRk`e;|&Eub#0mP?GPw)5+Mb*|uz%vzgrUVB_Ut|4aMcMIztG(Y)a;gRlf zBS%kK?RKax&q&YMi!YA+@jbzacML~O%AKZU|E$PUkBZGyV*Kr4ys-n8JjERu{L+aV zZW>0YMH7k)j-*kGd?)ry_C3CmTC;po;l$+uY3vT^dWmx5oZ@F4ZNJ#2NSP`1frP1D z)n)rnO4nVN+-f{~^QYhLq+MuBefR+VD_vXVjs3fMoc7dl4Ap1lZ8&%H%84@9fii7n zQ#;tUY;4Q{m31?RTmNKN@YjTUe{(dLt@k zaiYr3ROz}}hVB|w6q5iqEIoFa2}^MNF(`AxOisk0A%XGmvKz~mw8q8^JrEhUX5Q!B o;!WHq3BwPbowDP8bN8Q%2IfZ=JF;(lX#kag)C#S=I*-avOEeRwffkYCBAqx1Qgybe!NV02oAwi%9 ze2lafo$6>+>^P`JeBm3bRcN(7i?w!YTVJ(mt+qPUPJN9X?cD@He6&CIZqA~aU1o))JF5|F?PSQ{_s_4pVf zs6@lO1pLi?#!+4v!j>t~0+X4iq5Oa+#>AKaO^f3x{0=8!(PnlBLraOeS(YYnJP-(A zfe9Gpci}?0T#gGwxJbl@2tHHkV{Jjck4fSvA{<)4*!?8Ul9Z3faoS3#a#o3=(9i28 zci4Nl>2kj(%aHa1%<;o4}=T)z**Ay3OMI^0ZzLq zmSWr#-IG+GEqdH91VXuA9ulmt*;gll`UYiT# zlMdG1!`1uU2z;*R9b9Ifa;_96`$%{eQ7>0PuTP0eMX(6D0twR-2$B)qpu9_(q-$q zt1gQ+h6m}(mnTo2{CM`;nY5RtJf1RlC^S3Ak|mQ#WKu~@b@dP5pQb3^g86eEKYqM> zSL=-Fh1**~N`~Rd8$NBq1mlcGy;i3# zESyaFywj%@TCL{HOs!Nd%AT0je(*DiT%=Md^YYB;=?XrUJf5H2vSD4r!unl1w$7as z%FfoSbqZWCE;UU`xgc@bkSb&1>Cmhd?;qVA7++xWEpv2)Qw&dm3R99A4RedmiU4JOsK-Q9zhlDQGywTBcu=C#NQxBlF zjRvjST5#z0^4}`zR72;97reiwDM5Vi(wVBR1NSdIdX%m=sLME$&{Wbzev{Fuv=jx4?V zW^qCX|KxxH4}Oh3vRQni{;zYtRHyXs_xIor<8yIoCeCkYxEmirgv-Ep%<&cTj@N$? zYs#83rLCf3$saqBN5!q`)HtZjoAZu;d>mPcxZYX^-nOAJNJRPYiMv7uP$}&_JW8Oh-W*j}+p+at` zuL!WEwTnb&Vve1BtN-jfRUHVpjt@@=r6<1kQ9pcR*~rp;8}!$Nh_HF)$I>wmU(HL_eDo(ZU&-}4#kc*~7e!s=-rSqfB*qSI+;<@D+N290SknT=BJ@qOXSbPc# hHLg3<&{cbWQ*KjK%Yo`~$=tsb#GuR3Zp|pJ`xg*X`fmUL literal 0 HcmV?d00001 diff --git a/res/sgb-icon.svg b/res/sgb-icon.svg new file mode 100644 index 000000000..ca922f1aa --- /dev/null +++ b/res/sgb-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file From 1ca6f7e0939830275876e55895ebd0da0c67e15c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 Feb 2023 19:52:33 -0800 Subject: [PATCH 058/290] Scripting: Add WSAEWOULDBLOCK to error translation table --- src/script/socket.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/script/socket.c b/src/script/socket.c index 818c758f6..5b2e95b60 100644 --- a/src/script/socket.c +++ b/src/script/socket.c @@ -36,6 +36,7 @@ static const struct _mScriptSocketErrorMapping { #ifndef USE_GETHOSTBYNAME #ifdef _WIN32 { WSATRY_AGAIN, mSCRIPT_SOCKERR_AGAIN }, + { WSAEWOULDBLOCK, mSCRIPT_SOCKERR_AGAIN }, { WSANO_RECOVERY, mSCRIPT_SOCKERR_FAILED }, { WSANO_DATA, mSCRIPT_SOCKERR_NO_DATA }, { WSAHOST_NOT_FOUND, mSCRIPT_SOCKERR_NOT_FOUND }, From e07684e3acc492c9ab396f1bd95831f5148d6247 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 Feb 2023 20:23:44 -0800 Subject: [PATCH 059/290] CHANGES: Update --- CHANGES | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index a34b063f9..6a2bd8652 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,8 @@ Other fixes: - Qt: Disable attempted linking betwen incompatible platforms (fixes mgba.io/i/2702) - Qt: Fix modifier key names in shortcut editor (fixes mgba.io/i/2817) - Qt: Fix a handful of edge cases with graphics viewers (fixes mgba.io/i/2827) + - Scripting: Fix receiving packets for client sockets + - Scripting: Fix empty receive calls returning unknown error on Windows Misc: - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs @@ -50,7 +52,6 @@ Other fixes: - Qt: Fix initializing update revision info - Qt: Redo stable branch detection heuristic (fixes mgba.io/i/2679) - Res: Fix species name location in Ruby/Sapphire revs 1/2 (fixes mgba.io/i/2685) - - Scripting: Fix receiving packets for client sockets - VFS: Fix minizip write returning 0 on success instead of size Misc: - macOS: Add category to plist (closes mgba.io/i/2691) From 47941aa0b036b302d05dae49fe60e54fb300c26d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 24 Feb 2023 03:51:07 -0800 Subject: [PATCH 060/290] Qt: Automatically change video file extension as appropriate --- CHANGES | 1 + src/platform/qt/VideoView.cpp | 47 +++++++++++++++++++++++++++++++++++ src/platform/qt/VideoView.h | 4 +++ 3 files changed, 52 insertions(+) diff --git a/CHANGES b/CHANGES index 6a2bd8652..0e14f021b 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,7 @@ Misc: - GBA: Improve detection of valid ELF ROMs - Qt: Include wayland QPA in AppImage (fixes mgba.io/i/2796) - Qt: Stop eating boolean action key events (fixes mgba.io/i/2636) + - Qt: Automatically change video file extension as appropriate - Scripting: Add `callbacks:oneshot` for single-call callbacks 0.10.1: (2023-01-10) diff --git a/src/platform/qt/VideoView.cpp b/src/platform/qt/VideoView.cpp index 3b6382275..b03949c2f 100644 --- a/src/platform/qt/VideoView.cpp +++ b/src/platform/qt/VideoView.cpp @@ -20,6 +20,7 @@ using namespace QGBA; QMap VideoView::s_acodecMap; QMap VideoView::s_vcodecMap; QMap VideoView::s_containerMap; +QMap VideoView::s_extensionMap; bool VideoView::Preset::compatible(const Preset& other) const { if (!other.container.isNull() && !container.isNull() && other.container != container) { @@ -71,6 +72,23 @@ VideoView::VideoView(QWidget* parent) if (s_containerMap.empty()) { s_containerMap["mkv"] = "matroska"; } + if (s_extensionMap.empty()) { + s_extensionMap["matroska"] += ".mkv"; + s_extensionMap["matroska"] += ".mka"; + s_extensionMap["webm"] += ".webm"; + s_extensionMap["avi"] += ".avi"; + s_extensionMap["mp4"] += ".mp4"; + s_extensionMap["mp4"] += ".m4v"; + s_extensionMap["mp4"] += ".m4a"; + + s_extensionMap["flac"] += ".flac"; + s_extensionMap["mpeg"] += ".mpg"; + s_extensionMap["mpeg"] += ".mpeg"; + s_extensionMap["mpegts"] += ".ts"; + s_extensionMap["mp3"] += ".mp3"; + s_extensionMap["ogg"] += ".ogg"; + s_extensionMap["ogv"] += ".ogv"; + } connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &VideoView::close); connect(m_ui.start, &QAbstractButton::clicked, this, &VideoView::startRecording); @@ -195,6 +213,9 @@ void VideoView::setController(std::shared_ptr controller) { } void VideoView::startRecording() { + if (QFileInfo(m_filename).suffix().isEmpty()) { + changeExtension(); + } if (!validateSettings()) { return; } @@ -238,6 +259,7 @@ void VideoView::selectFile() { QString filename = GBAApp::app()->getSaveFileName(this, tr("Select output file")); if (!filename.isEmpty()) { m_ui.filename->setText(filename); + changeExtension(); } } @@ -289,6 +311,7 @@ void VideoView::setContainer(const QString& container) { m_containerCstr = nullptr; m_container = QString(); } + changeExtension(); validateSettings(); uncheckIncompatible(); } @@ -458,6 +481,30 @@ void VideoView::uncheckIncompatible() { } } +void VideoView::changeExtension() { + if (m_filename.isEmpty()) { + return; + } + + if (!s_extensionMap.contains(m_container)) { + return; + } + + QStringList extensions = s_extensionMap.value(m_container); + QString filename = m_filename; + int index = m_filename.lastIndexOf("."); + if (index >= 0) { + if (extensions.contains(filename.mid(index))) { + // This extension is already valid + return; + } + filename.truncate(index); + } + filename += extensions.front(); + + m_ui.filename->setText(filename); +} + QString VideoView::sanitizeCodec(const QString& codec, const QMap& mapping) { QString sanitized = codec.toLower(); sanitized = sanitized.remove(QChar('.')); diff --git a/src/platform/qt/VideoView.h b/src/platform/qt/VideoView.h index 7c2bd99e0..23b5ef034 100644 --- a/src/platform/qt/VideoView.h +++ b/src/platform/qt/VideoView.h @@ -7,6 +7,7 @@ #ifdef USE_FFMPEG +#include #include #include @@ -62,6 +63,8 @@ private slots: void uncheckIncompatible(); void updatePresets(); + void changeExtension(); + private: struct Preset { QString container; @@ -123,6 +126,7 @@ private: static QMap s_acodecMap; static QMap s_vcodecMap; static QMap s_containerMap; + static QMap s_extensionMap; }; } From 682471fa1e5a5597214de7f303453abda7475c82 Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Fri, 24 Feb 2023 00:40:25 -0300 Subject: [PATCH 061/290] Libretro: Fix undeclared constant The `SIZE_CART_FLASH1M` constant was renamed to `GBA_SIZE_FLASH1M` in https://github.com/mgba-emu/mgba/commit/8545271e9ee275b9e733bbfa13195365b1fb82e1 These leftovers make the Libretro build fail, when running: ``` cmake -DBUILD_LIBRETRO=ON .. && make ``` --- src/platform/libretro/libretro.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 68f54f2c7..2e4b67c80 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -346,7 +346,7 @@ static void _doDeferredSetup(void) { // On the off-hand chance that a core actually expects its buffers to be populated when // you actually first get them, you're out of luck without workarounds. Yup, seriously. // Here's that workaround, but really the API needs to be thrown out and rewritten. - struct VFile* save = VFileFromMemory(savedata, SIZE_CART_FLASH1M); + struct VFile* save = VFileFromMemory(savedata, GBA_SIZE_FLASH1M); if (!core->loadSave(core, save)) { save->close(save); } @@ -930,8 +930,8 @@ bool retro_load_game(const struct retro_game_info* game) { core->setPeripheral(core, mPERIPH_RUMBLE, &rumble); core->setPeripheral(core, mPERIPH_ROTATION, &rotation); - savedata = anonymousMemoryMap(SIZE_CART_FLASH1M); - memset(savedata, 0xFF, SIZE_CART_FLASH1M); + savedata = anonymousMemoryMap(GBA_SIZE_FLASH1M); + memset(savedata, 0xFF, GBA_SIZE_FLASH1M); _reloadSettings(); core->loadROM(core, rom); @@ -1008,7 +1008,7 @@ void retro_unload_game(void) { core->deinit(core); mappedMemoryFree(data, dataSize); data = 0; - mappedMemoryFree(savedata, SIZE_CART_FLASH1M); + mappedMemoryFree(savedata, GBA_SIZE_FLASH1M); savedata = 0; } @@ -1163,7 +1163,7 @@ size_t retro_get_memory_size(unsigned id) { case mPLATFORM_GBA: switch (((struct GBA*) core->board)->memory.savedata.type) { case SAVEDATA_AUTODETECT: - return SIZE_CART_FLASH1M; + return GBA_SIZE_FLASH1M; default: return GBASavedataSize(&((struct GBA*) core->board)->memory.savedata); } @@ -1362,7 +1362,7 @@ static void _startImage(struct mImageSource* image, unsigned w, unsigned h, int static void _stopImage(struct mImageSource* image) { UNUSED(image); - cam.stop(); + cam.stop(); } static void _requestImage(struct mImageSource* image, const void** buffer, size_t* stride, enum mColorFormat* colorFormat) { From 9b2ba64bc68241da199ca9427b99d1f1547d9305 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 24 Feb 2023 22:05:48 -0800 Subject: [PATCH 062/290] Windows: Fix build clang-cl --- include/mgba-util/platform/windows/getopt.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/mgba-util/platform/windows/getopt.h b/include/mgba-util/platform/windows/getopt.h index 5ea2dbe6f..22a196ef6 100644 --- a/include/mgba-util/platform/windows/getopt.h +++ b/include/mgba-util/platform/windows/getopt.h @@ -27,6 +27,8 @@ extern "C" { #endif +struct option; + #define REPLACE_GETOPT /* use this getopt as the system getopt(3) */ #ifdef REPLACE_GETOPT From a64dcf8e43333b6429d08484f6465aa9b9a85890 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 24 Feb 2023 22:34:45 -0800 Subject: [PATCH 063/290] All: Minor warning touching up --- CMakeLists.txt | 4 ++-- src/platform/qt/ConfigController.cpp | 1 + src/platform/qt/MemorySearch.cpp | 12 +++++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 32551c602..5dc1e38c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,8 +34,8 @@ if(NOT MSVC) # mingw32 likes to complain about using the "wrong" format strings despite them actually working set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-format") endif() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration -Werror=implicit-int") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration -Werror=missing-parameter-type -Werror=implicit-int") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS} -Woverloaded-virtual") else() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146 /wd4267 /Zc:preprocessor-") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146 /wd4267 /Zc:preprocessor-") diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp index 1116dba79..73097df09 100644 --- a/src/platform/qt/ConfigController.cpp +++ b/src/platform/qt/ConfigController.cpp @@ -343,6 +343,7 @@ constexpr const char* ConfigController::mruName(ConfigController::MRU mru) { case MRU::Script: return "recentScripts"; } + Q_UNREACHABLE(); } void ConfigController::write() { diff --git a/src/platform/qt/MemorySearch.cpp b/src/platform/qt/MemorySearch.cpp index 8589f9cfe..f61fb9e52 100644 --- a/src/platform/qt/MemorySearch.cpp +++ b/src/platform/qt/MemorySearch.cpp @@ -178,7 +178,7 @@ void MemorySearch::refresh() { mCoreMemorySearchResult* result = mCoreMemorySearchResultsGetPointer(&m_results, i); QTableWidgetItem* item = new QTableWidgetItem(QString("%1").arg(result->address, 8, 16, QChar('0'))); m_ui.results->setItem(i, 0, item); - QTableWidgetItem* type; + QTableWidgetItem* type = nullptr; QByteArray string; if (result->type == mCORE_MEMORY_SEARCH_INT && m_ui.numHex->isChecked()) { switch (result->width) { @@ -213,7 +213,12 @@ void MemorySearch::refresh() { string.append(core->rawRead8(core, result->address + i, result->segment)); } item = new QTableWidgetItem(QLatin1String(string)); // TODO + break; + case mCORE_MEMORY_SEARCH_GUESS: + item = nullptr; + break; } + Q_ASSERT(item); } QString divisor; if (result->guessDivisor > 1) { @@ -231,7 +236,12 @@ void MemorySearch::refresh() { break; case mCORE_MEMORY_SEARCH_STRING: type = new QTableWidgetItem("string"); + break; + case mCORE_MEMORY_SEARCH_GUESS: + break; } + Q_ASSERT(type); + m_ui.results->setItem(i, 1, item); m_ui.results->setItem(i, 2, type); m_ui.opDelta->setEnabled(true); From 5b722311669f328fa017c7be3f817917225551f4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 28 Feb 2023 18:11:21 -0800 Subject: [PATCH 064/290] GBA Overrides: Mark ASL* as no save (fixes #2843) --- src/gba/overrides.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gba/overrides.c b/src/gba/overrides.c index e15db50d8..ee286cfe0 100644 --- a/src/gba/overrides.c +++ b/src/gba/overrides.c @@ -159,6 +159,10 @@ static const struct GBACartridgeOverride _overrides[] = { // Shin Bokura no Taiyou: Gyakushuu no Sabata { "U33J", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE, false }, + // Stuart Little 2 + { "ASLE", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE, false }, + { "ASLF", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE, false }, + // Super Mario Advance 2 { "AA2J", SAVEDATA_EEPROM, HW_NONE, 0x800052E, false }, { "AA2E", SAVEDATA_EEPROM, HW_NONE, 0x800052E, false }, From 5f3ee83bcc23e7ca7f3e1ce85cbdc4cf6f027f69 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 28 Feb 2023 18:20:16 -0800 Subject: [PATCH 065/290] CMake: Fix configure issues on clang --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5dc1e38c0..ac498d0d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ if(NOT MSVC) # mingw32 likes to complain about using the "wrong" format strings despite them actually working set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-format") endif() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration -Werror=missing-parameter-type -Werror=implicit-int") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration -Werror=implicit-int") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS} -Woverloaded-virtual") else() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146 /wd4267 /Zc:preprocessor-") From 10a31656429b3639a98817fbc1439a19ee754cd8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 15:52:24 -0800 Subject: [PATCH 066/290] Qt: auto -> auto& cleanup --- src/platform/qt/CoreController.cpp | 2 +- src/platform/qt/InputController.cpp | 30 ++++++++++++++--------------- src/platform/qt/SettingsView.cpp | 4 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 37fc1b01f..1447fedba 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -80,7 +80,7 @@ CoreController::CoreController(mCore* core, QObject* parent) m_threadContext.resetCallback = [](mCoreThread* context) { CoreController* controller = static_cast(context->userData); - for (auto action : controller->m_resetActions) { + for (auto& action : controller->m_resetActions) { action(); } diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index d8b5dd783..3af600a64 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -151,7 +151,7 @@ bool InputController::loadConfiguration(uint32_t type) { if (!mInputMapLoad(&m_inputMap, type, m_config->input())) { return false; } - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver) { return false; } @@ -185,7 +185,7 @@ void InputController::saveConfiguration() { void InputController::saveConfiguration(uint32_t type) { mInputMapSave(&m_inputMap, type, m_config->input()); - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (driver) { driver->saveConfiguration(m_config); } @@ -201,7 +201,7 @@ void InputController::saveProfile(uint32_t type, const QString& profile) { } QString InputController::profileForType(uint32_t type) { - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver) { return {}; } @@ -209,7 +209,7 @@ QString InputController::profileForType(uint32_t type) { } void InputController::setGamepadDriver(uint32_t type) { - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver || !driver->supportsGamepads()) { return; } @@ -220,13 +220,13 @@ QStringList InputController::connectedGamepads(uint32_t type) const { if (!type) { type = m_gamepadDriver; } - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver) { return {}; } QStringList pads; - for (auto pad : driver->connectedGamepads()) { + for (auto& pad : driver->connectedGamepads()) { pads.append(pad->visibleName()); } return pads; @@ -236,7 +236,7 @@ int InputController::gamepadIndex(uint32_t type) const { if (!type) { type = m_gamepadDriver; } - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver) { return -1; } @@ -247,7 +247,7 @@ void InputController::setGamepad(uint32_t type, int index) { if (!type) { type = m_gamepadDriver; } - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver) { return; } @@ -265,7 +265,7 @@ void InputController::setPreferredGamepad(uint32_t type, int index) { if (!type) { type = m_gamepadDriver; } - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver) { return; } @@ -299,7 +299,7 @@ InputMapper InputController::mapper(InputSource* source) { } void InputController::setSensorDriver(uint32_t type) { - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver || !driver->supportsSensors()) { return; } @@ -308,7 +308,7 @@ void InputController::setSensorDriver(uint32_t type) { mRumble* InputController::rumble() { - auto driver = m_inputDrivers.value(m_sensorDriver); + auto& driver = m_inputDrivers.value(m_sensorDriver); if (driver) { return driver->rumble(); } @@ -316,7 +316,7 @@ mRumble* InputController::rumble() { } mRotationSource* InputController::rotationSource() { - auto driver = m_inputDrivers.value(m_sensorDriver); + auto& driver = m_inputDrivers.value(m_sensorDriver); if (driver) { return driver->rotationSource(); } @@ -341,7 +341,7 @@ void InputController::update() { int InputController::pollEvents() { int activeButtons = 0; - for (auto pad : gamepads()) { + for (auto& pad : gamepads()) { InputMapper im(mapper(pad)); activeButtons |= im.mapKeys(pad->currentButtons()); activeButtons |= im.mapAxes(pad->currentAxes()); @@ -356,7 +356,7 @@ int InputController::pollEvents() { } Gamepad* InputController::gamepad(uint32_t type) { - auto driver = m_inputDrivers.value(type); + auto& driver = m_inputDrivers.value(type); if (!driver) { return nullptr; } @@ -464,7 +464,7 @@ void InputController::testGamepad(uint32_t type) { } } } - for (auto axis : oldAxes) { + for (auto& axis : oldAxes) { GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, false, type, this); clearPendingEvent(event->platformKey()); sendGamepadEvent(event); diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 4eca3cb46..acf272cf6 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -51,7 +51,7 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC #ifdef M_CORE_GB m_pageIndex[Page::GB] = 9; - for (auto model : GameBoy::modelList()) { + for (auto& model : GameBoy::modelList()) { m_ui.gbModel->addItem(GameBoy::modelName(model), model); m_ui.sgbModel->addItem(GameBoy::modelName(model), model); m_ui.cgbModel->addItem(GameBoy::modelName(model), model); @@ -350,7 +350,7 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC QLocale englishLocale("en"); m_ui.languages->addItem(englishLocale.nativeLanguageName(), englishLocale); QDir ts(":/translations/"); - for (auto name : ts.entryList()) { + for (auto& name : ts.entryList()) { if (!name.endsWith(".qm") || !name.startsWith(binaryName)) { continue; } From ee21eed29c4acec6c58b9d1c9f06a3b3ac37c2c5 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 15:59:46 -0800 Subject: [PATCH 067/290] Qt: Fix full-buffer rewind --- CHANGES | 1 + src/platform/qt/CoreController.cpp | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 0e14f021b..d0bbd604b 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,7 @@ Other fixes: - Qt: Disable attempted linking betwen incompatible platforms (fixes mgba.io/i/2702) - Qt: Fix modifier key names in shortcut editor (fixes mgba.io/i/2817) - Qt: Fix a handful of edge cases with graphics viewers (fixes mgba.io/i/2827) + - Qt: Fix full-buffer rewind - Scripting: Fix receiving packets for client sockets - Scripting: Fix empty receive calls returning unknown error on Windows Misc: diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 1447fedba..07902facc 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -518,9 +518,6 @@ void CoreController::setRewinding(bool rewind) { } void CoreController::rewind(int states) { - if (!states) { - return; - } if (!m_threadContext.core->opts.rewindEnable) { emit statusPosted(tr("Rewinding not currently enabled")); } From 2cce155173beaf92c5269278b65e427ed4624423 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 16:02:28 -0800 Subject: [PATCH 068/290] GBA Savedata: Fix sanity check in Load --- src/gba/savedata.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gba/savedata.c b/src/gba/savedata.c index 455ee0802..7097e4a63 100644 --- a/src/gba/savedata.c +++ b/src/gba/savedata.c @@ -184,7 +184,7 @@ size_t GBASavedataSize(const struct GBASavedata* savedata) { bool GBASavedataLoad(struct GBASavedata* savedata, struct VFile* in) { if (savedata->data) { - if (!in && savedata->type != SAVEDATA_FORCE_NONE) { + if (!in || savedata->type != SAVEDATA_FORCE_NONE) { return false; } ssize_t size = GBASavedataSize(savedata); From 7ffa0ff2809b21019e859df4e28e7a89ee92ac00 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 16:08:56 -0800 Subject: [PATCH 069/290] OpenGL: Fix memory leak in failure path --- src/platform/opengl/gles2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 88ac000a5..ffbac5a04 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -1053,6 +1053,7 @@ bool mGLES2ShaderLoad(struct VideoShader* shader, struct VDir* dir) { for (n = 0; n < inShaders; ++n) { mGLES2ShaderDeinit(&shaderBlock[n]); } + free(shaderBlock); } } } From e504ac36654df863c9019b3811be8eee4a15db51 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 16:10:43 -0800 Subject: [PATCH 070/290] Qt: Fix crash if loading a shader fails --- CHANGES | 1 + src/platform/qt/DisplayGL.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index d0bbd604b..ffda80576 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,7 @@ Other fixes: - Qt: Fix modifier key names in shortcut editor (fixes mgba.io/i/2817) - Qt: Fix a handful of edge cases with graphics viewers (fixes mgba.io/i/2827) - Qt: Fix full-buffer rewind + - Qt: Fix crash if loading a shader fails - Scripting: Fix receiving packets for client sockets - Scripting: Fix empty receive calls returning unknown error on Windows Misc: diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 469b7e315..95549b0c8 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -915,8 +915,9 @@ void PainterGL::setShaders(struct VDir* dir) { mGLES2ShaderDetach(reinterpret_cast(m_backend)); mGLES2ShaderFree(&m_shader); } - mGLES2ShaderLoad(&m_shader, dir); - mGLES2ShaderAttach(reinterpret_cast(m_backend), static_cast(m_shader.passes), m_shader.nPasses); + if (mGLES2ShaderLoad(&m_shader, dir)) { + mGLES2ShaderAttach(reinterpret_cast(m_backend), static_cast(m_shader.passes), m_shader.nPasses); + } if (!m_started) { m_gl->doneCurrent(); From 622a6e9e2d4cd1eea336aa14edeeafa793e4ec3e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 16:13:35 -0800 Subject: [PATCH 071/290] GB Memory: Fix potential crash when directly accessing invalid SRAM --- src/gb/memory.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/gb/memory.c b/src/gb/memory.c index 5c4d97876..e6cdbdc54 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -474,13 +474,14 @@ uint8_t GBView8(struct SM83Core* cpu, uint16_t address, int segment) { if (memory->rtcAccess) { return memory->rtcRegs[memory->activeRtcReg]; } else if (memory->sramAccess) { - if (segment < 0 && memory->sram) { - return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; - } else if ((size_t) segment * GB_SIZE_EXTERNAL_RAM < gb->sramSize) { - return memory->sram[(address & (GB_SIZE_EXTERNAL_RAM - 1)) + segment *GB_SIZE_EXTERNAL_RAM]; - } else { - return 0xFF; + if (memory->sram) { + if (segment < 0) { + return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; + } else if ((size_t) segment * GB_SIZE_EXTERNAL_RAM < gb->sramSize) { + return memory->sram[(address & (GB_SIZE_EXTERNAL_RAM - 1)) + segment *GB_SIZE_EXTERNAL_RAM]; + } } + return 0xFF; } else if (memory->mbcRead) { return memory->mbcRead(memory, address); } else if (memory->mbcType == GB_HuC3) { From e3983d333024b2a72b4fbf6a90dbd799cba5fa30 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 16:16:51 -0800 Subject: [PATCH 072/290] Core: Add missing va_end --- src/core/log.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/log.c b/src/core/log.c index 2d90fe05e..b7feca30c 100644 --- a/src/core/log.c +++ b/src/core/log.c @@ -88,6 +88,7 @@ void mLogExplicit(struct mLogger* context, int category, enum mLogLevel level, c if (!context->filter || mLogFilterTest(context->filter, category, level)) { context->log(context, category, level, format, args); } + va_end(args); } void mLogFilterInit(struct mLogFilter* filter) { From 064d6ce1837461ae3ea6b233868bf3289e6357f2 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 20:11:26 -0800 Subject: [PATCH 073/290] GB: Fix potential double-free of non-pristine ROM memory --- src/gb/gb.c | 4 +++- src/gb/memory.c | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/gb/gb.c b/src/gb/gb.c index a3a870f28..6e3b92b37 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -405,7 +405,9 @@ void GBUnloadROM(struct GB* gb) { if (gb->romVf) { #ifndef FIXED_ROM_BUFFER - gb->romVf->unmap(gb->romVf, gb->memory.rom, gb->pristineRomSize); + if (gb->isPristine && gb->memory.rom) { + gb->romVf->unmap(gb->romVf, gb->memory.rom, gb->pristineRomSize); + } #endif gb->romVf->close(gb->romVf); gb->romVf = NULL; diff --git a/src/gb/memory.c b/src/gb/memory.c index e6cdbdc54..325761c1c 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -14,6 +14,7 @@ #include #include +#include mLOG_DEFINE_CATEGORY(GB_MEM, "GB Memory", "gb.memory"); @@ -1006,6 +1007,11 @@ void _pristineCow(struct GB* gb) { if (gb->memory.rom == gb->memory.romBase) { gb->memory.romBase = newRom; } + if (gb->romVf) { + gb->romVf->unmap(gb->romVf, gb->memory.rom, gb->memory.romSize); + gb->romVf->close(gb->romVf); + gb->romVf = NULL; + } gb->memory.rom = newRom; GBMBCSwitchBank(gb, gb->memory.currentBank); gb->isPristine = false; From bba57ce5307f7824e48c24893ca1176dce07a5e6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 20:16:40 -0800 Subject: [PATCH 074/290] All: Fix handling of strncat bounds --- src/core/config.c | 6 +++--- src/feature/updater-main.c | 2 +- src/gb/core.c | 6 +++--- src/gba/core.c | 2 +- src/script/storage.c | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/core/config.c b/src/core/config.c index 6bfcb8e8b..715628f0f 100644 --- a/src/core/config.c +++ b/src/core/config.c @@ -169,14 +169,14 @@ void mCoreConfigDeinit(struct mCoreConfig* config) { #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 bool mCoreConfigLoad(struct mCoreConfig* config) { - char path[PATH_MAX]; + char path[PATH_MAX + 1]; mCoreConfigDirectory(path, PATH_MAX); strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path)); return mCoreConfigLoadPath(config, path); } bool mCoreConfigSave(const struct mCoreConfig* config) { - char path[PATH_MAX]; + char path[PATH_MAX + 1]; mCoreConfigDirectory(path, PATH_MAX); strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path)); return mCoreConfigSavePath(config, path); @@ -304,7 +304,7 @@ void mCoreConfigPortablePath(char* out, size_t outLength) { CFRelease(suburl); } #endif - strncat(out, PATH_SEP "portable.ini", outLength - strlen(out)); + strncat(out, PATH_SEP "portable.ini", outLength - strlen(out) - 1); #endif } diff --git a/src/feature/updater-main.c b/src/feature/updater-main.c index bc29e5cd4..cd72fc4f3 100644 --- a/src/feature/updater-main.c +++ b/src/feature/updater-main.c @@ -114,7 +114,7 @@ int main(int argc, char* argv[]) { int ok = 1; mCoreConfigDirectory(bin, sizeof(bin)); - strncat(bin, "/updater.log", sizeof(bin)); + strncat(bin, "/updater.log", sizeof(bin) - 1); logfile = fopen(bin, "w"); mCoreConfigInit(&config, "updater"); diff --git a/src/gb/core.c b/src/gb/core.c index 7edb1a607..59fb7c016 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -607,16 +607,16 @@ static void _GBCoreReset(struct mCore* core) { switch (gb->model) { case GB_MODEL_DMG: case GB_MODEL_MGB: // TODO - strncat(path, PATH_SEP "gb_bios.bin", PATH_MAX - strlen(path)); + strncat(path, PATH_SEP "gb_bios.bin", PATH_MAX - strlen(path) - 1); break; case GB_MODEL_SGB: case GB_MODEL_SGB2: // TODO - strncat(path, PATH_SEP "sgb_bios.bin", PATH_MAX - strlen(path)); + strncat(path, PATH_SEP "sgb_bios.bin", PATH_MAX - strlen(path) - 1); break; case GB_MODEL_CGB: case GB_MODEL_AGB: case GB_MODEL_SCGB: - strncat(path, PATH_SEP "gbc_bios.bin", PATH_MAX - strlen(path)); + strncat(path, PATH_SEP "gbc_bios.bin", PATH_MAX - strlen(path) - 1); break; default: break; diff --git a/src/gba/core.c b/src/gba/core.c index c5f1946d6..50f0adf93 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -684,7 +684,7 @@ static void _GBACoreReset(struct mCore* core) { if (!found) { char path[PATH_MAX]; mCoreConfigDirectory(path, PATH_MAX); - strncat(path, PATH_SEP "gba_bios.bin", PATH_MAX - strlen(path)); + strncat(path, PATH_SEP "gba_bios.bin", PATH_MAX - strlen(path) - 1); bios = VFileOpen(path, O_RDONLY); if (bios && GBAIsBIOS(bios)) { found = true; diff --git a/src/script/storage.c b/src/script/storage.c index b1b740162..15946cce5 100644 --- a/src/script/storage.c +++ b/src/script/storage.c @@ -149,7 +149,7 @@ MAKE_SCALAR_SETTER(Bool, BOOL) void mScriptStorageGetBucketPath(const char* bucket, char* out) { mCoreConfigDirectory(out, PATH_MAX); - strncat(out, PATH_SEP "storage" PATH_SEP, PATH_MAX); + strncat(out, PATH_SEP "storage" PATH_SEP, PATH_MAX - 1); #ifdef _WIN32 // TODO: Move this to vfs somewhere WCHAR wout[MAX_PATH]; @@ -161,7 +161,7 @@ void mScriptStorageGetBucketPath(const char* bucket, char* out) { char suffix[STORAGE_LEN_MAX + 6]; snprintf(suffix, sizeof(suffix), "%s.json", bucket); - strncat(out, suffix, PATH_MAX); + strncat(out, suffix, PATH_MAX - 1); } static struct json_object* _tableToJson(struct mScriptValue* rootVal) { From bc048094b1d0406ac15163c7561ae1fc3cbeef21 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 20:27:51 -0800 Subject: [PATCH 075/290] Feature: Fix No-Intro cleanup on initial errors --- src/feature/sqlite3/no-intro.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/feature/sqlite3/no-intro.c b/src/feature/sqlite3/no-intro.c index d54e188ff..649364de6 100644 --- a/src/feature/sqlite3/no-intro.c +++ b/src/feature/sqlite3/no-intro.c @@ -17,7 +17,7 @@ struct NoIntroDB { }; struct NoIntroDB* NoIntroDBLoad(const char* path) { - struct NoIntroDB* db = malloc(sizeof(*db)); + struct NoIntroDB* db = calloc(1, sizeof(*db)); if (sqlite3_open_v2(path, &db->db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL)) { goto error; @@ -60,9 +60,6 @@ struct NoIntroDB* NoIntroDBLoad(const char* path) { return db; error: - if (db->crc32) { - sqlite3_finalize(db->crc32); - } NoIntroDBDestroy(db); return NULL; @@ -285,8 +282,12 @@ bool NoIntroDBLoadClrMamePro(struct NoIntroDB* db, struct VFile* vf) { } void NoIntroDBDestroy(struct NoIntroDB* db) { - sqlite3_finalize(db->crc32); - sqlite3_close(db->db); + if (db->crc32) { + sqlite3_finalize(db->crc32); + } + if (db->db) { + sqlite3_close(db->db); + } free(db); } From a2072b67ba6b5b44c3e08cf27a8c7b2d18025d4b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 20:32:18 -0800 Subject: [PATCH 076/290] Core: Fix GBK string memory handling in .cht loading --- src/core/cheats.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/core/cheats.c b/src/core/cheats.c index 688b32ebe..60f73abcc 100644 --- a/src/core/cheats.c +++ b/src/core/cheats.c @@ -489,8 +489,10 @@ bool mCheatParseEZFChtFile(struct mCheatDevice* device, struct VFile* vf) { return false; } char* name = gbkToUtf8(&cheat[1], end - cheat - 1); - strncpy(cheatName, name, sizeof(cheatName) - 1); - free(name); + if (name) { + strncpy(cheatName, name, sizeof(cheatName) - 1); + free(name); + } cheatNameLength = strlen(cheatName); continue; } @@ -501,7 +503,10 @@ bool mCheatParseEZFChtFile(struct mCheatDevice* device, struct VFile* vf) { } if (strncmp(cheat, "ON", eq - cheat) != 0) { char* subname = gbkToUtf8(cheat, eq - cheat); - snprintf(&cheatName[cheatNameLength], sizeof(cheatName) - cheatNameLength - 1, ": %s", subname); + if (subname) { + snprintf(&cheatName[cheatNameLength], sizeof(cheatName) - cheatNameLength - 1, ": %s", subname); + free(subname); + } } set = device->createSet(device, cheatName); set->enabled = false; From cd4dbaeb60570ddcef4a52dfb8e7a0bd5b472d2a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 21:02:49 -0800 Subject: [PATCH 077/290] Feature: Initialize z_stream more cleanly --- src/feature/video-logger.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/feature/video-logger.c b/src/feature/video-logger.c index ae2fa046e..fb2065b42 100644 --- a/src/feature/video-logger.c +++ b/src/feature/video-logger.c @@ -382,10 +382,7 @@ static void _copyVf(struct VFile* dest, struct VFile* src) { static void _compress(struct VFile* dest, struct VFile* src) { uint8_t writeBuffer[0x800]; uint8_t compressBuffer[0x400]; - z_stream zstr; - zstr.zalloc = Z_NULL; - zstr.zfree = Z_NULL; - zstr.opaque = Z_NULL; + z_stream zstr = {0}; zstr.avail_in = 0; zstr.avail_out = sizeof(compressBuffer); zstr.next_out = (Bytef*) compressBuffer; @@ -425,10 +422,7 @@ static void _compress(struct VFile* dest, struct VFile* src) { static bool _decompress(struct VFile* dest, struct VFile* src, size_t compressedLength) { uint8_t fbuffer[0x400]; uint8_t zbuffer[0x800]; - z_stream zstr; - zstr.zalloc = Z_NULL; - zstr.zfree = Z_NULL; - zstr.opaque = Z_NULL; + z_stream zstr = {0}; zstr.avail_in = 0; zstr.avail_out = sizeof(zbuffer); zstr.next_out = (Bytef*) zbuffer; From 7d014f1ae6b021b379cdf8c05be095b91b5da209 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 22:51:48 -0800 Subject: [PATCH 078/290] Core: Negative log types are invalid --- src/core/log.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/log.c b/src/core/log.c index b7feca30c..5fe2fe9ee 100644 --- a/src/core/log.c +++ b/src/core/log.c @@ -50,7 +50,7 @@ const char* mLogCategoryName(int category) { } const char* mLogCategoryId(int category) { - if (category < MAX_CATEGORY) { + if (category >= 0 && category < MAX_CATEGORY) { return _categoryIds[category]; } return NULL; From 222d48efe718d16e117284073e2f99d3501398e4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 22:52:05 -0800 Subject: [PATCH 079/290] Qt: Initialize log-to members --- src/platform/qt/LogController.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/LogController.h b/src/platform/qt/LogController.h index 548be8b4d..6bd4e5707 100644 --- a/src/platform/qt/LogController.h +++ b/src/platform/qt/LogController.h @@ -79,8 +79,8 @@ public slots: private: mLogFilter m_filter; - bool m_logToFile; - bool m_logToStdout; + bool m_logToFile = false; + bool m_logToStdout = false; std::unique_ptr m_logFile; std::unique_ptr m_logStream; From 54b9fbd8815a5d0ecf501ec88d6c097d9cae1d18 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 22:52:23 -0800 Subject: [PATCH 080/290] GBA SIO: Who wrote this code? Oh, me --- src/gba/sio/lockstep.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gba/sio/lockstep.c b/src/gba/sio/lockstep.c index 3ea1b5cc9..8e641c539 100644 --- a/src/gba/sio/lockstep.c +++ b/src/gba/sio/lockstep.c @@ -463,7 +463,7 @@ static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user, struct GBASIOLockstepNode* node = user; mLockstepLock(&node->p->d); - int32_t cycles = cycles = node->nextEvent; + int32_t cycles = node->nextEvent; node->nextEvent -= cyclesLate; node->eventDiff += cyclesLate; if (node->p->d.attached < 2) { From f45b4e3ef0a8f21fc06d1c063e543353e8301c76 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 23:05:28 -0800 Subject: [PATCH 081/290] Qt: Initialize Shortcut::m_direction --- src/platform/qt/ShortcutController.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/ShortcutController.h b/src/platform/qt/ShortcutController.h index 7eed7e1d7..6e72649b5 100644 --- a/src/platform/qt/ShortcutController.h +++ b/src/platform/qt/ShortcutController.h @@ -57,7 +57,7 @@ private: int m_shortcut = 0; int m_button = -1; int m_axis = -1; - GamepadAxisEvent::Direction m_direction; + GamepadAxisEvent::Direction m_direction = GamepadAxisEvent::NEUTRAL; }; class ShortcutController : public QObject { From 001135ef914ae158620bd9a5c9139f6768be9b57 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 Mar 2023 23:11:23 -0800 Subject: [PATCH 082/290] Qt: Better fps non-zero division check --- src/platform/qt/Window.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 6003b88a3..02f8459cf 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1138,14 +1138,14 @@ void Window::recordFrame() { } void Window::showFPS() { - if (m_frameList.isEmpty()) { - updateTitle(); - return; - } qint64 total = 0; for (qint64 t : m_frameList) { total += t; } + if (!total) { + updateTitle(); + return; + } double fps = (m_frameList.size() * 1e10) / total; m_frameList.clear(); fps = round(fps) / 10.f; From e06fa02d14b44fe321b6be3a6122ac973a6d22c1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 2 Mar 2023 20:28:48 -0800 Subject: [PATCH 083/290] Util: Fix potential socket leak --- include/mgba-util/socket.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/mgba-util/socket.h b/include/mgba-util/socket.h index 9f07491bd..96edc3d92 100644 --- a/include/mgba-util/socket.h +++ b/include/mgba-util/socket.h @@ -204,6 +204,7 @@ static inline Socket SocketOpenTCP(int port, const struct Address* bindAddress) err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); #endif if (err) { + SocketCloseQuiet(sock); return INVALID_SOCKET; } From 077aa04f48056857ef66745eb795deddcd17cff2 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 2 Mar 2023 20:31:11 -0800 Subject: [PATCH 084/290] Qt: Fix potential directory handle leak --- src/platform/qt/Display.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index 704f9463a..fcbec86bc 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -113,9 +113,9 @@ void QGBA::Display::configure(ConfigController* config) { config->updateOption("showOSD"); config->updateOption("showFrameCounter"); #if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(BUILD_GLES3) - if (opts->shader) { + if (opts->shader && supportsShaders()) { struct VDir* shader = VDirOpen(opts->shader); - if (shader && supportsShaders()) { + if (shader) { setShaders(shader); shader->close(shader); } From ceb66b133f94d9db1a637e6dde9bcbe81c36cff1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 2 Mar 2023 20:38:59 -0800 Subject: [PATCH 085/290] VFS: Improve zip invariant handling --- src/util/vfs/vfs-zip.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/util/vfs/vfs-zip.c b/src/util/vfs/vfs-zip.c index 2f8234b94..65e1c1e32 100644 --- a/src/util/vfs/vfs-zip.c +++ b/src/util/vfs/vfs-zip.c @@ -309,6 +309,9 @@ ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size) { if (!vfz->buffer) { vfz->bufferSize = BLOCK_SIZE; vfz->buffer = malloc(BLOCK_SIZE); + if (vfz->readSize) { + abort(); + } } while (bytesRead < size) { @@ -714,7 +717,7 @@ struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode) { } } - struct VFileZip* vfz = malloc(sizeof(struct VFileZip)); + struct VFileZip* vfz = calloc(1, sizeof(struct VFileZip)); vfz->uz = vdz->uz; vfz->z = vdz->z; vfz->buffer = 0; From 30fc0007347748b682ed19a9f027e370327f9c17 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 2 Mar 2023 21:28:16 -0800 Subject: [PATCH 086/290] Scripting: Fix potential crash if a bucket can't be opened --- src/script/storage.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/script/storage.c b/src/script/storage.c index 15946cce5..e1299d53e 100644 --- a/src/script/storage.c +++ b/src/script/storage.c @@ -307,6 +307,9 @@ bool mScriptStorageBucketFlush(struct mScriptStorageBucket* bucket) { char path[PATH_MAX]; mScriptStorageGetBucketPath(bucket->name, path); struct VFile* vf = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + return false; + } return _mScriptStorageBucketFlushVF(bucket, vf); } @@ -329,6 +332,9 @@ bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucket char path[PATH_MAX]; mScriptStorageGetBucketPath(bucketName, path); struct VFile* vf = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + return false; + } return mScriptStorageSaveBucketVF(context, bucketName, vf); } From 59ebf1c12dcbbe0141000cddccf667cd42e386a1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 3 Mar 2023 00:17:08 -0800 Subject: [PATCH 087/290] GB Video: Implement DMG-style sprite ordering --- CHANGES | 1 + cinema/gb/acid/cgb-acid2/baseline_0000.png | Bin 0 -> 1362 bytes cinema/gb/acid/cgb-acid2/test.gbc | Bin 0 -> 32768 bytes cinema/gb/acid/config.ini | 6 ++++ cinema/gb/acid/dmg-acid2/baseline_0000.png | Bin 0 -> 1360 bytes cinema/gb/acid/dmg-acid2/test.gb | Bin 0 -> 32768 bytes .../sprite_priority/baseline_0000.png | Bin 702 -> 703 bytes .../sprite_priority/xbaseline_0000.png | Bin 711 -> 0 bytes src/gb/renderers/software.c | 30 ++++++++++++++---- 9 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 cinema/gb/acid/cgb-acid2/baseline_0000.png create mode 100644 cinema/gb/acid/cgb-acid2/test.gbc create mode 100644 cinema/gb/acid/config.ini create mode 100644 cinema/gb/acid/dmg-acid2/baseline_0000.png create mode 100644 cinema/gb/acid/dmg-acid2/test.gb delete mode 100644 cinema/gb/mooneye-gb/manual-only/sprite_priority/xbaseline_0000.png diff --git a/CHANGES b/CHANGES index ffda80576..169a69a15 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,7 @@ Features: - New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81 - Debugger: Add range watchpoints Emulation fixes: + - GB Video: Implement DMG-style sprite ordering - 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) diff --git a/cinema/gb/acid/cgb-acid2/baseline_0000.png b/cinema/gb/acid/cgb-acid2/baseline_0000.png new file mode 100644 index 0000000000000000000000000000000000000000..4baff34257e5a33acb5b125d16ffa5dea6b3be52 GIT binary patch literal 1362 zcmeAS@N?(olHy`uVBq!ia0vp^3xIe62NRHFxc>b*0|Tq1r;B4q#hkZu4pu%^5O8b% zyG=`eGT$AiK0nKohPpeLpTF3)g*|7cYl8z*=9ka=_1jyfwhQ_zGk?0pb67$5=bzYr zx7KejklAi~qqW?@&n`ayp~b<+d%s1@yUZ+_IUlf#{H+iYJK0`apL8d_}?WPa=h1WJk?Xb|MXwI<-v{5-d@zcvGsM|1B+kp zw&uSVJ{D3iNAmu%6j^Ui&HSFYgPgm|UYtF@`huM9u{9+zBKJ$zgl)LwIjy$+b4TyL zAJ2miAM(3?gkNn9lPJC{``3U`SCvo)Yb?rZCc**bgDpR-I+&9d9wBT ze9AL)oL441{cbz`fs-G<7whV|Q;r49-Vk)~)0)o0vMh1+V>2Eq@8XfNW80k&yCEn_ z?EbRl+iF}lCs;f3-doztCzRxMvVVQOy7%w6E!&?;-|hGzactkLoRXGD;<@hcw~DWg zEzo!%aqzxZjPSP78oQhmsSUPB@bXNeAbm{8{L0;F-E|saRi9iW1aLAo|c>SOXuQnnR zWO1$A`d+@r*ld=1>E(j>P?4H#yKlC17iqiR-Tubo6Jwv@`po^mt3`7T?CM{>N@dS8 z6K;TWZ+G=I@7yWr7cHbRiF00Qm4i{cQuqgxwF!$h zKemuQB7Og5=hHb4A1(UG$M@`+oN;bs!|I#77S;-ufAw~{KA+zoUH|Uu4vUut`zP`?c(PL9(%xBFM#Ta)(L<4Vb|^O@os zvoep literal 0 HcmV?d00001 diff --git a/cinema/gb/acid/cgb-acid2/test.gbc b/cinema/gb/acid/cgb-acid2/test.gbc new file mode 100644 index 0000000000000000000000000000000000000000..5f71bd36060b46eefcf7785f81ce16a5a6c5fc67 GIT binary patch literal 32768 zcmeI4e`s6R702(B6iIIULvq*Rfw3ebr>2mSWRz%~_VVn=jpBvQHdA8u&xD&fT4z$% zrkOUxlcJ2yHpOuhmfGMZ#(%8TEA{qA3nLKHn%NPK?f*u@njw-bW^7%x1yW}b@9x}t z-_w&Nx!$1J#yI!!{OVbFZHNdXJDVr~T>63G<(ivd|^1e%jxEy@u?x4wEya zn$!@d+cPpsYCpMt^6t%>H>NIMKJnto+P_@C`N`C!%fI=WeDF~F?zV%6L;FOvR|`e| z`NF901Q`c+ zJ;?`h3T})F`yZBnA)f|0Ddc<0_vN!7-xl)X@?CiX(c8rR#w0s~f;0%!PTtE=~z)fI(nY{=>wgnRMhR#!@b!FCc;>`E*Q zXBV8k4kZ?aGY03NgYr~3rBO@Pw{LVY3wn1B4 zGqnp^FlcJ4m3f#tl~NS9+tjSLcdx0PPC_OpP0f1Q>}FlASPq-boJV)J+uhxL_N*k; z)-vxdm?TepOM_Sue(mX;@;IB`Pg{{Bd$zdr;`=v})a zk)uZ=k(L&rhll;Vf6{oH0>NeG~uPHbQxBg zpQ;KkcCwxEHk^Nia~(9LQ}jIC&A^qydyWZh5%h22%p~DOM>D>N7YrtjCyvC!i6e>Q zEMGj%dwzZwUQ2}hbK0VohI3AX8rXQAlkLyKYk_(c{WV5aRohu@W{bz;VK~6v)Fkgy z`?SUU>l>4~93e2D1^NaZg=!brm_#BGf=bxl=kay>J)RTh!}S#Xo8hnXNo#*^s&8uI z^`^Cp^bCDK&#-z`Rr`o*JwEU`O&`=%@uRK~Z^4L{Owtdb|6%gS-~o1>7ylKq`5M~b z~OQI^>Z_4%Bqmmnu! zAGKd?r@x2seEl>H<}xKtF(2SH1>X96bo&(ZVRoMVl9mP!UV)*ti@6EGTl)xXZ-76~ z2X;1=W#jqy+VuzhtO~xr*i2xp^LO$;&F78%rM(9GiTmer5El+3-{+sxw_r7Y&JQ5K zv;<*WcBg*+zwAxmi4pG^m(Br@%ZnJ?RfpS#QbsnVXgm1*FTX^pHB{ZzM33XpHCzT z&x6AAN|xd2wz@H?t-_9w34#5Zq378zvvSLYva`UTUcQofy8iK<@n3s4 zGVsNi1}l6j#zz#1IN@tCnPbgh`HA;Irn$zO&(yxuV5yDWPu;41`qmEb!_0~UK2g(_ zJqVwXxt8sK56Rrg9)i!i#O8e$!geQo35R|3X5I(ip&{OtXV~X#iiD47wKFUG!*X|4 z$rN6buUs_VMFL0w2_OL^fCP{L5b*0|TqLr;B4q#hkZu4{lqmCE&X8 z`tL&)XTDoriTB};IJ=rvgpqZv%sIKcW>Hgex;vz*f7aOLshr+4=U&(|wZ|@&%Y8-4 zyiPy={4?co%Ju$f*_F$*MZP5o{J(qQ*uMAYc9zC&uJ^uFy2XO=eyY)xUF%M764|BS zwfX(-`zq4vXN{xQJze>9ajt2H{dbe#1kpL&0=s;y^L8D{?5($*uXiW#pj@u~;ZLQp zTNMjqyuP|mJyRUv_IIa$L1n}@mon3v<#H^IKuP9Bn)7G(_g^XxozFJy>7DX|zeUq) z%B}v2sQr$W{&!Aq|BU@-+`k>ORgWy^kdunH{;iv{Xl;DG*xggj=@$>PPEN1Gb7uuYMm|bn3_PLYdYkIcR>%X7ezs7XtZ|msgIyWs};r3D4c!Mzcc)lxpquzH19@Jde#-()l_EEnR za)Z@1+XD|CJ8tV)rj*XUxasV?DOodoZ>&i8zFzIn&6V@#v`nu|yZPPrH`jZq zb4S0woT(e19=XBZ@oe9vNOsxvVb_j7pSs4o;&`Uu?j^gq<*#0rs=saaxa_X~ot%yd z(}gaFt-Jnl!4LntIR>!@Bb~(cn_eiN{+NI4-PRXOFY9A2ZGY{4eE;M;XOmaO=N3!a zmi)gb;5%ypdX(*CiT`QuS3jkbg?($Vx)U{<|HE^q)UAGo~aW9Cl9f;Ntb~(;v6D zp0=N5mY=mn^T+jP`_%4CiPnC0Z`0#0^MYwl|6i{ASuS34U|)Ow^p^E+(wZM+od0WI z+kc`~#Q3q!oy`a4E zWe(d#u9s?o|CjY%d-L$+qRy~$&(yAaPM_nDHH~q9nTPN0t8R?KB0PXdz6V4>!m!<;58?ebi4*ViArcsF2;AtrLP=8C8ejUpUXO@geCy22bVGc literal 0 HcmV?d00001 diff --git a/cinema/gb/acid/dmg-acid2/test.gb b/cinema/gb/acid/dmg-acid2/test.gb new file mode 100644 index 0000000000000000000000000000000000000000..a25ef9485e1ab176c80415548f053ca950b3e4f5 GIT binary patch literal 32768 zcmeI4e`s6R700iB*s@&zcyg5DQDaNSPSdg!FIkCFwCCqvjqIhxGgD&v&r~;4#AY(Z zra?)?ldO!AnBqAJ+iXbVtbt}TrD+EnjI_nAsT<)R>0g7eW_=`?&0t%!xH!%%-rc$P zzGumDvi;MI(Vg>ndiQ(JdFP({xli}M_XzoN?9X3Ln*V;5>2AxyU&GQ0^_6`#!=Uc7PR`uLSAhh92d@t3(9pN&sk`Q0wL{kgq6TG~3>cNSJV@vCnx ze}i6|-xrv#33%Trb`jk&O6Io&yvg&jRN=1omwVQpx?nWCn$?vYvk)r_(CjX9&Z6*O zPmxO*Guojd7fD(q3yXoVcDSNb5dArsne^2|iSD~{j>}P@fEV3W{vQ{aVXdPaUwYYTcD5=h}-dAM_x^`Hv zGchy!H@OL ziM&iZ^K9KON$br#x6|yjfSK*hev8ElW=3rCb1#qJDGL4Iwq3AV&Geh^%8|H}WsMa{ck zR15{=_2oC$i#8gx&OtY_Y&avoNv8~zG z*c{m;GELbUT1joJ&#~fItek81(F{a`WwV)OZArT`_4(B2-Q9MnyVu^-+imae?X^dJ z%EeJdx%jCs1lQ2TPo?bvA~k)VNWOj|DR2gUM5NvWL<+$*GC-s#^u-Pm2{zS;uZ%>L zG9?m#vm4IdawQUiGXiI{ob-IlNqYRHq^F;qPU3r}h$zn#6W`$?k8-%!6Z>vu-sDTb zUU?<21DbpHZq$Y9)~#GmNeLliV>!KLi`$*aa+HJ$@ktH8cKqR2)Fx-FWm0sWuXY^>~uOiJI|dnHKXQs z-@9jOao^OGsXMsEVy2e3WipbaX{^s>nCZ9M{r>iLzu(l1+L<#izG!N8RTQ&2LYRMe z_{b5{|HKp9!LD94b!TS_SX-N^>+Ah~k0)2Z*>7G&eWj&Nr`>L|v3;(k#bPNfy?fVg zudWs~o3VIB0!RP}AOR$R1dsp{Kmter2_OL^fCP{L5*^Akz0x=`l1kmYDfH7%cXTv1Zryt5kkH|9Fc=QEg9*KD zTQInPe=ykCDD>c9LqmPN*K59Q$=;o0Z8Z8JFPv6P!z+DIKS0p*IazcwIRj*JnU~3}-r#AS9Pxfn=_#mon%xtR$J?P2_U!ZghJTrl3G?B4oc`U6H~6IVyR}ud zwY=Vxex9D7_vr~%ud3=Fac$%WK11|=rIR1<`mm92+RT?o&<~;gVd5A>fZyQ7|AlP6 zrZ)N1)g_M5PqOFeCy66D=@WwJ~JwK>mPyd zG{p1qz|Zbwqw(?h+J*fMtP1{ov6;YHXK!bJoXwigm;Nd|Pdq-8fxK{-CeUee0?S$##moIKe1Qx=f#-MdVWJs z!)##p z>a+3q`hKv!U&%7;ZVRhp`T{%=G9mE1rs-Ms&wQ|D+VxS-Kho^WF6z~J5}5rU{bbda zM`G`KR@3n1nQEJhjCzS;Atmrdne>5r@ErHtPuG`wvgwK+R9h<|E6JN>Pu?u@tfl82 zfy-Kp)&`%LxvF)*M`mtodoO$8^EJdX|5N~4dc}qL-2wSKnv$l8-|YY4jtAF-y+{BF zAOR$R1dsp{Kmter2_OL^fCP{L5xg88td#JK|n)1Nz+9g%S6IIDjs=}w*FHomy2 z{BPIh=Pj4nQ|VP~z4-C`cjqO2+0M1^yI*9yIyfI(* z#(tfcWq0Mym9(e#emwj2>t*q6OVj(!4{P`lL9oL+qju^J9q+ibny4R9^9~|{=Xv?X z0%U%A0xY=x*-M-=28A0aC^>*(`KR7sPC76$DkcjsnTXwym5-MelPPLC!~g`Iu6{1- HoD!M<*n{Xy delta 459 zcmV;+0W|)<1-=E4Br}CcL_t(|+U?p=a)K}b0MVr5eeo#XIDTYIGbBWab}Z|@FN%R8 zD~L)=DFt!;e0l@)x03+^Auqd>sq6KcCvLafr?*P)lTu14Ez6P~>S>*wr)W3>!xO_6MwkN^pY{g5JLBhuo%~0kmbQy{m|C@?t1FMt3|r&IXeF7 z_MFjV;Iiw-Emwob%RbE+cE6t5!S$HA?K!EL!1XgvDXn%yo>PzC+H2#w?RvtS&IGG9 z11b*8pQ4Z2I;FSs_Z*So;A>@T!TQhcOX}=qF0$$!vGY8ZhjmZpWq*FXb7PG8&(+MM zn|ky#Ti@*R(tiCn=HF(IVy8U%%Ej8C?)O||5w0UN;OAht$-w3Qo-)yWbTFr# zS+BFUXZ+S`5B=Xg+k86nr|A6r+Z(vrd%M~_)qSYDs@2S`@!k3D;&bacHQ$5bd=G{* zFr0zm3=C&rxJ&4Ll^eJ$OPOYTTKHNX!EgqKGccTi;S3DtchPVj!IO~!6czp+4CfIH zXJ9x3!xb*0|Qf^r;B4q#hkad0*jgq7#J=t zdCa+2JSd1XdUD+#}M?WUzGM4RDV{av3`xL>Mom@sD% znr^Uxdj0J=9foZJyW(4pM$|h_V^4qT9DRECYfbiW|Cj5YzxdI9yVz}V(E4^0U60G#Jtjk{C`+D1J&UzE!kZiUPnHlpJ_1~Ix z>Gsp=z7qu>_HO!m-o=}if{dE`{RmD6T+x-_nRN0@Nq;J emKYo$p?!?nozi0Sr7|yq5~!!EpUXO@geCyFDmo?r diff --git a/src/gb/renderers/software.c b/src/gb/renderers/software.c index a5e41162f..d157920fa 100644 --- a/src/gb/renderers/software.c +++ b/src/gb/renderers/software.c @@ -571,20 +571,38 @@ static void _cleanOAM(struct GBVideoSoftwareRenderer* renderer, int y) { } int o = 0; int i; + int16_t ids[GB_VIDEO_MAX_LINE_OBJ]; for (i = 0; i < GB_VIDEO_MAX_OBJ && o < GB_VIDEO_MAX_LINE_OBJ; ++i) { uint8_t oy = renderer->d.oam->obj[i].y; if (y < oy - 16 || y >= oy - 16 + spriteHeight) { continue; } - // TODO: Sort - renderer->obj[o].obj = renderer->d.oam->obj[i]; - renderer->obj[o].index = i; + ids[o] = (renderer->d.oam->obj[i].x << 7) | i; ++o; - if (o == 10) { - break; - } } renderer->objMax = o; + if (renderer->model < GB_MODEL_CGB) { + // Terrble n^2 sort, but it's only 10 elements so it shouldn't be that bad + int16_t ids2[GB_VIDEO_MAX_LINE_OBJ]; + int min = -1; + int j; + for (i = 0; i < o; ++i) { + int min2 = 0xFFFF; + for (j = 0; j < o; ++j) { + if (ids[j] > min && ids[j] < min2) { + min2 = ids[j]; + } + } + min = min2; + ids2[i] = min; + } + memcpy(ids, ids2, sizeof(ids)); + } + for (i = 0; i < o; ++i) { + int id = ids[i] & 0x7F; + renderer->obj[i].obj = renderer->d.oam->obj[id]; + renderer->obj[i].index = id; + } } static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, int startX, int endX, int y) { From dd531637c20b2848d123440386895cc5db5a737f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 12 Feb 2023 02:10:51 -0800 Subject: [PATCH 088/290] Core: Revise screen size/info API --- include/mgba/core/core.h | 6 ++- include/mgba/core/interface.h | 9 +++++ include/mgba/internal/gb/video.h | 3 ++ src/core/core.c | 2 +- src/core/serialize.c | 4 +- src/gb/core.c | 44 ++++++++++++++++++--- src/gba/core.c | 43 +++++++++++++++++--- src/platform/3ds/main.c | 2 +- src/platform/example/client-server/server.c | 2 +- src/platform/libretro/libretro.c | 18 +++------ src/platform/openemu/mGBAGameCore.m | 6 +-- src/platform/psp2/psp2-context.c | 4 +- src/platform/python/mgba/core.py | 2 +- src/platform/qt/CoreController.cpp | 4 +- src/platform/qt/FrameView.cpp | 2 +- src/platform/sdl/gl-sdl.c | 2 +- src/platform/sdl/main.c | 4 +- src/platform/sdl/sw-sdl1.c | 2 +- src/platform/sdl/sw-sdl2.c | 2 +- src/platform/switch/main.c | 4 +- src/platform/test/cinema-main.c | 6 +-- src/platform/wii/main.c | 2 +- 22 files changed, 125 insertions(+), 48 deletions(-) diff --git a/include/mgba/core/core.h b/include/mgba/core/core.h index d3f2b4e04..7a6e89f56 100644 --- a/include/mgba/core/core.h +++ b/include/mgba/core/core.h @@ -66,7 +66,11 @@ struct mCore { void (*loadConfig)(struct mCore*, const struct mCoreConfig*); void (*reloadConfigOption)(struct mCore*, const char* option, const struct mCoreConfig*); - void (*desiredVideoDimensions)(const struct mCore*, unsigned* width, unsigned* height); + void (*baseVideoSize)(const struct mCore*, unsigned* width, unsigned* height); + void (*currentVideoSize)(const struct mCore*, unsigned* width, unsigned* height); + unsigned (*videoScale)(const struct mCore*); + size_t (*screenRegions)(const struct mCore*, const struct mCoreScreenRegion**); + void (*setVideoBuffer)(struct mCore*, color_t* buffer, size_t stride); void (*setVideoGLTex)(struct mCore*, unsigned texid); diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h index 9046d1a4f..21c776744 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -293,6 +293,15 @@ struct mCoreMemoryBlock { uint32_t segmentStart; }; +struct mCoreScreenRegion { + size_t id; + const char* description; + int16_t x; + int16_t y; + int16_t w; + int16_t h; +}; + enum mCoreRegisterType { mCORE_REGISTER_GPR = 0, mCORE_REGISTER_FPR, diff --git a/include/mgba/internal/gb/video.h b/include/mgba/internal/gb/video.h index 31c54b089..c9b179739 100644 --- a/include/mgba/internal/gb/video.h +++ b/include/mgba/internal/gb/video.h @@ -22,6 +22,9 @@ enum { GB_VIDEO_VBLANK_PIXELS = 10, GB_VIDEO_VERTICAL_TOTAL_PIXELS = 154, + SGB_VIDEO_HORIZONTAL_PIXELS = 256, + SGB_VIDEO_VERTICAL_PIXELS = 224, + // TODO: Figure out exact lengths GB_VIDEO_MODE_2_LENGTH = 80, GB_VIDEO_MODE_3_LENGTH_BASE = 172, diff --git a/src/core/core.c b/src/core/core.c index c4c3a3ce8..f28b8b245 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -361,7 +361,7 @@ bool mCoreTakeScreenshotVF(struct mCore* core, struct VFile* vf) { size_t stride; const void* pixels = 0; unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); core->getPixels(core, &pixels, &stride); png_structp png = PNGWriteOpen(vf); png_infop info = PNGWriteHeader(png, width, height); diff --git a/src/core/serialize.c b/src/core/serialize.c index 53e0aa168..c4b0b70a3 100644 --- a/src/core/serialize.c +++ b/src/core/serialize.c @@ -176,7 +176,7 @@ static bool _savePNGState(struct mCore* core, struct VFile* vf, struct mStateExt mappedMemoryFree(state, stateSize); unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); png_structp png = PNGWriteOpen(vf); png_infop info = PNGWriteHeader(png, width, height); if (!png || !info) { @@ -529,7 +529,7 @@ bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags) { mappedMemoryFree(state, core->stateSize(core)); unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); struct mStateExtdataItem item; if (flags & SAVESTATE_SCREENSHOT && mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) { diff --git a/src/gb/core.c b/src/gb/core.c index 59fb7c016..b35616f2b 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -60,6 +60,15 @@ static const struct mCoreMemoryBlock _GBCMemoryBlocks[] = { { GB_BASE_HRAM, "hram", "HRAM", "High RAM", GB_BASE_HRAM, GB_BASE_HRAM + GB_SIZE_HRAM, GB_SIZE_HRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED }, }; +static const struct mCoreScreenRegion _GBScreenRegions[] = { + { 0, "Screen", 0, 0, GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS } +}; + +static const struct mCoreScreenRegion _SGBScreenRegions[] = { + { 0, "Screen", (SGB_VIDEO_HORIZONTAL_PIXELS - GB_VIDEO_HORIZONTAL_PIXELS) / 2, (SGB_VIDEO_VERTICAL_PIXELS - GB_VIDEO_VERTICAL_PIXELS) / 2, GB_VIDEO_HORIZONTAL_PIXELS, GB_VIDEO_VERTICAL_PIXELS }, + { 1, "Border", 0, 0, SGB_VIDEO_HORIZONTAL_PIXELS, SGB_VIDEO_VERTICAL_PIXELS }, +}; + static const struct mCoreRegisterInfo _GBRegisters[] = { { "b", NULL, 1, 0xFF, mCORE_REGISTER_GPR }, { "c", NULL, 1, 0xFF, mCORE_REGISTER_GPR }, @@ -355,14 +364,36 @@ static void _GBCoreReloadConfigOption(struct mCore* core, const char* option, co } } -static void _GBCoreDesiredVideoDimensions(const struct mCore* core, unsigned* width, unsigned* height) { +static void _GBCoreBaseVideoSize(const struct mCore* core, unsigned* width, unsigned* height) { + UNUSED(core); + *width = SGB_VIDEO_HORIZONTAL_PIXELS; + *height = SGB_VIDEO_VERTICAL_PIXELS; +} + +static void _GBCoreCurrentVideoSize(const struct mCore* core, unsigned* width, unsigned* height) { const struct GB* gb = core->board; if (gb && (!(gb->model & GB_MODEL_SGB) || !gb->video.sgbBorders)) { *width = GB_VIDEO_HORIZONTAL_PIXELS; *height = GB_VIDEO_VERTICAL_PIXELS; } else { - *width = 256; - *height = 224; + *width = SGB_VIDEO_HORIZONTAL_PIXELS; + *height = SGB_VIDEO_VERTICAL_PIXELS; + } +} + +static unsigned _GBCoreVideoScale(const struct mCore* core) { + UNUSED(core); + return 1; +} + +static size_t _GBCoreScreenRegions(const struct mCore* core, const struct mCoreScreenRegion** regions) { + const struct GB* gb = core->board; + if (gb && (!(gb->model & GB_MODEL_SGB) || !gb->video.sgbBorders)) { + *regions = _GBScreenRegions; + return 1; + } else { + *regions = _SGBScreenRegions; + return 2; } } @@ -424,7 +455,7 @@ static void _GBCoreSetAVStream(struct mCore* core, struct mAVStream* stream) { gb->stream = stream; if (stream && stream->videoDimensionsChanged) { unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); stream->videoDimensionsChanged(stream, width, height); } if (stream && stream->audioRateChanged) { @@ -1232,7 +1263,10 @@ struct mCore* GBCoreCreate(void) { core->setSync = _GBCoreSetSync; core->loadConfig = _GBCoreLoadConfig; core->reloadConfigOption = _GBCoreReloadConfigOption; - core->desiredVideoDimensions = _GBCoreDesiredVideoDimensions; + core->baseVideoSize = _GBCoreBaseVideoSize; + core->currentVideoSize = _GBCoreCurrentVideoSize; + core->videoScale = _GBCoreVideoScale; + core->screenRegions = _GBCoreScreenRegions; core->setVideoBuffer = _GBCoreSetVideoBuffer; core->setVideoGLTex = _GBCoreSetVideoGLTex; core->getPixels = _GBCoreGetPixels; diff --git a/src/gba/core.c b/src/gba/core.c index 50f0adf93..851a9dee6 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -129,6 +129,10 @@ static const struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = { { GBA_REGION_SRAM_MIRROR, "eeprom", "EEPROM", "EEPROM (8kiB)", 0, GBA_SIZE_EEPROM, GBA_SIZE_EEPROM, mCORE_MEMORY_RW }, }; +static const struct mCoreScreenRegion _GBAScreenRegions[] = { + { 0, "Screen", 0, 0, GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS } +}; + static const struct mCoreRegisterInfo _GBARegisters[] = { { "r0", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR }, { "r1", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR }, @@ -422,19 +426,45 @@ static void _GBACoreReloadConfigOption(struct mCore* core, const char* option, c } } -static void _GBACoreDesiredVideoDimensions(const struct mCore* core, unsigned* width, unsigned* height) { +static void _GBACoreBaseVideoSize(const struct mCore* core, unsigned* width, unsigned* height) { + UNUSED(core); + *width = GBA_VIDEO_HORIZONTAL_PIXELS; + *height = GBA_VIDEO_VERTICAL_PIXELS; +} + +static void _GBACoreCurrentVideoSize(const struct mCore* core, unsigned* width, unsigned* height) { + int scale = 1; #ifdef BUILD_GLES3 const struct GBACore* gbacore = (const struct GBACore*) core; - int scale = gbacore->glRenderer.scale; + if (gbacore->glRenderer.outputTex != (unsigned) -1) { + scale = gbacore->glRenderer.scale; + } #else UNUSED(core); - int scale = 1; #endif *width = GBA_VIDEO_HORIZONTAL_PIXELS * scale; *height = GBA_VIDEO_VERTICAL_PIXELS * scale; } +static unsigned _GBACoreVideoScale(const struct mCore* core) { +#ifdef BUILD_GLES3 + const struct GBACore* gbacore = (const struct GBACore*) core; + if (gbacore->glRenderer.outputTex != (unsigned) -1) { + return gbacore->glRenderer.scale; + } +#else + UNUSED(core); +#endif + return 1; +} + +static size_t _GBACoreScreenRegions(const struct mCore* core, const struct mCoreScreenRegion** regions) { + UNUSED(core); + *regions = _GBAScreenRegions; + return 1; +} + static void _GBACoreSetVideoBuffer(struct mCore* core, color_t* buffer, size_t stride) { struct GBACore* gbacore = (struct GBACore*) core; gbacore->renderer.outputBuffer = buffer; @@ -500,7 +530,7 @@ static void _GBACoreSetAVStream(struct mCore* core, struct mAVStream* stream) { gba->stream = stream; if (stream && stream->videoDimensionsChanged) { unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); stream->videoDimensionsChanged(stream, width, height); } if (stream && stream->audioRateChanged) { @@ -1358,7 +1388,10 @@ struct mCore* GBACoreCreate(void) { core->setSync = _GBACoreSetSync; core->loadConfig = _GBACoreLoadConfig; core->reloadConfigOption = _GBACoreReloadConfigOption; - core->desiredVideoDimensions = _GBACoreDesiredVideoDimensions; + core->baseVideoSize = _GBACoreBaseVideoSize; + core->currentVideoSize = _GBACoreCurrentVideoSize; + core->videoScale = _GBACoreVideoScale; + core->screenRegions = _GBACoreScreenRegions; core->setVideoBuffer = _GBACoreSetVideoBuffer; core->setVideoGLTex = _GBACoreSetVideoGLTex; core->getPixels = _GBACoreGetPixels; diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 5356130ce..e0dd2a7dc 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -496,7 +496,7 @@ static void _drawTex(struct mCore* core, bool faded, bool both) { int wide = isWide ? 2 : 1; unsigned corew, coreh; - core->desiredVideoDimensions(core, &corew, &coreh); + core->currentVideoSize(core, &corew, &coreh); int w = corew; int h = coreh; diff --git a/src/platform/example/client-server/server.c b/src/platform/example/client-server/server.c index ba0347b69..0dcec9b15 100644 --- a/src/platform/example/client-server/server.c +++ b/src/platform/example/client-server/server.c @@ -83,7 +83,7 @@ bool _mExampleRun(const struct mArguments* args, Socket client) { // Get the dimensions required for this core and send them to the client. unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->baseVideoSize(core, &width, &height); ssize_t bufferSize = width * height * BYTES_PER_PIXEL; uint32_t sendNO; sendNO = htonl(width); diff --git a/src/platform/libretro/libretro.c b/src/platform/libretro/libretro.c index 2e4b67c80..8c08e7bb6 100644 --- a/src/platform/libretro/libretro.c +++ b/src/platform/libretro/libretro.c @@ -414,19 +414,13 @@ void retro_get_system_info(struct retro_system_info* info) { void retro_get_system_av_info(struct retro_system_av_info* info) { unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); info->geometry.base_width = width; info->geometry.base_height = height; -#ifdef M_CORE_GB - if (core->platform(core) == mPLATFORM_GB) { - info->geometry.max_width = 256; - info->geometry.max_height = 224; - } else -#endif - { - info->geometry.max_width = width; - info->geometry.max_height = height; - } + + core->baseVideoSize(core, &width, &height); + info->geometry.max_width = width; + info->geometry.max_height = height; info->geometry.aspect_ratio = width / (double) height; info->timing.fps = core->frequency(core) / (float) core->frameCycles(core); @@ -614,7 +608,7 @@ void retro_run(void) { core->runFrame(core); unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); videoCallback(outputBuffer, width, height, BYTES_PER_PIXEL * 256); #ifdef M_CORE_GBA diff --git a/src/platform/openemu/mGBAGameCore.m b/src/platform/openemu/mGBAGameCore.m index 36bb3464a..7656a8945 100644 --- a/src/platform/openemu/mGBAGameCore.m +++ b/src/platform/openemu/mGBAGameCore.m @@ -68,7 +68,7 @@ outputBuffer = nil; unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->baseVideoSize(core, &width, &height); outputBuffer = malloc(width * height * BYTES_PER_PIXEL); core->setVideoBuffer(core, outputBuffer, width); core->setAudioBufferSize(core, SAMPLES); @@ -143,14 +143,14 @@ - (OEIntRect)screenRect { unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->currentVideoSize(core, &width, &height); return OEIntRectMake(0, 0, width, height); } - (OEIntSize)bufferSize { unsigned width, height; - core->desiredVideoDimensions(core, &width, &height); + core->baseVideoSize(core, &width, &height); return OEIntSizeMake(width, height); } diff --git a/src/platform/psp2/psp2-context.c b/src/platform/psp2/psp2-context.c index f860bc4a1..749ff4589 100644 --- a/src/platform/psp2/psp2-context.c +++ b/src/platform/psp2/psp2-context.c @@ -323,7 +323,7 @@ void mPSP2Setup(struct mGUIRunner* runner) { mInputBindAxis(&runner->core->inputMap, PSP2_INPUT, 1, &desc); unsigned width, height; - runner->core->desiredVideoDimensions(runner->core, &width, &height); + runner->core->baseVideoSize(runner->core, &width, &height); tex[0] = vita2d_create_empty_texture_format(256, toPow2(height), SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); tex[1] = vita2d_create_empty_texture_format(256, toPow2(height), SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); currentTex = 0; @@ -614,7 +614,7 @@ void mPSP2Swap(struct mGUIRunner* runner) { void mPSP2Draw(struct mGUIRunner* runner, bool faded) { unsigned width, height; - runner->core->desiredVideoDimensions(runner->core, &width, &height); + runner->core->currentVideoSize(runner->core, &width, &height); if (interframeBlending) { _drawTex(tex[!currentTex], width, height, faded, false); } diff --git a/src/platform/python/mgba/core.py b/src/platform/python/mgba/core.py index 3a9b90c3a..51f543d3a 100644 --- a/src/platform/python/mgba/core.py +++ b/src/platform/python/mgba/core.py @@ -235,7 +235,7 @@ class Core(object): def desired_video_dimensions(self): width = ffi.new("unsigned*") height = ffi.new("unsigned*") - self._core.desiredVideoDimensions(self._core, width, height) + self._core.currentVideoSize(self._core, width, height) return width[0], height[0] @protected diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 07902facc..c3f12a19f 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -266,7 +266,7 @@ mPlatform CoreController::platform() const { QSize CoreController::screenDimensions() const { unsigned width, height; - m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height); + m_threadContext.core->currentVideoSize(m_threadContext.core, &width, &height); return QSize(width, height); } @@ -1186,7 +1186,7 @@ int CoreController::updateAutofire() { void CoreController::finishFrame() { if (!m_hwaccel) { unsigned width, height; - m_threadContext.core->desiredVideoDimensions(m_threadContext.core, &width, &height); + m_threadContext.core->currentVideoSize(m_threadContext.core, &width, &height); QMutexLocker locker(&m_bufferMutex); memcpy(m_completeBuffer.data(), m_activeBuffer.constData(), width * height * BYTES_PER_PIXEL); diff --git a/src/platform/qt/FrameView.cpp b/src/platform/qt/FrameView.cpp index 1f9d4705d..630d0ecd8 100644 --- a/src/platform/qt/FrameView.cpp +++ b/src/platform/qt/FrameView.cpp @@ -559,7 +559,7 @@ void FrameView::newVl() { } #endif unsigned width, height; - m_vl->desiredVideoDimensions(m_vl, &width, &height); + m_vl->baseVideoSize(m_vl, &width, &height); m_framebuffer = QImage(width, height, QImage::Format_RGBX8888); m_vl->setVideoBuffer(m_vl, reinterpret_cast(m_framebuffer.bits()), width); m_vl->reset(m_vl); diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c index 6c1508d33..437bff75b 100644 --- a/src/platform/sdl/gl-sdl.c +++ b/src/platform/sdl/gl-sdl.c @@ -64,7 +64,7 @@ void mSDLGLRunloop(struct mSDLRenderer* renderer, void* user) { renderer->player.windowUpdated = 0; } } - renderer->core->desiredVideoDimensions(renderer->core, &renderer->width, &renderer->height); + renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height); if (renderer->width != v->width || renderer->height != v->height) { renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, renderer->width); v->setDimensions(v, renderer->width, renderer->height); diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index 96a797986..e82a00483 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -107,7 +107,7 @@ int main(int argc, char** argv) { return 1; } - renderer.core->desiredVideoDimensions(renderer.core, &renderer.width, &renderer.height); + renderer.core->baseVideoSize(renderer.core, &renderer.width, &renderer.height); renderer.ratio = graphicsOpts.multiplier; if (renderer.ratio == 0) { renderer.ratio = 1; @@ -275,7 +275,7 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { if (!didFail) { #if SDL_VERSION_ATLEAST(2, 0, 0) - renderer->core->desiredVideoDimensions(renderer->core, &renderer->width, &renderer->height); + renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height); unsigned width = renderer->width * renderer->ratio; unsigned height = renderer->height * renderer->ratio; if (width != (unsigned) renderer->viewportWidth && height != (unsigned) renderer->viewportHeight) { diff --git a/src/platform/sdl/sw-sdl1.c b/src/platform/sdl/sw-sdl1.c index bfe5d2037..cf917fd0a 100644 --- a/src/platform/sdl/sw-sdl1.c +++ b/src/platform/sdl/sw-sdl1.c @@ -28,7 +28,7 @@ bool mSDLSWInit(struct mSDLRenderer* renderer) { SDL_WM_SetCaption(projectName, ""); unsigned width, height; - renderer->core->desiredVideoDimensions(renderer->core, &width, &height); + renderer->core->baseVideoSize(renderer->core, &width, &height); SDL_Surface* surface = SDL_GetVideoSurface(); SDL_LockSurface(surface); diff --git a/src/platform/sdl/sw-sdl2.c b/src/platform/sdl/sw-sdl2.c index 48afc33a6..75ad1c2ea 100644 --- a/src/platform/sdl/sw-sdl2.c +++ b/src/platform/sdl/sw-sdl2.c @@ -21,7 +21,7 @@ void mSDLSWCreate(struct mSDLRenderer* renderer) { bool mSDLSWInit(struct mSDLRenderer* renderer) { unsigned width, height; - renderer->core->desiredVideoDimensions(renderer->core, &width, &height); + renderer->core->baseVideoSize(renderer->core, &width, &height); renderer->window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen)); SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); renderer->player.window = renderer->window; diff --git a/src/platform/switch/main.c b/src/platform/switch/main.c index 14996d5b0..3e891ab3d 100644 --- a/src/platform/switch/main.c +++ b/src/platform/switch/main.c @@ -477,7 +477,7 @@ static void _drawFrame(struct mGUIRunner* runner, bool faded) { } unsigned width, height; - runner->core->desiredVideoDimensions(runner->core, &width, &height); + runner->core->currentVideoSize(runner->core, &width, &height); glActiveTexture(GL_TEXTURE0); if (usePbo) { @@ -530,7 +530,7 @@ static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, un glBindTexture(GL_TEXTURE_2D, screenshotTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - runner->core->desiredVideoDimensions(runner->core, &width, &height); + runner->core->currentVideoSize(runner->core, &width, &height); glDisable(GL_BLEND); bool wasPbo = usePbo; usePbo = false; diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index 28bb07693..c9d85003c 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -1032,7 +1032,7 @@ void CInemaTestRun(struct CInemaTest* test) { return; } struct CInemaImage image; - core->desiredVideoDimensions(core, &image.width, &image.height); + core->baseVideoSize(core, &image.width, &image.height); ssize_t bufferSize = image.width * image.height * BYTES_PER_PIXEL; image.data = malloc(bufferSize); image.stride = image.width; @@ -1075,7 +1075,7 @@ void CInemaTestRun(struct CInemaTest* test) { for (frame = 0; frame < skip; ++frame) { core->runFrame(core); } - core->desiredVideoDimensions(core, &image.width, &image.height); + core->currentVideoSize(core, &image.width, &image.height); #ifdef USE_FFMPEG struct FFmpegDecoder decoder; @@ -1139,7 +1139,7 @@ void CInemaTestRun(struct CInemaTest* test) { break; } CIlog(3, "Test frame: %u\n", frameCounter); - core->desiredVideoDimensions(core, &image.width, &image.height); + core->currentVideoSize(core, &image.width, &image.height); uint8_t* diff = NULL; struct CInemaImage expected = { .data = NULL, diff --git a/src/platform/wii/main.c b/src/platform/wii/main.c index cdfe00b2f..db3ed7242 100644 --- a/src/platform/wii/main.c +++ b/src/platform/wii/main.c @@ -1511,7 +1511,7 @@ void _prepareForFrame(struct mGUIRunner* runner) { } void _drawFrame(struct mGUIRunner* runner, bool faded) { - runner->core->desiredVideoDimensions(runner->core, &corew, &coreh); + runner->core->currentVideoSize(runner->core, &corew, &coreh); uint32_t color = 0xFFFFFF3F; if (!faded) { color |= 0xC0; From d6c3b012d1ddb8843bc16ac6a0fd8e70a619169e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 12 Feb 2023 17:33:44 -0800 Subject: [PATCH 089/290] Video: Start revising VideoBackend API --- include/mgba-util/geometry.h | 22 ++++++ src/platform/opengl/gl.c | 139 ++++++++++++++++++++-------------- src/platform/opengl/gl.h | 4 +- src/platform/opengl/gles2.c | 108 +++++++++++++++----------- src/platform/opengl/gles2.h | 4 +- src/platform/qt/DisplayGL.cpp | 12 +-- src/platform/sdl/gl-common.c | 2 +- src/platform/sdl/gl-sdl.c | 18 ++++- src/platform/sdl/gles2-sdl.c | 11 ++- src/platform/video-backend.h | 21 +++-- 10 files changed, 219 insertions(+), 122 deletions(-) create mode 100644 include/mgba-util/geometry.h diff --git a/include/mgba-util/geometry.h b/include/mgba-util/geometry.h new file mode 100644 index 000000000..a98693006 --- /dev/null +++ b/include/mgba-util/geometry.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2013-2023 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 GEOMETRY_H +#define GEOMETRY_H + +#include + +CXX_GUARD_START + +struct Rectangle { + int x; + int y; + unsigned width; + unsigned height; +}; + +CXX_GUARD_END + +#endif diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index 76089c4c0..8ad34197c 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -21,76 +21,95 @@ static const GLint _glTexCoords[] = { 0, 1 }; +static inline void _initTex(void) { + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); +#ifndef _WIN32 + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#endif +} + static void mGLContextInit(struct VideoBackend* v, WHandle handle) { UNUSED(handle); struct mGLContext* context = (struct mGLContext*) v; - v->width = 1; - v->height = 1; + memset(context->layerDims, 0, sizeof(context->layerDims)); glGenTextures(2, context->tex); glBindTexture(GL_TEXTURE_2D, context->tex[0]); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); -#ifndef _WIN32 - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#endif + _initTex(); glBindTexture(GL_TEXTURE_2D, context->tex[1]); - glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); -#ifndef _WIN32 - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#endif + _initTex(); context->activeTex = 0; + + glGenTextures(VIDEO_LAYER_MAX, context->tex); + int i; + for (i = 0; i < VIDEO_LAYER_MAX; ++i) { + glBindTexture(GL_TEXTURE_2D, context->layers[i]); + _initTex(); + } } -static void mGLContextSetDimensions(struct VideoBackend* v, unsigned width, unsigned height) { +static inline void _setTexDims(const struct Rectangle* dims) { +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(dims->width), toPow2(dims->height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(dims->width), toPow2(dims->height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); +#endif +#elif defined(__BIG_ENDIAN__) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(dims->width), toPow2(dims->height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(dims->width), toPow2(dims->height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); +#endif +} + +static void mGLContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct Rectangle* dims) { struct mGLContext* context = (struct mGLContext*) v; - if (width == v->width && height == v->height) { + if (layer >= VIDEO_LAYER_MAX) { return; } - v->width = width; - v->height = height; + context->layerDims[layer].x = dims->x; + context->layerDims[layer].y = dims->y; + if (dims->width == context->layerDims[layer].width && dims->height == context->layerDims[layer].height) { + return; + } + context->layerDims[layer].width = dims->width; + context->layerDims[layer].height = dims->height; - glBindTexture(GL_TEXTURE_2D, context->tex[0]); -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); -#endif -#elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); -#endif + if (layer == VIDEO_LAYER_IMAGE) { + glBindTexture(GL_TEXTURE_2D, context->tex[0]); + _setTexDims(dims); + glBindTexture(GL_TEXTURE_2D, context->tex[1]); + _setTexDims(dims); + } else { + glBindTexture(GL_TEXTURE_2D, context->layers[layer]); + _setTexDims(dims); + } +} - glBindTexture(GL_TEXTURE_2D, context->tex[1]); -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); -#endif -#elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); -#endif +static void mGLContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct Rectangle* dims) { + struct mGLContext* context = (struct mGLContext*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + memcpy(dims, &context->layerDims[layer], sizeof(*dims)); } static void mGLContextDeinit(struct VideoBackend* v) { struct mGLContext* context = (struct mGLContext*) v; glDeleteTextures(2, context->tex); + glDeleteTextures(VIDEO_LAYER_MAX, context->layers); } static void mGLContextResized(struct VideoBackend* v, unsigned w, unsigned h) { + struct mGLContext* context = (struct mGLContext*) v; unsigned drawW = w; unsigned drawH = h; if (v->lockAspectRatio) { - lockAspectRatioUInt(v->width, v->height, &drawW, &drawH); + lockAspectRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].width, context->layerDims[VIDEO_LAYER_IMAGE].height, &drawW, &drawH); } if (v->lockIntegerScaling) { - lockIntegerRatioUInt(v->width, &drawW); - lockIntegerRatioUInt(v->height, &drawH); + lockIntegerRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].width, &drawW); + lockIntegerRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].height, &drawH); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); @@ -114,7 +133,7 @@ void mGLContextDrawFrame(struct VideoBackend* v) { glTexCoordPointer(2, GL_INT, 0, _glTexCoords); glMatrixMode(GL_PROJECTION); glLoadIdentity(); - glOrtho(0, v->width, v->height, 0, 0, 1); + glOrtho(0, context->layerDims[VIDEO_LAYER_IMAGE].width, context->layerDims[VIDEO_LAYER_IMAGE].height, 0, 0, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); if (v->interframeBlending) { @@ -143,32 +162,38 @@ void mGLContextDrawFrame(struct VideoBackend* v) { glDisable(GL_BLEND); } -void mGLContextPostFrame(struct VideoBackend* v, const void* frame) { +void mGLContextPostFrame(struct VideoBackend* v, enum VideoLayer layer, const void* frame) { struct mGLContext* context = (struct mGLContext*) v; - context->activeTex ^= 1; - glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]); + if (layer >= VIDEO_LAYER_MAX) { + return; + } + if (layer == VIDEO_LAYER_IMAGE) { + context->activeTex ^= 1; + glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]); + } else { + glBindTexture(GL_TEXTURE_2D, context->layers[layer]); + } #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, context->layerDims[layer].width, context->layerDims[layer].height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); #else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, context->layerDims[layer].width, context->layerDims[layer].height, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); #endif #elif defined(__BIG_ENDIAN__) - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, context->layerDims[layer].width, context->layerDims[layer].height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); #else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, v->width, v->height, GL_RGBA, GL_UNSIGNED_BYTE, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, context->layerDims[layer].width, context->layerDims[layer].height, GL_RGBA, GL_UNSIGNED_BYTE, frame); #endif } void mGLContextCreate(struct mGLContext* context) { context->d.init = mGLContextInit; context->d.deinit = mGLContextDeinit; - context->d.setDimensions = mGLContextSetDimensions; - context->d.resized = mGLContextResized; - context->d.swap = 0; + context->d.setLayerDimensions = mGLContextSetLayerDimensions; + context->d.layerDimensions = mGLContextLayerDimensions; + context->d.contextResized = mGLContextResized; + context->d.swap = NULL; context->d.clear = mGLContextClear; - context->d.postFrame = mGLContextPostFrame; + context->d.setImage = mGLContextPostFrame; context->d.drawFrame = mGLContextDrawFrame; - context->d.setMessage = 0; - context->d.clearMessage = 0; } diff --git a/src/platform/opengl/gl.h b/src/platform/opengl/gl.h index 3ff528864..c819e2f37 100644 --- a/src/platform/opengl/gl.h +++ b/src/platform/opengl/gl.h @@ -26,8 +26,10 @@ CXX_GUARD_START struct mGLContext { struct VideoBackend d; - GLuint tex[2]; int activeTex; + GLuint tex[2]; + GLuint layers[VIDEO_LAYER_MAX]; + struct Rectangle layerDims[VIDEO_LAYER_MAX]; }; void mGLContextCreate(struct mGLContext*); diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index ffbac5a04..fb2a07bba 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -101,12 +101,15 @@ static const GLfloat _vertices[] = { static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) { UNUSED(handle); struct mGLES2Context* context = (struct mGLES2Context*) v; - v->width = 1; - v->height = 1; - glGenTextures(1, &context->tex); - glBindTexture(GL_TEXTURE_2D, context->tex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + memset(context->layerDims, 0, sizeof(context->layerDims)); + + glGenTextures(VIDEO_LAYER_MAX, context->tex); + int i; + for (i = 0; i < VIDEO_LAYER_MAX; ++i) { + glBindTexture(GL_TEXTURE_2D, context->tex[i]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } glGenBuffers(1, &context->vbo); glBindBuffer(GL_ARRAY_BUFFER, context->vbo); @@ -177,40 +180,55 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) { context->finalShader.tex = 0; } -static void mGLES2ContextSetDimensions(struct VideoBackend* v, unsigned width, unsigned height) { +static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct Rectangle* dims) { struct mGLES2Context* context = (struct mGLES2Context*) v; - if (width == v->width && height == v->height) { + if (layer >= VIDEO_LAYER_MAX) { return; } - v->width = width; - v->height = height; + context->layerDims[layer].x = dims->x; + context->layerDims[layer].y = dims->y; + if (dims->width == context->layerDims[layer].width && dims->height == context->layerDims[layer].height) { + return; + } + context->layerDims[layer].width = dims->width; + context->layerDims[layer].height = dims->height; - glBindTexture(GL_TEXTURE_2D, context->tex); + glBindTexture(GL_TEXTURE_2D, context->tex[layer]); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); #endif #elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); #endif - size_t n; - for (n = 0; n < context->nShaders; ++n) { - if (context->shaders[n].width < 0 || context->shaders[n].height < 0) { - context->shaders[n].dirty = true; + if (layer == VIDEO_LAYER_BACKGROUND) { + size_t n; + for (n = 0; n < context->nShaders; ++n) { + if (context->shaders[n].width < 0 || context->shaders[n].height < 0) { + context->shaders[n].dirty = true; + } } + context->initialShader.dirty = true; + context->interframeShader.dirty = true; } - context->initialShader.dirty = true; - context->interframeShader.dirty = true; +} + +static void mGLES2ContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct Rectangle* dims) { + struct mGLES2Context* context = (struct mGLES2Context*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + memcpy(dims, &context->layerDims[layer], sizeof(*dims)); } static void mGLES2ContextDeinit(struct VideoBackend* v) { struct mGLES2Context* context = (struct mGLES2Context*) v; - glDeleteTextures(1, &context->tex); + glDeleteTextures(VIDEO_LAYER_MAX, context->tex); glDeleteBuffers(1, &context->vbo); mGLES2ShaderDeinit(&context->initialShader); mGLES2ShaderDeinit(&context->finalShader); @@ -223,11 +241,11 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h) unsigned drawW = w; unsigned drawH = h; if (v->lockAspectRatio) { - lockAspectRatioUInt(v->width, v->height, &drawW, &drawH); + lockAspectRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].width, context->layerDims[VIDEO_LAYER_IMAGE].height, &drawW, &drawH); } if (v->lockIntegerScaling) { - lockIntegerRatioUInt(v->width, &drawW); - lockIntegerRatioUInt(v->height, &drawH); + lockIntegerRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].width, &drawW); + lockIntegerRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].height, &drawH); } size_t n; for (n = 0; n < context->nShaders; ++n) { @@ -260,19 +278,19 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { drawW = viewport[2]; padW = viewport[0]; } else if (shader->width < 0) { - drawW = context->d.width * -shader->width; + drawW = context->layerDims[VIDEO_LAYER_IMAGE].width * -shader->width; } if (!drawH) { drawH = viewport[3]; padH = viewport[1]; } else if (shader->height < 0) { - drawH = context->d.height * -shader->height; + drawH = context->layerDims[VIDEO_LAYER_IMAGE].height * -shader->height; } if (shader->integerScaling) { padW = 0; padH = 0; - drawW -= drawW % context->d.width; - drawH -= drawH % context->d.height; + drawW -= drawW % context->layerDims[VIDEO_LAYER_IMAGE].width; + drawH -= drawH % context->layerDims[VIDEO_LAYER_IMAGE].height; } if (shader->dirty) { @@ -301,7 +319,7 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glUseProgram(shader->program); glUniform1i(shader->texLocation, 0); - glUniform2f(shader->texSizeLocation, context->d.width, context->d.height); + glUniform2f(shader->texSizeLocation, context->layerDims[VIDEO_LAYER_IMAGE].width, context->layerDims[VIDEO_LAYER_IMAGE].height); glUniform2f(shader->outputSizeLocation, drawW, drawH); #ifdef BUILD_GLES3 if (shader->vao != (GLuint) -1) { @@ -371,7 +389,7 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { void mGLES2ContextDrawFrame(struct VideoBackend* v) { struct mGLES2Context* context = (struct mGLES2Context*) v; glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, context->tex); + glBindTexture(GL_TEXTURE_2D, context->tex[VIDEO_LAYER_IMAGE]); GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); @@ -392,7 +410,7 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { _drawShader(context, &context->finalShader); if (v->interframeBlending) { context->interframeShader.blend = false; - glBindTexture(GL_TEXTURE_2D, context->tex); + glBindTexture(GL_TEXTURE_2D, context->tex[VIDEO_LAYER_IMAGE]); _drawShader(context, &context->initialShader); glViewport(0, 0, viewport[2], viewport[3]); _drawShader(context, &context->interframeShader); @@ -406,33 +424,35 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { #endif } -void mGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) { +void mGLES2ContextPostFrame(struct VideoBackend* v, enum VideoLayer layer, const void* frame) { struct mGLES2Context* context = (struct mGLES2Context*) v; - glBindTexture(GL_TEXTURE_2D, context->tex); + if (layer >= VIDEO_LAYER_MAX) { + return; + } + glBindTexture(GL_TEXTURE_2D, context->tex[layer]); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context->layerDims[layer].width, context->layerDims[layer].height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context->layerDims[layer].width, context->layerDims[layer].height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); #endif #elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context->layerDims[layer].width, context->layerDims[layer].height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context->layerDims[layer].width, context->layerDims[layer].height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); #endif } void mGLES2ContextCreate(struct mGLES2Context* context) { context->d.init = mGLES2ContextInit; context->d.deinit = mGLES2ContextDeinit; - context->d.setDimensions = mGLES2ContextSetDimensions; - context->d.resized = mGLES2ContextResized; - context->d.swap = 0; + context->d.setLayerDimensions = mGLES2ContextSetLayerDimensions; + context->d.layerDimensions = mGLES2ContextLayerDimensions; + context->d.contextResized = mGLES2ContextResized; + context->d.swap = NULL; context->d.clear = mGLES2ContextClear; - context->d.postFrame = mGLES2ContextPostFrame; + context->d.setImage = mGLES2ContextPostFrame; context->d.drawFrame = mGLES2ContextDrawFrame; - context->d.setMessage = 0; - context->d.clearMessage = 0; context->shaders = 0; context->nShaders = 0; } diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h index b497b4bfa..a789305db 100644 --- a/src/platform/opengl/gles2.h +++ b/src/platform/opengl/gles2.h @@ -79,9 +79,11 @@ struct mGLES2Shader { struct mGLES2Context { struct VideoBackend d; - GLuint tex; + GLuint tex[VIDEO_LAYER_MAX]; GLuint vbo; + struct Rectangle layerDims[VIDEO_LAYER_MAX]; + struct mGLES2Shader initialShader; struct mGLES2Shader finalShader; struct mGLES2Shader interframeShader; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 95549b0c8..41ff32bbc 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -624,7 +624,9 @@ void PainterGL::resizeContext() { return; } dequeueAll(false); - m_backend->setDimensions(m_backend, size.width(), size.height()); + + Rectangle dims = {0, 0, size.width(), size.height()}; + m_backend->setLayerDimensions(m_backend, VIDEO_LAYER_IMAGE, &dims); } void PainterGL::setMessagePainter(MessagePainter* messagePainter) { @@ -797,9 +799,9 @@ void PainterGL::unpause() { void PainterGL::performDraw() { float r = m_window->devicePixelRatio(); - m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r); + m_backend->contextResized(m_backend, m_size.width() * r, m_size.height() * r); if (m_buffer) { - m_backend->postFrame(m_backend, m_buffer); + m_backend->setImage(m_backend, VIDEO_LAYER_IMAGE, m_buffer); } m_backend->drawFrame(m_backend); if (m_showOSD && m_messagePainter && !glContextHasBug(OpenGLBug::IG4ICD_CRASH)) { @@ -855,7 +857,7 @@ void PainterGL::dequeue() { #if defined(BUILD_GLES2) || defined(BUILD_GLES3) if (supportsShaders()) { mGLES2Context* gl2Backend = reinterpret_cast(m_backend); - gl2Backend->tex = m_bridgeTexOut; + gl2Backend->tex[VIDEO_LAYER_IMAGE] = m_bridgeTexOut; } #endif } @@ -953,7 +955,7 @@ int PainterGL::glTex() { #if defined(BUILD_GLES2) || defined(BUILD_GLES3) if (supportsShaders()) { mGLES2Context* gl2Backend = reinterpret_cast(m_backend); - return gl2Backend->tex; + return gl2Backend->tex[VIDEO_LAYER_IMAGE]; } #endif #ifdef BUILD_GL diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c index f0073a904..264e3cafe 100644 --- a/src/platform/sdl/gl-common.c +++ b/src/platform/sdl/gl-common.c @@ -8,7 +8,7 @@ #include void mSDLGLDoViewport(int w, int h, struct VideoBackend* v) { - v->resized(v, w, h); + v->contextResized(v, w, h); v->clear(v); v->swap(v); v->clear(v); diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c index 437bff75b..a6d0a4e0e 100644 --- a/src/platform/sdl/gl-sdl.c +++ b/src/platform/sdl/gl-sdl.c @@ -37,7 +37,13 @@ bool mSDLGLInit(struct mSDLRenderer* renderer) { renderer->gl.d.filter = renderer->filter; renderer->gl.d.swap = mSDLGLCommonSwap; renderer->gl.d.init(&renderer->gl.d, 0); - renderer->gl.d.setDimensions(&renderer->gl.d, renderer->width, renderer->height); + struct Rectangle dims = { + .x = 0, + .y = 0, + .width = renderer->width, + .height = renderer->height + }; + renderer->gl.d.setLayerDimensions(&renderer->gl.d, VIDEO_LAYER_IMAGE, &dims); mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, &renderer->gl.d); return true; @@ -65,13 +71,17 @@ void mSDLGLRunloop(struct mSDLRenderer* renderer, void* user) { } } renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height); - if (renderer->width != v->width || renderer->height != v->height) { + struct Rectangle dims; + v->layerDimensions(v, VIDEO_LAYER_IMAGE, &dims); + if (renderer->width != dims.width || renderer->height != dims.height) { renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, renderer->width); - v->setDimensions(v, renderer->width, renderer->height); + dims.width = renderer->width; + dims.height = renderer->height; + v->setLayerDimensions(v, VIDEO_LAYER_IMAGE, &dims); } if (mCoreSyncWaitFrameStart(&context->impl->sync)) { - v->postFrame(v, renderer->outputBuffer); + v->setImage(v, VIDEO_LAYER_IMAGE, renderer->outputBuffer); } mCoreSyncWaitFrameEnd(&context->impl->sync); v->drawFrame(v); diff --git a/src/platform/sdl/gles2-sdl.c b/src/platform/sdl/gles2-sdl.c index 7237b6273..e65d430ee 100644 --- a/src/platform/sdl/gles2-sdl.c +++ b/src/platform/sdl/gles2-sdl.c @@ -50,7 +50,14 @@ bool mSDLGLES2Init(struct mSDLRenderer* renderer) { renderer->gl2.d.swap = mSDLGLCommonSwap; #endif renderer->gl2.d.init(&renderer->gl2.d, 0); - renderer->gl2.d.setDimensions(&renderer->gl2.d, renderer->width, renderer->height); + + struct Rectangle dims = { + .x = 0, + .y = 0, + .width = renderer->width, + .height = renderer->height + }; + renderer->gl2.d.setLayerDimensions(&renderer->gl2.d, VIDEO_LAYER_IMAGE, &dims); mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, &renderer->gl2.d); return true; @@ -79,7 +86,7 @@ void mSDLGLES2Runloop(struct mSDLRenderer* renderer, void* user) { } if (mCoreSyncWaitFrameStart(&context->impl->sync)) { - v->postFrame(v, renderer->outputBuffer); + v->setImage(v, VIDEO_LAYER_IMAGE, renderer->outputBuffer); } mCoreSyncWaitFrameEnd(&context->impl->sync); v->drawFrame(v); diff --git a/src/platform/video-backend.h b/src/platform/video-backend.h index 6d714629a..3b7a4b245 100644 --- a/src/platform/video-backend.h +++ b/src/platform/video-backend.h @@ -10,6 +10,8 @@ CXX_GUARD_START +#include + #ifdef _WIN32 #include typedef HWND WHandle; @@ -17,26 +19,31 @@ typedef HWND WHandle; typedef void* WHandle; #endif +enum VideoLayer { + VIDEO_LAYER_BACKGROUND = 0, + VIDEO_LAYER_IMAGE, + VIDEO_LAYER_OVERLAY, + VIDEO_LAYER_MAX +}; + struct VideoBackend { void (*init)(struct VideoBackend*, WHandle handle); void (*deinit)(struct VideoBackend*); - void (*setDimensions)(struct VideoBackend*, unsigned width, unsigned height); + void (*setLayerDimensions)(struct VideoBackend*, enum VideoLayer, const struct Rectangle*); + void (*layerDimensions)(const struct VideoBackend*, enum VideoLayer, struct Rectangle*); void (*swap)(struct VideoBackend*); void (*clear)(struct VideoBackend*); - void (*resized)(struct VideoBackend*, unsigned w, unsigned h); - void (*postFrame)(struct VideoBackend*, const void* frame); + void (*contextResized)(struct VideoBackend*, unsigned w, unsigned h); + void (*setImage)(struct VideoBackend*, enum VideoLayer, const void* frame); void (*drawFrame)(struct VideoBackend*); - void (*setMessage)(struct VideoBackend*, const char* message); - void (*clearMessage)(struct VideoBackend*); void* user; - unsigned width; - unsigned height; bool filter; bool lockAspectRatio; bool lockIntegerScaling; bool interframeBlending; + enum VideoLayer cropToLayer; }; struct VideoShader { From bd6edce5cffc4e4376850dd9579b6cc394b6c05e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 13 Feb 2023 15:13:33 -0800 Subject: [PATCH 090/290] Qt: Start adding background/bezel image support --- src/platform/qt/Display.h | 1 + src/platform/qt/DisplayGL.cpp | 4 ++ src/platform/qt/DisplayGL.h | 2 + src/platform/qt/DisplayQt.cpp | 80 ++++++++++++++++++++++++++++++-- src/platform/qt/DisplayQt.h | 2 + src/platform/qt/SettingsView.cpp | 12 +++++ src/platform/qt/SettingsView.h | 1 + src/platform/qt/SettingsView.ui | 35 ++++++++++++++ src/platform/qt/Window.cpp | 9 ++++ 9 files changed, 143 insertions(+), 3 deletions(-) diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index 574a0e8e9..dda48d875 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -54,6 +54,7 @@ public: virtual VideoShader* shaders() = 0; virtual int framebufferHandle() { return -1; } virtual void setVideoScale(int) {} + virtual void setBackgroundImage(const QImage&) = 0; virtual void setVideoProxy(std::shared_ptr proxy) { m_videoProxy = proxy; } std::shared_ptr videoProxy() { return m_videoProxy; } diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 41ff32bbc..fce25624d 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -396,6 +396,10 @@ void DisplayGL::setVideoScale(int scale) { QMetaObject::invokeMethod(m_painter.get(), "resizeContext"); } +void DisplayGL::setBackgroundImage(const QImage& image) { + // TODO +} + void DisplayGL::resizeEvent(QResizeEvent* event) { Display::resizeEvent(event); resizePainter(); diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 3ed31a9d8..87dd983dd 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -107,6 +107,8 @@ public slots: void clearShaders() override; void resizeContext() override; void setVideoScale(int scale) override; + void setBackgroundImage(const QImage&) override; + protected: virtual void paintEvent(QPaintEvent*) override { forceDraw(); } diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 20ec44f0d..7669b5cf1 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -92,19 +92,93 @@ void DisplayQt::resizeContext() { } } +void DisplayQt::setBackgroundImage(const QImage& image) { + m_background = image; + update(); +} + void DisplayQt::paintEvent(QPaintEvent*) { QPainter painter(this); painter.fillRect(QRect(QPoint(), size()), Qt::black); if (isFiltered()) { painter.setRenderHint(QPainter::SmoothPixmapTransform); } - QRect full(clampSize(QSize(m_width, m_height), size(), isAspectRatioLocked(), isIntegerScalingLocked())); + + QRect bgRect(0, 0, m_background.width(), m_background.height()); + QRect imRect(0, 0, m_width, m_height); + QSize outerFrame; + + if (bgRect.width() > imRect.width()) { + imRect.moveLeft(bgRect.width() - imRect.width()); + outerFrame.setWidth(bgRect.width()); + } else { + bgRect.moveLeft(imRect.width() - bgRect.width()); + outerFrame.setWidth(imRect.width()); + } + + if (bgRect.height() > imRect.height()) { + imRect.moveTop(bgRect.height() - imRect.height()); + outerFrame.setHeight(bgRect.height()); + } else { + bgRect.moveTop(imRect.height() - bgRect.height()); + outerFrame.setHeight(imRect.height()); + } + + QRect full(clampSize(outerFrame, size(), isAspectRatioLocked(), isIntegerScalingLocked())); + + if (m_background.isNull()) { + imRect = full; + } else { + if (imRect.x()) { + imRect.moveLeft(imRect.x() * full.width() / bgRect.width() / 2); + imRect.setWidth(imRect.width() * full.width() / bgRect.width()); + bgRect.setWidth(full.width()); + } else { + bgRect.moveLeft(bgRect.x() * full.width() / imRect.width() / 2); + bgRect.setWidth(bgRect.width() * full.width() / imRect.width()); + imRect.setWidth(full.width()); + } + if (imRect.y()) { + imRect.moveTop(imRect.y() * full.height() / bgRect.height() / 2); + imRect.setHeight(imRect.height() * full.height() / bgRect.height()); + bgRect.setHeight(full.height()); + } else { + bgRect.moveTop(bgRect.y() * full.height() / imRect.height() / 2); + bgRect.setHeight(bgRect.height() * full.height() / imRect.height()); + imRect.setHeight(full.height()); + } + + if (bgRect.right() > imRect.right()) { + if (bgRect.right() < full.right()) { + imRect.translate((full.right() - bgRect.right()), 0); + bgRect.translate((full.right() - bgRect.right()), 0); + } + } else { + if (imRect.right() < full.right()) { + bgRect.translate((full.right() - imRect.right()), 0); + imRect.translate((full.right() - imRect.right()), 0); + } + } + + if (bgRect.bottom() > imRect.bottom()) { + if (bgRect.bottom() < full.bottom()) { + imRect.translate(0, (full.bottom() - bgRect.bottom())); + bgRect.translate(0, (full.bottom() - bgRect.bottom())); + } + } else { + if (imRect.bottom() < full.bottom()) { + bgRect.translate(0, (full.bottom() - imRect.bottom())); + imRect.translate(0, (full.bottom() - imRect.bottom())); + } + } + painter.drawImage(bgRect, m_background); + } if (hasInterframeBlending()) { - painter.drawImage(full, m_oldBacking, QRect(0, 0, m_width, m_height)); + painter.drawImage(imRect, m_oldBacking, QRect(0, 0, m_width, m_height)); painter.setOpacity(0.5); } - painter.drawImage(full, m_backing, QRect(0, 0, m_width, m_height)); + painter.drawImage(imRect, m_backing, QRect(0, 0, m_width, m_height)); painter.setOpacity(1); if (isShowOSD() || isShowFrameCounter()) { messagePainter()->paint(&painter); diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index e8623468f..acdd95f24 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -36,6 +36,7 @@ public slots: void setShaders(struct VDir*) override {} void clearShaders() override {} void resizeContext() override; + void setBackgroundImage(const QImage&) override; protected: virtual void paintEvent(QPaintEvent*) override; @@ -46,6 +47,7 @@ private: int m_height; QImage m_backing{nullptr}; QImage m_oldBacking{nullptr}; + QImage m_background; std::shared_ptr m_context = nullptr; }; diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index acf272cf6..75a3908dc 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -143,6 +143,9 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC connect(m_ui.cheatsBrowse, &QAbstractButton::pressed, [this] () { selectPath(m_ui.cheatsPath, m_ui.cheatsSameDir); }); + connect(m_ui.bgImageBrowse, &QAbstractButton::pressed, [this] () { + selectImage(m_ui.bgImage); + }); connect(m_ui.clearCache, &QAbstractButton::pressed, this, &SettingsView::libraryCleared); // TODO: Move to reloadConfig() @@ -445,6 +448,13 @@ void SettingsView::selectPath(QLineEdit* field, QCheckBox* sameDir) { } } +void SettingsView::selectImage(QLineEdit* field) { + QString path = GBAApp::app()->getOpenFileName(this, tr("Select image"), tr("Image file (*.png *.jpg *.jpeg)")); + if (!path.isNull()) { + field->setText(makePortablePath(path)); + } +} + void SettingsView::updateConfig() { saveSetting("gba.bios", m_ui.gbaBios); saveSetting("gb.bios", m_ui.gbBios); @@ -501,6 +511,7 @@ void SettingsView::updateConfig() { saveSetting("vbaBugCompat", m_ui.vbaBugCompat); saveSetting("updateAutoCheck", m_ui.updateAutoCheck); saveSetting("showFilenameInLibrary", m_ui.showFilenameInLibrary); + saveSetting("backgroundImage", m_ui.bgImage); if (m_ui.audioBufferSize->currentText().toInt() > 8192) { m_ui.audioBufferSize->setCurrentText("8192"); @@ -725,6 +736,7 @@ void SettingsView::reloadConfig() { loadSetting("vbaBugCompat", m_ui.vbaBugCompat, true); loadSetting("updateAutoCheck", m_ui.updateAutoCheck); loadSetting("showFilenameInLibrary", m_ui.showFilenameInLibrary); + loadSetting("backgroundImage", m_ui.bgImage); m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt()); diff --git a/src/platform/qt/SettingsView.h b/src/platform/qt/SettingsView.h index d13a6b453..3543419fc 100644 --- a/src/platform/qt/SettingsView.h +++ b/src/platform/qt/SettingsView.h @@ -71,6 +71,7 @@ public slots: private slots: void selectBios(QLineEdit*); void selectPath(QLineEdit*, QCheckBox*); + void selectImage(QLineEdit*); void updateConfig(); void reloadConfig(); void updateChecked(); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 95f71565b..03050e607 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -907,6 +907,41 @@ + + + + + + + 0 + 0 + + + + + + + + Browse + + + + + + + + + Qt::Horizontal + + + + + + + Custom border: + + + diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 02f8459cf..ce0c64a06 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1053,6 +1053,8 @@ void Window::reloadDisplayDriver() { #elif defined(M_CORE_GBA) m_display->setMinimumSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); #endif + + m_display->setBackgroundImage(QImage{m_config->getOption("backgroundImage")}); } void Window::reloadAudioDriver() { @@ -1875,6 +1877,13 @@ void Window::setupOptions() { updateTitle(); }, this); + ConfigOption* backgroundImage = m_config->addOption("backgroundImage"); + backgroundImage->connect([this](const QVariant& value) { + if (m_display) { + m_display->setBackgroundImage(QImage{value.toString()}); + } + }, this); + m_config->updateOption("backgroundImage"); } void Window::attachWidget(QWidget* widget) { From efbc4a49ce88eb313e6220ad7d16c035f8875801 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 Feb 2023 22:59:23 -0800 Subject: [PATCH 091/290] Util: Add some basic geometry math --- include/mgba-util/geometry.h | 7 +- src/util/CMakeLists.txt | 2 + src/util/geometry.c | 36 ++++++ src/util/test/geometry.c | 213 +++++++++++++++++++++++++++++++++++ 4 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 src/util/geometry.c create mode 100644 src/util/test/geometry.c diff --git a/include/mgba-util/geometry.h b/include/mgba-util/geometry.h index a98693006..4ed46f0b0 100644 --- a/include/mgba-util/geometry.h +++ b/include/mgba-util/geometry.h @@ -13,10 +13,13 @@ CXX_GUARD_START struct Rectangle { int x; int y; - unsigned width; - unsigned height; + int width; + int height; }; +void RectangleUnion(struct Rectangle* dst, const struct Rectangle* add); +void RectangleCenter(const struct Rectangle* ref, struct Rectangle* rect); + CXX_GUARD_END #endif diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 9246b20c4..e0aa3f8a4 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -16,6 +16,7 @@ set(SOURCE_FILES convolve.c elf-read.c export.c + geometry.c patch.c patch-fast.c patch-ips.c @@ -33,6 +34,7 @@ set(GUI_FILES gui/menu.c) set(TEST_FILES + test/geometry.c test/sfo.c test/string-parser.c test/string-utf8.c diff --git a/src/util/geometry.c b/src/util/geometry.c new file mode 100644 index 000000000..92eb92b9a --- /dev/null +++ b/src/util/geometry.c @@ -0,0 +1,36 @@ +/* Copyright (c) 2013-2023 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 + +void RectangleUnion(struct Rectangle* dst, const struct Rectangle* add) { + int x0 = dst->x; + int y0 = dst->y; + int x1 = dst->x + dst->width; + int y1 = dst->y + dst->height; + + if (add->x < x0) { + x0 = add->x; + } + if (add->y < y0) { + y0 = add->y; + } + if (add->x + add->width > x1) { + x1 = add->x + add->width; + } + if (add->y + add->height > y1) { + y1 = add->y + add->height; + } + + dst->x = x0; + dst->y = y0; + dst->width = x1 - x0; + dst->height = y1 - y0; +} + +void RectangleCenter(const struct Rectangle* ref, struct Rectangle* rect) { + rect->x = ref->x + (ref->width - rect->width) / 2; + rect->y = ref->y + (ref->height - rect->height) / 2; +} diff --git a/src/util/test/geometry.c b/src/util/test/geometry.c new file mode 100644 index 000000000..74171e0f7 --- /dev/null +++ b/src/util/test/geometry.c @@ -0,0 +1,213 @@ +/* Copyright (c) 2013-2023 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 "util/test/suite.h" + +#include + +M_TEST_DEFINE(unionRectOrigin) { + struct Rectangle a = { + .x = 0, + .y = 0, + .width = 1, + .height = 1 + }; + struct Rectangle b = { + .x = 1, + .y = 1, + .width = 1, + .height = 1 + }; + RectangleUnion(&a, &b); + assert_int_equal(a.x, 0); + assert_int_equal(a.y, 0); + assert_int_equal(a.width, 2); + assert_int_equal(a.height, 2); +} + +M_TEST_DEFINE(unionRectOriginSwapped) { + struct Rectangle a = { + .x = 1, + .y = 1, + .width = 1, + .height = 1 + }; + struct Rectangle b = { + .x = 0, + .y = 0, + .width = 1, + .height = 1 + }; + RectangleUnion(&a, &b); + assert_int_equal(a.x, 0); + assert_int_equal(a.y, 0); + assert_int_equal(a.width, 2); + assert_int_equal(a.height, 2); +} + +M_TEST_DEFINE(unionRectNonOrigin) { + struct Rectangle a = { + .x = 1, + .y = 1, + .width = 1, + .height = 1 + }; + struct Rectangle b = { + .x = 2, + .y = 2, + .width = 1, + .height = 1 + }; + RectangleUnion(&a, &b); + assert_int_equal(a.x, 1); + assert_int_equal(a.y, 1); + assert_int_equal(a.width, 2); + assert_int_equal(a.height, 2); +} + +M_TEST_DEFINE(unionRectOverlapping) { + struct Rectangle a = { + .x = 0, + .y = 0, + .width = 2, + .height = 2 + }; + struct Rectangle b = { + .x = 1, + .y = 1, + .width = 2, + .height = 2 + }; + RectangleUnion(&a, &b); + assert_int_equal(a.x, 0); + assert_int_equal(a.y, 0); + assert_int_equal(a.width, 3); + assert_int_equal(a.height, 3); +} + +M_TEST_DEFINE(unionRectSubRect) { + struct Rectangle a = { + .x = 0, + .y = 0, + .width = 3, + .height = 3 + }; + struct Rectangle b = { + .x = 1, + .y = 1, + .width = 1, + .height = 1 + }; + RectangleUnion(&a, &b); + assert_int_equal(a.x, 0); + assert_int_equal(a.y, 0); + assert_int_equal(a.width, 3); + assert_int_equal(a.height, 3); +} + +M_TEST_DEFINE(unionRectNegativeOrigin) { + struct Rectangle a = { + .x = -1, + .y = -1, + .width = 1, + .height = 1 + }; + struct Rectangle b = { + .x = 0, + .y = 0, + .width = 1, + .height = 1 + }; + RectangleUnion(&a, &b); + assert_int_equal(a.x, -1); + assert_int_equal(a.y, -1); + assert_int_equal(a.width, 2); + assert_int_equal(a.height, 2); +} + +M_TEST_DEFINE(centerRectBasic) { + struct Rectangle ref = { + .x = 0, + .y = 0, + .width = 4, + .height = 4 + }; + struct Rectangle a = { + .x = 0, + .y = 0, + .width = 2, + .height = 2 + }; + RectangleCenter(&ref, &a); + assert_int_equal(a.x, 1); + assert_int_equal(a.y, 1); +} + +M_TEST_DEFINE(centerRectRoundDown) { + struct Rectangle ref = { + .x = 0, + .y = 0, + .width = 4, + .height = 4 + }; + struct Rectangle a = { + .x = 0, + .y = 0, + .width = 1, + .height = 1 + }; + RectangleCenter(&ref, &a); + assert_int_equal(a.x, 1); + assert_int_equal(a.y, 1); +} + +M_TEST_DEFINE(centerRectRoundDown2) { + struct Rectangle ref = { + .x = 0, + .y = 0, + .width = 4, + .height = 4 + }; + struct Rectangle a = { + .x = 0, + .y = 0, + .width = 3, + .height = 2 + }; + RectangleCenter(&ref, &a); + assert_int_equal(a.x, 0); + assert_int_equal(a.y, 1); +} + +M_TEST_DEFINE(centerRectOffset) { + struct Rectangle ref = { + .x = 1, + .y = 1, + .width = 4, + .height = 4 + }; + struct Rectangle a = { + .x = 0, + .y = 0, + .width = 2, + .height = 2 + }; + RectangleCenter(&ref, &a); + assert_int_equal(a.x, 2); + assert_int_equal(a.y, 2); +} + +M_TEST_SUITE_DEFINE(Geometry, + cmocka_unit_test(unionRectOrigin), + cmocka_unit_test(unionRectOriginSwapped), + cmocka_unit_test(unionRectNonOrigin), + cmocka_unit_test(unionRectOverlapping), + cmocka_unit_test(unionRectSubRect), + cmocka_unit_test(unionRectNegativeOrigin), + cmocka_unit_test(centerRectBasic), + cmocka_unit_test(centerRectRoundDown), + cmocka_unit_test(centerRectRoundDown2), + cmocka_unit_test(centerRectOffset), +) From 09a53abe998e81d44c00eb10248e4f824881b7f6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 Feb 2023 23:12:30 -0800 Subject: [PATCH 092/290] OpenGL: Add basic border rendering to GL 1.x driver --- CMakeLists.txt | 5 ++ src/platform/opengl/gl.c | 101 ++++++++++++++++++++++------------ src/platform/opengl/gles2.c | 11 +++- src/platform/qt/DisplayGL.cpp | 45 ++++++++++++++- src/platform/qt/DisplayGL.h | 4 +- src/platform/video-backend.c | 22 ++++++++ src/platform/video-backend.h | 3 + 7 files changed, 152 insertions(+), 39 deletions(-) create mode 100644 src/platform/video-backend.c diff --git a/CMakeLists.txt b/CMakeLists.txt index ac498d0d2..e86a761bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -738,6 +738,11 @@ elseif(BUILD_GLES2) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libgles2") endif() +if(USE_EPOXY OR BUILD_GL OR BUILD_GLES2) + # This file should probably go somewhere else + list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/video-backend.c) +endif() + if(WIN32 AND NOT (LIBMGBA_ONLY OR SKIP_LIBRARY OR USE_EPOXY)) message(FATAL_ERROR "Windows requires epoxy module!") endif() diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index 8ad34197c..03bd831d2 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -9,9 +9,9 @@ static const GLint _glVertices[] = { 0, 0, - 256, 0, - 256, 256, - 0, 256 + 1, 0, + 1, 1, + 0, 1 }; static const GLint _glTexCoords[] = { @@ -101,15 +101,19 @@ static void mGLContextDeinit(struct VideoBackend* v) { } static void mGLContextResized(struct VideoBackend* v, unsigned w, unsigned h) { - struct mGLContext* context = (struct mGLContext*) v; unsigned drawW = w; unsigned drawH = h; + + unsigned maxW; + unsigned maxH; + VideoBackendGetFrameSize(v, &maxW, &maxH); + if (v->lockAspectRatio) { - lockAspectRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].width, context->layerDims[VIDEO_LAYER_IMAGE].height, &drawW, &drawH); + lockAspectRatioUInt(maxW, maxH, &drawW, &drawH); } if (v->lockIntegerScaling) { - lockIntegerRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].width, &drawW); - lockIntegerRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].height, &drawH); + lockIntegerRatioUInt(maxW, &drawW); + lockIntegerRatioUInt(maxH, &drawH); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); @@ -124,33 +128,7 @@ static void mGLContextClear(struct VideoBackend* v) { glClear(GL_COLOR_BUFFER_BIT); } -void mGLContextDrawFrame(struct VideoBackend* v) { - struct mGLContext* context = (struct mGLContext*) v; - glEnable(GL_TEXTURE_2D); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glEnableClientState(GL_VERTEX_ARRAY); - glVertexPointer(2, GL_INT, 0, _glVertices); - glTexCoordPointer(2, GL_INT, 0, _glTexCoords); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, context->layerDims[VIDEO_LAYER_IMAGE].width, context->layerDims[VIDEO_LAYER_IMAGE].height, 0, 0, 1); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - if (v->interframeBlending) { - glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); - glBlendColor(1, 1, 1, 0.5); - glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex ^ 1]); - if (v->filter) { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } else { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - } - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glEnable(GL_BLEND); - } - glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]); +static void _setFilter(struct VideoBackend* v) { if (v->filter) { glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -158,6 +136,61 @@ void mGLContextDrawFrame(struct VideoBackend* v) { glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } +} + +static void _setFrame(struct Rectangle* dims, int frameW, int frameH) { + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + glScissor(viewport[0] + dims->x * viewport[2] / frameW, + viewport[1] + dims->y * viewport[3] / frameH, + dims->width * viewport[2] / frameW, + dims->height * viewport[3] / frameH); + glTranslatef(dims->x, dims->y, 0); + glScalef(toPow2(dims->width), toPow2(dims->height), 1); +} + +void mGLContextDrawFrame(struct VideoBackend* v) { + struct mGLContext* context = (struct mGLContext*) v; + glEnable(GL_TEXTURE_2D); + glEnable(GL_SCISSOR_TEST); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(2, GL_INT, 0, _glVertices); + glTexCoordPointer(2, GL_INT, 0, _glTexCoords); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + unsigned frameW, frameH; + VideoBackendGetFrameSize(v, &frameW, &frameH); + glOrtho(0, frameW, frameH, 0, 0, 1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + int layer; + for (layer = 0; layer < VIDEO_LAYER_IMAGE; ++layer) { + if (context->layerDims[layer].width < 1 || context->layerDims[layer].height < 1) { + continue; + } + + glDisable(GL_BLEND); + glBindTexture(GL_TEXTURE_2D, context->layers[layer]); + _setFilter(v); + glPushMatrix(); + _setFrame(&context->layerDims[layer], frameW, frameH); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glPopMatrix(); + } + + _setFrame(&context->layerDims[VIDEO_LAYER_IMAGE], frameW, frameH); + if (v->interframeBlending) { + glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); + glBlendColor(1, 1, 1, 0.5); + glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex ^ 1]); + _setFilter(v); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glEnable(GL_BLEND); + } + glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]); + _setFilter(v); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDisable(GL_BLEND); } diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index fb2a07bba..674955a77 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -240,12 +240,17 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h) struct mGLES2Context* context = (struct mGLES2Context*) v; unsigned drawW = w; unsigned drawH = h; + + unsigned maxW; + unsigned maxH; + VideoBackendGetFrameSize(v, &maxW, &maxH); + if (v->lockAspectRatio) { - lockAspectRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].width, context->layerDims[VIDEO_LAYER_IMAGE].height, &drawW, &drawH); + lockAspectRatioUInt(maxW, maxH, &drawW, &drawH); } if (v->lockIntegerScaling) { - lockIntegerRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].width, &drawW); - lockIntegerRatioUInt(context->layerDims[VIDEO_LAYER_IMAGE].height, &drawH); + lockIntegerRatioUInt(maxW, &drawW); + lockIntegerRatioUInt(maxH, &drawH); } size_t n; for (n = 0; n < context->nShaders; ++n) { diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index fce25624d..e3155dc62 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -397,7 +397,7 @@ void DisplayGL::setVideoScale(int scale) { } void DisplayGL::setBackgroundImage(const QImage& image) { - // TODO + QMetaObject::invokeMethod(m_painter.get(), "setBackgroundImage", Q_ARG(const QImage&, image)); } void DisplayGL::resizeEvent(QResizeEvent* event) { @@ -631,12 +631,29 @@ void PainterGL::resizeContext() { Rectangle dims = {0, 0, size.width(), size.height()}; m_backend->setLayerDimensions(m_backend, VIDEO_LAYER_IMAGE, &dims); + recenterLayers(); } void PainterGL::setMessagePainter(MessagePainter* messagePainter) { m_messagePainter = messagePainter; } +void PainterGL::recenterLayers() { + const static std::initializer_list centeredLayers{VIDEO_LAYER_BACKGROUND, VIDEO_LAYER_IMAGE}; + Rectangle frame = {0}; + for (VideoLayer l : centeredLayers) { + Rectangle dims; + m_backend->layerDimensions(m_backend, l, &dims); + RectangleUnion(&frame, &dims); + } + for (VideoLayer l : centeredLayers) { + Rectangle dims; + m_backend->layerDimensions(m_backend, l, &dims); + RectangleCenter(&frame, &dims); + m_backend->setLayerDimensions(m_backend, l, &dims); + } +} + void PainterGL::resize(const QSize& size) { qreal r = m_window->devicePixelRatio(); m_size = size; @@ -995,6 +1012,32 @@ void PainterGL::updateFramebufferHandle() { m_context->setFramebufferHandle(m_bridgeTexIn); } +void PainterGL::setBackgroundImage(const QImage& image) { + if (!m_started) { + makeCurrent(); + } + + Rectangle dims = {0, 0, 0, 0}; + if (!image.isNull()) { + dims.width = static_cast(image.width()); + dims.height = static_cast(image.height()); + } + m_backend->setLayerDimensions(m_backend, VIDEO_LAYER_BACKGROUND, &dims); + recenterLayers(); + + if (!image.isNull()) { + m_background = image.convertToFormat(QImage::Format_RGB32); + m_background = m_background.rgbSwapped(); + m_backend->setImage(m_backend, VIDEO_LAYER_BACKGROUND, m_background.constBits()); + } else { + m_background = QImage(); + } + + if (!m_started) { + m_gl->doneCurrent(); + } +} + void PainterGL::swapTex() { if (!m_started) { return; diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 87dd983dd..eed3a37c0 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -109,7 +109,6 @@ public slots: void setVideoScale(int scale) override; void setBackgroundImage(const QImage&) override; - protected: virtual void paintEvent(QPaintEvent*) override { forceDraw(); } virtual void resizeEvent(QResizeEvent*) override; @@ -178,6 +177,7 @@ public slots: void filter(bool filter); void resizeContext(); void updateFramebufferHandle(); + void setBackgroundImage(const QImage&); void setShaders(struct VDir*); void clearShaders(); @@ -196,6 +196,7 @@ private: void performDraw(); void dequeue(); void dequeueAll(bool keep = false); + void recenterLayers(); std::array, 3> m_buffers; QList m_free; @@ -214,6 +215,7 @@ private: QWindow* m_window; QSurface* m_surface; QSurfaceFormat m_format; + QImage m_background; std::unique_ptr m_paintDev; std::unique_ptr m_gl; int m_finalTexIdx = 0; diff --git a/src/platform/video-backend.c b/src/platform/video-backend.c new file mode 100644 index 000000000..3447fcdd8 --- /dev/null +++ b/src/platform/video-backend.c @@ -0,0 +1,22 @@ +/* Copyright (c) 2013-2023 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 "video-backend.h" + +void VideoBackendGetFrameSize(const struct VideoBackend* v, unsigned* width, unsigned* height) { + *width = 0; + *height = 0; + int i; + for (i = 0; i < VIDEO_LAYER_MAX; ++i) { + struct Rectangle dims; + v->layerDimensions(v, i, &dims); + if (dims.x + dims.width > *width) { + *width = dims.x + dims.width; + } + if (dims.y + dims.height > *height) { + *height = dims.y + dims.height; + } + } +} diff --git a/src/platform/video-backend.h b/src/platform/video-backend.h index 3b7a4b245..3bb1c0a69 100644 --- a/src/platform/video-backend.h +++ b/src/platform/video-backend.h @@ -21,6 +21,7 @@ typedef void* WHandle; enum VideoLayer { VIDEO_LAYER_BACKGROUND = 0, + VIDEO_LAYER_BEZEL, VIDEO_LAYER_IMAGE, VIDEO_LAYER_OVERLAY, VIDEO_LAYER_MAX @@ -55,6 +56,8 @@ struct VideoShader { size_t nPasses; }; +void VideoBackendGetFrameSize(const struct VideoBackend*, unsigned* width, unsigned* height); + CXX_GUARD_END #endif From c7e4db58e30b78af8d7bc47f10ea6737df61c71d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 Feb 2023 23:36:57 -0800 Subject: [PATCH 093/290] OpenGL: Add basic border rendering to modern GL driver --- src/platform/opengl/gles2.c | 66 ++++++++++++++++++++++++++----------- src/platform/opengl/gles2.h | 2 ++ 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 674955a77..653b863a9 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -206,7 +206,10 @@ static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLa glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); #endif - if (layer == VIDEO_LAYER_BACKGROUND) { + unsigned newW; + unsigned newH; + VideoBackendGetFrameSize(v, &newW, &newH); + if (newW != context->width || newH != context->height) { size_t n; for (n = 0; n < context->nShaders; ++n) { if (context->shaders[n].width < 0 || context->shaders[n].height < 0) { @@ -215,6 +218,8 @@ static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLa } context->initialShader.dirty = true; context->interframeShader.dirty = true; + context->width = newW; + context->height = newH; } } @@ -241,9 +246,8 @@ static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h) unsigned drawW = w; unsigned drawH = h; - unsigned maxW; - unsigned maxH; - VideoBackendGetFrameSize(v, &maxW, &maxH); + unsigned maxW = context->width; + unsigned maxH = context->height; if (v->lockAspectRatio) { lockAspectRatioUInt(maxW, maxH, &drawW, &drawH); @@ -272,7 +276,7 @@ static void mGLES2ContextClear(struct VideoBackend* v) { glClear(GL_COLOR_BUFFER_BIT); } -void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { +static void _drawShaderEx(struct mGLES2Context* context, struct mGLES2Shader* shader, int layer) { GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); int drawW = shader->width; @@ -283,19 +287,19 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { drawW = viewport[2]; padW = viewport[0]; } else if (shader->width < 0) { - drawW = context->layerDims[VIDEO_LAYER_IMAGE].width * -shader->width; + drawW = context->width * -shader->width; } if (!drawH) { drawH = viewport[3]; padH = viewport[1]; } else if (shader->height < 0) { - drawH = context->layerDims[VIDEO_LAYER_IMAGE].height * -shader->height; + drawH = context->height * -shader->height; } if (shader->integerScaling) { padW = 0; padH = 0; - drawW -= drawW % context->layerDims[VIDEO_LAYER_IMAGE].width; - drawH -= drawH % context->layerDims[VIDEO_LAYER_IMAGE].height; + drawW -= drawW % context->width; + drawH -= drawH % context->height; } if (shader->dirty) { @@ -309,22 +313,29 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { shader->dirty = false; } + if (layer >= 0 && layer < VIDEO_LAYER_MAX) { + glViewport(context->layerDims[layer].x, context->layerDims[layer].y, context->layerDims[layer].width, context->layerDims[layer].height); + } else { + glViewport(padW, padH, drawW, drawH); + } + glBindFramebuffer(GL_FRAMEBUFFER, shader->fbo); - glViewport(padW, padH, drawW, drawH); if (shader->blend) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } else { glDisable(GL_BLEND); - glClearColor(0.f, 0.f, 0.f, 1.f); - glClear(GL_COLOR_BUFFER_BIT); + if (layer <= VIDEO_LAYER_BACKGROUND) { + glClearColor(0.f, 0.f, 0.f, 1.f); + glClear(GL_COLOR_BUFFER_BIT); + } } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glUseProgram(shader->program); glUniform1i(shader->texLocation, 0); - glUniform2f(shader->texSizeLocation, context->layerDims[VIDEO_LAYER_IMAGE].width, context->layerDims[VIDEO_LAYER_IMAGE].height); + glUniform2f(shader->texSizeLocation, context->width, context->height); glUniform2f(shader->outputSizeLocation, drawW, drawH); #ifdef BUILD_GLES3 if (shader->vao != (GLuint) -1) { @@ -391,21 +402,36 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { glBindTexture(GL_TEXTURE_2D, shader->tex); } +static void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { + _drawShaderEx(context, shader, -1); +} + void mGLES2ContextDrawFrame(struct VideoBackend* v) { struct mGLES2Context* context = (struct mGLES2Context*) v; glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, context->tex[VIDEO_LAYER_IMAGE]); GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); context->finalShader.filter = v->filter; - _drawShader(context, &context->initialShader); - if (v->interframeBlending) { - context->interframeShader.blend = true; - glViewport(0, 0, viewport[2], viewport[3]); - _drawShader(context, &context->interframeShader); + + int layer; + for (layer = 0; layer <= VIDEO_LAYER_IMAGE; ++layer) { + if (context->layerDims[layer].width < 1 || context->layerDims[layer].height < 1) { + continue; + } + glBindTexture(GL_TEXTURE_2D, context->tex[layer]); + _drawShaderEx(context, &context->initialShader, layer); + if (layer != VIDEO_LAYER_IMAGE) { + continue; + } + if (v->interframeBlending) { + context->interframeShader.blend = true; + glViewport(0, 0, viewport[2], viewport[3]); + _drawShader(context, &context->interframeShader); + } } + size_t n; for (n = 0; n < context->nShaders; ++n) { glViewport(0, 0, viewport[2], viewport[3]); @@ -416,7 +442,7 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { if (v->interframeBlending) { context->interframeShader.blend = false; glBindTexture(GL_TEXTURE_2D, context->tex[VIDEO_LAYER_IMAGE]); - _drawShader(context, &context->initialShader); + _drawShaderEx(context, &context->initialShader, VIDEO_LAYER_IMAGE); glViewport(0, 0, viewport[2], viewport[3]); _drawShader(context, &context->interframeShader); } diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h index a789305db..ade50e455 100644 --- a/src/platform/opengl/gles2.h +++ b/src/platform/opengl/gles2.h @@ -83,6 +83,8 @@ struct mGLES2Context { GLuint vbo; struct Rectangle layerDims[VIDEO_LAYER_MAX]; + unsigned width; + unsigned height; struct mGLES2Shader initialShader; struct mGLES2Shader finalShader; From 08f360af90613f1a2b4d0ae2b8a0d334f6eb9129 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 28 Feb 2023 00:13:32 -0800 Subject: [PATCH 094/290] Qt: Ask the display, not the core, what size it should be --- src/platform/qt/Display.h | 1 + src/platform/qt/DisplayGL.cpp | 16 ++++++++++++++++ src/platform/qt/DisplayGL.h | 4 ++++ src/platform/qt/DisplayQt.cpp | 18 +++++++++++++----- src/platform/qt/DisplayQt.h | 1 + src/platform/qt/Window.cpp | 4 ++-- 6 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index dda48d875..4fc100799 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -55,6 +55,7 @@ public: virtual int framebufferHandle() { return -1; } virtual void setVideoScale(int) {} virtual void setBackgroundImage(const QImage&) = 0; + virtual QSize contentSize() const = 0; virtual void setVideoProxy(std::shared_ptr proxy) { m_videoProxy = proxy; } std::shared_ptr videoProxy() { return m_videoProxy; } diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index e3155dc62..dc7ad9a86 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -245,6 +245,8 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { show(); m_gl->reset(); } + + QTimer::singleShot(8, this, &DisplayGL::updateContentSize); } bool DisplayGL::supportsFormat(const QSurfaceFormat& format) { @@ -331,12 +333,14 @@ void DisplayGL::unpauseDrawing() { if (!m_gl && shouldDisableUpdates()) { setUpdatesEnabled(false); } + QMetaObject::invokeMethod(this, "updateContentSize", Qt::QueuedConnection); } } void DisplayGL::forceDraw() { if (m_hasStarted) { QMetaObject::invokeMethod(m_painter.get(), "forceDraw"); + QMetaObject::invokeMethod(this, "updateContentSize", Qt::QueuedConnection); } } @@ -398,6 +402,7 @@ void DisplayGL::setVideoScale(int scale) { void DisplayGL::setBackgroundImage(const QImage& image) { QMetaObject::invokeMethod(m_painter.get(), "setBackgroundImage", Q_ARG(const QImage&, image)); + QMetaObject::invokeMethod(this, "updateContentSize", Qt::QueuedConnection); } void DisplayGL::resizeEvent(QResizeEvent* event) { @@ -452,6 +457,10 @@ void DisplayGL::setupProxyThread() { m_proxyThread.start(); } +void DisplayGL::updateContentSize() { + QMetaObject::invokeMethod(m_painter.get(), "contentSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QSize, m_cachedContentSize)); +} + int DisplayGL::framebufferHandle() { return m_painter->glTex(); } @@ -972,6 +981,13 @@ VideoShader* PainterGL::shaders() { return &m_shader; } +QSize PainterGL::contentSize() const { + unsigned width, height; + VideoBackendGetFrameSize(m_backend, &width, &height); + return {static_cast(width > static_cast(INT_MAX) ? INT_MAX : width), + static_cast(height > static_cast(INT_MAX) ? INT_MAX : height)}; +} + int PainterGL::glTex() { #if defined(BUILD_GLES2) || defined(BUILD_GLES3) if (supportsShaders()) { diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index eed3a37c0..a02b2289c 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -88,6 +88,7 @@ public: VideoShader* shaders() override; void setVideoProxy(std::shared_ptr) override; int framebufferHandle() override; + QSize contentSize() const override { return m_cachedContentSize; } static bool supportsFormat(const QSurfaceFormat&); @@ -115,6 +116,7 @@ protected: private slots: void setupProxyThread(); + void updateContentSize(); private: void resizePainter(); @@ -131,6 +133,7 @@ private: mGLWidget* m_gl; QOffscreenSurface m_proxySurface; std::unique_ptr m_proxyContext; + QSize m_cachedContentSize; }; class PainterGL : public QObject { @@ -182,6 +185,7 @@ public slots: void setShaders(struct VDir*); void clearShaders(); VideoShader* shaders(); + QSize contentSize() const; signals: void created(); diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 7669b5cf1..dee0d1244 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -106,22 +106,18 @@ void DisplayQt::paintEvent(QPaintEvent*) { QRect bgRect(0, 0, m_background.width(), m_background.height()); QRect imRect(0, 0, m_width, m_height); - QSize outerFrame; + QSize outerFrame = contentSize(); if (bgRect.width() > imRect.width()) { imRect.moveLeft(bgRect.width() - imRect.width()); - outerFrame.setWidth(bgRect.width()); } else { bgRect.moveLeft(imRect.width() - bgRect.width()); - outerFrame.setWidth(imRect.width()); } if (bgRect.height() > imRect.height()) { imRect.moveTop(bgRect.height() - imRect.height()); - outerFrame.setHeight(bgRect.height()); } else { bgRect.moveTop(imRect.height() - bgRect.height()); - outerFrame.setHeight(imRect.height()); } QRect full(clampSize(outerFrame, size(), isAspectRatioLocked(), isIntegerScalingLocked())); @@ -184,3 +180,15 @@ void DisplayQt::paintEvent(QPaintEvent*) { messagePainter()->paint(&painter); } } + +QSize DisplayQt::contentSize() const { + QSize outerFrame(m_width, m_height); + + if (m_background.width() > outerFrame.width()) { + outerFrame.setWidth(m_background.width()); + } + if (m_background.height() > outerFrame.height()) { + outerFrame.setHeight(m_background.height()); + } + return outerFrame; +} diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index acdd95f24..889a6a367 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -22,6 +22,7 @@ public: bool isDrawing() const override { return m_isDrawing; } bool supportsShaders() const override { return false; } VideoShader* shaders() override { return nullptr; } + QSize contentSize() const override; public slots: void stopDrawing() override; diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index ce0c64a06..083732cd8 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1488,8 +1488,8 @@ void Window::setupMenu(QMenuBar* menubar) { Action* setSize = m_frameSizes[i]; showNormal(); QSize size(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); - if (m_controller) { - size = m_controller->screenDimensions(); + if (m_display) { + size = m_display->contentSize(); } size *= i; m_savedScale = i; From 48c9261b057a137caaf906c2482705b4a45c2f68 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 28 Feb 2023 22:27:11 -0800 Subject: [PATCH 095/290] SDL: Refactor use of VideoBackend to slim down GL backends --- src/platform/sdl/gl-common.c | 42 ++++++++++++++++++++++++++++++++ src/platform/sdl/gl-common.h | 1 + src/platform/sdl/gl-sdl.c | 46 ++---------------------------------- src/platform/sdl/gles2-sdl.c | 36 ++-------------------------- src/platform/sdl/main.h | 2 ++ 5 files changed, 49 insertions(+), 78 deletions(-) diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c index 264e3cafe..47988497d 100644 --- a/src/platform/sdl/gl-common.c +++ b/src/platform/sdl/gl-common.c @@ -5,6 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "main.h" +#include +#include #include void mSDLGLDoViewport(int w, int h, struct VideoBackend* v) { @@ -66,3 +68,43 @@ bool mSDLGLCommonInit(struct mSDLRenderer* renderer) { #endif return true; } + +void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user) { + struct mCoreThread* context = user; + SDL_Event event; + struct VideoBackend* v = renderer->backend; + + while (mCoreThreadIsActive(context)) { + while (SDL_PollEvent(&event)) { + mSDLHandleEvent(context, &renderer->player, &event); + // Event handling can change the size of the screen + if (renderer->player.windowUpdated) { +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); +#else + renderer->viewportWidth = renderer->player.newWidth; + renderer->viewportHeight = renderer->player.newHeight; + mSDLGLCommonInit(renderer); +#endif + mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, v); + renderer->player.windowUpdated = 0; + } + } + renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height); + struct Rectangle dims; + v->layerDimensions(v, VIDEO_LAYER_IMAGE, &dims); + if (renderer->width != dims.width || renderer->height != dims.height) { + renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, renderer->width); + dims.width = renderer->width; + dims.height = renderer->height; + v->setLayerDimensions(v, VIDEO_LAYER_IMAGE, &dims); + } + + if (mCoreSyncWaitFrameStart(&context->impl->sync)) { + v->setImage(v, VIDEO_LAYER_IMAGE, renderer->outputBuffer); + } + mCoreSyncWaitFrameEnd(&context->impl->sync); + v->drawFrame(v); + v->swap(v); + } +} diff --git a/src/platform/sdl/gl-common.h b/src/platform/sdl/gl-common.h index be98d7964..5658abee4 100644 --- a/src/platform/sdl/gl-common.h +++ b/src/platform/sdl/gl-common.h @@ -15,6 +15,7 @@ struct mSDLRenderer; void mSDLGLDoViewport(int w, int h, struct VideoBackend* v); void mSDLGLCommonSwap(struct VideoBackend* context); bool mSDLGLCommonInit(struct mSDLRenderer* renderer); +void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user); CXX_GUARD_END diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c index a6d0a4e0e..226353f90 100644 --- a/src/platform/sdl/gl-sdl.c +++ b/src/platform/sdl/gl-sdl.c @@ -8,19 +8,17 @@ #include "gl-common.h" #include -#include -#include #include "platform/opengl/gl.h" static bool mSDLGLInit(struct mSDLRenderer* renderer); -static void mSDLGLRunloop(struct mSDLRenderer* renderer, void* user); static void mSDLGLDeinit(struct mSDLRenderer* renderer); void mSDLGLCreate(struct mSDLRenderer* renderer) { renderer->init = mSDLGLInit; renderer->deinit = mSDLGLDeinit; - renderer->runloop = mSDLGLRunloop; + renderer->runloop = mSDLGLCommonRunloop; + renderer->backend = &renderer->gl.d; } bool mSDLGLInit(struct mSDLRenderer* renderer) { @@ -49,46 +47,6 @@ bool mSDLGLInit(struct mSDLRenderer* renderer) { return true; } -void mSDLGLRunloop(struct mSDLRenderer* renderer, void* user) { - struct mCoreThread* context = user; - SDL_Event event; - struct VideoBackend* v = &renderer->gl.d; - - while (mCoreThreadIsActive(context)) { - while (SDL_PollEvent(&event)) { - mSDLHandleEvent(context, &renderer->player, &event); - // Event handling can change the size of the screen - if (renderer->player.windowUpdated) { -#if SDL_VERSION_ATLEAST(2, 0, 0) - SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); -#else - renderer->viewportWidth = renderer->player.newWidth; - renderer->viewportHeight = renderer->player.newHeight; - mSDLGLCommonInit(renderer); -#endif - mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, v); - renderer->player.windowUpdated = 0; - } - } - renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height); - struct Rectangle dims; - v->layerDimensions(v, VIDEO_LAYER_IMAGE, &dims); - if (renderer->width != dims.width || renderer->height != dims.height) { - renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, renderer->width); - dims.width = renderer->width; - dims.height = renderer->height; - v->setLayerDimensions(v, VIDEO_LAYER_IMAGE, &dims); - } - - if (mCoreSyncWaitFrameStart(&context->impl->sync)) { - v->setImage(v, VIDEO_LAYER_IMAGE, renderer->outputBuffer); - } - mCoreSyncWaitFrameEnd(&context->impl->sync); - v->drawFrame(v); - v->swap(v); - } -} - void mSDLGLDeinit(struct mSDLRenderer* renderer) { if (renderer->gl.d.deinit) { renderer->gl.d.deinit(&renderer->gl.d); diff --git a/src/platform/sdl/gles2-sdl.c b/src/platform/sdl/gles2-sdl.c index e65d430ee..f5574435f 100644 --- a/src/platform/sdl/gles2-sdl.c +++ b/src/platform/sdl/gles2-sdl.c @@ -11,20 +11,19 @@ #endif #include -#include #ifdef __linux__ #include #endif static bool mSDLGLES2Init(struct mSDLRenderer* renderer); -static void mSDLGLES2Runloop(struct mSDLRenderer* renderer, void* user); static void mSDLGLES2Deinit(struct mSDLRenderer* renderer); void mSDLGLES2Create(struct mSDLRenderer* renderer) { renderer->init = mSDLGLES2Init; renderer->deinit = mSDLGLES2Deinit; - renderer->runloop = mSDLGLES2Runloop; + renderer->runloop = mSDLGLCommonRunloop; + renderer->backend = &renderer->gl2.d; } bool mSDLGLES2Init(struct mSDLRenderer* renderer) { @@ -63,37 +62,6 @@ bool mSDLGLES2Init(struct mSDLRenderer* renderer) { return true; } -void mSDLGLES2Runloop(struct mSDLRenderer* renderer, void* user) { - struct mCoreThread* context = user; - SDL_Event event; - struct VideoBackend* v = &renderer->gl2.d; - - while (mCoreThreadIsActive(context)) { - while (SDL_PollEvent(&event)) { - mSDLHandleEvent(context, &renderer->player, &event); - // Event handling can change the size of the screen - if (renderer->player.windowUpdated) { -#if SDL_VERSION_ATLEAST(2, 0, 0) - SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); -#else - renderer->viewportWidth = renderer->player.newWidth; - renderer->viewportHeight = renderer->player.newHeight; - mSDLGLCommonInit(renderer); -#endif - mSDLGLDoViewport(renderer->viewportWidth, renderer->viewportHeight, v); - renderer->player.windowUpdated = 0; - } - } - - if (mCoreSyncWaitFrameStart(&context->impl->sync)) { - v->setImage(v, VIDEO_LAYER_IMAGE, renderer->outputBuffer); - } - mCoreSyncWaitFrameEnd(&context->impl->sync); - v->drawFrame(v); - v->swap(v); - } -} - void mSDLGLES2Deinit(struct mSDLRenderer* renderer) { if (renderer->gl2.d.deinit) { renderer->gl2.d.deinit(&renderer->gl2.d); diff --git a/src/platform/sdl/main.h b/src/platform/sdl/main.h index c97e50e33..10135da2f 100644 --- a/src/platform/sdl/main.h +++ b/src/platform/sdl/main.h @@ -76,6 +76,8 @@ struct mSDLRenderer { struct mGLES2Context gl2; #endif + struct VideoBackend* backend; + #ifdef USE_PIXMAN pixman_image_t* pix; pixman_image_t* screenpix; From 44fb887737a1b742b6349fb63716677993073b84 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 28 Feb 2023 23:34:55 -0800 Subject: [PATCH 096/290] SDL: Border rendering --- src/platform/sdl/gl-common.c | 77 ++++++++++++++++++++++++++++++++++++ src/platform/sdl/gl-common.h | 1 + src/platform/video-backend.c | 19 ++++----- src/platform/video-backend.h | 1 + 4 files changed, 89 insertions(+), 9 deletions(-) diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c index 47988497d..53219a7bb 100644 --- a/src/platform/sdl/gl-common.c +++ b/src/platform/sdl/gl-common.c @@ -9,6 +9,11 @@ #include #include +#ifdef USE_PNG +#include +#include +#endif + void mSDLGLDoViewport(int w, int h, struct VideoBackend* v) { v->contextResized(v, w, h); v->clear(v); @@ -26,6 +31,60 @@ void mSDLGLCommonSwap(struct VideoBackend* context) { #endif } +bool mSDLGLCommonLoadBackground(struct VideoBackend* context) { +#ifdef USE_PNG + struct mSDLRenderer* renderer = context->user; + const char* bgImage = mCoreConfigGetValue(&renderer->core->config, "backgroundImage"); + if (!bgImage) { + return false; + } + struct VFile* vf = VFileOpen(bgImage, O_RDONLY); + if (!vf) { + return false; + } + + bool ok = false; + png_structp png = PNGReadOpen(vf, 0); + png_infop info = png_create_info_struct(png); + png_infop end = png_create_info_struct(png); + if (!png || !info || !end) { + goto done; + } + + if (!PNGReadHeader(png, info)) { + goto done; + } + unsigned width = png_get_image_width(png, info); + unsigned height = png_get_image_height(png, info); + uint32_t* pixels = malloc(width * height * 4); + if (!pixels) { + goto done; + } + + if (!PNGReadPixels(png, info, pixels, width, height, width) || !PNGReadFooter(png, end)) { + free(pixels); + goto done; + } + + struct Rectangle dims = { + .width = width, + .height = height + }; + context->setLayerDimensions(context, VIDEO_LAYER_BACKGROUND, &dims); + context->setImage(context, VIDEO_LAYER_BACKGROUND, pixels); + free(pixels); + ok = true; + +done: + PNGReadClose(png, info, end); + vf->close(vf); + return ok; +#else + UNUSED(context); + return false; +#endif +} + bool mSDLGLCommonInit(struct mSDLRenderer* renderer) { #ifndef COLOR_16_BIT SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); @@ -74,6 +133,24 @@ void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user) { SDL_Event event; struct VideoBackend* v = renderer->backend; + if (mSDLGLCommonLoadBackground(v)) { + renderer->player.windowUpdated = true; + + struct Rectangle frame; + VideoBackendGetFrame(v, &frame); + int i; + for (i = 0; i <= VIDEO_LAYER_IMAGE; ++i) { + struct Rectangle dims; + v->layerDimensions(v, i, &dims); + RectangleCenter(&frame, &dims); + v->setLayerDimensions(v, i, &dims); + } + +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_SetWindowSize(renderer->window, frame.width * renderer->ratio, frame.height * renderer->ratio); +#endif + } + while (mCoreThreadIsActive(context)) { while (SDL_PollEvent(&event)) { mSDLHandleEvent(context, &renderer->player, &event); diff --git a/src/platform/sdl/gl-common.h b/src/platform/sdl/gl-common.h index 5658abee4..f52cfd633 100644 --- a/src/platform/sdl/gl-common.h +++ b/src/platform/sdl/gl-common.h @@ -16,6 +16,7 @@ void mSDLGLDoViewport(int w, int h, struct VideoBackend* v); void mSDLGLCommonSwap(struct VideoBackend* context); bool mSDLGLCommonInit(struct mSDLRenderer* renderer); void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user); +bool mSDLGLCommonLoadBackground(struct VideoBackend* context); CXX_GUARD_END diff --git a/src/platform/video-backend.c b/src/platform/video-backend.c index 3447fcdd8..998764d0d 100644 --- a/src/platform/video-backend.c +++ b/src/platform/video-backend.c @@ -5,18 +5,19 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "video-backend.h" -void VideoBackendGetFrameSize(const struct VideoBackend* v, unsigned* width, unsigned* height) { - *width = 0; - *height = 0; +void VideoBackendGetFrame(const struct VideoBackend* v, struct Rectangle* frame) { + memset(frame, 0, sizeof(*frame)); int i; for (i = 0; i < VIDEO_LAYER_MAX; ++i) { struct Rectangle dims; v->layerDimensions(v, i, &dims); - if (dims.x + dims.width > *width) { - *width = dims.x + dims.width; - } - if (dims.y + dims.height > *height) { - *height = dims.y + dims.height; - } + RectangleUnion(frame, &dims); } } + +void VideoBackendGetFrameSize(const struct VideoBackend* v, unsigned* width, unsigned* height) { + struct Rectangle frame; + VideoBackendGetFrame(v, &frame); + *width = frame.width; + *height = frame.height; +} diff --git a/src/platform/video-backend.h b/src/platform/video-backend.h index 3bb1c0a69..5a3319721 100644 --- a/src/platform/video-backend.h +++ b/src/platform/video-backend.h @@ -56,6 +56,7 @@ struct VideoShader { size_t nPasses; }; +void VideoBackendGetFrame(const struct VideoBackend*, struct Rectangle* frame); void VideoBackendGetFrameSize(const struct VideoBackend*, unsigned* width, unsigned* height); CXX_GUARD_END From 89f8873df366a99dfdf40347f72e68763fb50bd9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Mar 2023 14:21:47 -0800 Subject: [PATCH 097/290] GBA Saveata: Fix fumbled check --- src/gba/savedata.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gba/savedata.c b/src/gba/savedata.c index 7097e4a63..2d377a875 100644 --- a/src/gba/savedata.c +++ b/src/gba/savedata.c @@ -184,7 +184,7 @@ size_t GBASavedataSize(const struct GBASavedata* savedata) { bool GBASavedataLoad(struct GBASavedata* savedata, struct VFile* in) { if (savedata->data) { - if (!in || savedata->type != SAVEDATA_FORCE_NONE) { + if (!in || savedata->type == SAVEDATA_FORCE_NONE) { return false; } ssize_t size = GBASavedataSize(savedata); From a7c232b2841af19eb66c619d1e06361696494de8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 5 Mar 2023 23:55:54 -0800 Subject: [PATCH 098/290] Qt: Fix black screen when starting with a game (fixes #2781) --- CHANGES | 1 + src/platform/qt/DisplayGL.cpp | 18 +++++++++++++++++- src/platform/qt/DisplayGL.h | 2 ++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 169a69a15..11e7601df 100644 --- a/CHANGES +++ b/CHANGES @@ -24,6 +24,7 @@ Other fixes: - Qt: Fix a handful of edge cases with graphics viewers (fixes mgba.io/i/2827) - Qt: Fix full-buffer rewind - Qt: Fix crash if loading a shader fails + - Qt: Fix black screen when starting with a game (fixes mgba.io/i/2781) - Scripting: Fix receiving packets for client sockets - Scripting: Fix empty receive calls returning unknown error on Windows Misc: diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 95549b0c8..acc001f9a 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -48,6 +48,11 @@ using QOpenGLFunctions_Baseline = QOpenGLFunctions_3_2_Core; using namespace QGBA; +enum ThreadStartFrom { + START = 1, + PROXY = 2, +}; + QHash DisplayGL::s_supports; uint qHash(const QSurfaceFormat& format, uint seed) { @@ -235,7 +240,16 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { messagePainter()->resize(size(), devicePixelRatio()); #endif - CoreController::Interrupter interrupter(controller); + startThread(ThreadStartFrom::START); +} + +void DisplayGL::startThread(int from) { + m_threadStartPending |= from; + if (m_threadStartPending < 3) { + return; + } + + CoreController::Interrupter interrupter(m_context); QMetaObject::invokeMethod(m_painter.get(), "start"); if (!m_gl) { if (shouldDisableUpdates()) { @@ -310,6 +324,7 @@ void DisplayGL::stopDrawing() { hide(); } setUpdatesEnabled(true); + m_threadStartPending &= ~1; } m_context.reset(); } @@ -435,6 +450,7 @@ void DisplayGL::setupProxyThread() { #if defined(_WIN32) && defined(USE_EPOXY) epoxy_handle_external_wglMakeCurrent(); #endif + QMetaObject::invokeMethod(this, "startThread", Q_ARG(int, ThreadStartFrom::PROXY)); }); connect(m_painter.get(), &PainterGL::texSwapped, m_proxyContext.get(), [this]() { if (!m_context->hardwareAccelerated()) { diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 3ed31a9d8..a0c0c17d4 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -113,6 +113,7 @@ protected: virtual void resizeEvent(QResizeEvent*) override; private slots: + void startThread(int); void setupProxyThread(); private: @@ -123,6 +124,7 @@ private: bool m_isDrawing = false; bool m_hasStarted = false; + int m_threadStartPending = 0; std::unique_ptr m_painter; QThread m_drawThread; QThread m_proxyThread; From 5265573c0c2f9ac0b1480eed86ac36548a039db0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 6 Mar 2023 00:08:41 -0800 Subject: [PATCH 099/290] Qt: Fix buffer termination issue --- src/platform/qt/CoreManager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/qt/CoreManager.cpp b/src/platform/qt/CoreManager.cpp index cac1c2560..7bd6bd309 100644 --- a/src/platform/qt/CoreManager.cpp +++ b/src/platform/qt/CoreManager.cpp @@ -16,6 +16,7 @@ #endif #include +#include #include using namespace QGBA; @@ -161,7 +162,7 @@ CoreController* CoreManager::loadBIOS(int platform, const QString& path) { mCoreConfigSetOverrideIntValue(&core->config, "skipBios", 0); QByteArray bytes(info.baseName().toUtf8()); - strncpy(core->dirs.baseName, bytes.constData(), sizeof(core->dirs.baseName)); + strlcpy(core->dirs.baseName, bytes.constData(), sizeof(core->dirs.baseName)); bytes = info.dir().canonicalPath().toUtf8(); mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData())); From 7386e60ac0367337ede01d72cf8acf649127208b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 6 Mar 2023 14:51:22 -0800 Subject: [PATCH 100/290] GDB: Enable NODELAY on GDB stub connections --- src/debugger/gdb-stub.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index ca8c0d1ec..d83c6a3ab 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -846,6 +846,7 @@ void GDBStubUpdate(struct GDBStub* stub) { } else { goto connectionLost; } + SocketSetTCPPush(stub->connection); } while (true) { if (stub->shouldBlock) { From fe8b436b41244fd52ac001dcad319aef327d238d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 6 Mar 2023 14:57:05 -0800 Subject: [PATCH 101/290] GDB: Ugh --- src/debugger/gdb-stub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index d83c6a3ab..6c318179c 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -846,7 +846,7 @@ void GDBStubUpdate(struct GDBStub* stub) { } else { goto connectionLost; } - SocketSetTCPPush(stub->connection); + SocketSetTCPPush(stub->connection, 1); } while (true) { if (stub->shouldBlock) { From fd0deaaecceec513849e2d141c3fbe420dd6aee9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 14 Mar 2023 01:11:11 -0700 Subject: [PATCH 102/290] GBA Memory: Play slightly nicer with CSE --- src/gba/memory.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gba/memory.c b/src/gba/memory.c index 4f3063cfb..6d0ff6156 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -405,7 +405,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { #define LOAD_CART \ wait += waitstatesRegion[address >> BASE_OFFSET]; \ - if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) { \ + if ((address & (GBA_SIZE_ROM0 - 4)) < memory->romSize) { \ LOAD_32(value, address & (GBA_SIZE_ROM0 - 4), memory->rom); \ } else if (memory->vfame.cartType) { \ value = GBAVFameGetPatternValue(address, 32); \ @@ -570,11 +570,11 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { case GBA_REGION_ROM1_EX: case GBA_REGION_ROM2: wait = memory->waitstatesNonseq16[address >> BASE_OFFSET]; - if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) { + if ((address & (GBA_SIZE_ROM0 - 2)) < memory->romSize) { LOAD_16(value, address & (GBA_SIZE_ROM0 - 2), memory->rom); } else if (memory->vfame.cartType) { value = GBAVFameGetPatternValue(address, 16); - } else if ((address & (GBA_SIZE_ROM0 - 1)) >= AGB_PRINT_BASE) { + } else if ((address & (GBA_SIZE_ROM0 - 2)) >= AGB_PRINT_BASE) { uint32_t agbPrintAddr = address & 0x00FFFFFF; if (agbPrintAddr == AGB_PRINT_PROTECT) { value = memory->agbPrintProtect; @@ -595,7 +595,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { value = GBASavedataReadEEPROM(&memory->savedata); } else if ((address & 0x0DFC0000) >= 0x0DF80000 && memory->hw.devices & HW_EREADER) { value = GBACartEReaderRead(&memory->ereader, address); - } else if ((address & (GBA_SIZE_ROM0 - 1)) < memory->romSize) { + } else if ((address & (GBA_SIZE_ROM0 - 2)) < memory->romSize) { LOAD_16(value, address & (GBA_SIZE_ROM0 - 2), memory->rom); } else if (memory->vfame.cartType) { value = GBAVFameGetPatternValue(address, 16); @@ -1219,7 +1219,7 @@ void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* o mLOG(GBA_MEM, STUB, "Unimplemented memory Patch32: 0x%08X", address); break; case GBA_REGION_PALETTE_RAM: - LOAD_32(oldValue, address & (GBA_SIZE_PALETTE_RAM - 1), gba->video.palette); + LOAD_32(oldValue, address & (GBA_SIZE_PALETTE_RAM - 4), gba->video.palette); STORE_32(value, address & (GBA_SIZE_PALETTE_RAM - 4), gba->video.palette); gba->video.renderer->writePalette(gba->video.renderer, address & (GBA_SIZE_PALETTE_RAM - 4), value); gba->video.renderer->writePalette(gba->video.renderer, (address & (GBA_SIZE_PALETTE_RAM - 4)) + 2, value >> 16); @@ -1320,7 +1320,7 @@ void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* o case GBA_REGION_ROM2: case GBA_REGION_ROM2_EX: _pristineCow(gba); - if ((address & (GBA_SIZE_ROM0 - 1)) >= gba->memory.romSize) { + if ((address & (GBA_SIZE_ROM0 - 2)) >= gba->memory.romSize) { gba->memory.romSize = (address & (GBA_SIZE_ROM0 - 2)) + 2; gba->memory.romMask = toPow2(gba->memory.romSize) - 1; } From 434789c6d1ab825bc4b2954df852af3817f63133 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 14 Mar 2023 01:27:19 -0700 Subject: [PATCH 103/290] Qt: Placate Coverity a bit --- src/platform/qt/ApplicationUpdater.cpp | 1 + src/platform/qt/BattleChipModel.h | 4 ++-- src/platform/qt/DisplayQt.h | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/platform/qt/ApplicationUpdater.cpp b/src/platform/qt/ApplicationUpdater.cpp index 21def050c..7eebe2c57 100644 --- a/src/platform/qt/ApplicationUpdater.cpp +++ b/src/platform/qt/ApplicationUpdater.cpp @@ -100,6 +100,7 @@ ApplicationUpdater::UpdateInfo ApplicationUpdater::currentVersion() { info.version = QLatin1String(projectVersion); info.rev = gitRevision; info.commit = QLatin1String(gitCommit); + info.size = 0; return info; } diff --git a/src/platform/qt/BattleChipModel.h b/src/platform/qt/BattleChipModel.h index c0f115896..9c22002ee 100644 --- a/src/platform/qt/BattleChipModel.h +++ b/src/platform/qt/BattleChipModel.h @@ -49,10 +49,10 @@ private: BattleChip createChip(int id) const; QMap m_chipIdToName; - int m_flavor; + int m_flavor = 0; int m_scale = 1; QList m_deck; }; -} \ No newline at end of file +} diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index e8623468f..269848229 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -42,8 +42,8 @@ protected: private: bool m_isDrawing = false; - int m_width; - int m_height; + int m_width = -1; + int m_height = -1; QImage m_backing{nullptr}; QImage m_oldBacking{nullptr}; std::shared_ptr m_context = nullptr; From ea3e6d6b548bf0b81e39a57d51baa4c0e959d411 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 15 Mar 2023 20:52:52 -0700 Subject: [PATCH 104/290] Core: Fixx M_*8 macros --- include/mgba/core/interface.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h index 9046d1a4f..fc6f7ee6c 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -27,9 +27,9 @@ typedef uint32_t color_t; #define M_G5(X) (((X) >> 5) & 0x1F) #define M_B5(X) (((X) >> 10) & 0x1F) -#define M_R8(X) (((((X) << 3) & 0xF8) * 0x21) >> 5) -#define M_G8(X) (((((X) >> 2) & 0xF8) * 0x21) >> 5) -#define M_B8(X) (((((X) >> 7) & 0xF8) * 0x21) >> 5) +#define M_R8(X) (((((X) << 3) & 0xF8) * 0x21) >> 2) +#define M_G8(X) (((((X) >> 2) & 0xF8) * 0x21) >> 2) +#define M_B8(X) (((((X) >> 7) & 0xF8) * 0x21) >> 2) #define M_RGB5_TO_BGR8(X) ((M_R5(X) << 3) | (M_G5(X) << 11) | (M_B5(X) << 19)) #define M_RGB5_TO_RGB8(X) ((M_R5(X) << 19) | (M_G5(X) << 11) | (M_B5(X) << 3)) From cd0b5193cb1eccad4400c6de587cf3352fd3fca3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 15 Mar 2023 22:44:52 -0700 Subject: [PATCH 105/290] Core: An empty config string is a null config value --- src/core/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/config.c b/src/core/config.c index 715628f0f..b9f0167ec 100644 --- a/src/core/config.c +++ b/src/core/config.c @@ -79,7 +79,7 @@ static const char* _lookupValue(const struct mCoreConfig* config, const char* ke static bool _lookupCharValue(const struct mCoreConfig* config, const char* key, char** out) { const char* value = _lookupValue(config, key); - if (!value) { + if (!value || !value[0]) { return false; } if (*out) { From fc35395ab83fbe8328515e1a1528f614a1899297 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 16 Mar 2023 23:37:54 -0700 Subject: [PATCH 106/290] Core: Handle relative paths for saves, screenshots, etc consistently (fixes #2826) --- CHANGES | 1 + CMakeLists.txt | 2 ++ include/mgba-util/vfs.h | 3 +++ src/core/directories.c | 39 ++++++++++++++++++++++++--------------- src/util/vfs.c | 39 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 15 deletions(-) diff --git a/CHANGES b/CHANGES index 11e7601df..dfcbfceb8 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,7 @@ Other fixes: - Scripting: Fix receiving packets for client sockets - Scripting: Fix empty receive calls returning unknown error on Windows Misc: + - Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826) - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs - Qt: Include wayland QPA in AppImage (fixes mgba.io/i/2796) diff --git a/CMakeLists.txt b/CMakeLists.txt index ac498d0d2..aebbfecae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -338,6 +338,8 @@ find_function(popcount32) find_function(futimens) find_function(futimes) +find_function(realpath) + if(ANDROID AND ANDROID_NDK_MAJOR GREATER 13) find_function(localtime_r) set(HAVE_STRTOF_L ON) diff --git a/include/mgba-util/vfs.h b/include/mgba-util/vfs.h index 334f84419..eee68b99e 100644 --- a/include/mgba-util/vfs.h +++ b/include/mgba-util/vfs.h @@ -100,6 +100,9 @@ struct VFile* VFileFromFILE(FILE* file); void separatePath(const char* path, char* dirname, char* basename, char* extension); +bool isAbsolute(const char* path); +void makeAbsolute(const char* path, const char* base, char* out); + struct VFile* VDirFindFirst(struct VDir* dir, bool (*filter)(struct VFile*)); struct VFile* VDirFindNextAvailable(struct VDir*, const char* basename, const char* infix, const char* suffix, int mode); diff --git a/src/core/directories.c b/src/core/directories.c index 63c66e564..c35773f1e 100644 --- a/src/core/directories.c +++ b/src/core/directories.c @@ -116,10 +116,15 @@ struct VFile* mDirectorySetOpenSuffix(struct mDirectorySet* dirs, struct VDir* d } void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptions* opts) { + char abspath[PATH_MAX + 1]; + char configDir[PATH_MAX + 1]; + mCoreConfigDirectory(configDir, sizeof(configDir)); + if (opts->savegamePath) { - struct VDir* dir = VDirOpen(opts->savegamePath); - if (!dir && VDirCreate(opts->savegamePath)) { - dir = VDirOpen(opts->savegamePath); + makeAbsolute(opts->savegamePath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->save && dirs->save != dirs->base) { @@ -130,9 +135,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio } if (opts->savestatePath) { - struct VDir* dir = VDirOpen(opts->savestatePath); - if (!dir && VDirCreate(opts->savestatePath)) { - dir = VDirOpen(opts->savestatePath); + makeAbsolute(opts->savestatePath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->state && dirs->state != dirs->base) { @@ -143,9 +149,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio } if (opts->screenshotPath) { - struct VDir* dir = VDirOpen(opts->screenshotPath); - if (!dir && VDirCreate(opts->screenshotPath)) { - dir = VDirOpen(opts->screenshotPath); + makeAbsolute(opts->screenshotPath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->screenshot && dirs->screenshot != dirs->base) { @@ -156,9 +163,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio } if (opts->patchPath) { - struct VDir* dir = VDirOpen(opts->patchPath); - if (!dir && VDirCreate(opts->patchPath)) { - dir = VDirOpen(opts->patchPath); + makeAbsolute(opts->patchPath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->patch && dirs->patch != dirs->base) { @@ -169,9 +177,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio } if (opts->cheatsPath) { - struct VDir* dir = VDirOpen(opts->cheatsPath); - if (!dir && VDirCreate(opts->cheatsPath)) { - dir = VDirOpen(opts->cheatsPath); + makeAbsolute(opts->cheatsPath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->cheats && dirs->cheats != dirs->base) { diff --git a/src/util/vfs.c b/src/util/vfs.c index 28d366c53..d99f5020f 100644 --- a/src/util/vfs.c +++ b/src/util/vfs.c @@ -13,6 +13,10 @@ #ifdef __3DS__ #include #endif +#ifdef _WIN32 +#include +#include +#endif struct VFile* VFileOpen(const char* path, int flags) { #ifdef USE_VFS_FILE @@ -207,6 +211,41 @@ void separatePath(const char* path, char* dirname, char* basename, char* extensi } } +bool isAbsolute(const char* path) { + // XXX: Is this robust? +#ifdef _WIN32 + WCHAR wpath[PATH_MAX]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, PATH_MAX); + return !PathIsRelativeW(wpath); +#else + return path[0] == '/'; +#endif +} + +void makeAbsolute(const char* path, const char* base, char* out) { + if (isAbsolute(path)) { + strncpy(out, path, PATH_MAX); + return; + } + + char buf[PATH_MAX]; + snprintf(buf, sizeof(buf), "%s" PATH_SEP "%s", base, path); +#ifdef _WIN32 + WCHAR wbuf[PATH_MAX]; + WCHAR wout[PATH_MAX]; + MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, PATH_MAX); + if (GetFullPathNameW(wbuf, PATH_MAX, wout, NULL)) { + WideCharToMultiByte(CP_UTF8, 0, wout, -1, out, PATH_MAX, 0, 0); + return; + } +#elif defined(HAVE_REALPATH) + if (realpath(buf, out)) { + return; + } +#endif + strncpy(out, buf, PATH_MAX); +} + struct VFile* VDirFindFirst(struct VDir* dir, bool (*filter)(struct VFile*)) { dir->rewind(dir); struct VDirEntry* dirent = dir->listNext(dir); From eb7b90e5d9d226fdba025e9594ccfc82f6e43ad4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 17 Mar 2023 02:29:47 -0700 Subject: [PATCH 107/290] Qt: Fix OSD on modern macOS (fixes #2736) --- CHANGES | 1 + src/platform/qt/DisplayGL.cpp | 39 +++++++++++++++++++++++++++++++---- src/platform/qt/DisplayGL.h | 6 ++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index dfcbfceb8..8f3aac614 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,7 @@ Other fixes: - Qt: Fix full-buffer rewind - Qt: Fix crash if loading a shader fails - Qt: Fix black screen when starting with a game (fixes mgba.io/i/2781) + - Qt: Fix OSD on modern macOS (fixes mgba.io/i/2736) - Scripting: Fix receiving packets for client sockets - Scripting: Fix empty receive calls returning unknown error on Windows Misc: diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index acc001f9a..9d93a6b57 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -70,6 +70,10 @@ mGLWidget::mGLWidget(QWidget* parent) connect(&m_refresh, &QTimer::timeout, this, static_cast(&QWidget::update)); } +mGLWidget::~mGLWidget() { + // This is needed for unique_ptr to work +} + void mGLWidget::initializeGL() { m_vao = std::make_unique(); m_vao->create(); @@ -99,6 +103,8 @@ void mGLWidget::initializeGL() { m_vaoDone = false; m_tex = 0; + + m_paintDev = std::make_unique(); } bool mGLWidget::finalizeVAO() { @@ -150,6 +156,23 @@ void mGLWidget::paintGL() { } else { m_refresh.start(17); } + + if (m_showOSD && m_messagePainter) { + qreal r = window()->devicePixelRatio(); + m_paintDev->setDevicePixelRatio(r); + m_paintDev->setSize(size() * r); + QPainter painter(m_paintDev.get()); + m_messagePainter->paint(&painter); + painter.end(); + } +} + +void mGLWidget::setMessagePainter(MessagePainter* messagePainter) { + m_messagePainter = messagePainter; +} + +void mGLWidget::setShowOSD(bool showOSD) { + m_showOSD = showOSD; } DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) @@ -170,6 +193,7 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) m_gl = new mGLWidget; m_gl->setAttribute(Qt::WA_NativeWindow); m_gl->setFormat(format); + m_gl->setMessagePainter(messagePainter()); QBoxLayout* layout = new QVBoxLayout; layout->addWidget(m_gl); layout->setContentsMargins(0, 0, 0, 0); @@ -372,6 +396,9 @@ void DisplayGL::interframeBlending(bool enable) { void DisplayGL::showOSDMessages(bool enable) { Display::showOSDMessages(enable); + if (m_gl) { + m_gl->setShowOSD(enable); + } QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable)); } @@ -525,7 +552,9 @@ void PainterGL::create() { mGLES2Context* gl2Backend; #endif - m_paintDev = std::make_unique(); + if (!m_widget) { + m_paintDev = std::make_unique(); + } #if defined(BUILD_GLES2) || defined(BUILD_GLES3) if (m_supportsShaders) { @@ -650,8 +679,10 @@ void PainterGL::setMessagePainter(MessagePainter* messagePainter) { void PainterGL::resize(const QSize& size) { qreal r = m_window->devicePixelRatio(); m_size = size; - m_paintDev->setSize(m_size * r); - m_paintDev->setDevicePixelRatio(r); + if (m_paintDev) { + m_paintDev->setSize(m_size * r); + m_paintDev->setDevicePixelRatio(r); + } if (m_started && !m_active) { forceDraw(); } @@ -818,7 +849,7 @@ void PainterGL::performDraw() { m_backend->postFrame(m_backend, m_buffer); } m_backend->drawFrame(m_backend); - if (m_showOSD && m_messagePainter && !glContextHasBug(OpenGLBug::IG4ICD_CRASH)) { + if (m_showOSD && m_messagePainter && m_paintDev && !glContextHasBug(OpenGLBug::IG4ICD_CRASH)) { m_painter.begin(m_paintDev.get()); m_messagePainter->paint(&m_painter); m_painter.end(); diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index a0c0c17d4..129d8adc4 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -51,9 +51,12 @@ Q_OBJECT public: mGLWidget(QWidget* parent = nullptr); + ~mGLWidget(); void setTex(GLuint tex) { m_tex = tex; } void setVBO(GLuint vbo) { m_vbo = vbo; } + void setMessagePainter(MessagePainter*); + void setShowOSD(bool showOSD); bool finalizeVAO(); void reset(); @@ -72,6 +75,9 @@ private: QTimer m_refresh; int m_refreshResidue = 0; + std::unique_ptr m_paintDev; + MessagePainter* m_messagePainter = nullptr; + bool m_showOSD = false; }; class PainterGL; From ce0b1507c3f2aaaa0d1919b7b1fa69c30fe12692 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 19 Mar 2023 01:24:33 -0700 Subject: [PATCH 108/290] OpenGL: Fix layers not recentering properly when scale is reduced --- src/platform/opengl/gles2.c | 24 ++++++++++++------------ src/platform/qt/DisplayGL.cpp | 2 ++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 653b863a9..43d9a772b 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -185,26 +185,26 @@ static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLa if (layer >= VIDEO_LAYER_MAX) { return; } - context->layerDims[layer].x = dims->x; - context->layerDims[layer].y = dims->y; - if (dims->width == context->layerDims[layer].width && dims->height == context->layerDims[layer].height) { - return; - } - context->layerDims[layer].width = dims->width; - context->layerDims[layer].height = dims->height; + if (dims->width != context->layerDims[layer].width && dims->height != context->layerDims[layer].height) { + context->layerDims[layer].width = dims->width; + context->layerDims[layer].height = dims->height; - glBindTexture(GL_TEXTURE_2D, context->tex[layer]); + glBindTexture(GL_TEXTURE_2D, context->tex[layer]); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); #endif #elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); #endif + } + + context->layerDims[layer].x = dims->x; + context->layerDims[layer].y = dims->y; unsigned newW; unsigned newH; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index dc7ad9a86..162160121 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -653,6 +653,8 @@ void PainterGL::recenterLayers() { for (VideoLayer l : centeredLayers) { Rectangle dims; m_backend->layerDimensions(m_backend, l, &dims); + dims.x = 0; + dims.y = 0; RectangleUnion(&frame, &dims); } for (VideoLayer l : centeredLayers) { From e3e8296105acd6d6c031ed4c29a41e8e8c1645f6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 19 Mar 2023 02:23:37 -0700 Subject: [PATCH 109/290] OpenGL: Separate sizes of image and drawn layer for image --- include/mgba-util/geometry.h | 5 ++ src/platform/opengl/gl.c | 87 ++++++++++++++++++++++++------ src/platform/opengl/gl.h | 1 + src/platform/opengl/gles2.c | 80 +++++++++++++++++++++------ src/platform/opengl/gles2.h | 1 + src/platform/qt/CoreController.cpp | 4 ++ src/platform/qt/CoreController.h | 1 + src/platform/qt/DisplayGL.cpp | 25 +++++---- src/platform/video-backend.h | 2 + 9 files changed, 164 insertions(+), 42 deletions(-) diff --git a/include/mgba-util/geometry.h b/include/mgba-util/geometry.h index 4ed46f0b0..344eb074d 100644 --- a/include/mgba-util/geometry.h +++ b/include/mgba-util/geometry.h @@ -10,6 +10,11 @@ CXX_GUARD_START +struct Size { + int width; + int height; +}; + struct Rectangle { int x; int y; diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index 03bd831d2..fea950c17 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -33,6 +33,7 @@ static void mGLContextInit(struct VideoBackend* v, WHandle handle) { UNUSED(handle); struct mGLContext* context = (struct mGLContext*) v; memset(context->layerDims, 0, sizeof(context->layerDims)); + memset(context->imageSizes, -1, sizeof(context->imageSizes)); glGenTextures(2, context->tex); glBindTexture(GL_TEXTURE_2D, context->tex[0]); _initTex(); @@ -48,17 +49,17 @@ static void mGLContextInit(struct VideoBackend* v, WHandle handle) { } } -static inline void _setTexDims(const struct Rectangle* dims) { +static inline void _setTexDims(int width, int height) { #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(dims->width), toPow2(dims->height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(dims->width), toPow2(dims->height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); #endif #elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(dims->width), toPow2(dims->height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(dims->width), toPow2(dims->height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, toPow2(width), toPow2(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); #endif } @@ -75,14 +76,16 @@ static void mGLContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer context->layerDims[layer].width = dims->width; context->layerDims[layer].height = dims->height; - if (layer == VIDEO_LAYER_IMAGE) { - glBindTexture(GL_TEXTURE_2D, context->tex[0]); - _setTexDims(dims); - glBindTexture(GL_TEXTURE_2D, context->tex[1]); - _setTexDims(dims); - } else { - glBindTexture(GL_TEXTURE_2D, context->layers[layer]); - _setTexDims(dims); + if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) { + if (layer == VIDEO_LAYER_IMAGE) { + glBindTexture(GL_TEXTURE_2D, context->tex[0]); + _setTexDims(dims->width, dims->height); + glBindTexture(GL_TEXTURE_2D, context->tex[1]); + _setTexDims(dims->width, dims->height); + } else { + glBindTexture(GL_TEXTURE_2D, context->layers[layer]); + _setTexDims(dims->width, dims->height); + } } } @@ -195,6 +198,46 @@ void mGLContextDrawFrame(struct VideoBackend* v) { glDisable(GL_BLEND); } +static void mGLContextSetImageSize(struct VideoBackend* v, enum VideoLayer layer, int width, int height) { + struct mGLContext* context = (struct mGLContext*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + if (width <= 0 || height <= 0) { + context->imageSizes[layer].width = -1; + context->imageSizes[layer].height = -1; + width = context->layerDims[layer].width; + height = context->layerDims[layer].height; + } else { + context->imageSizes[layer].width = width; + context->imageSizes[layer].height = height; + } + if (layer == VIDEO_LAYER_IMAGE) { + glBindTexture(GL_TEXTURE_2D, context->tex[0]); + _setTexDims(width, height); + glBindTexture(GL_TEXTURE_2D, context->tex[1]); + _setTexDims(width, height); + } else { + glBindTexture(GL_TEXTURE_2D, context->layers[layer]); + _setTexDims(width, height); + } +} + +static void mGLContextImageSize(struct VideoBackend* v, enum VideoLayer layer, int* width, int* height) { + struct mGLContext* context = (struct mGLContext*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + + if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) { + *width = context->layerDims[layer].width; + *height = context->layerDims[layer].height; + } else { + *width = context->imageSizes[layer].width; + *height = context->imageSizes[layer].height; + } +} + void mGLContextPostFrame(struct VideoBackend* v, enum VideoLayer layer, const void* frame) { struct mGLContext* context = (struct mGLContext*) v; if (layer >= VIDEO_LAYER_MAX) { @@ -206,16 +249,24 @@ void mGLContextPostFrame(struct VideoBackend* v, enum VideoLayer layer, const vo } else { glBindTexture(GL_TEXTURE_2D, context->layers[layer]); } + + int width = context->imageSizes[layer].width; + int height = context->imageSizes[layer].height; + + if (width <= 0 || height <= 0) { + width = context->layerDims[layer].width; + height = context->layerDims[layer].height; + } #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, context->layerDims[layer].width, context->layerDims[layer].height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); #else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, context->layerDims[layer].width, context->layerDims[layer].height, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); #endif #elif defined(__BIG_ENDIAN__) - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, context->layerDims[layer].width, context->layerDims[layer].height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); #else - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, context->layerDims[layer].width, context->layerDims[layer].height, GL_RGBA, GL_UNSIGNED_BYTE, frame); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, frame); #endif } @@ -227,6 +278,8 @@ void mGLContextCreate(struct mGLContext* context) { context->d.contextResized = mGLContextResized; context->d.swap = NULL; context->d.clear = mGLContextClear; + context->d.setImageSize = mGLContextSetImageSize; + context->d.imageSize = mGLContextImageSize; context->d.setImage = mGLContextPostFrame; context->d.drawFrame = mGLContextDrawFrame; } diff --git a/src/platform/opengl/gl.h b/src/platform/opengl/gl.h index c819e2f37..ae3dc27fa 100644 --- a/src/platform/opengl/gl.h +++ b/src/platform/opengl/gl.h @@ -30,6 +30,7 @@ struct mGLContext { GLuint tex[2]; GLuint layers[VIDEO_LAYER_MAX]; struct Rectangle layerDims[VIDEO_LAYER_MAX]; + struct Size imageSizes[VIDEO_LAYER_MAX]; }; void mGLContextCreate(struct mGLContext*); diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 43d9a772b..dbe22650c 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -180,6 +180,20 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) { context->finalShader.tex = 0; } +static inline void _setTexDims(int width, int height) { +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); +#endif +#elif defined(__BIG_ENDIAN__) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); +#endif +} + static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct Rectangle* dims) { struct mGLES2Context* context = (struct mGLES2Context*) v; if (layer >= VIDEO_LAYER_MAX) { @@ -190,17 +204,9 @@ static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLa context->layerDims[layer].height = dims->height; glBindTexture(GL_TEXTURE_2D, context->tex[layer]); -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0); -#endif -#elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); -#else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, dims->width, dims->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); -#endif + if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) { + _setTexDims(dims->width, dims->height); + } } context->layerDims[layer].x = dims->x; @@ -455,22 +461,64 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { #endif } +static void mGLES2ContextSetImageSize(struct VideoBackend* v, enum VideoLayer layer, int width, int height) { + struct mGLES2Context* context = (struct mGLES2Context*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + + glBindTexture(GL_TEXTURE_2D, context->tex[layer]); + if (width <= 0 || height <= 0) { + context->imageSizes[layer].width = -1; + context->imageSizes[layer].height = -1; + width = context->layerDims[layer].width; + height = context->layerDims[layer].height; + } else { + context->imageSizes[layer].width = width; + context->imageSizes[layer].height = height; + } + _setTexDims(width, height); +} + +static void mGLES2ContextImageSize(struct VideoBackend* v, enum VideoLayer layer, int* width, int* height) { + struct mGLES2Context* context = (struct mGLES2Context*) v; + if (layer >= VIDEO_LAYER_MAX) { + return; + } + + if (context->imageSizes[layer].width <= 0 || context->imageSizes[layer].height <= 0) { + *width = context->layerDims[layer].width; + *height = context->layerDims[layer].height; + } else { + *width = context->imageSizes[layer].width; + *height = context->imageSizes[layer].height; + } +} + void mGLES2ContextPostFrame(struct VideoBackend* v, enum VideoLayer layer, const void* frame) { struct mGLES2Context* context = (struct mGLES2Context*) v; if (layer >= VIDEO_LAYER_MAX) { return; } + + int width = context->imageSizes[layer].width; + int height = context->imageSizes[layer].height; + + if (width <= 0 || height <= 0) { + width = context->layerDims[layer].width; + height = context->layerDims[layer].height; + } glBindTexture(GL_TEXTURE_2D, context->tex[layer]); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context->layerDims[layer].width, context->layerDims[layer].height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context->layerDims[layer].width, context->layerDims[layer].height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame); #endif #elif defined(__BIG_ENDIAN__) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context->layerDims[layer].width, context->layerDims[layer].height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame); #else - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context->layerDims[layer].width, context->layerDims[layer].height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame); #endif } @@ -482,6 +530,8 @@ void mGLES2ContextCreate(struct mGLES2Context* context) { context->d.contextResized = mGLES2ContextResized; context->d.swap = NULL; context->d.clear = mGLES2ContextClear; + context->d.setImageSize = mGLES2ContextSetImageSize; + context->d.imageSize = mGLES2ContextImageSize; context->d.setImage = mGLES2ContextPostFrame; context->d.drawFrame = mGLES2ContextDrawFrame; context->shaders = 0; diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h index ade50e455..5cbc53acc 100644 --- a/src/platform/opengl/gles2.h +++ b/src/platform/opengl/gles2.h @@ -83,6 +83,7 @@ struct mGLES2Context { GLuint vbo; struct Rectangle layerDims[VIDEO_LAYER_MAX]; + struct Size imageSizes[VIDEO_LAYER_MAX]; unsigned width; unsigned height; diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index c3f12a19f..8b7929024 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -271,6 +271,10 @@ QSize CoreController::screenDimensions() const { return QSize(width, height); } +unsigned CoreController::videoScale() const { + return m_threadContext.core->videoScale(m_threadContext.core); +} + void CoreController::loadConfig(ConfigController* config) { Interrupter interrupter(this); m_loadStateFlags = config->getOption("loadStateExtdata", m_loadStateFlags).toInt(); diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index e2cd03c12..9e64bb838 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -94,6 +94,7 @@ public: mPlatform platform() const; QSize screenDimensions() const; + unsigned videoScale() const; bool supportsFeature(Feature feature) const { return m_threadContext.core->supportsFeature(m_threadContext.core, static_cast(feature)); } bool hardwareAccelerated() const { return m_hwaccel; } diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 162160121..a666156fc 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -648,13 +648,23 @@ void PainterGL::setMessagePainter(MessagePainter* messagePainter) { } void PainterGL::recenterLayers() { + if (!m_context) { + return; + } const static std::initializer_list centeredLayers{VIDEO_LAYER_BACKGROUND, VIDEO_LAYER_IMAGE}; Rectangle frame = {0}; + unsigned scale = std::max(1U, m_context->videoScale()); for (VideoLayer l : centeredLayers) { - Rectangle dims; - m_backend->layerDimensions(m_backend, l, &dims); - dims.x = 0; - dims.y = 0; + Rectangle dims{}; + int width, height; + m_backend->imageSize(m_backend, l, &width, &height); + dims.width = width; + dims.height = height; + if (l != VIDEO_LAYER_IMAGE) { + dims.width *= scale; + dims.height *= scale; + m_backend->setLayerDimensions(m_backend, l, &dims); + } RectangleUnion(&frame, &dims); } for (VideoLayer l : centeredLayers) { @@ -1035,12 +1045,7 @@ void PainterGL::setBackgroundImage(const QImage& image) { makeCurrent(); } - Rectangle dims = {0, 0, 0, 0}; - if (!image.isNull()) { - dims.width = static_cast(image.width()); - dims.height = static_cast(image.height()); - } - m_backend->setLayerDimensions(m_backend, VIDEO_LAYER_BACKGROUND, &dims); + m_backend->setImageSize(m_backend, VIDEO_LAYER_BACKGROUND, image.width(), image.height()); recenterLayers(); if (!image.isNull()) { diff --git a/src/platform/video-backend.h b/src/platform/video-backend.h index 5a3319721..993e8b0e3 100644 --- a/src/platform/video-backend.h +++ b/src/platform/video-backend.h @@ -35,6 +35,8 @@ struct VideoBackend { void (*swap)(struct VideoBackend*); void (*clear)(struct VideoBackend*); void (*contextResized)(struct VideoBackend*, unsigned w, unsigned h); + void (*setImageSize)(struct VideoBackend*, enum VideoLayer, int w, int h); + void (*imageSize)(struct VideoBackend*, enum VideoLayer, int* w, int* h); void (*setImage)(struct VideoBackend*, enum VideoLayer, const void* frame); void (*drawFrame)(struct VideoBackend*); From 9a4cf2877642fe32dda692270547cd81e4705654 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 19 Mar 2023 03:03:55 -0700 Subject: [PATCH 110/290] Util: Namespace geometry structs to avoid conflicts --- include/mgba-util/geometry.h | 8 ++++---- src/platform/opengl/gl.c | 6 +++--- src/platform/opengl/gl.h | 4 ++-- src/platform/opengl/gles2.c | 4 ++-- src/platform/opengl/gles2.h | 4 ++-- src/platform/qt/DisplayGL.cpp | 12 ++++++------ src/platform/sdl/gl-common.c | 10 +++++----- src/platform/sdl/gl-sdl.c | 2 +- src/platform/sdl/gles2-sdl.c | 2 +- src/platform/video-backend.c | 8 ++++---- src/platform/video-backend.h | 6 +++--- src/util/geometry.c | 4 ++-- 12 files changed, 35 insertions(+), 35 deletions(-) diff --git a/include/mgba-util/geometry.h b/include/mgba-util/geometry.h index 344eb074d..5626a1aa0 100644 --- a/include/mgba-util/geometry.h +++ b/include/mgba-util/geometry.h @@ -10,20 +10,20 @@ CXX_GUARD_START -struct Size { +struct mSize { int width; int height; }; -struct Rectangle { +struct mRectangle { int x; int y; int width; int height; }; -void RectangleUnion(struct Rectangle* dst, const struct Rectangle* add); -void RectangleCenter(const struct Rectangle* ref, struct Rectangle* rect); +void mRectangleUnion(struct mRectangle* dst, const struct mRectangle* add); +void mRectangleCenter(const struct mRectangle* ref, struct mRectangle* rect); CXX_GUARD_END diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index fea950c17..832e78cee 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -63,7 +63,7 @@ static inline void _setTexDims(int width, int height) { #endif } -static void mGLContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct Rectangle* dims) { +static void mGLContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct mRectangle* dims) { struct mGLContext* context = (struct mGLContext*) v; if (layer >= VIDEO_LAYER_MAX) { return; @@ -89,7 +89,7 @@ static void mGLContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer } } -static void mGLContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct Rectangle* dims) { +static void mGLContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct mRectangle* dims) { struct mGLContext* context = (struct mGLContext*) v; if (layer >= VIDEO_LAYER_MAX) { return; @@ -141,7 +141,7 @@ static void _setFilter(struct VideoBackend* v) { } } -static void _setFrame(struct Rectangle* dims, int frameW, int frameH) { +static void _setFrame(struct mRectangle* dims, int frameW, int frameH) { GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); glScissor(viewport[0] + dims->x * viewport[2] / frameW, diff --git a/src/platform/opengl/gl.h b/src/platform/opengl/gl.h index ae3dc27fa..5e4873b3d 100644 --- a/src/platform/opengl/gl.h +++ b/src/platform/opengl/gl.h @@ -29,8 +29,8 @@ struct mGLContext { int activeTex; GLuint tex[2]; GLuint layers[VIDEO_LAYER_MAX]; - struct Rectangle layerDims[VIDEO_LAYER_MAX]; - struct Size imageSizes[VIDEO_LAYER_MAX]; + struct mRectangle layerDims[VIDEO_LAYER_MAX]; + struct mSize imageSizes[VIDEO_LAYER_MAX]; }; void mGLContextCreate(struct mGLContext*); diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index dbe22650c..a2d0b6e50 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -194,7 +194,7 @@ static inline void _setTexDims(int width, int height) { #endif } -static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct Rectangle* dims) { +static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct mRectangle* dims) { struct mGLES2Context* context = (struct mGLES2Context*) v; if (layer >= VIDEO_LAYER_MAX) { return; @@ -229,7 +229,7 @@ static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLa } } -static void mGLES2ContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct Rectangle* dims) { +static void mGLES2ContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct mRectangle* dims) { struct mGLES2Context* context = (struct mGLES2Context*) v; if (layer >= VIDEO_LAYER_MAX) { return; diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h index 5cbc53acc..dc15041d9 100644 --- a/src/platform/opengl/gles2.h +++ b/src/platform/opengl/gles2.h @@ -82,8 +82,8 @@ struct mGLES2Context { GLuint tex[VIDEO_LAYER_MAX]; GLuint vbo; - struct Rectangle layerDims[VIDEO_LAYER_MAX]; - struct Size imageSizes[VIDEO_LAYER_MAX]; + struct mRectangle layerDims[VIDEO_LAYER_MAX]; + struct mSize imageSizes[VIDEO_LAYER_MAX]; unsigned width; unsigned height; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index a454e26a6..ca35c6bb6 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -683,7 +683,7 @@ void PainterGL::resizeContext() { } dequeueAll(false); - Rectangle dims = {0, 0, size.width(), size.height()}; + mRectangle dims = {0, 0, size.width(), size.height()}; m_backend->setLayerDimensions(m_backend, VIDEO_LAYER_IMAGE, &dims); recenterLayers(); } @@ -697,10 +697,10 @@ void PainterGL::recenterLayers() { return; } const static std::initializer_list centeredLayers{VIDEO_LAYER_BACKGROUND, VIDEO_LAYER_IMAGE}; - Rectangle frame = {0}; + mRectangle frame = {0}; unsigned scale = std::max(1U, m_context->videoScale()); for (VideoLayer l : centeredLayers) { - Rectangle dims{}; + mRectangle dims{}; int width, height; m_backend->imageSize(m_backend, l, &width, &height); dims.width = width; @@ -710,12 +710,12 @@ void PainterGL::recenterLayers() { dims.height *= scale; m_backend->setLayerDimensions(m_backend, l, &dims); } - RectangleUnion(&frame, &dims); + mRectangleUnion(&frame, &dims); } for (VideoLayer l : centeredLayers) { - Rectangle dims; + mRectangle dims; m_backend->layerDimensions(m_backend, l, &dims); - RectangleCenter(&frame, &dims); + mRectangleCenter(&frame, &dims); m_backend->setLayerDimensions(m_backend, l, &dims); } } diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c index 53219a7bb..b16f6060f 100644 --- a/src/platform/sdl/gl-common.c +++ b/src/platform/sdl/gl-common.c @@ -66,7 +66,7 @@ bool mSDLGLCommonLoadBackground(struct VideoBackend* context) { goto done; } - struct Rectangle dims = { + struct mRectangle dims = { .width = width, .height = height }; @@ -136,13 +136,13 @@ void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user) { if (mSDLGLCommonLoadBackground(v)) { renderer->player.windowUpdated = true; - struct Rectangle frame; + struct mRectangle frame; VideoBackendGetFrame(v, &frame); int i; for (i = 0; i <= VIDEO_LAYER_IMAGE; ++i) { - struct Rectangle dims; + struct mRectangle dims; v->layerDimensions(v, i, &dims); - RectangleCenter(&frame, &dims); + mRectangleCenter(&frame, &dims); v->setLayerDimensions(v, i, &dims); } @@ -168,7 +168,7 @@ void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user) { } } renderer->core->currentVideoSize(renderer->core, &renderer->width, &renderer->height); - struct Rectangle dims; + struct mRectangle dims; v->layerDimensions(v, VIDEO_LAYER_IMAGE, &dims); if (renderer->width != dims.width || renderer->height != dims.height) { renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, renderer->width); diff --git a/src/platform/sdl/gl-sdl.c b/src/platform/sdl/gl-sdl.c index 226353f90..139d5a36e 100644 --- a/src/platform/sdl/gl-sdl.c +++ b/src/platform/sdl/gl-sdl.c @@ -35,7 +35,7 @@ bool mSDLGLInit(struct mSDLRenderer* renderer) { renderer->gl.d.filter = renderer->filter; renderer->gl.d.swap = mSDLGLCommonSwap; renderer->gl.d.init(&renderer->gl.d, 0); - struct Rectangle dims = { + struct mRectangle dims = { .x = 0, .y = 0, .width = renderer->width, diff --git a/src/platform/sdl/gles2-sdl.c b/src/platform/sdl/gles2-sdl.c index f5574435f..9bae33b95 100644 --- a/src/platform/sdl/gles2-sdl.c +++ b/src/platform/sdl/gles2-sdl.c @@ -50,7 +50,7 @@ bool mSDLGLES2Init(struct mSDLRenderer* renderer) { #endif renderer->gl2.d.init(&renderer->gl2.d, 0); - struct Rectangle dims = { + struct mRectangle dims = { .x = 0, .y = 0, .width = renderer->width, diff --git a/src/platform/video-backend.c b/src/platform/video-backend.c index 998764d0d..d8e5826ea 100644 --- a/src/platform/video-backend.c +++ b/src/platform/video-backend.c @@ -5,18 +5,18 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "video-backend.h" -void VideoBackendGetFrame(const struct VideoBackend* v, struct Rectangle* frame) { +void VideoBackendGetFrame(const struct VideoBackend* v, struct mRectangle* frame) { memset(frame, 0, sizeof(*frame)); int i; for (i = 0; i < VIDEO_LAYER_MAX; ++i) { - struct Rectangle dims; + struct mRectangle dims; v->layerDimensions(v, i, &dims); - RectangleUnion(frame, &dims); + mRectangleUnion(frame, &dims); } } void VideoBackendGetFrameSize(const struct VideoBackend* v, unsigned* width, unsigned* height) { - struct Rectangle frame; + struct mRectangle frame; VideoBackendGetFrame(v, &frame); *width = frame.width; *height = frame.height; diff --git a/src/platform/video-backend.h b/src/platform/video-backend.h index 993e8b0e3..17584fea4 100644 --- a/src/platform/video-backend.h +++ b/src/platform/video-backend.h @@ -30,8 +30,8 @@ enum VideoLayer { struct VideoBackend { void (*init)(struct VideoBackend*, WHandle handle); void (*deinit)(struct VideoBackend*); - void (*setLayerDimensions)(struct VideoBackend*, enum VideoLayer, const struct Rectangle*); - void (*layerDimensions)(const struct VideoBackend*, enum VideoLayer, struct Rectangle*); + void (*setLayerDimensions)(struct VideoBackend*, enum VideoLayer, const struct mRectangle*); + void (*layerDimensions)(const struct VideoBackend*, enum VideoLayer, struct mRectangle*); void (*swap)(struct VideoBackend*); void (*clear)(struct VideoBackend*); void (*contextResized)(struct VideoBackend*, unsigned w, unsigned h); @@ -58,7 +58,7 @@ struct VideoShader { size_t nPasses; }; -void VideoBackendGetFrame(const struct VideoBackend*, struct Rectangle* frame); +void VideoBackendGetFrame(const struct VideoBackend*, struct mRectangle* frame); void VideoBackendGetFrameSize(const struct VideoBackend*, unsigned* width, unsigned* height); CXX_GUARD_END diff --git a/src/util/geometry.c b/src/util/geometry.c index 92eb92b9a..f0e41380e 100644 --- a/src/util/geometry.c +++ b/src/util/geometry.c @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include -void RectangleUnion(struct Rectangle* dst, const struct Rectangle* add) { +void mRectangleUnion(struct mRectangle* dst, const struct mRectangle* add) { int x0 = dst->x; int y0 = dst->y; int x1 = dst->x + dst->width; @@ -30,7 +30,7 @@ void RectangleUnion(struct Rectangle* dst, const struct Rectangle* add) { dst->height = y1 - y0; } -void RectangleCenter(const struct Rectangle* ref, struct Rectangle* rect) { +void mRectangleCenter(const struct mRectangle* ref, struct mRectangle* rect) { rect->x = ref->x + (ref->width - rect->width) / 2; rect->y = ref->y + (ref->height - rect->height) / 2; } From dfe9177374935791a1e769b12cc83963d7d1a4fc Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 19 Mar 2023 03:29:00 -0700 Subject: [PATCH 111/290] Util: Fix test build --- src/util/test/geometry.c | 60 ++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/util/test/geometry.c b/src/util/test/geometry.c index 74171e0f7..fac85e34a 100644 --- a/src/util/test/geometry.c +++ b/src/util/test/geometry.c @@ -8,19 +8,19 @@ #include M_TEST_DEFINE(unionRectOrigin) { - struct Rectangle a = { + struct mRectangle a = { .x = 0, .y = 0, .width = 1, .height = 1 }; - struct Rectangle b = { + struct mRectangle b = { .x = 1, .y = 1, .width = 1, .height = 1 }; - RectangleUnion(&a, &b); + mRectangleUnion(&a, &b); assert_int_equal(a.x, 0); assert_int_equal(a.y, 0); assert_int_equal(a.width, 2); @@ -28,19 +28,19 @@ M_TEST_DEFINE(unionRectOrigin) { } M_TEST_DEFINE(unionRectOriginSwapped) { - struct Rectangle a = { + struct mRectangle a = { .x = 1, .y = 1, .width = 1, .height = 1 }; - struct Rectangle b = { + struct mRectangle b = { .x = 0, .y = 0, .width = 1, .height = 1 }; - RectangleUnion(&a, &b); + mRectangleUnion(&a, &b); assert_int_equal(a.x, 0); assert_int_equal(a.y, 0); assert_int_equal(a.width, 2); @@ -48,19 +48,19 @@ M_TEST_DEFINE(unionRectOriginSwapped) { } M_TEST_DEFINE(unionRectNonOrigin) { - struct Rectangle a = { + struct mRectangle a = { .x = 1, .y = 1, .width = 1, .height = 1 }; - struct Rectangle b = { + struct mRectangle b = { .x = 2, .y = 2, .width = 1, .height = 1 }; - RectangleUnion(&a, &b); + mRectangleUnion(&a, &b); assert_int_equal(a.x, 1); assert_int_equal(a.y, 1); assert_int_equal(a.width, 2); @@ -68,19 +68,19 @@ M_TEST_DEFINE(unionRectNonOrigin) { } M_TEST_DEFINE(unionRectOverlapping) { - struct Rectangle a = { + struct mRectangle a = { .x = 0, .y = 0, .width = 2, .height = 2 }; - struct Rectangle b = { + struct mRectangle b = { .x = 1, .y = 1, .width = 2, .height = 2 }; - RectangleUnion(&a, &b); + mRectangleUnion(&a, &b); assert_int_equal(a.x, 0); assert_int_equal(a.y, 0); assert_int_equal(a.width, 3); @@ -88,19 +88,19 @@ M_TEST_DEFINE(unionRectOverlapping) { } M_TEST_DEFINE(unionRectSubRect) { - struct Rectangle a = { + struct mRectangle a = { .x = 0, .y = 0, .width = 3, .height = 3 }; - struct Rectangle b = { + struct mRectangle b = { .x = 1, .y = 1, .width = 1, .height = 1 }; - RectangleUnion(&a, &b); + mRectangleUnion(&a, &b); assert_int_equal(a.x, 0); assert_int_equal(a.y, 0); assert_int_equal(a.width, 3); @@ -108,19 +108,19 @@ M_TEST_DEFINE(unionRectSubRect) { } M_TEST_DEFINE(unionRectNegativeOrigin) { - struct Rectangle a = { + struct mRectangle a = { .x = -1, .y = -1, .width = 1, .height = 1 }; - struct Rectangle b = { + struct mRectangle b = { .x = 0, .y = 0, .width = 1, .height = 1 }; - RectangleUnion(&a, &b); + mRectangleUnion(&a, &b); assert_int_equal(a.x, -1); assert_int_equal(a.y, -1); assert_int_equal(a.width, 2); @@ -128,73 +128,73 @@ M_TEST_DEFINE(unionRectNegativeOrigin) { } M_TEST_DEFINE(centerRectBasic) { - struct Rectangle ref = { + struct mRectangle ref = { .x = 0, .y = 0, .width = 4, .height = 4 }; - struct Rectangle a = { + struct mRectangle a = { .x = 0, .y = 0, .width = 2, .height = 2 }; - RectangleCenter(&ref, &a); + mRectangleCenter(&ref, &a); assert_int_equal(a.x, 1); assert_int_equal(a.y, 1); } M_TEST_DEFINE(centerRectRoundDown) { - struct Rectangle ref = { + struct mRectangle ref = { .x = 0, .y = 0, .width = 4, .height = 4 }; - struct Rectangle a = { + struct mRectangle a = { .x = 0, .y = 0, .width = 1, .height = 1 }; - RectangleCenter(&ref, &a); + mRectangleCenter(&ref, &a); assert_int_equal(a.x, 1); assert_int_equal(a.y, 1); } M_TEST_DEFINE(centerRectRoundDown2) { - struct Rectangle ref = { + struct mRectangle ref = { .x = 0, .y = 0, .width = 4, .height = 4 }; - struct Rectangle a = { + struct mRectangle a = { .x = 0, .y = 0, .width = 3, .height = 2 }; - RectangleCenter(&ref, &a); + mRectangleCenter(&ref, &a); assert_int_equal(a.x, 0); assert_int_equal(a.y, 1); } M_TEST_DEFINE(centerRectOffset) { - struct Rectangle ref = { + struct mRectangle ref = { .x = 1, .y = 1, .width = 4, .height = 4 }; - struct Rectangle a = { + struct mRectangle a = { .x = 0, .y = 0, .width = 2, .height = 2 }; - RectangleCenter(&ref, &a); + mRectangleCenter(&ref, &a); assert_int_equal(a.x, 2); assert_int_equal(a.y, 2); } From ea5db5f72dcb4c29bc2a99d846be8aba2432185c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 19 Mar 2023 03:47:51 -0700 Subject: [PATCH 112/290] Scripting: Fix scalar hashing on different union layouts, e.g. big endian --- src/script/types.c | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/script/types.c b/src/script/types.c index dd50f7968..bbfb016dc 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -364,24 +364,6 @@ static uint32_t _hashString(const struct mScriptValue* val) { return hash32(buffer, size, 0); } -uint32_t _hashScalar(const struct mScriptValue* val) { - // From https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key - uint32_t x = 0; - switch (val->type->base) { - case mSCRIPT_TYPE_SINT: - x = val->value.s32; - break; - case mSCRIPT_TYPE_UINT: - default: - x = val->value.u32; - break; - } - x = ((x >> 16) ^ x) * 0x45D9F3B; - x = ((x >> 16) ^ x) * 0x45D9F3B; - x = (x >> 16) ^ x; - return x; -} - bool _wstrCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) { if (input->type->base != mSCRIPT_TYPE_WRAPPER) { return false; @@ -519,6 +501,16 @@ bool _castScalar(const struct mScriptValue* input, const struct mScriptType* typ return true; } +uint32_t _hashScalar(const struct mScriptValue* val) { + // From https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key + uint32_t x = 0; + _asUInt32(val, &x); + x = ((x >> 16) ^ x) * 0x45D9F3B; + x = ((x >> 16) ^ x) * 0x45D9F3B; + x = (x >> 16) ^ x; + return x; +} + uint32_t _valHash(const void* val, size_t len, uint32_t seed) { UNUSED(len); const struct mScriptValue* value = val; From e3fbb55854fb1f539c43f51f9d2c6e4efb584735 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 19 Mar 2023 04:59:00 -0700 Subject: [PATCH 113/290] Scripting: Return proper callback ID from socket.add --- CHANGES | 1 + src/script/engines/lua.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 8f3aac614..5375202e6 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,7 @@ Other fixes: - Qt: Fix OSD on modern macOS (fixes mgba.io/i/2736) - Scripting: Fix receiving packets for client sockets - Scripting: Fix empty receive calls returning unknown error on Windows + - Scripting: Return proper callback ID from socket.add Misc: - Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826) - GB Serialize: Add missing savestate support for MBC6 and NT (newer) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 58c13d96f..4197a819f 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -101,7 +101,7 @@ static const char* _socketLuaSource = " local cbid = self._nextCallback\n" " self._nextCallback = cbid + 1\n" " self._callbacks[event][cbid] = callback\n" - " return id\n" + " return cbid\n" " end,\n" " remove = function(self, cbid)\n" " for _, group in pairs(self._callbacks) do\n" From 603c1800d5c7b75f054bec0436741d41c8adc591 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 Mar 2023 03:43:44 -0700 Subject: [PATCH 114/290] Util: Move some image stuff around --- include/mgba-util/image.h | 167 +++++++++++++++++++++++++ include/mgba-util/{ => image}/export.h | 10 +- include/mgba-util/{ => image}/png-io.h | 0 include/mgba/core/interface.h | 153 +--------------------- src/core/core.c | 2 +- src/core/serialize.c | 2 +- src/feature/gui/gui-runner.c | 2 +- src/gba/cart/ereader.c | 2 +- src/platform/3ds/gui-font.c | 2 +- src/platform/python/_builder.h | 2 +- src/platform/python/_builder.py | 2 +- src/platform/qt/MapView.cpp | 2 +- src/platform/qt/PaletteView.cpp | 6 +- src/platform/qt/ReportView.cpp | 2 +- src/platform/sdl/gl-common.c | 2 +- src/platform/switch/gui-font.c | 2 +- src/platform/test/cinema-main.c | 2 +- src/util/CMakeLists.txt | 4 +- src/util/{ => image}/export.c | 8 +- src/util/{ => image}/png-io.c | 2 +- 20 files changed, 195 insertions(+), 179 deletions(-) create mode 100644 include/mgba-util/image.h rename include/mgba-util/{ => image}/export.h (56%) rename include/mgba-util/{ => image}/png-io.h (100%) rename src/util/{ => image}/export.c (86%) rename src/util/{ => image}/png-io.c (99%) diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h new file mode 100644 index 000000000..b1c14c8ee --- /dev/null +++ b/include/mgba-util/image.h @@ -0,0 +1,167 @@ +/* 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 M_IMAGE_H +#define M_IMAGE_H + +#include + +CXX_GUARD_START + +#ifdef COLOR_16_BIT +typedef uint16_t color_t; +#define BYTES_PER_PIXEL 2 +#else +typedef uint32_t color_t; +#define BYTES_PER_PIXEL 4 +#endif + +#define M_R5(X) ((X) & 0x1F) +#define M_G5(X) (((X) >> 5) & 0x1F) +#define M_B5(X) (((X) >> 10) & 0x1F) + +#define M_R8(X) (((((X) << 3) & 0xF8) * 0x21) >> 2) +#define M_G8(X) (((((X) >> 2) & 0xF8) * 0x21) >> 2) +#define M_B8(X) (((((X) >> 7) & 0xF8) * 0x21) >> 2) + +#define M_RGB5_TO_BGR8(X) ((M_R5(X) << 3) | (M_G5(X) << 11) | (M_B5(X) << 19)) +#define M_RGB5_TO_RGB8(X) ((M_R5(X) << 19) | (M_G5(X) << 11) | (M_B5(X) << 3)) +#define M_RGB8_TO_BGR5(X) ((((X) & 0xF8) >> 3) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 9)) +#define M_RGB8_TO_RGB5(X) ((((X) & 0xF8) << 7) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 19)) + +#ifndef COLOR_16_BIT +#define M_COLOR_RED 0x000000FF +#define M_COLOR_GREEN 0x0000FF00 +#define M_COLOR_BLUE 0x00FF0000 +#define M_COLOR_ALPHA 0xFF000000 +#define M_COLOR_WHITE 0x00FFFFFF + +#define M_RGB8_TO_NATIVE(X) (((X) & 0x00FF00) | (((X) & 0x0000FF) << 16) | (((X) & 0xFF0000) >> 16)) +#elif defined(COLOR_5_6_5) +#define M_COLOR_RED 0x001F +#define M_COLOR_GREEN 0x07E0 +#define M_COLOR_BLUE 0xF800 +#define M_COLOR_ALPHA 0x0000 +#define M_COLOR_WHITE 0xFFDF + +#define M_RGB8_TO_NATIVE(X) ((((X) & 0xF8) << 8) | (((X) & 0xFC00) >> 5) | (((X) & 0xF80000) >> 19)) +#else +#define M_COLOR_RED 0x001F +#define M_COLOR_GREEN 0x03E0 +#define M_COLOR_BLUE 0x7C00 +#define M_COLOR_ALPHA 0x1000 +#define M_COLOR_WHITE 0x7FFF + +#define M_RGB8_TO_NATIVE(X) M_RGB8_TO_BGR5(X) +#endif + +enum mColorFormat { + mCOLOR_XBGR8 = 0x00001, + mCOLOR_XRGB8 = 0x00002, + mCOLOR_BGRX8 = 0x00004, + mCOLOR_RGBX8 = 0x00008, + mCOLOR_ABGR8 = 0x00010, + mCOLOR_ARGB8 = 0x00020, + mCOLOR_BGRA8 = 0x00040, + mCOLOR_RGBA8 = 0x00080, + mCOLOR_RGB5 = 0x00100, + mCOLOR_BGR5 = 0x00200, + mCOLOR_RGB565 = 0x00400, + mCOLOR_BGR565 = 0x00800, + mCOLOR_ARGB5 = 0x01000, + mCOLOR_ABGR5 = 0x02000, + mCOLOR_RGBA5 = 0x04000, + mCOLOR_BGRA5 = 0x08000, + mCOLOR_RGB8 = 0x10000, + mCOLOR_BGR8 = 0x20000, + mCOLOR_L8 = 0x40000, + + mCOLOR_ANY = -1 +}; + +#ifndef PYCPARSE +static inline color_t mColorFrom555(uint16_t value) { +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + color_t color = 0; + color |= (value & 0x001F) << 11; + color |= (value & 0x03E0) << 1; + color |= (value & 0x7C00) >> 10; +#else + color_t color = value; +#endif +#else + color_t color = M_RGB5_TO_BGR8(value); + color |= (color >> 5) & 0x070707; +#endif + return color; +} + +ATTRIBUTE_UNUSED static unsigned mColorMix5Bit(int weightA, unsigned colorA, int weightB, unsigned colorB) { + unsigned c = 0; + unsigned a, b; +#ifdef COLOR_16_BIT +#ifdef COLOR_5_6_5 + a = colorA & 0xF81F; + b = colorB & 0xF81F; + a |= (colorA & 0x7C0) << 16; + b |= (colorB & 0x7C0) << 16; + c = ((a * weightA + b * weightB) / 16); + if (c & 0x08000000) { + c = (c & ~0x0FC00000) | 0x07C00000; + } + if (c & 0x0020) { + c = (c & ~0x003F) | 0x001F; + } + if (c & 0x10000) { + c = (c & ~0x1F800) | 0xF800; + } + c = (c & 0xF81F) | ((c >> 16) & 0x07C0); +#else + a = colorA & 0x7C1F; + b = colorB & 0x7C1F; + a |= (colorA & 0x3E0) << 16; + b |= (colorB & 0x3E0) << 16; + c = ((a * weightA + b * weightB) / 16); + if (c & 0x04000000) { + c = (c & ~0x07E00000) | 0x03E00000; + } + if (c & 0x0020) { + c = (c & ~0x003F) | 0x001F; + } + if (c & 0x8000) { + c = (c & ~0xF800) | 0x7C00; + } + c = (c & 0x7C1F) | ((c >> 16) & 0x03E0); +#endif +#else + a = colorA & 0xFF; + b = colorB & 0xFF; + c |= ((a * weightA + b * weightB) / 16) & 0x1FF; + if (c & 0x00000100) { + c = 0x000000FF; + } + + a = colorA & 0xFF00; + b = colorB & 0xFF00; + c |= ((a * weightA + b * weightB) / 16) & 0x1FF00; + if (c & 0x00010000) { + c = (c & 0x000000FF) | 0x0000FF00; + } + + a = colorA & 0xFF0000; + b = colorB & 0xFF0000; + c |= ((a * weightA + b * weightB) / 16) & 0x1FF0000; + if (c & 0x01000000) { + c = (c & 0x0000FFFF) | 0x00FF0000; + } +#endif + return c; +} +#endif + +CXX_GUARD_END + +#endif diff --git a/include/mgba-util/export.h b/include/mgba-util/image/export.h similarity index 56% rename from include/mgba-util/export.h rename to include/mgba-util/image/export.h index 92047b93b..bc657cb87 100644 --- a/include/mgba-util/export.h +++ b/include/mgba-util/image/export.h @@ -1,10 +1,10 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau +/* Copyright (c) 2013-2023 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 EXPORT_H -#define EXPORT_H +#ifndef M_IMAGE_EXPORT_H +#define M_IMAGE_EXPORT_H #include @@ -12,8 +12,8 @@ CXX_GUARD_START struct VFile; -bool exportPaletteRIFF(struct VFile* vf, size_t entries, const uint16_t* colors); -bool exportPaletteACT(struct VFile* vf, size_t entries, const uint16_t* colors); +bool mPaletteExportRIFF(struct VFile* vf, size_t entries, const uint16_t* colors); +bool mPaletteExportACT(struct VFile* vf, size_t entries, const uint16_t* colors); CXX_GUARD_END diff --git a/include/mgba-util/png-io.h b/include/mgba-util/image/png-io.h similarity index 100% rename from include/mgba-util/png-io.h rename to include/mgba-util/image/png-io.h diff --git a/include/mgba/core/interface.h b/include/mgba/core/interface.h index fd3fd64e3..f1af5e50d 100644 --- a/include/mgba/core/interface.h +++ b/include/mgba/core/interface.h @@ -10,165 +10,14 @@ CXX_GUARD_START +#include #include struct mCore; struct mStateExtdataItem; -#ifdef COLOR_16_BIT -typedef uint16_t color_t; -#define BYTES_PER_PIXEL 2 -#else -typedef uint32_t color_t; -#define BYTES_PER_PIXEL 4 -#endif - -#define M_R5(X) ((X) & 0x1F) -#define M_G5(X) (((X) >> 5) & 0x1F) -#define M_B5(X) (((X) >> 10) & 0x1F) - -#define M_R8(X) (((((X) << 3) & 0xF8) * 0x21) >> 2) -#define M_G8(X) (((((X) >> 2) & 0xF8) * 0x21) >> 2) -#define M_B8(X) (((((X) >> 7) & 0xF8) * 0x21) >> 2) - -#define M_RGB5_TO_BGR8(X) ((M_R5(X) << 3) | (M_G5(X) << 11) | (M_B5(X) << 19)) -#define M_RGB5_TO_RGB8(X) ((M_R5(X) << 19) | (M_G5(X) << 11) | (M_B5(X) << 3)) -#define M_RGB8_TO_BGR5(X) ((((X) & 0xF8) >> 3) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 9)) -#define M_RGB8_TO_RGB5(X) ((((X) & 0xF8) << 7) | (((X) & 0xF800) >> 6) | (((X) & 0xF80000) >> 19)) - -#ifndef COLOR_16_BIT -#define M_COLOR_RED 0x000000FF -#define M_COLOR_GREEN 0x0000FF00 -#define M_COLOR_BLUE 0x00FF0000 -#define M_COLOR_ALPHA 0xFF000000 -#define M_COLOR_WHITE 0x00FFFFFF - -#define M_RGB8_TO_NATIVE(X) (((X) & 0x00FF00) | (((X) & 0x0000FF) << 16) | (((X) & 0xFF0000) >> 16)) -#elif defined(COLOR_5_6_5) -#define M_COLOR_RED 0x001F -#define M_COLOR_GREEN 0x07E0 -#define M_COLOR_BLUE 0xF800 -#define M_COLOR_ALPHA 0x0000 -#define M_COLOR_WHITE 0xFFDF - -#define M_RGB8_TO_NATIVE(X) ((((X) & 0xF8) << 8) | (((X) & 0xFC00) >> 5) | (((X) & 0xF80000) >> 19)) -#else -#define M_COLOR_RED 0x001F -#define M_COLOR_GREEN 0x03E0 -#define M_COLOR_BLUE 0x7C00 -#define M_COLOR_ALPHA 0x1000 -#define M_COLOR_WHITE 0x7FFF - -#define M_RGB8_TO_NATIVE(X) M_RGB8_TO_BGR5(X) -#endif - -#ifndef PYCPARSE -static inline color_t mColorFrom555(uint16_t value) { -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - color_t color = 0; - color |= (value & 0x001F) << 11; - color |= (value & 0x03E0) << 1; - color |= (value & 0x7C00) >> 10; -#else - color_t color = value; -#endif -#else - color_t color = M_RGB5_TO_BGR8(value); - color |= (color >> 5) & 0x070707; -#endif - return color; -} - -ATTRIBUTE_UNUSED static unsigned mColorMix5Bit(int weightA, unsigned colorA, int weightB, unsigned colorB) { - unsigned c = 0; - unsigned a, b; -#ifdef COLOR_16_BIT -#ifdef COLOR_5_6_5 - a = colorA & 0xF81F; - b = colorB & 0xF81F; - a |= (colorA & 0x7C0) << 16; - b |= (colorB & 0x7C0) << 16; - c = ((a * weightA + b * weightB) / 16); - if (c & 0x08000000) { - c = (c & ~0x0FC00000) | 0x07C00000; - } - if (c & 0x0020) { - c = (c & ~0x003F) | 0x001F; - } - if (c & 0x10000) { - c = (c & ~0x1F800) | 0xF800; - } - c = (c & 0xF81F) | ((c >> 16) & 0x07C0); -#else - a = colorA & 0x7C1F; - b = colorB & 0x7C1F; - a |= (colorA & 0x3E0) << 16; - b |= (colorB & 0x3E0) << 16; - c = ((a * weightA + b * weightB) / 16); - if (c & 0x04000000) { - c = (c & ~0x07E00000) | 0x03E00000; - } - if (c & 0x0020) { - c = (c & ~0x003F) | 0x001F; - } - if (c & 0x8000) { - c = (c & ~0xF800) | 0x7C00; - } - c = (c & 0x7C1F) | ((c >> 16) & 0x03E0); -#endif -#else - a = colorA & 0xFF; - b = colorB & 0xFF; - c |= ((a * weightA + b * weightB) / 16) & 0x1FF; - if (c & 0x00000100) { - c = 0x000000FF; - } - - a = colorA & 0xFF00; - b = colorB & 0xFF00; - c |= ((a * weightA + b * weightB) / 16) & 0x1FF00; - if (c & 0x00010000) { - c = (c & 0x000000FF) | 0x0000FF00; - } - - a = colorA & 0xFF0000; - b = colorB & 0xFF0000; - c |= ((a * weightA + b * weightB) / 16) & 0x1FF0000; - if (c & 0x01000000) { - c = (c & 0x0000FFFF) | 0x00FF0000; - } -#endif - return c; -} -#endif - struct blip_t; -enum mColorFormat { - mCOLOR_XBGR8 = 0x00001, - mCOLOR_XRGB8 = 0x00002, - mCOLOR_BGRX8 = 0x00004, - mCOLOR_RGBX8 = 0x00008, - mCOLOR_ABGR8 = 0x00010, - mCOLOR_ARGB8 = 0x00020, - mCOLOR_BGRA8 = 0x00040, - mCOLOR_RGBA8 = 0x00080, - mCOLOR_RGB5 = 0x00100, - mCOLOR_BGR5 = 0x00200, - mCOLOR_RGB565 = 0x00400, - mCOLOR_BGR565 = 0x00800, - mCOLOR_ARGB5 = 0x01000, - mCOLOR_ABGR5 = 0x02000, - mCOLOR_RGBA5 = 0x04000, - mCOLOR_BGRA5 = 0x08000, - mCOLOR_RGB8 = 0x10000, - mCOLOR_BGR8 = 0x20000, - mCOLOR_L8 = 0x40000, - - mCOLOR_ANY = -1 -}; - enum mCoreFeature { mCORE_FEATURE_OPENGL = 1, }; diff --git a/src/core/core.c b/src/core/core.c index f28b8b245..8091599bf 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -87,7 +87,7 @@ struct mCore* mCoreCreate(enum mPlatform platform) { } #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 -#include +#include #ifdef PSP2 #include diff --git a/src/core/serialize.c b/src/core/serialize.c index c4b0b70a3..fc64c437e 100644 --- a/src/core/serialize.c +++ b/src/core/serialize.c @@ -13,7 +13,7 @@ #include #ifdef USE_PNG -#include +#include #include #include #endif diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 33146ef88..140e5f637 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -15,8 +15,8 @@ #include #include #include +#include #include -#include #include #ifdef PSP2 diff --git a/src/gba/cart/ereader.c b/src/gba/cart/ereader.c index 030e0d183..5edfe193e 100644 --- a/src/gba/cart/ereader.c +++ b/src/gba/cart/ereader.c @@ -13,7 +13,7 @@ #ifdef USE_FFMPEG #include #ifdef USE_PNG -#include +#include #include #endif diff --git a/src/platform/3ds/gui-font.c b/src/platform/3ds/gui-font.c index 668c7382d..c9846c628 100644 --- a/src/platform/3ds/gui-font.c +++ b/src/platform/3ds/gui-font.c @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include -#include +#include #include #include "icons.h" diff --git a/src/platform/python/_builder.h b/src/platform/python/_builder.h index 62b5cb870..eef4cf046 100644 --- a/src/platform/python/_builder.h +++ b/src/platform/python/_builder.h @@ -52,7 +52,7 @@ void free(void*); #undef PYEXPORT #ifdef USE_PNG -#include +#include #endif #ifdef M_CORE_GBA #include diff --git a/src/platform/python/_builder.py b/src/platform/python/_builder.py index a69f65616..1e21cc82f 100644 --- a/src/platform/python/_builder.py +++ b/src/platform/python/_builder.py @@ -40,7 +40,7 @@ ffi.set_source("mgba._pylib", """ #include #include #include -#include +#include #include #define PYEXPORT diff --git a/src/platform/qt/MapView.cpp b/src/platform/qt/MapView.cpp index d565f2fea..324f9800a 100644 --- a/src/platform/qt/MapView.cpp +++ b/src/platform/qt/MapView.cpp @@ -9,7 +9,7 @@ #include "GBAApp.h" #include "LogController.h" -#include +#include #include #ifdef M_CORE_GBA #include diff --git a/src/platform/qt/PaletteView.cpp b/src/platform/qt/PaletteView.cpp index 7d70c9741..09eaed347 100644 --- a/src/platform/qt/PaletteView.cpp +++ b/src/platform/qt/PaletteView.cpp @@ -19,7 +19,7 @@ #ifdef M_CORE_GB #include #endif -#include +#include #include using namespace QGBA; @@ -145,9 +145,9 @@ void PaletteView::exportPalette(int start, int length) { return; } if (filename.endsWith(".pal", Qt::CaseInsensitive)) { - exportPaletteRIFF(vf, length, &static_cast(m_controller->thread()->core->board)->video.palette[start]); + mPaletteExportRIFF(vf, length, &static_cast(m_controller->thread()->core->board)->video.palette[start]); } else if (filename.endsWith(".act", Qt::CaseInsensitive)) { - exportPaletteACT(vf, length, &static_cast(m_controller->thread()->core->board)->video.palette[start]); + mPaletteExportACT(vf, length, &static_cast(m_controller->thread()->core->board)->video.palette[start]); } vf->close(vf); } diff --git a/src/platform/qt/ReportView.cpp b/src/platform/qt/ReportView.cpp index c38c84a03..a380d04de 100644 --- a/src/platform/qt/ReportView.cpp +++ b/src/platform/qt/ReportView.cpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include "CoreController.h" diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c index b16f6060f..f62e04a6b 100644 --- a/src/platform/sdl/gl-common.c +++ b/src/platform/sdl/gl-common.c @@ -10,7 +10,7 @@ #include #ifdef USE_PNG -#include +#include #include #endif diff --git a/src/platform/switch/gui-font.c b/src/platform/switch/gui-font.c index e164a5e64..16add0128 100644 --- a/src/platform/switch/gui-font.c +++ b/src/platform/switch/gui-font.c @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include -#include +#include #include #include diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index c9d85003c..416e8bb81 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include #include diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index e0aa3f8a4..11c9d27d1 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -15,13 +15,13 @@ set(SOURCE_FILES ${BASE_SOURCE_FILES} convolve.c elf-read.c - export.c geometry.c + image/export.c + image/png-io.c patch.c patch-fast.c patch-ips.c patch-ups.c - png-io.c ring-fifo.c sfo.c text-codec.c) diff --git a/src/util/export.c b/src/util/image/export.c similarity index 86% rename from src/util/export.c rename to src/util/image/export.c index cb138ce5a..2b4aad767 100644 --- a/src/util/export.c +++ b/src/util/image/export.c @@ -1,14 +1,14 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau +/* Copyright (c) 2013-2023 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 +#include #include #include -bool exportPaletteRIFF(struct VFile* vf, size_t entries, const uint16_t* colors) { +bool mPaletteExportRIFF(struct VFile* vf, size_t entries, const uint16_t* colors) { if (entries > 0xFFFF) { return false; } @@ -56,7 +56,7 @@ bool exportPaletteRIFF(struct VFile* vf, size_t entries, const uint16_t* colors) return true; } -bool exportPaletteACT(struct VFile* vf, size_t entries, const uint16_t* colors) { +bool mPaletteExportACT(struct VFile* vf, size_t entries, const uint16_t* colors) { if (entries > 256) { return false; } diff --git a/src/util/png-io.c b/src/util/image/png-io.c similarity index 99% rename from src/util/png-io.c rename to src/util/image/png-io.c index 6c25f399c..cba3df060 100644 --- a/src/util/png-io.c +++ b/src/util/image/png-io.c @@ -3,7 +3,7 @@ * 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 +#include #ifdef USE_PNG From 646a0e9b338edbea5203017d02e3bc492b8d4d15 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 20 Mar 2023 21:11:57 -0700 Subject: [PATCH 115/290] GBA Memory: Clean up stall function slightly --- src/gba/memory.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/gba/memory.c b/src/gba/memory.c index 6d0ff6156..cf228076a 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -1747,15 +1747,13 @@ int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait) { maxLoads -= previousLoads; } - int32_t s = cpu->memory.activeSeqCycles16; - int32_t n2s = cpu->memory.activeNonseqCycles16 - cpu->memory.activeSeqCycles16 + 1; - // Figure out how many sequential loads we can jam in + int32_t s = cpu->memory.activeSeqCycles16; int32_t stall = s + 1; int32_t loads = 1; while (stall < wait && loads < maxLoads) { - stall += s; + stall += s + 1; ++loads; } memory->lastPrefetchedPc = cpu->gprs[ARM_PC] + WORD_SIZE_THUMB * (loads + previousLoads - 1); @@ -1766,10 +1764,10 @@ int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait) { } // This instruction used to have an N, convert it to an S. - wait -= n2s; + wait -= cpu->memory.activeNonseqCycles16 - s; // The next |loads|S waitstates disappear entirely, so long as they're all in a row - wait -= stall - 1; + wait -= stall; return wait; } From e79ae2860bd8ac3ae7f1007d2235042e1550003c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 Mar 2023 01:42:27 -0700 Subject: [PATCH 116/290] Util: Start mImage/mColor APIs and tests --- include/mgba-util/image.h | 53 ++++- src/util/CMakeLists.txt | 3 + src/util/image.c | 269 +++++++++++++++++++++++ src/util/test/color.c | 163 ++++++++++++++ src/util/test/image.c | 450 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 935 insertions(+), 3 deletions(-) create mode 100644 src/util/image.c create mode 100644 src/util/test/color.c create mode 100644 src/util/test/image.c diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index b1c14c8ee..74793af82 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -22,9 +22,9 @@ typedef uint32_t color_t; #define M_G5(X) (((X) >> 5) & 0x1F) #define M_B5(X) (((X) >> 10) & 0x1F) -#define M_R8(X) (((((X) << 3) & 0xF8) * 0x21) >> 2) -#define M_G8(X) (((((X) >> 2) & 0xF8) * 0x21) >> 2) -#define M_B8(X) (((((X) >> 7) & 0xF8) * 0x21) >> 2) +#define M_R8(X) ((M_R5(X) * 0x21) >> 2) +#define M_G8(X) ((M_G5(X) * 0x21) >> 2) +#define M_B8(X) ((M_B5(X) * 0x21) >> 2) #define M_RGB5_TO_BGR8(X) ((M_R5(X) << 3) | (M_G5(X) << 11) | (M_B5(X) << 19)) #define M_RGB5_TO_RGB8(X) ((M_R5(X) << 19) | (M_G5(X) << 11) | (M_B5(X) << 3)) @@ -81,7 +81,54 @@ enum mColorFormat { mCOLOR_ANY = -1 }; +struct mImage { + void* data; + unsigned width; + unsigned height; + unsigned stride; + unsigned depth; + enum mColorFormat format; +}; + +uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y); +uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y); +void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color); +void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color); + +uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to); + #ifndef PYCPARSE +static inline unsigned mColorFormatBytes(enum mColorFormat format) { + switch (format) { + case mCOLOR_XBGR8: + case mCOLOR_XRGB8: + case mCOLOR_BGRX8: + case mCOLOR_RGBX8: + case mCOLOR_ABGR8: + case mCOLOR_ARGB8: + case mCOLOR_BGRA8: + case mCOLOR_RGBA8: + return 4; + case mCOLOR_RGB5: + case mCOLOR_BGR5: + case mCOLOR_RGB565: + case mCOLOR_BGR565: + case mCOLOR_ARGB5: + case mCOLOR_ABGR5: + case mCOLOR_RGBA5: + case mCOLOR_BGRA5: + return 2; + case mCOLOR_RGB8: + case mCOLOR_BGR8: + return 3; + case mCOLOR_L8: + return 1; + case mCOLOR_ANY: + break; + } + return 0; +} + static inline color_t mColorFrom555(uint16_t value) { #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 11c9d27d1..eb82215d7 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -16,6 +16,7 @@ set(SOURCE_FILES convolve.c elf-read.c geometry.c + image.c image/export.c image/png-io.c patch.c @@ -34,7 +35,9 @@ set(GUI_FILES gui/menu.c) set(TEST_FILES + test/color.c test/geometry.c + test/image.c test/sfo.c test/string-parser.c test/string-utf8.c diff --git a/src/util/image.c b/src/util/image.c new file mode 100644 index 000000000..73018cc29 --- /dev/null +++ b/src/util/image.c @@ -0,0 +1,269 @@ +/* Copyright (c) 2013-2023 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 + +#define PIXEL(IM, X, Y) \ + (void*) (((IM)->stride * (Y) + (X)) * (IM)->depth + (uintptr_t) (IM)->data) + +uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y) { + if (x >= image->width || y >= image->height) { + return 0; + } + const void* pixel = PIXEL(image, x, y); + uint32_t color; + switch (image->depth) { + case 1: + color = *(const uint8_t*) pixel; + break; + case 2: + color = *(const uint16_t*) pixel; + break; + case 4: + color = *(const uint32_t*) pixel; + break; + case 3: +#ifdef __BIG_ENDIAN__ + color = ((const uint8_t*) pixel)[0] << 16; + color |= ((const uint8_t*) pixel)[1] << 8; + color |= ((const uint8_t*) pixel)[2]; +#else + color = ((const uint8_t*) pixel)[0]; + color |= ((const uint8_t*) pixel)[1] << 8; + color |= ((const uint8_t*) pixel)[2] << 16; +#endif + break; + } + return color; +} + +uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y) { + return mColorConvert(mImageGetPixelRaw(image, x, y), image->format, mCOLOR_ARGB8); +} + +void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color) { + if (x >= image->width || y >= image->height) { + return; + } + void* pixel = PIXEL(image, x, y); + switch (image->depth) { + case 1: + *(uint8_t*) pixel = color; + break; + case 2: + *(uint16_t*) pixel = color; + break; + case 4: + *(uint32_t*) pixel = color; + break; + case 3: +#ifdef __BIG_ENDIAN__ + ((uint8_t*) pixel)[0] = color >> 16; + ((uint8_t*) pixel)[1] = color >> 8; + ((uint8_t*) pixel)[2] = color; +#else + ((uint8_t*) pixel)[0] = color; + ((uint8_t*) pixel)[1] = color >> 8; + ((uint8_t*) pixel)[2] = color >> 16; +#endif + break; + } +} + +void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color) { + mImageSetPixelRaw(image, x, y, mColorConvert(color, mCOLOR_ARGB8, image->format)); +} + +uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to) { + if (from == to) { + return color; + } + + int r; + int g; + int b; + int a = 0xFF; + + switch (from) { + case mCOLOR_ARGB8: + a = color >> 24; + // Fall through + case mCOLOR_XRGB8: + case mCOLOR_RGB8: + r = (color >> 16) & 0xFF; + g = (color >> 8) & 0xFF; + b = color & 0xFF; + break; + + case mCOLOR_ABGR8: + a = color >> 24; + // Fall through + case mCOLOR_XBGR8: + case mCOLOR_BGR8: + b = (color >> 16) & 0xFF; + g = (color >> 8) & 0xFF; + r = color & 0xFF; + break; + + case mCOLOR_RGBA8: + a = color & 0xFF; + // Fall through + case mCOLOR_RGBX8: + r = (color >> 24) & 0xFF; + g = (color >> 16) & 0xFF; + b = (color >> 8) & 0xFF; + break; + + case mCOLOR_BGRA8: + a = color & 0xFF; + // Fall through + case mCOLOR_BGRX8: + b = (color >> 24) & 0xFF; + g = (color >> 16) & 0xFF; + r = (color >> 8) & 0xFF; + break; + + case mCOLOR_ARGB5: + a = (color >> 15) * 0xFF; + // Fall through + case mCOLOR_RGB5: + r = (((color >> 10) & 0x1F) * 0x21) >> 2; + g = (((color >> 5) & 0x1F) * 0x21) >> 2; + b = ((color & 0x1F) * 0x21) >> 2; + break; + + case mCOLOR_ABGR5: + a = (color >> 15) * 0xFF; + // Fall through + case mCOLOR_BGR5: + b = (((color >> 10) & 0x1F) * 0x21) >> 2; + g = (((color >> 5) & 0x1F) * 0x21) >> 2; + r = ((color & 0x1F) * 0x21) >> 2; + break; + + case mCOLOR_RGBA5: + a = (color & 1) * 0xFF; + r = (((color >> 11) & 0x1F) * 0x21) >> 2; + g = (((color >> 6) & 0x1F) * 0x21) >> 2; + b = (((color >> 1) & 0x1F) * 0x21) >> 2; + break; + case mCOLOR_BGRA5: + a = (color & 1) * 0xFF; + b = (((color >> 11) & 0x1F) * 0x21) >> 2; + g = (((color >> 6) & 0x1F) * 0x21) >> 2; + r = (((color >> 1) & 0x1F) * 0x21) >> 2; + break; + + case mCOLOR_RGB565: + r = (((color >> 10) & 0x1F) * 0x21) >> 2; + g = (((color >> 5) & 0x3F) * 0x41) >> 4; + b = ((color & 0x1F) * 0x21) >> 2; + break; + case mCOLOR_BGR565: + b = (((color >> 10) & 0x1F) * 0x21) >> 2; + g = (((color >> 5) & 0x3F) * 0x41) >> 4; + r = ((color & 0x1F) * 0x21) >> 2; + break; + + case mCOLOR_L8: + r = color; + g = color; + b = color; + break; + + case mCOLOR_ANY: + return 0; + } + + color = 0; + switch (to) { + case mCOLOR_XRGB8: + a = 0xFF; + // Fall through + case mCOLOR_ARGB8: + color |= a << 24; + // Fall through + case mCOLOR_RGB8: + color |= r << 16; + color |= g << 8; + color |= b; + break; + case mCOLOR_XBGR8: + a = 0xFF; + // Fall through + case mCOLOR_ABGR8: + color |= a << 24; + // Fall through + case mCOLOR_BGR8: + color |= b << 16; + color |= g << 8; + color |= r; + break; + case mCOLOR_RGBX8: + a = 0xFF; + // Fall through + case mCOLOR_RGBA8: + color |= a; + color |= r << 24; + color |= g << 16; + color |= b << 8; + break; + case mCOLOR_BGRX8: + a = 0xFF; + // Fall through + case mCOLOR_BGRA8: + color |= a; + color |= b << 24; + color |= g << 16; + color |= r << 8; + break; + case mCOLOR_ARGB5: + color |= (!!a << 15); + // Fall through + case mCOLOR_RGB5: + color |= (r >> 3) << 10; + color |= (g >> 3) << 5; + color |= b >> 3; + break; + case mCOLOR_ABGR5: + color |= (!!a << 15); + // Fall through + case mCOLOR_BGR5: + color |= (b >> 3) << 10; + color |= (g >> 3) << 5; + color |= r >> 3; + break; + case mCOLOR_RGBA5: + color |= !!a; + color |= (r >> 3) << 11; + color |= (g >> 3) << 6; + color |= (b >> 3) << 1; + break; + case mCOLOR_BGRA5: + color |= !!a; + color |= (b >> 3) << 11; + color |= (g >> 3) << 6; + color |= (r >> 3) << 1; + break; + case mCOLOR_RGB565: + color |= (r >> 3) << 11; + color |= (g >> 2) << 5; + color |= b >> 3; + break; + case mCOLOR_BGR565: + color |= (b >> 3) << 11; + color |= (g >> 2) << 5; + color |= r >> 3; + break; + case mCOLOR_L8: + // sRGB primaries in fixed point, roughly fudged to saturate to 0xFFFF + color = (55 * r + 184 * g + 18 * b) >> 8; + break; + case mCOLOR_ANY: + return 0; + } + + return color; +} diff --git a/src/util/test/color.c b/src/util/test/color.c new file mode 100644 index 000000000..ab4b58ac3 --- /dev/null +++ b/src/util/test/color.c @@ -0,0 +1,163 @@ +/* Copyright (c) 2013-2023 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 "util/test/suite.h" + +#include + +M_TEST_DEFINE(channelSwap32) { + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ARGB8, mCOLOR_ABGR8), 0xFFCCBBAA); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ABGR8, mCOLOR_ARGB8), 0xFFCCBBAA); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_XRGB8, mCOLOR_XBGR8), 0xFFCCBBAA); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_XBGR8, mCOLOR_XRGB8), 0xFFCCBBAA); + assert_int_equal(mColorConvert(0xAABBCC, mCOLOR_RGB8, mCOLOR_BGR8), 0xCCBBAA); + assert_int_equal(mColorConvert(0xAABBCC, mCOLOR_BGR8, mCOLOR_RGB8), 0xCCBBAA); + + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ARGB8, mCOLOR_RGBA8), 0xAABBCCFF); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ABGR8, mCOLOR_BGRA8), 0xAABBCCFF); + assert_int_equal(mColorConvert(0xAABBCCFF, mCOLOR_RGBA8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xAABBCCFF, mCOLOR_BGRA8, mCOLOR_ABGR8), 0xFFAABBCC); + + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ARGB8, mCOLOR_BGRA8), 0xCCBBAAFF); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ABGR8, mCOLOR_RGBA8), 0xCCBBAAFF); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_RGBA8, mCOLOR_ABGR8), 0xCCBBAAFF); + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_BGRA8, mCOLOR_ARGB8), 0xCCBBAAFF); +} + +M_TEST_DEFINE(channelSwap16) { + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_ARGB5, mCOLOR_ABGR5), 0x83FF); + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_ABGR5, mCOLOR_ARGB5), 0x83FF); + assert_int_equal(mColorConvert(0x7FE0, mCOLOR_RGB5, mCOLOR_BGR5), 0x03FF); + assert_int_equal(mColorConvert(0x7FE0, mCOLOR_BGR5, mCOLOR_RGB5), 0x03FF); + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_RGB565, mCOLOR_BGR565), 0x07FF); + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_BGR565, mCOLOR_RGB565), 0x07FF); + + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_ARGB5, mCOLOR_RGBA5), 0xFFC1); + assert_int_equal(mColorConvert(0xFFE0, mCOLOR_RGBA5, mCOLOR_ARGB5), 0x7FF0); +} + +M_TEST_DEFINE(convertQuantizeOpaque) { + assert_int_equal(mColorConvert(0xA0B0C0, mCOLOR_XRGB8, mCOLOR_RGB5), (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA1B1C1, mCOLOR_XRGB8, mCOLOR_RGB5), (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA2B2C2, mCOLOR_XRGB8, mCOLOR_RGB5), (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA4B4C4, mCOLOR_XRGB8, mCOLOR_RGB5), (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA8B8C8, mCOLOR_XRGB8, mCOLOR_RGB5), (0xA << 11) | (0x1B << 6) | (0x1C << 1) | 1); + + assert_int_equal(mColorConvert(0xA0B0C0, mCOLOR_XRGB8, mCOLOR_BGR5), (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA1B1C1, mCOLOR_XRGB8, mCOLOR_BGR5), (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA2B2C2, mCOLOR_XRGB8, mCOLOR_BGR5), (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA4B4C4, mCOLOR_XRGB8, mCOLOR_BGR5), (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA8B8C8, mCOLOR_XRGB8, mCOLOR_BGR5), (0xC << 11) | (0x1B << 6) | (0x1A << 1) | 1); + + assert_int_equal(mColorConvert(0xA0B0C0, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0xB << 7) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA1B1C1, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0xB << 7) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA2B2C2, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0xB << 7) | (0xC << 1)); + assert_int_equal(mColorConvert(0xA4B4C4, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0xB << 7) | (0x1C << 1)); + assert_int_equal(mColorConvert(0xA8B8C8, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0x1B << 7) | (0x2C << 1) | 1); + assert_int_equal(mColorConvert(0xACBCCC, mCOLOR_XRGB8, mCOLOR_RGB565), (0xA << 12) | (0x1B << 7) | (0x3C << 1) | 1); + + assert_int_equal(mColorConvert(0xA0B0C0, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0xB << 7) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA1B1C1, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0xB << 7) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA2B2C2, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0xB << 7) | (0xA << 1)); + assert_int_equal(mColorConvert(0xA4B4C4, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0xB << 7) | (0x1A << 1)); + assert_int_equal(mColorConvert(0xA8B8C8, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0x1B << 7) | (0x2A << 1) | 1); + assert_int_equal(mColorConvert(0xACBCCC, mCOLOR_XRGB8, mCOLOR_BGR565), (0xC << 12) | (0x1B << 7) | (0x3A << 1) | 1); +} + +M_TEST_DEFINE(convertQuantizeTransparent) { + assert_int_equal(mColorConvert(0xFFA0B0C0, mCOLOR_ARGB8, mCOLOR_ARGB5), 0x8000 | (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0x00A0B0C0, mCOLOR_ARGB8, mCOLOR_ARGB5), (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0xFEA0B0C0, mCOLOR_ARGB8, mCOLOR_ARGB5), 0x8000 | (0xA << 11) | (0xB << 6) | (0xC << 1)); + assert_int_equal(mColorConvert(0x01A0B0C0, mCOLOR_ARGB8, mCOLOR_ARGB5), 0x8000 | (0xA << 11) | (0xB << 6) | (0xC << 1)); + + assert_int_equal(mColorConvert(0xFFA0B0C0, mCOLOR_ARGB8, mCOLOR_ABGR5), 0x8000 | (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0x00A0B0C0, mCOLOR_ARGB8, mCOLOR_ABGR5), (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0xFEA0B0C0, mCOLOR_ARGB8, mCOLOR_ABGR5), 0x8000 | (0xC << 11) | (0xB << 6) | (0xA << 1)); + assert_int_equal(mColorConvert(0x01A0B0C0, mCOLOR_ARGB8, mCOLOR_ABGR5), 0x8000 | (0xC << 11) | (0xB << 6) | (0xA << 1)); + + assert_int_equal(mColorConvert(0xFFA0B0C0, mCOLOR_ARGB8, mCOLOR_RGBA5), 1 | (0xA << 12) | (0xB << 7) | (0xC << 2)); + assert_int_equal(mColorConvert(0x00A0B0C0, mCOLOR_ARGB8, mCOLOR_RGBA5), (0xA << 12) | (0xB << 7) | (0xC << 2)); + assert_int_equal(mColorConvert(0xFEA0B0C0, mCOLOR_ARGB8, mCOLOR_RGBA5), 1 | (0xA << 12) | (0xB << 7) | (0xC << 2)); + assert_int_equal(mColorConvert(0x01A0B0C0, mCOLOR_ARGB8, mCOLOR_RGBA5), 1 | (0xA << 12) | (0xB << 7) | (0xC << 2)); + + assert_int_equal(mColorConvert(0xFFA0B0C0, mCOLOR_ARGB8, mCOLOR_BGRA5), 1 | (0xC << 12) | (0xB << 7) | (0xA << 2)); + assert_int_equal(mColorConvert(0x00A0B0C0, mCOLOR_ARGB8, mCOLOR_BGRA5), (0xC << 12) | (0xB << 7) | (0xA << 2)); + assert_int_equal(mColorConvert(0xFEA0B0C0, mCOLOR_ARGB8, mCOLOR_BGRA5), 1 | (0xC << 12) | (0xB << 7) | (0xA << 2)); + assert_int_equal(mColorConvert(0x01A0B0C0, mCOLOR_ARGB8, mCOLOR_BGRA5), 1 | (0xC << 12) | (0xB << 7) | (0xA << 2)); +} + +M_TEST_DEFINE(convertToOpaque) { + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ARGB8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xFEAABBCC, mCOLOR_ARGB8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x01AABBCC, mCOLOR_ARGB8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x00AABBCC, mCOLOR_ARGB8, mCOLOR_XRGB8), 0xFFAABBCC); + + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_ARGB8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0xFEAABBCC, mCOLOR_ARGB8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0x01AABBCC, mCOLOR_ARGB8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0x00AABBCC, mCOLOR_ARGB8, mCOLOR_RGB8), 0xAABBCC); + + assert_int_equal(mColorConvert(0xAABBCCFF, mCOLOR_RGBA8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xAABBCCFE, mCOLOR_RGBA8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xAABBCC01, mCOLOR_RGBA8, mCOLOR_XRGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xAABBCC00, mCOLOR_RGBA8, mCOLOR_XRGB8), 0xFFAABBCC); + + assert_int_equal(mColorConvert(0xAABBCCFF, mCOLOR_RGBA8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0xAABBCCFE, mCOLOR_RGBA8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0xAABBCC01, mCOLOR_RGBA8, mCOLOR_RGB8), 0xAABBCC); + assert_int_equal(mColorConvert(0xAABBCC00, mCOLOR_RGBA8, mCOLOR_RGB8), 0xAABBCC); + + assert_int_equal(mColorConvert(0x7FFF, mCOLOR_ARGB5, mCOLOR_RGB5), 0x7FFF); + assert_int_equal(mColorConvert(0xFFFF, mCOLOR_ARGB5, mCOLOR_RGB5), 0x7FFF); + + assert_int_equal(mColorConvert(0xFFFE, mCOLOR_RGBA5, mCOLOR_RGB5), 0x7FFF); + assert_int_equal(mColorConvert(0xFFFF, mCOLOR_RGBA5, mCOLOR_RGB5), 0x7FFF); +} + +M_TEST_DEFINE(convertToAlpha) { + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_XRGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xFEAABBCC, mCOLOR_XRGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x01AABBCC, mCOLOR_XRGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x00AABBCC, mCOLOR_XRGB8, mCOLOR_ARGB8), 0xFFAABBCC); + + assert_int_equal(mColorConvert(0xFFAABBCC, mCOLOR_RGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0xFEAABBCC, mCOLOR_RGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x01AABBCC, mCOLOR_RGB8, mCOLOR_ARGB8), 0xFFAABBCC); + assert_int_equal(mColorConvert(0x00AABBCC, mCOLOR_RGB8, mCOLOR_ARGB8), 0xFFAABBCC); + + assert_int_equal(mColorConvert(0x7FFF, mCOLOR_RGB5, mCOLOR_ARGB5), 0xFFFF); + assert_int_equal(mColorConvert(0xFFFF, mCOLOR_RGB5, mCOLOR_ARGB5), 0xFFFF); + + assert_int_equal(mColorConvert(0x7FFF, mCOLOR_RGB5, mCOLOR_RGBA5), 0xFFFF); + assert_int_equal(mColorConvert(0xFFFF, mCOLOR_RGB5, mCOLOR_RGBA5), 0xFFFF); +} + +M_TEST_DEFINE(convertFromGray) { + int i; + for (i = 0; i < 256; ++i) { + assert_int_equal(mColorConvert(i, mCOLOR_L8, mCOLOR_RGB8), (i << 16) | (i << 8) | i); + assert_int_equal(mColorConvert(i, mCOLOR_L8, mCOLOR_ARGB8), 0xFF000000 | (i << 16) | (i << 8) | i); + } +} + +M_TEST_DEFINE(convertToGray) { + int i; + for (i = 0; i < 256; ++i) { + assert_int_equal(mColorConvert((i << 16) | (i << 8) | i, mCOLOR_RGB8, mCOLOR_L8), i); + assert_int_equal(mColorConvert((i << 16) | (i << 8) | i, mCOLOR_ARGB8, mCOLOR_L8), i); + assert_int_equal(mColorConvert(0xFF000000 | (i << 16) | (i << 8) | i, mCOLOR_ARGB8, mCOLOR_L8), i); + } +} + +M_TEST_SUITE_DEFINE(Color, + cmocka_unit_test(channelSwap32), + cmocka_unit_test(channelSwap16), + cmocka_unit_test(convertQuantizeOpaque), + cmocka_unit_test(convertQuantizeTransparent), + cmocka_unit_test(convertToOpaque), + cmocka_unit_test(convertToAlpha), + cmocka_unit_test(convertFromGray), + cmocka_unit_test(convertToGray), +) diff --git a/src/util/test/image.c b/src/util/test/image.c new file mode 100644 index 000000000..518515c83 --- /dev/null +++ b/src/util/test/image.c @@ -0,0 +1,450 @@ +/* Copyright (c) 2013-2023 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 "util/test/suite.h" + +#include + +M_TEST_DEFINE(pitchRead) { + static uint8_t buffer[12] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB + }; + + struct mImage image = { + .data = buffer, + .height = 1 + }; + int i; + + image.depth = 1; + image.width = 12; + image.format = mCOLOR_L8; + + for (i = 0; i < 12; ++i) { + assert_int_equal(mImageGetPixelRaw(&image, i, 0), i); + } + + image.depth = 2; + image.width = 6; + image.format = mCOLOR_RGB5; + + for (i = 0; i < 6; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 2) << 8 | (i * 2 + 1)); +#else + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 2 + 1) << 8 | (i * 2)); +#endif + } + + image.depth = 3; + image.width = 4; + image.format = mCOLOR_RGB8; + + for (i = 0; i < 4; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 3) << 16 | (i * 3 + 1) << 8 | (i * 3 + 2)); +#else + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 3 + 2) << 16 | (i * 3 + 1) << 8 | (i * 3)); +#endif + } + + image.depth = 4; + image.width = 3; + image.format = mCOLOR_ARGB8; + + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 4) << 24 | (i * 4 + 1) << 16 | (i * 4 + 2) << 8 | (i * 4 + 3)); +#else + assert_int_equal(mImageGetPixelRaw(&image, i, 0), (i * 4 + 3) << 24 | (i * 4 + 2) << 16 | (i * 4 + 1) << 8 | (i * 4)); +#endif + } +} + +M_TEST_DEFINE(strideRead) { + static uint8_t buffer[12] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB + }; + + struct mImage image = { + .data = buffer, + .width = 1 + }; + int i; + + image.depth = 1; + image.stride = 1; + image.height = 12; + image.format = mCOLOR_L8; + + for (i = 0; i < 12; ++i) { + assert_int_equal(mImageGetPixelRaw(&image, 0, i), i); + } + + image.depth = 1; + image.stride = 2; + image.height = 6; + image.format = mCOLOR_L8; + + for (i = 0; i < 6; ++i) { + assert_int_equal(mImageGetPixelRaw(&image, 0, i), i * 2); + } + + image.depth = 2; + image.stride = 1; + image.height = 6; + image.format = mCOLOR_RGB5; + + for (i = 0; i < 6; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 2) << 8 | (i * 2 + 1)); +#else + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 2 + 1) << 8 | (i * 2)); +#endif + } + + image.depth = 2; + image.stride = 2; + image.height = 3; + image.format = mCOLOR_RGB5; + + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 4) << 8 | (i * 4 + 1)); +#else + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 4 + 1) << 8 | (i * 4)); +#endif + } + + image.depth = 3; + image.stride = 1; + image.height = 4; + image.format = mCOLOR_RGB8; + + for (i = 0; i < 4; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 3) << 16 | (i * 3 + 1) << 8 | (i * 3 + 2)); +#else + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 3 + 2) << 16 | (i * 3 + 1) << 8 | (i * 3)); +#endif + } + + image.depth = 3; + image.stride = 2; + image.height = 2; + image.format = mCOLOR_RGB8; + + for (i = 0; i < 2; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 6) << 16 | (i * 6 + 1) << 8 | (i * 6 + 2)); +#else + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 6 + 2) << 16 | (i * 6 + 1) << 8 | (i * 6)); +#endif + } + + image.depth = 4; + image.stride = 1; + image.height = 3; + image.format = mCOLOR_ARGB8; + + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 4) << 24 | (i * 4 + 1) << 16 | (i * 4 + 2) << 8 | (i * 4 + 3)); +#else + assert_int_equal(mImageGetPixelRaw(&image, 0, i), (i * 4 + 3) << 24 | (i * 4 + 2) << 16 | (i * 4 + 1) << 8 | (i * 4)); +#endif + } +} + +M_TEST_DEFINE(oobRead) { + static uint8_t buffer[8] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + + struct mImage image = { + .data = buffer, + .width = 1, + .height = 1, + .stride = 1 + }; + + image.depth = 1; + image.format = mCOLOR_L8; + + assert_int_equal(mImageGetPixelRaw(&image, 0, 0), 0xFF); + assert_int_equal(mImageGetPixelRaw(&image, 1, 0), 0); + assert_int_equal(mImageGetPixelRaw(&image, 0, 1), 0); + assert_int_equal(mImageGetPixelRaw(&image, 1, 1), 0); + + image.depth = 2; + image.format = mCOLOR_RGB5; + + assert_int_equal(mImageGetPixelRaw(&image, 0, 0), 0xFFFF); + assert_int_equal(mImageGetPixelRaw(&image, 1, 0), 0); + assert_int_equal(mImageGetPixelRaw(&image, 0, 1), 0); + assert_int_equal(mImageGetPixelRaw(&image, 1, 1), 0); + + image.depth = 3; + image.format = mCOLOR_RGB8; + + assert_int_equal(mImageGetPixelRaw(&image, 0, 0), 0xFFFFFF); + assert_int_equal(mImageGetPixelRaw(&image, 1, 0), 0); + assert_int_equal(mImageGetPixelRaw(&image, 0, 1), 0); + assert_int_equal(mImageGetPixelRaw(&image, 1, 1), 0); + + image.depth = 4; + image.format = mCOLOR_ARGB8; + + assert_int_equal(mImageGetPixelRaw(&image, 0, 0), 0xFFFFFFFF); + assert_int_equal(mImageGetPixelRaw(&image, 1, 0), 0); + assert_int_equal(mImageGetPixelRaw(&image, 0, 1), 0); + assert_int_equal(mImageGetPixelRaw(&image, 1, 1), 0); +} + +M_TEST_DEFINE(pitchWrite) { + static const uint8_t baseline[12] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB + }; + + uint8_t buffer[12]; + + struct mImage image = { + .data = buffer, + .height = 1 + }; + int i; + + image.depth = 1; + image.width = 12; + image.format = mCOLOR_L8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 12; ++i) { + mImageSetPixelRaw(&image, i, 0, i); + } + assert_memory_equal(baseline, buffer, sizeof(baseline)); + + image.depth = 2; + image.width = 6; + image.format = mCOLOR_RGB5; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 6; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, i, 0, (i * 2) << 8 | (i * 2 + 1)); +#else + mImageSetPixelRaw(&image, i, 0, (i * 2 + 1) << 8 | (i * 2)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(baseline)); + + image.depth = 3; + image.width = 4; + image.format = mCOLOR_RGB8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 4; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, i, 0, (i * 3) << 16 | (i * 3 + 1) << 8 | (i * 3 + 2)); +#else + mImageSetPixelRaw(&image, i, 0, (i * 3 + 2) << 16 | (i * 3 + 1) << 8 | (i * 3)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(baseline)); + + image.depth = 4; + image.width = 3; + image.format = mCOLOR_ARGB8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, i, 0, (i * 4) << 24 | (i * 4 + 1) << 16 | (i * 4 + 2) << 8 | (i * 4 + 3)); +#else + mImageSetPixelRaw(&image, i, 0, (i * 4 + 3) << 24 | (i * 4 + 2) << 16 | (i * 4 + 1) << 8 | (i * 4)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(baseline)); +} + +M_TEST_DEFINE(strideWrite) { + static const uint8_t baseline[12] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB + }; + static const uint8_t baseline2x1[12] = { + 0x0, 0x0, 0x2, 0x0, 0x4, 0x0, 0x6, 0x0, 0x8, 0x0, 0xA, 0x0 + }; + static const uint8_t baseline2x2[12] = { + 0x0, 0x1, 0x0, 0x0, 0x4, 0x5, 0x0, 0x0, 0x8, 0x9, 0x0, 0x0 + }; + static const uint8_t baseline3x2[12] = { + 0x0, 0x1, 0x2, 0x0, 0x0, 0x0, 0x6, 0x7, 0x8, 0x0, 0x0, 0x0 + }; + + uint8_t buffer[12]; + + struct mImage image = { + .data = buffer, + .width = 1 + }; + int i; + + image.depth = 1; + image.stride = 1; + image.height = 12; + image.format = mCOLOR_L8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 12; ++i) { + mImageSetPixelRaw(&image, 0, i, i); + } + assert_memory_equal(baseline, buffer, sizeof(buffer)); + + image.depth = 1; + image.stride = 2; + image.height = 6; + image.format = mCOLOR_L8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 6; ++i) { + mImageSetPixelRaw(&image, 0, i, i * 2); + } + assert_memory_equal(baseline2x1, buffer, sizeof(buffer)); + + image.depth = 2; + image.stride = 1; + image.height = 6; + image.format = mCOLOR_RGB5; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 6; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, 0, i, (i * 2) << 8 | (i * 2 + 1)); +#else + mImageSetPixelRaw(&image, 0, i, (i * 2 + 1) << 8 | (i * 2)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(buffer)); + + image.depth = 2; + image.stride = 2; + image.height = 3; + image.format = mCOLOR_RGB5; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, 0, i, (i * 4) << 8 | (i * 4 + 1)); +#else + mImageSetPixelRaw(&image, 0, i, (i * 4 + 1) << 8 | (i * 4)); +#endif + } + assert_memory_equal(baseline2x2, buffer, sizeof(buffer)); + + image.depth = 3; + image.stride = 1; + image.height = 4; + image.format = mCOLOR_RGB8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 4; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, 0, i, (i * 3) << 16 | (i * 3 + 1) << 8 | (i * 3 + 2)); +#else + mImageSetPixelRaw(&image, 0, i, (i * 3 + 2) << 16 | (i * 3 + 1) << 8 | (i * 3)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(buffer)); + + image.depth = 3; + image.stride = 2; + image.height = 2; + image.format = mCOLOR_RGB8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 2; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, 0, i, (i * 6) << 16 | (i * 6 + 1) << 8 | (i * 6 + 2)); +#else + mImageSetPixelRaw(&image, 0, i, (i * 6 + 2) << 16 | (i * 6 + 1) << 8 | (i * 6)); +#endif + } + assert_memory_equal(baseline3x2, buffer, sizeof(buffer)); + + image.depth = 4; + image.stride = 1; + image.height = 3; + image.format = mCOLOR_ARGB8; + + memset(buffer, 0, sizeof(buffer)); + for (i = 0; i < 3; ++i) { +#ifdef __BIG_ENDIAN__ + mImageSetPixelRaw(&image, 0, i, (i * 4) << 24 | (i * 4 + 1) << 16 | (i * 4 + 2) << 8 | (i * 4 + 3)); +#else + mImageSetPixelRaw(&image, 0, i, (i * 4 + 3) << 24 | (i * 4 + 2) << 16 | (i * 4 + 1) << 8 | (i * 4)); +#endif + } + assert_memory_equal(baseline, buffer, sizeof(buffer)); +} + +M_TEST_DEFINE(oobWrite) { + static uint8_t buffer[8]; + + struct mImage image = { + .data = buffer, + .width = 1, + .height = 1, + .stride = 1 + }; + + image.depth = 1; + image.format = mCOLOR_L8; + + memset(buffer, 0, sizeof(buffer)); + mImageSetPixelRaw(&image, 0, 0, 0xFF); + mImageSetPixelRaw(&image, 1, 0, 0); + mImageSetPixelRaw(&image, 0, 1, 0); + mImageSetPixelRaw(&image, 1, 1, 0); + assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF }), sizeof(buffer)); + + image.depth = 2; + image.format = mCOLOR_RGB5; + + memset(buffer, 0, sizeof(buffer)); + mImageSetPixelRaw(&image, 0, 0, 0xFFFF); + mImageSetPixelRaw(&image, 1, 0, 0); + mImageSetPixelRaw(&image, 0, 1, 0); + mImageSetPixelRaw(&image, 1, 1, 0); + assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF, 0xFF }), sizeof(buffer)); + + image.depth = 3; + image.format = mCOLOR_RGB8; + + memset(buffer, 0, sizeof(buffer)); + mImageSetPixelRaw(&image, 0, 0, 0xFFFFFF); + mImageSetPixelRaw(&image, 1, 0, 0); + mImageSetPixelRaw(&image, 0, 1, 0); + mImageSetPixelRaw(&image, 1, 1, 0); + assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF, 0xFF, 0xFF }), sizeof(buffer)); + + image.depth = 4; + image.format = mCOLOR_ARGB8; + + memset(buffer, 0, sizeof(buffer)); + mImageSetPixelRaw(&image, 0, 0, 0xFFFFFFFF); + mImageSetPixelRaw(&image, 1, 0, 0); + mImageSetPixelRaw(&image, 0, 1, 0); + mImageSetPixelRaw(&image, 1, 1, 0); + assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF, 0xFF, 0xFF, 0xFF }), sizeof(buffer)); +} + +M_TEST_SUITE_DEFINE(Image, + cmocka_unit_test(pitchRead), + cmocka_unit_test(strideRead), + cmocka_unit_test(oobRead), + cmocka_unit_test(pitchWrite), + cmocka_unit_test(strideWrite), + cmocka_unit_test(oobWrite), +) From a064306916924efccc4bf3fce64f8e0870366503 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 22 Mar 2023 01:47:30 -0700 Subject: [PATCH 117/290] GB MBC: Fix crash with NT Old 2 if rumble callback isn't installed --- src/gb/mbc/unlicensed.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gb/mbc/unlicensed.c b/src/gb/mbc/unlicensed.c index 58725fa98..1577a15fa 100644 --- a/src/gb/mbc/unlicensed.c +++ b/src/gb/mbc/unlicensed.c @@ -227,7 +227,7 @@ void _GBNTOld2(struct GB* gb, uint16_t address, uint8_t value) { mbcState->rumble = !!(value & 0x80); } - if (mbcState->rumble) { + if (mbcState->rumble && memory->rumble) { memory->rumble->setRumble(memory->rumble, !!(mbcState->swapped ? value & 0x08 : value & 0x02)); } break; From 542792215e0b212eca6ddbeb7315e79f37e07df0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 24 Mar 2023 15:45:21 -0700 Subject: [PATCH 118/290] GBA Overrides: Fix saving in PMD:RRT (JP) (fixes #2862) --- CHANGES | 1 + src/gba/overrides.c | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 5375202e6..0c7a5918b 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,7 @@ Emulation fixes: - GB Video: Implement DMG-style sprite ordering - 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 Overrides: Fix saving in PMD:RRT (JP) (fixes mgba.io/i/2862) - 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) diff --git a/src/gba/overrides.c b/src/gba/overrides.c index ee286cfe0..1d417d86b 100644 --- a/src/gba/overrides.c +++ b/src/gba/overrides.c @@ -123,10 +123,8 @@ static const struct GBACartridgeOverride _overrides[] = { { "BPEF", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6, false }, // Pokemon Mystery Dungeon - { "B24J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false }, { "B24E", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false }, { "B24P", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false }, - { "B24U", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false }, // Pokemon FireRed { "BPRJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false }, From cb0ad844c15e28e21ab61682142a58c5657c2d0e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 25 Mar 2023 01:43:22 -0700 Subject: [PATCH 119/290] Util: Strip loading 16-bit PNGs down to 8-bit --- src/util/image/png-io.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/util/image/png-io.c b/src/util/image/png-io.c index cba3df060..9af634a5c 100644 --- a/src/util/image/png-io.c +++ b/src/util/image/png-io.c @@ -279,6 +279,14 @@ bool PNGReadPixels(png_structp png, png_infop info, void* pixels, unsigned width return false; } + if (png_get_bit_depth(png, info) == 16) { +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED + png_set_scale_16(png); +#else + png_set_strip_16(png); +#endif + } + uint8_t* pixelData = pixels; unsigned pngHeight = png_get_image_height(png, info); if (height < pngHeight) { @@ -334,6 +342,14 @@ bool PNGReadPixelsA(png_structp png, png_infop info, void* pixels, unsigned widt return false; } + if (png_get_bit_depth(png, info) == 16) { +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED + png_set_scale_16(png); +#else + png_set_strip_16(png); +#endif + } + uint8_t* pixelData = pixels; unsigned pngHeight = png_get_image_height(png, info); if (height < pngHeight) { @@ -389,6 +405,14 @@ bool PNGReadPixels8(png_structp png, png_infop info, void* pixels, unsigned widt return false; } + if (png_get_bit_depth(png, info) == 16) { +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED + png_set_scale_16(png); +#else + png_set_strip_16(png); +#endif + } + uint8_t* pixelData = pixels; unsigned pngHeight = png_get_image_height(png, info); if (height < pngHeight) { From cdb0c4193b101ec7cb2955800b09e001a0bc9b84 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 25 Mar 2023 04:27:54 -0700 Subject: [PATCH 120/290] GBA Audio: Clear GB audio state when disabled --- CHANGES | 1 + src/gba/audio.c | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGES b/CHANGES index 0c7a5918b..3b8022655 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,7 @@ Features: Emulation fixes: - GB Video: Implement DMG-style sprite ordering - GBA Audio: Fix improperly deserializing GB audio registers (fixes mgba.io/i/2793) + - GBA Audio: Clear GB audio state when disabled - GBA Memory: Make VRAM access stalls only apply to BG RAM - GBA Overrides: Fix saving in PMD:RRT (JP) (fixes mgba.io/i/2862) - GBA SIO: Fix SIOCNT SI pin value after attaching player 2 (fixes mgba.io/i/2805) diff --git a/src/gba/audio.c b/src/gba/audio.c index 2c61aa1ce..83c20da7d 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -231,8 +231,22 @@ void GBAAudioWriteSOUNDCNT_HI(struct GBAAudio* audio, uint16_t value) { } void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value) { + GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing)); audio->enable = GBAudioEnableGetEnable(value); GBAudioWriteNR52(&audio->psg, value); + if (!audio->enable) { + int i; + for (i = REG_SOUND1CNT_LO; i < REG_SOUNDCNT_HI; i += 2) { + audio->p->memory.io[i >> 1] = 0; + } + audio->psg.ch3.size = 0; + audio->psg.ch3.bank = 0; + audio->psg.ch3.volume = 0; + audio->volume = 0; + audio->volumeChA = 0; + audio->volumeChB = 0; + audio->p->memory.io[REG_SOUNDCNT_HI >> 1] &= 0xFF00; + } } void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value) { From 2cba34d83a2d48d2163a2fc5b3351ba8ee7862ea Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 26 Mar 2023 17:49:15 -0700 Subject: [PATCH 121/290] Util: Add image loading API --- include/mgba-util/image.h | 6 +++ src/util/image.c | 99 +++++++++++++++++++++++++++++++++++++++ src/util/test/image.c | 62 ++++++++++++++++++++++++ 3 files changed, 167 insertions(+) diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index 74793af82..544e38e54 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -90,6 +90,12 @@ struct mImage { enum mColorFormat format; }; +struct VFile; +struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format); +struct mImage* mImageLoad(const char* path); +struct mImage* mImageLoadVF(struct VFile* vf); +void mImageDestroy(struct mImage*); + uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y); uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y); void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color); diff --git a/src/util/image.c b/src/util/image.c index 73018cc29..87807a85c 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -5,9 +5,108 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include +#include + #define PIXEL(IM, X, Y) \ (void*) (((IM)->stride * (Y) + (X)) * (IM)->depth + (uintptr_t) (IM)->data) +struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format) { + struct mImage* image = calloc(1, sizeof(struct mImage)); + if (!image) { + return NULL; + } + image->width = width; + image->height = height; + image->stride = width; + image->format = format; + image->depth = mColorFormatBytes(format); + image->data = calloc(width * height, image->depth); + if (!image->data) { + free(image); + return NULL; + } + return image; +} + +struct mImage* mImageLoad(const char* path) { + struct VFile* vf = VFileOpen(path, O_RDONLY); + if (!vf) { + return NULL; + } + struct mImage* image = mImageLoadVF(vf); + vf->close(vf); + return image; +} + +static struct mImage* mImageLoadPNG(struct VFile* vf) { + png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES); + png_infop info = png_create_info_struct(png); + png_infop end = png_create_info_struct(png); + if (!png || !info || !end) { + PNGReadClose(png, info, end); + return NULL; + } + + if (!PNGReadHeader(png, info)) { + PNGReadClose(png, info, end); + return NULL; + } + unsigned width = png_get_image_width(png, info); + unsigned height = png_get_image_height(png, info); + + struct mImage* image = calloc(1, sizeof(*image)); + + image->width = width; + image->height = height; + image->stride = width; + + switch (png_get_channels(png, info)) { + case 3: + image->format = mCOLOR_XBGR8; + image->depth = 4; + image->data = malloc(width * height * 4); + if (!PNGReadPixels(png, info, image->data, width, height, width)) { + free(image->data); + free(image); + PNGReadClose(png, info, end); + return NULL; + } + break; + case 4: + image->format = mCOLOR_ABGR8; + image->depth = 4; + image->data = malloc(width * height * 4); + if (!PNGReadPixelsA(png, info, image->data, width, height, width)) { + free(image->data); + free(image); + PNGReadClose(png, info, end); + return NULL; + } + break; + default: + // Not supported yet + free(image); + PNGReadClose(png, info, end); + return NULL; + } + return image; +} + +struct mImage* mImageLoadVF(struct VFile* vf) { + vf->seek(vf, 0, SEEK_SET); + if (isPNG(vf)) { + return mImageLoadPNG(vf); + } + vf->seek(vf, 0, SEEK_SET); + return NULL; +} + +void mImageDestroy(struct mImage* image) { + free(image->data); + free(image); +} + uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y) { if (x >= image->width || y >= image->height) { return 0; diff --git a/src/util/test/image.c b/src/util/test/image.c index 518515c83..fd76c3d51 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -6,6 +6,7 @@ #include "util/test/suite.h" #include +#include M_TEST_DEFINE(pitchRead) { static uint8_t buffer[12] = { @@ -440,6 +441,65 @@ M_TEST_DEFINE(oobWrite) { assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF, 0xFF, 0xFF, 0xFF }), sizeof(buffer)); } +M_TEST_DEFINE(loadPng24) { + const uint8_t data[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, + 0x08, 0x02, 0x00, 0x00, 0x00, 0xfd, 0xd4, 0x9a, 0x73, 0x00, 0x00, 0x00, + 0x12, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x63, 0xf8, 0xff, 0xff, 0x3f, + 0x03, 0x03, 0x03, 0x03, 0x84, 0x00, 0x00, 0x2a, 0xe3, 0x04, 0xfc, 0xe8, + 0x51, 0xc0, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, + 0x42, 0x60, 0x82 + }; + size_t len = 75; + + struct VFile* vf = VFileFromConstMemory(data, len); + struct mImage* image = mImageLoadVF(vf); + vf->close(vf); + + assert_non_null(image); + assert_int_equal(image->width, 2); + assert_int_equal(image->height, 2); + assert_int_equal(image->format, mCOLOR_XBGR8); + + assert_int_equal(mImageGetPixel(image, 0, 0) & 0xFFFFFF, 0xFFFFFF); + assert_int_equal(mImageGetPixel(image, 1, 0) & 0xFFFFFF, 0x000000); + assert_int_equal(mImageGetPixel(image, 0, 1) & 0xFFFFFF, 0xFF0000); + assert_int_equal(mImageGetPixel(image, 1, 1) & 0xFFFFFF, 0x0000FF); + + mImageDestroy(image); +} + + +M_TEST_DEFINE(loadPng32) { + unsigned char data[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x72, 0xb6, 0x0d, 0x24, 0x00, 0x00, 0x00, + 0x1a, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x05, 0xc1, 0x31, 0x01, 0x00, + 0x00, 0x08, 0xc0, 0x20, 0x6c, 0x66, 0x25, 0xfb, 0x1f, 0x13, 0xa6, 0x0a, + 0xa7, 0x5a, 0x78, 0x58, 0x7b, 0x07, 0xac, 0xe9, 0x00, 0x3d, 0x95, 0x00, + 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 + }; + unsigned int len = 83; + + struct VFile* vf = VFileFromConstMemory(data, len); + struct mImage* image = mImageLoadVF(vf); + vf->close(vf); + + assert_non_null(image); + assert_int_equal(image->width, 2); + assert_int_equal(image->height, 2); + assert_int_equal(image->format, mCOLOR_ABGR8); + + assert_int_equal(mImageGetPixel(image, 0, 0) >> 24, 0xFF); + assert_int_equal(mImageGetPixel(image, 1, 0) >> 24, 0x70); + assert_int_equal(mImageGetPixel(image, 0, 1) >> 24, 0x40); + assert_int_equal(mImageGetPixel(image, 1, 1) >> 24, 0x00); + + mImageDestroy(image); +} + M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(pitchRead), cmocka_unit_test(strideRead), @@ -447,4 +507,6 @@ M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(pitchWrite), cmocka_unit_test(strideWrite), cmocka_unit_test(oobWrite), + cmocka_unit_test(loadPng24), + cmocka_unit_test(loadPng32), ) From 3c353b572bd391de45337b0ead05fb3d3b707ff7 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 26 Mar 2023 23:58:44 -0700 Subject: [PATCH 122/290] Qt: Swap P1 and other player's save if P1 loaded it first (closes #2750) --- CHANGES | 1 + src/platform/qt/CoreController.cpp | 14 ++++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index 3b8022655..1d3653e28 100644 --- a/CHANGES +++ b/CHANGES @@ -38,6 +38,7 @@ Misc: - Qt: Include wayland QPA in AppImage (fixes mgba.io/i/2796) - Qt: Stop eating boolean action key events (fixes mgba.io/i/2636) - Qt: Automatically change video file extension as appropriate + - Qt: Swap P1 and other player's save if P1 loaded it first (closes mgba.io/i/2750) - Scripting: Add `callbacks:oneshot` for single-call callbacks 0.10.1: (2023-01-10) diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 8b7929024..5bdc997c2 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -293,13 +293,6 @@ void CoreController::loadConfig(ConfigController* config) { mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "mute"); m_preload = config->getOption("preload").toInt(); - int playerId = m_multiplayer->playerId(this) + 1; - QVariant savePlayerId = config->getOption("savePlayerId"); - if (m_multiplayer->attached() < 2 && savePlayerId.canConvert()) { - playerId = savePlayerId.toInt(); - } - mCoreConfigSetOverrideIntValue(&m_threadContext.core->config, "savePlayerId", playerId); - QSize sizeBefore = screenDimensions(); m_activeBuffer.resize(256 * 224 * sizeof(color_t)); m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast(m_activeBuffer.data()), sizeBefore.width()); @@ -1220,7 +1213,12 @@ void CoreController::updatePlayerSave() { int savePlayerId = 0; mCoreConfigGetIntValue(&m_threadContext.core->config, "savePlayerId", &savePlayerId); if (savePlayerId == 0 || m_multiplayer->attached() > 1) { - savePlayerId = m_multiplayer->playerId(this) + 1; + if (savePlayerId == m_multiplayer->playerId(this) + 1) { + // Player 1 is using our save, so let's use theirs, at least for now. + savePlayerId = 1; + } else { + savePlayerId = m_multiplayer->playerId(this) + 1; + } } QString saveSuffix; From 7ef8cd961fac1b7ac5bdf79cc4b548e4d30784ee Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 27 Mar 2023 00:16:26 -0700 Subject: [PATCH 123/290] Util: Fix non-USE_PNG build --- src/util/image.c | 4 ++++ src/util/test/image.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/util/image.c b/src/util/image.c index 87807a85c..b24fd3f50 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -39,6 +39,7 @@ struct mImage* mImageLoad(const char* path) { return image; } +#ifdef USE_PNG static struct mImage* mImageLoadPNG(struct VFile* vf) { png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES); png_infop info = png_create_info_struct(png); @@ -92,13 +93,16 @@ static struct mImage* mImageLoadPNG(struct VFile* vf) { } return image; } +#endif struct mImage* mImageLoadVF(struct VFile* vf) { vf->seek(vf, 0, SEEK_SET); +#ifdef USE_PNG if (isPNG(vf)) { return mImageLoadPNG(vf); } vf->seek(vf, 0, SEEK_SET); +#endif return NULL; } diff --git a/src/util/test/image.c b/src/util/test/image.c index fd76c3d51..579e8c44b 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -441,6 +441,7 @@ M_TEST_DEFINE(oobWrite) { assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF, 0xFF, 0xFF, 0xFF }), sizeof(buffer)); } +#ifdef USE_PNG M_TEST_DEFINE(loadPng24) { const uint8_t data[] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, @@ -499,6 +500,7 @@ M_TEST_DEFINE(loadPng32) { mImageDestroy(image); } +#endif M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(pitchRead), @@ -507,6 +509,8 @@ M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(pitchWrite), cmocka_unit_test(strideWrite), cmocka_unit_test(oobWrite), +#ifdef USE_PNG cmocka_unit_test(loadPng24), cmocka_unit_test(loadPng32), +#endif ) From 832d0a7c0525b1ef79fcfe1c449893d1f0c86fb3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 27 Mar 2023 01:09:51 -0700 Subject: [PATCH 124/290] Qt: Fix checked state of mute menu option at load (fixes #2701) --- CHANGES | 1 + src/platform/qt/Window.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 1d3653e28..041f3f4ce 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,7 @@ Other fixes: - Qt: Fix crash if loading a shader fails - Qt: Fix black screen when starting with a game (fixes mgba.io/i/2781) - Qt: Fix OSD on modern macOS (fixes mgba.io/i/2736) + - Qt: Fix checked state of mute menu option at load (fixes mgba.io/i/2701) - Scripting: Fix receiving packets for client sockets - Scripting: Fix empty receive calls returning unknown error on Windows - Scripting: Return proper callback ID from socket.add diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 083732cd8..bb97bf5c3 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1566,7 +1566,8 @@ void Window::setupMenu(QMenuBar* menubar) { m_actions.addSeparator("av"); ConfigOption* mute = m_config->addOption("mute"); - mute->addBoolean(tr("Mute"), &m_actions, "av"); + Action* muteAction = mute->addBoolean(tr("Mute"), &m_actions, "av"); + muteAction->setActive(m_config->getOption("mute").toInt()); mute->connect([this](const QVariant& value) { m_config->setOption("fastForwardMute", static_cast(value.toInt())); reloadConfig(); From 4543bcf9de3e793e065c75a57e65f83fd2bf73ed Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 29 Mar 2023 01:23:47 -0700 Subject: [PATCH 125/290] Scripting: Make mScriptEngineExportDocNamespace take ownership --- src/script/context.c | 1 + src/script/engines/lua.c | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/context.c b/src/script/context.c index e49d2527b..352c4d278 100644 --- a/src/script/context.c +++ b/src/script/context.c @@ -356,6 +356,7 @@ void mScriptEngineExportDocNamespace(struct mScriptEngineContext* ctx, const cha struct mScriptValue* key = mScriptStringCreateFromUTF8(values[i].key); mScriptTableInsert(table, key, values[i].value); mScriptValueDeref(key); + mScriptValueDeref(values[i].value); } HashTableInsert(&ctx->docroot, nspace, table); } diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 4197a819f..4873a5d20 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -449,7 +449,6 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS mSCRIPT_KV_PAIR(connect, mSCRIPT_VALUE_DOC_FUNCTION(socket_connect)), mSCRIPT_KV_SENTINEL }); - mScriptValueDeref(errors); mScriptEngineSetDocstring(&luaContext->d, "socket", "A basic TCP socket library"); mScriptEngineSetDocstring(&luaContext->d, "socket.ERRORS", "Error strings corresponding to the C.SOCKERR error codes, indexed both by name and by value"); From 1306cfe15e61f7f25913a5770850439d761aa005 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 29 Mar 2023 01:25:01 -0700 Subject: [PATCH 126/290] Scripting: Add Lua-specific local `script` table for info about the script --- src/script/engines/lua.c | 43 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 4873a5d20..d4386c4da 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -463,6 +463,16 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS "the connection either succeeds or fails"); } + mScriptEngineExportDocNamespace(&luaContext->d, "script", (struct mScriptKVPair[]) { + mSCRIPT_KV_PAIR(dir, mScriptStringCreateFromASCII("/")), + mSCRIPT_KV_PAIR(path, mScriptStringCreateFromASCII("/lua")), + mSCRIPT_KV_SENTINEL + }); + + mScriptEngineSetDocstring(&luaContext->d, "script", "Information about the currently loaded script"); + mScriptEngineSetDocstring(&luaContext->d, "script.dir", "The path to the directory containing the script"); + mScriptEngineSetDocstring(&luaContext->d, "script.path", "The path of the current script file"); + return &luaContext->d; } @@ -912,14 +922,43 @@ bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFi #endif switch (ret) { case LUA_OK: + // Create new _ENV + lua_newtable(luaContext->lua); + + // Make the old _ENV the __index in the metatable + lua_newtable(luaContext->lua); + lua_pushliteral(luaContext->lua, "__index"); + lua_getupvalue(luaContext->lua, -4, 1); + lua_rawset(luaContext->lua, -3); + + lua_pushliteral(luaContext->lua, "__newindex"); + lua_getupvalue(luaContext->lua, -4, 1); + lua_rawset(luaContext->lua, -3); + + lua_setmetatable(luaContext->lua, -2); + + lua_pushliteral(luaContext->lua, "script"); + lua_newtable(luaContext->lua); + if (dirname[0]) { - lua_getupvalue(luaContext->lua, -1, 1); lua_pushliteral(luaContext->lua, "require"); lua_pushstring(luaContext->lua, dirname); lua_pushcclosure(luaContext->lua, _luaRequireShim, 1); + lua_rawset(luaContext->lua, -5); + + lua_pushliteral(luaContext->lua, "dir"); + lua_pushstring(luaContext->lua, dirname); lua_rawset(luaContext->lua, -3); - lua_pop(luaContext->lua, 1); } + + if (name[0] == '@') { + lua_pushliteral(luaContext->lua, "path"); + lua_pushstring(luaContext->lua, &name[1]); + lua_rawset(luaContext->lua, -3); + } + + lua_rawset(luaContext->lua, -3); + lua_setupvalue(luaContext->lua, -2, 1); luaContext->func = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX); return true; case LUA_ERRSYNTAX: From e80b5335490b9111fb06a0492195454506e42a5c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 30 Mar 2023 02:39:08 -0700 Subject: [PATCH 127/290] Scripting: Add read-only struct members --- include/mgba/script/macros.h | 14 ++++++++-- include/mgba/script/types.h | 1 + src/script/docgen.c | 3 ++ src/script/test/classes.c | 53 ++++++++++++++++++++++++++++++++++++ src/script/types.c | 4 +++ 5 files changed, 73 insertions(+), 2 deletions(-) diff --git a/include/mgba/script/macros.h b/include/mgba/script/macros.h index bbde17b07..09ef58df0 100644 --- a/include/mgba/script/macros.h +++ b/include/mgba/script/macros.h @@ -212,20 +212,30 @@ CXX_GUARD_START } \ }, -#define mSCRIPT_DEFINE_STRUCT_MEMBER_NAMED(STRUCT, TYPE, EXPORTED_NAME, NAME) { \ +#define _mSCRIPT_DEFINE_STRUCT_MEMBER(STRUCT, TYPE, EXPORTED_NAME, NAME, RO) { \ .type = mSCRIPT_CLASS_INIT_INSTANCE_MEMBER, \ .info = { \ .member = { \ .name = #EXPORTED_NAME, \ .type = mSCRIPT_TYPE_MS_ ## TYPE, \ - .offset = offsetof(struct STRUCT, NAME) \ + .offset = offsetof(struct STRUCT, NAME), \ + .readonly = RO \ } \ } \ }, +#define mSCRIPT_DEFINE_STRUCT_MEMBER_NAMED(STRUCT, TYPE, EXPORTED_NAME, NAME) \ + _mSCRIPT_DEFINE_STRUCT_MEMBER(STRUCT, TYPE, EXPORTED_NAME, NAME, false) + +#define mSCRIPT_DEFINE_STRUCT_CONST_MEMBER_NAMED(STRUCT, TYPE, EXPORTED_NAME, NAME) \ + _mSCRIPT_DEFINE_STRUCT_MEMBER(STRUCT, TYPE, EXPORTED_NAME, NAME, true) + #define mSCRIPT_DEFINE_STRUCT_MEMBER(STRUCT, TYPE, NAME) \ mSCRIPT_DEFINE_STRUCT_MEMBER_NAMED(STRUCT, TYPE, NAME, NAME) +#define mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(STRUCT, TYPE, NAME) \ + mSCRIPT_DEFINE_STRUCT_CONST_MEMBER_NAMED(STRUCT, TYPE, NAME, NAME) + #define mSCRIPT_DEFINE_INHERIT(PARENT) { \ .type = mSCRIPT_CLASS_INIT_INHERIT, \ .info = { \ diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index da13883d2..a771e42c0 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -231,6 +231,7 @@ struct mScriptClassMember { const char* docstring; const struct mScriptType* type; size_t offset; + bool readonly; }; struct mScriptClassCastMember { diff --git a/src/script/docgen.c b/src/script/docgen.c index 502baa705..6c5d553df 100644 --- a/src/script/docgen.c +++ b/src/script/docgen.c @@ -188,6 +188,9 @@ void explainClass(struct mScriptTypeClass* cls, int level) { } docstring = NULL; } + if (details->info.member.readonly) { + fprintf(out, "%s readonly: true\n", indent); + } fprintf(out, "%s type: %s\n", indent, details->info.member.type->name); break; case mSCRIPT_CLASS_INIT_END: diff --git a/src/script/test/classes.c b/src/script/test/classes.c index f4dec7722..ac25ec279 100644 --- a/src/script/test/classes.c +++ b/src/script/test/classes.c @@ -54,6 +54,11 @@ struct TestG { const char* c; }; +struct TestH { + int32_t i; + int32_t j; +}; + static int32_t testAi0(struct TestA* a) { return a->i; } @@ -198,6 +203,12 @@ mSCRIPT_DEFINE_STRUCT(TestG) mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setC) mSCRIPT_DEFINE_END; + +mSCRIPT_DEFINE_STRUCT(TestH) + mSCRIPT_DEFINE_STRUCT_MEMBER(TestH, S32, i) + mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(TestH, S32, j) +mSCRIPT_DEFINE_END; + M_TEST_DEFINE(testALayout) { struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestA)->details.cls; assert_false(cls->init); @@ -1134,6 +1145,47 @@ M_TEST_DEFINE(testGSet) { assert_false(cls->init); } +M_TEST_DEFINE(testHSet) { + struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestH)->details.cls; + + struct TestH s = { + .i = 1, + .j = 2, + }; + + struct mScriptValue sval = mSCRIPT_MAKE_S(TestH, &s); + struct mScriptValue val; + struct mScriptValue compare; + + compare = mSCRIPT_MAKE_S32(1); + assert_true(mScriptObjectGet(&sval, "i", &val)); + assert_true(compare.type->equal(&compare, &val)); + + compare = mSCRIPT_MAKE_S32(2); + assert_true(mScriptObjectGet(&sval, "j", &val)); + assert_true(compare.type->equal(&compare, &val)); + + val = mSCRIPT_MAKE_S32(3); + assert_true(mScriptObjectSet(&sval, "i", &val)); + assert_int_equal(s.i, 3); + + val = mSCRIPT_MAKE_S32(4); + assert_false(mScriptObjectSet(&sval, "j", &val)); + assert_int_equal(s.j, 2); + + compare = mSCRIPT_MAKE_S32(3); + assert_true(mScriptObjectGet(&sval, "i", &val)); + assert_true(compare.type->equal(&compare, &val)); + + compare = mSCRIPT_MAKE_S32(2); + assert_true(mScriptObjectGet(&sval, "j", &val)); + assert_true(compare.type->equal(&compare, &val)); + + assert_true(cls->init); + mScriptClassDeinit(cls); + assert_false(cls->init); +} + M_TEST_SUITE_DEFINE(mScriptClasses, cmocka_unit_test(testALayout), cmocka_unit_test(testASignatures), @@ -1150,4 +1202,5 @@ M_TEST_SUITE_DEFINE(mScriptClasses, cmocka_unit_test(testEGet), cmocka_unit_test(testFDeinit), cmocka_unit_test(testGSet), + cmocka_unit_test(testHSet), ) diff --git a/src/script/types.c b/src/script/types.c index bbfb016dc..7382d0411 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -1472,6 +1472,10 @@ bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScri return true; } + if (m->readonly) { + return false; + } + void* rawMember = (void *)((uintptr_t) obj->value.opaque + m->offset); if (m->type != val->type) { if (!mScriptCast(m->type, val, val)) { From 64408b8fd13e17cafa47a38c10fff8a93de70b24 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 30 Mar 2023 05:36:21 -0700 Subject: [PATCH 128/290] Scripting: Tidy up public headers --- include/mgba/script.h | 16 ++++++++++++++ include/mgba/script/base.h | 21 +++++++++++++++++++ include/mgba/script/context.h | 2 -- .../qt/scripting/ScriptingController.cpp | 3 +-- src/script/docgen.c | 1 + src/script/stdlib.c | 3 ++- src/script/test/classes.c | 4 +--- src/script/test/context.c | 4 +--- src/script/test/input.c | 4 +--- src/script/test/stdlib.c | 4 +--- src/script/test/storage.c | 3 +-- src/script/test/types.c | 4 +--- 12 files changed, 47 insertions(+), 22 deletions(-) create mode 100644 include/mgba/script.h create mode 100644 include/mgba/script/base.h diff --git a/include/mgba/script.h b/include/mgba/script.h new file mode 100644 index 000000000..3497cd0d0 --- /dev/null +++ b/include/mgba/script.h @@ -0,0 +1,16 @@ +/* Copyright (c) 2013-2023 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 M_SCRIPT_H +#define M_SCRIPT_H + +#include +#include +#include +#include +#include +#include + +#endif diff --git a/include/mgba/script/base.h b/include/mgba/script/base.h new file mode 100644 index 000000000..de8eef033 --- /dev/null +++ b/include/mgba/script/base.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2013-2023 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 M_SCRIPT_BASE_H +#define M_SCRIPT_BASE_H + +#include + +CXX_GUARD_START + +#include + +struct mScriptContext; +void mScriptContextAttachStdlib(struct mScriptContext* context); +void mScriptContextAttachSocket(struct mScriptContext* context); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/script/context.h b/include/mgba/script/context.h index 9e8e5ec23..435277323 100644 --- a/include/mgba/script/context.h +++ b/include/mgba/script/context.h @@ -91,8 +91,6 @@ struct mScriptValue* mScriptContextAccessWeakref(struct mScriptContext*, struct void mScriptContextClearWeakref(struct mScriptContext*, uint32_t weakref); void mScriptContextDisownWeakref(struct mScriptContext*, uint32_t weakref); -void mScriptContextAttachStdlib(struct mScriptContext* context); -void mScriptContextAttachSocket(struct mScriptContext* context); void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants); void mScriptContextExportNamespace(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* value); diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index 990cbca9b..3062dc96a 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -19,8 +19,7 @@ #include "scripting/ScriptingTextBuffer.h" #include "scripting/ScriptingTextBufferModel.h" -#include -#include +#include #include #include diff --git a/src/script/docgen.c b/src/script/docgen.c index 6c5d553df..a81e59422 100644 --- a/src/script/docgen.c +++ b/src/script/docgen.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include diff --git a/src/script/stdlib.c b/src/script/stdlib.c index 7397bb09e..ee6ebfd93 100644 --- a/src/script/stdlib.c +++ b/src/script/stdlib.c @@ -3,10 +3,11 @@ * 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 +#include #include #include +#include #include #ifdef M_CORE_GBA #include diff --git a/src/script/test/classes.c b/src/script/test/classes.c index ac25ec279..f7c5eb128 100644 --- a/src/script/test/classes.c +++ b/src/script/test/classes.c @@ -5,9 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "util/test/suite.h" -#include -#include -#include +#include struct TestA { int32_t i; diff --git a/src/script/test/context.c b/src/script/test/context.c index 178dc25a7..da3ff7ea8 100644 --- a/src/script/test/context.c +++ b/src/script/test/context.c @@ -5,9 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "util/test/suite.h" -#include -#include -#include +#include M_TEST_DEFINE(weakrefBasic) { struct mScriptContext context; diff --git a/src/script/test/input.c b/src/script/test/input.c index 825e005dd..4c689a6ef 100644 --- a/src/script/test/input.c +++ b/src/script/test/input.c @@ -6,9 +6,7 @@ #include "util/test/suite.h" #include -#include -#include -#include +#include #include "script/test.h" diff --git a/src/script/test/stdlib.c b/src/script/test/stdlib.c index 0f821dbb4..852894a34 100644 --- a/src/script/test/stdlib.c +++ b/src/script/test/stdlib.c @@ -6,9 +6,7 @@ #include "util/test/suite.h" #include -#include -#include -#include +#include #include "script/test.h" diff --git a/src/script/test/storage.c b/src/script/test/storage.c index e9bb535ba..14851318a 100644 --- a/src/script/test/storage.c +++ b/src/script/test/storage.c @@ -6,8 +6,7 @@ #include "util/test/suite.h" #include -#include -#include +#include #include "script/test.h" diff --git a/src/script/test/types.c b/src/script/test/types.c index ae1df5ba0..d5e7834b4 100644 --- a/src/script/test/types.c +++ b/src/script/test/types.c @@ -5,9 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "util/test/suite.h" -#include -#include -#include +#include struct Test { int32_t a; From 1fd974272c443284d00cdd9a981093c3029fc9d3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 30 Mar 2023 17:20:43 -0700 Subject: [PATCH 129/290] Scripting: Make functions able to have default arguments --- include/mgba/script/macros.h | 28 +++++++++++++++++++++++----- src/script/test/types.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/include/mgba/script/macros.h b/include/mgba/script/macros.h index 09ef58df0..9ce97f067 100644 --- a/include/mgba/script/macros.h +++ b/include/mgba/script/macros.h @@ -403,6 +403,9 @@ CXX_GUARD_START static const struct mScriptValue _mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX]; \ _mSCRIPT_DECLARE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME, S, 0, 0, NPARAMS, _mIDENT(_mSTStructBindingDefaults_doc_ ## TYPE ## _ ## NAME), __VA_ARGS__) \ +#define mSCRIPT_DEFINE_FUNCTION_BINDING_DEFAULTS(NAME) \ + static const struct mScriptValue _bindingDefaults_ ## NAME[mSCRIPT_PARAMS_MAX] = { + #define mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(TYPE, NAME) \ static const struct mScriptValue _mSTStructBindingDefaults_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX] = { \ mSCRIPT_NO_DEFAULT, @@ -449,7 +452,7 @@ CXX_GUARD_START #define mSCRIPT_DEFINE_END { .type = mSCRIPT_CLASS_INIT_END } } } -#define _mSCRIPT_BIND_FUNCTION(NAME, NRET, RETURN, NPARAMS, ...) \ +#define _mSCRIPT_BIND_FUNCTION(NAME, NRET, RETURN, DEFAULTS, NPARAMS, ...) \ static struct mScriptFunction _function_ ## NAME = { \ .call = _binding_ ## NAME \ }; \ @@ -466,6 +469,7 @@ CXX_GUARD_START .count = NPARAMS, \ .entries = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(mSCRIPT_PREFIX_ ## NPARAMS, mSCRIPT_TYPE_MS_, _mEVEN_ ## NPARAMS(__VA_ARGS__)) }, \ .names = { _mCALL(_mIF0_ ## NPARAMS, 0) _mCALL(_mCALL_ ## NPARAMS, _mSTRINGIFY, _mODD_ ## NPARAMS(__VA_ARGS__)) }, \ + .defaults = DEFAULTS, \ }, \ .returnType = { \ .count = NRET, \ @@ -482,7 +486,7 @@ CXX_GUARD_START } \ } -#define mSCRIPT_BIND_FUNCTION(NAME, RETURN, FUNCTION, NPARAMS, ...) \ +#define _mSCRIPT_BIND_N_FUNCTION(NAME, RETURN, FUNCTION, DEFAULTS, NPARAMS, ...) \ static bool _binding_ ## NAME(struct mScriptFrame* frame, void* ctx) { \ UNUSED(ctx); \ _mCALL(mSCRIPT_POP_ ## NPARAMS, &frame->arguments, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \ @@ -492,9 +496,9 @@ CXX_GUARD_START _mSCRIPT_CALL(RETURN, FUNCTION, NPARAMS); \ return true; \ } \ - _mSCRIPT_BIND_FUNCTION(NAME, 1, mSCRIPT_TYPE_MS_ ## RETURN, NPARAMS, __VA_ARGS__) + _mSCRIPT_BIND_FUNCTION(NAME, 1, mSCRIPT_TYPE_MS_ ## RETURN, DEFAULTS, NPARAMS, __VA_ARGS__) -#define mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, NPARAMS, ...) \ +#define _mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, DEFAULTS, NPARAMS, ...) \ static bool _binding_ ## NAME(struct mScriptFrame* frame, void* ctx) { \ UNUSED(ctx); \ _mCALL(mSCRIPT_POP_ ## NPARAMS, &frame->arguments, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \ @@ -504,7 +508,21 @@ CXX_GUARD_START _mSCRIPT_CALL_VOID(FUNCTION, NPARAMS); \ return true; \ } \ - _mSCRIPT_BIND_FUNCTION(NAME, 0, 0, NPARAMS, __VA_ARGS__) + _mSCRIPT_BIND_FUNCTION(NAME, 0, 0, NULL, NPARAMS, __VA_ARGS__) + +#define mSCRIPT_BIND_FUNCTION(NAME, RETURN, FUNCTION, NPARAMS, ...) \ + _mSCRIPT_BIND_N_FUNCTION(NAME, RETURN, FUNCTION, NULL, NPARAMS, __VA_ARGS__) + +#define mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, NPARAMS, ...) \ + _mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, NULL, NPARAMS, __VA_ARGS__) + +#define mSCRIPT_BIND_FUNCTION_WITH_DEFAULTS(NAME, RETURN, FUNCTION, NPARAMS, ...) \ + static const struct mScriptValue _bindingDefaults_ ## NAME[mSCRIPT_PARAMS_MAX]; \ + _mSCRIPT_BIND_N_FUNCTION(NAME, RETURN, FUNCTION, _mIDENT(_bindingDefaults_ ## NAME), NPARAMS, __VA_ARGS__) + +#define mSCRIPT_BIND_VOID_FUNCTION_WITH_DEFAULTS(NAME, FUNCTION, NPARAMS, ...) \ + static const struct mScriptValue _bindingDefaults_ ## _ ## NAME[mSCRIPT_PARAMS_MAX]; \ + _mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, _mIDENT(_bindingDefaults_ ## NAME), NPARAMS, __VA_ARGS__) #define _mSCRIPT_DEFINE_DOC_FUNCTION(SCOPE, NAME, NRET, RETURN, NPARAMS, ...) \ static const struct mScriptType _mScriptDocType_ ## NAME = { \ diff --git a/src/script/test/types.c b/src/script/test/types.c index d5e7834b4..25ec29809 100644 --- a/src/script/test/types.c +++ b/src/script/test/types.c @@ -94,6 +94,12 @@ mSCRIPT_BIND_FUNCTION(boundIsHello, S32, isHello, 1, CHARP, str); mSCRIPT_BIND_FUNCTION(boundIsSequential, S32, isSequential, 1, LIST, list); mSCRIPT_BIND_FUNCTION(boundIsNullCharp, BOOL, isNullCharp, 1, CHARP, arg); mSCRIPT_BIND_FUNCTION(boundIsNullStruct, BOOL, isNullStruct, 1, S(Test), arg); +mSCRIPT_BIND_FUNCTION_WITH_DEFAULTS(boundAddIntWithDefaults, S32, addInts, 2, S32, a, S32, b); + +mSCRIPT_DEFINE_FUNCTION_BINDING_DEFAULTS(boundAddIntWithDefaults) + mSCRIPT_NO_DEFAULT, + mSCRIPT_S32(0) +mSCRIPT_DEFINE_DEFAULTS_END; M_TEST_DEFINE(voidArgs) { struct mScriptFrame frame; @@ -170,6 +176,30 @@ M_TEST_DEFINE(addS32) { mScriptFrameDeinit(&frame); } +M_TEST_DEFINE(addS32Defaults) { + struct mScriptFrame frame; + int32_t val; + + mScriptFrameInit(&frame); + mSCRIPT_PUSH(&frame.arguments, S32, 1); + mSCRIPT_PUSH(&frame.arguments, S32, 2); + assert_true(mScriptInvoke(&boundAddIntWithDefaults, &frame)); + assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_int_equal(val, 3); + mScriptFrameDeinit(&frame); + + mScriptFrameInit(&frame); + mSCRIPT_PUSH(&frame.arguments, S32, 1); + assert_true(mScriptInvoke(&boundAddIntWithDefaults, &frame)); + assert_true(mScriptPopS32(&frame.returnValues, &val)); + assert_int_equal(val, 1); + mScriptFrameDeinit(&frame); + + mScriptFrameInit(&frame); + assert_false(mScriptInvoke(&boundAddIntWithDefaults, &frame)); + mScriptFrameDeinit(&frame); +} + M_TEST_DEFINE(subS32) { struct mScriptFrame frame; mScriptFrameInit(&frame); @@ -1314,6 +1344,7 @@ M_TEST_SUITE_DEFINE(mScript, cmocka_unit_test(identityFunctionF32), cmocka_unit_test(identityFunctionStruct), cmocka_unit_test(addS32), + cmocka_unit_test(addS32Defaults), cmocka_unit_test(subS32), cmocka_unit_test(wrongArgCountLo), cmocka_unit_test(wrongArgCountHi), From bd3a3b8360772eb9a18f0b0cc2d9a6109091e570 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 31 Mar 2023 01:57:08 -0700 Subject: [PATCH 130/290] Util: Fix memory leak in mImageLoadVF --- src/util/image.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util/image.c b/src/util/image.c index b24fd3f50..c074c6b91 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -91,6 +91,7 @@ static struct mImage* mImageLoadPNG(struct VFile* vf) { PNGReadClose(png, info, end); return NULL; } + PNGReadClose(png, info, end); return image; } #endif From d62688a0ef0cf36f04cc91c2d8487159e2fc24e5 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 31 Mar 2023 02:14:51 -0700 Subject: [PATCH 131/290] Util: Fix extracting top channel from 565 formats --- src/util/image.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/image.c b/src/util/image.c index c074c6b91..a0053b573 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -261,12 +261,12 @@ uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat break; case mCOLOR_RGB565: - r = (((color >> 10) & 0x1F) * 0x21) >> 2; + r = (((color >> 11) & 0x1F) * 0x21) >> 2; g = (((color >> 5) & 0x3F) * 0x41) >> 4; b = ((color & 0x1F) * 0x21) >> 2; break; case mCOLOR_BGR565: - b = (((color >> 10) & 0x1F) * 0x21) >> 2; + b = (((color >> 11) & 0x1F) * 0x21) >> 2; g = (((color >> 5) & 0x3F) * 0x41) >> 4; r = ((color & 0x1F) * 0x21) >> 2; break; From c8ce215d58e42c83a7c4ba0f4fe8a5f0b2fb7749 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 31 Mar 2023 02:23:24 -0700 Subject: [PATCH 132/290] Util: Add image format conversion --- include/mgba-util/image.h | 1 + src/util/image.c | 43 +++++++++++ src/util/test/image.c | 154 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 198 insertions(+) diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index 544e38e54..0cb4dc24a 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -94,6 +94,7 @@ struct VFile; struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format); struct mImage* mImageLoad(const char* path); struct mImage* mImageLoadVF(struct VFile* vf); +struct mImage* mImageConvertToFormat(const struct mImage*, enum mColorFormat format); void mImageDestroy(struct mImage*); uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y); diff --git a/src/util/image.c b/src/util/image.c index a0053b573..0e8a0fbed 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -11,6 +11,8 @@ #define PIXEL(IM, X, Y) \ (void*) (((IM)->stride * (Y) + (X)) * (IM)->depth + (uintptr_t) (IM)->data) +#define ROW(IM, Y) PIXEL(IM, 0, Y) + struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format) { struct mImage* image = calloc(1, sizeof(struct mImage)); if (!image) { @@ -107,6 +109,47 @@ struct mImage* mImageLoadVF(struct VFile* vf) { return NULL; } +struct mImage* mImageConvertToFormat(const struct mImage* image, enum mColorFormat format) { + struct mImage* newImage = calloc(1, sizeof(*newImage)); + newImage->width = image->width; + newImage->height = image->height; + newImage->format = format; + if (format == image->format) { + newImage->depth = image->depth; + newImage->stride = image->stride; + newImage->data = malloc(image->stride * image->height * image->depth); + memcpy(newImage->data, image->data, image->stride * image->height * image->depth); + return newImage; + } + newImage->depth = mColorFormatBytes(format); + newImage->stride = image->width; + newImage->data = malloc(image->width * image->height * newImage->depth); + + // TODO: Implement more specializations, e.g. alpha narrowing/widening, channel swapping + size_t x, y; + for (y = 0; y < newImage->height; ++y) { + uintptr_t src = (uintptr_t) ROW(image, y); + uintptr_t dst = (uintptr_t) ROW(newImage, y); + for (x = 0; x < newImage->width; ++x, src += image->depth, dst += newImage->depth) { + uint32_t color = 0; + memcpy(&color, (void*) src, image->depth); +#ifdef __BIG_ENDIAN__ + if (image->depth < 4) { + color >>= (32 - 8 * image->depth); + } +#endif + color = mColorConvert(color, image->format, format); +#ifdef __BIG_ENDIAN__ + if (newImage->depth < 4) { + color <<= (32 - 8 * newImage->depth); + } +#endif + memcpy((void*) dst, &color, newImage->depth); + } + } + return newImage; +} + void mImageDestroy(struct mImage* image) { free(image->data); free(image); diff --git a/src/util/test/image.c b/src/util/test/image.c index 579e8c44b..7903a2dcb 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -502,6 +502,156 @@ M_TEST_DEFINE(loadPng32) { } #endif +M_TEST_DEFINE(convert1x1) { + const enum mColorFormat formats[] = { + mCOLOR_XBGR8, mCOLOR_XRGB8, + mCOLOR_BGRX8, mCOLOR_RGBX8, + mCOLOR_ABGR8, mCOLOR_ARGB8, + mCOLOR_BGRA8, mCOLOR_RGBA8, + mCOLOR_RGB5, mCOLOR_BGR5, + mCOLOR_RGB565, mCOLOR_BGR565, + mCOLOR_ARGB5, mCOLOR_ABGR5, + mCOLOR_RGBA5, mCOLOR_BGRA5, + mCOLOR_RGB8, mCOLOR_BGR8, + mCOLOR_L8, + 0 + }; + + int i, j; + for (i = 0; formats[i]; ++i) { + for (j = 0; formats[j]; ++j) { + struct mImage* src = mImageCreate(1, 1, formats[i]); + mImageSetPixel(src, 0, 0, 0xFF181818); + + struct mImage* dst = mImageConvertToFormat(src, formats[j]); + assert_non_null(dst); + assert_int_equal(dst->format, formats[j]); + assert_int_equal(mImageGetPixel(dst, 0, 0), 0xFF181818); + + mImageDestroy(src); + mImageDestroy(dst); + } + } +} + +M_TEST_DEFINE(convert2x1) { + const enum mColorFormat formats[] = { + mCOLOR_XBGR8, mCOLOR_XRGB8, + mCOLOR_BGRX8, mCOLOR_RGBX8, + mCOLOR_ABGR8, mCOLOR_ARGB8, + mCOLOR_BGRA8, mCOLOR_RGBA8, + mCOLOR_RGB5, mCOLOR_BGR5, + mCOLOR_RGB565, mCOLOR_BGR565, + mCOLOR_ARGB5, mCOLOR_ABGR5, + mCOLOR_RGBA5, mCOLOR_BGRA5, + mCOLOR_RGB8, mCOLOR_BGR8, + mCOLOR_L8, + 0 + }; + + int i, j; + for (i = 0; formats[i]; ++i) { + for (j = 0; formats[j]; ++j) { + struct mImage* src = mImageCreate(2, 1, formats[i]); + mImageSetPixel(src, 0, 0, 0xFF181818); + mImageSetPixel(src, 1, 0, 0xFF101010); + + struct mImage* dst = mImageConvertToFormat(src, formats[j]); + assert_non_null(dst); + assert_int_equal(dst->format, formats[j]); + assert_int_equal(mImageGetPixel(dst, 0, 0), 0xFF181818); + assert_int_equal(mImageGetPixel(dst, 1, 0), 0xFF101010); + + mImageDestroy(src); + mImageDestroy(dst); + } + } +} + +M_TEST_DEFINE(convert1x2) { + const enum mColorFormat formats[] = { + mCOLOR_XBGR8, mCOLOR_XRGB8, + mCOLOR_BGRX8, mCOLOR_RGBX8, + mCOLOR_ABGR8, mCOLOR_ARGB8, + mCOLOR_BGRA8, mCOLOR_RGBA8, + mCOLOR_RGB5, mCOLOR_BGR5, + mCOLOR_RGB565, mCOLOR_BGR565, + mCOLOR_ARGB5, mCOLOR_ABGR5, + mCOLOR_RGBA5, mCOLOR_BGRA5, + mCOLOR_RGB8, mCOLOR_BGR8, + mCOLOR_L8, + 0 + }; + + int i, j; + for (i = 0; formats[i]; ++i) { + for (j = 0; formats[j]; ++j) { + struct mImage* src = calloc(1, sizeof(*src)); + src->width = 1; + src->height = 2; + src->stride = 8; // Use an unusual stride to make sure the right parts get copied + src->format = formats[i]; + src->depth = mColorFormatBytes(src->format); + src->data = calloc(src->stride * src->depth, src->height); + mImageSetPixel(src, 0, 0, 0xFF181818); + mImageSetPixel(src, 0, 1, 0xFF101010); + + struct mImage* dst = mImageConvertToFormat(src, formats[j]); + assert_non_null(dst); + assert_int_equal(dst->format, formats[j]); + assert_int_equal(mImageGetPixel(dst, 0, 0), 0xFF181818); + assert_int_equal(mImageGetPixel(dst, 0, 1), 0xFF101010); + + mImageDestroy(src); + mImageDestroy(dst); + } + } +} + +M_TEST_DEFINE(convert2x2) { + const enum mColorFormat formats[] = { + mCOLOR_XBGR8, mCOLOR_XRGB8, + mCOLOR_BGRX8, mCOLOR_RGBX8, + mCOLOR_ABGR8, mCOLOR_ARGB8, + mCOLOR_BGRA8, mCOLOR_RGBA8, + mCOLOR_RGB5, mCOLOR_BGR5, + mCOLOR_RGB565, mCOLOR_BGR565, + mCOLOR_ARGB5, mCOLOR_ABGR5, + mCOLOR_RGBA5, mCOLOR_BGRA5, + mCOLOR_RGB8, mCOLOR_BGR8, + mCOLOR_L8, + 0 + }; + + int i, j; + for (i = 0; formats[i]; ++i) { + for (j = 0; formats[j]; ++j) { + struct mImage* src = calloc(1, sizeof(*src)); + src->width = 2; + src->height = 2; + src->stride = 8; // Use an unusual stride to make sure the right parts get copied + src->format = formats[i]; + src->depth = mColorFormatBytes(src->format); + src->data = calloc(src->stride * src->depth, src->height); + mImageSetPixel(src, 0, 0, 0xFF181818); + mImageSetPixel(src, 0, 1, 0xFF101010); + mImageSetPixel(src, 1, 0, 0xFF000000); + mImageSetPixel(src, 1, 1, 0xFF080808); + + struct mImage* dst = mImageConvertToFormat(src, formats[j]); + assert_non_null(dst); + assert_int_equal(dst->format, formats[j]); + assert_int_equal(mImageGetPixel(dst, 0, 0), 0xFF181818); + assert_int_equal(mImageGetPixel(dst, 0, 1), 0xFF101010); + assert_int_equal(mImageGetPixel(dst, 1, 0), 0xFF000000); + assert_int_equal(mImageGetPixel(dst, 1, 1), 0xFF080808); + + mImageDestroy(src); + mImageDestroy(dst); + } + } +} + M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(pitchRead), cmocka_unit_test(strideRead), @@ -513,4 +663,8 @@ M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(loadPng24), cmocka_unit_test(loadPng32), #endif + cmocka_unit_test(convert1x1), + cmocka_unit_test(convert2x1), + cmocka_unit_test(convert1x2), + cmocka_unit_test(convert2x2), ) From 6d719b529a2df5296bdc63ae170f485cb1370584 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 31 Mar 2023 02:42:01 -0700 Subject: [PATCH 133/290] Util: Add mImage saving --- include/mgba-util/image.h | 31 ++++++++++++++++++++ src/util/image.c | 60 +++++++++++++++++++++++++++++++++++++++ src/util/test/image.c | 44 ++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+) diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index 0cb4dc24a..214d24c85 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -97,6 +97,8 @@ struct mImage* mImageLoadVF(struct VFile* vf); struct mImage* mImageConvertToFormat(const struct mImage*, enum mColorFormat format); void mImageDestroy(struct mImage*); +bool mImageSave(const struct mImage*, const char* path, const char* format); +bool mImageSaveVF(const struct mImage*, struct VFile* vf, const char* format); uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y); uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y); void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color); @@ -136,6 +138,35 @@ static inline unsigned mColorFormatBytes(enum mColorFormat format) { return 0; } +static inline bool mColorFormatHasAlpha(enum mColorFormat format) { + switch (format) { + case mCOLOR_XBGR8: + case mCOLOR_XRGB8: + case mCOLOR_BGRX8: + case mCOLOR_RGBX8: + case mCOLOR_RGB5: + case mCOLOR_BGR5: + case mCOLOR_RGB565: + case mCOLOR_BGR565: + case mCOLOR_RGB8: + case mCOLOR_BGR8: + case mCOLOR_L8: + return false; + case mCOLOR_ABGR8: + case mCOLOR_ARGB8: + case mCOLOR_BGRA8: + case mCOLOR_RGBA8: + case mCOLOR_ARGB5: + case mCOLOR_ABGR5: + case mCOLOR_RGBA5: + case mCOLOR_BGRA5: + return true; + case mCOLOR_ANY: + break; + } + return false; +} + static inline color_t mColorFrom555(uint16_t value) { #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 diff --git a/src/util/image.c b/src/util/image.c index 0e8a0fbed..ad44d2991 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -155,6 +155,66 @@ void mImageDestroy(struct mImage* image) { free(image); } +bool mImageSave(const struct mImage* image, const char* path, const char* format) { + struct VFile* vf = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); + if (!vf) { + return false; + } + + char extension[PATH_MAX]; + if (!format) { + separatePath(path, NULL, NULL, extension); + format = extension; + } + bool success = mImageSaveVF(image, vf, format); + vf->close(vf); + return success; +} + +#ifdef USE_PNG +bool mImageSavePNG(const struct mImage* image, struct VFile* vf) { + if (image->format != mCOLOR_XBGR8 && image->format != mCOLOR_ABGR8) { + struct mImage* newImage; + if (mColorFormatHasAlpha(image->format)) { + newImage = mImageConvertToFormat(image, mCOLOR_ABGR8); + } else { + newImage = mImageConvertToFormat(image, mCOLOR_XBGR8); + } + bool ret = mImageSavePNG(newImage, vf); + mImageDestroy(newImage); + return ret; + } + + png_structp png = PNGWriteOpen(vf); + png_infop info = NULL; + bool ok = false; + if (png) { + if (image->format == mCOLOR_XBGR8) { + info = PNGWriteHeader(png, image->width, image->height); + if (info) { + ok = PNGWritePixels(png, image->width, image->height, image->stride, image->data); + } + } else { + info = PNGWriteHeaderA(png, image->width, image->height); + if (info) { + ok = PNGWritePixelsA(png, image->width, image->height, image->stride, image->data); + } + } + PNGWriteClose(png, info); + } + return ok; +} +#endif + +bool mImageSaveVF(const struct mImage* image, struct VFile* vf, const char* format) { +#ifdef USE_PNG + if (strcasecmp(format, "png") == 0) { + return mImageSavePNG(image, vf); + } +#endif + return false; +} + uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y) { if (x >= image->width || y >= image->height) { return 0; diff --git a/src/util/test/image.c b/src/util/test/image.c index 7903a2dcb..7da86b4a6 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -6,6 +6,9 @@ #include "util/test/suite.h" #include +#ifdef USE_PNG +#include +#endif #include M_TEST_DEFINE(pitchRead) { @@ -500,6 +503,45 @@ M_TEST_DEFINE(loadPng32) { mImageDestroy(image); } + +M_TEST_DEFINE(savePngNative) { + struct mImage* image = mImageCreate(1, 1, mCOLOR_ABGR8); + mImageSetPixel(image, 0, 0, 0x01234567); + + struct VFile* vf = VFileMemChunk(NULL, 0); + assert_true(mImageSaveVF(image, vf, "png")); + mImageDestroy(image); + + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + assert_true(isPNG(vf)); + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + + image = mImageLoadVF(vf); + vf->close(vf); + assert_non_null(image); + assert_int_equal(mImageGetPixel(image, 0, 0), 0x01234567); + mImageDestroy(image); +} + +M_TEST_DEFINE(savePngNonNative) { + struct mImage* image = mImageCreate(1, 1, mCOLOR_ARGB8); + mImageSetPixel(image, 0, 0, 0x01234567); + + struct VFile* vf = VFileMemChunk(NULL, 0); + assert_true(mImageSaveVF(image, vf, "png")); + mImageDestroy(image); + + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + assert_true(isPNG(vf)); + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + + image = mImageLoadVF(vf); + vf->close(vf); + assert_non_null(image); + assert_int_equal(image->format, mCOLOR_ABGR8); + assert_int_equal(mImageGetPixel(image, 0, 0), 0x01234567); + mImageDestroy(image); +} #endif M_TEST_DEFINE(convert1x1) { @@ -662,6 +704,8 @@ M_TEST_SUITE_DEFINE(Image, #ifdef USE_PNG cmocka_unit_test(loadPng24), cmocka_unit_test(loadPng32), + cmocka_unit_test(savePngNative), + cmocka_unit_test(savePngNonNative), #endif cmocka_unit_test(convert1x1), cmocka_unit_test(convert2x1), From 285f22927be950b46850b17021f3cfee0b4952cb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 1 Apr 2023 22:30:57 -0700 Subject: [PATCH 134/290] Util: Reject 0-width/height images --- src/util/image.c | 3 +++ src/util/test/image.c | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/util/image.c b/src/util/image.c index ad44d2991..e38cf786d 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -14,6 +14,9 @@ #define ROW(IM, Y) PIXEL(IM, 0, Y) struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format) { + if (width < 1 || height < 1) { + return NULL; + } struct mImage* image = calloc(1, sizeof(struct mImage)); if (!image) { return NULL; diff --git a/src/util/test/image.c b/src/util/test/image.c index 7da86b4a6..100d4e69f 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -11,6 +11,15 @@ #endif #include +M_TEST_DEFINE(zeroDim) { + assert_null(mImageCreate(0, 0, mCOLOR_ABGR8)); + assert_null(mImageCreate(1, 0, mCOLOR_ABGR8)); + assert_null(mImageCreate(0, 1, mCOLOR_ABGR8)); + struct mImage* image = mImageCreate(1, 1, mCOLOR_ABGR8); + assert_non_null(image); + mImageDestroy(image); +} + M_TEST_DEFINE(pitchRead) { static uint8_t buffer[12] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB @@ -695,6 +704,7 @@ M_TEST_DEFINE(convert2x2) { } M_TEST_SUITE_DEFINE(Image, + cmocka_unit_test(zeroDim), cmocka_unit_test(pitchRead), cmocka_unit_test(strideRead), cmocka_unit_test(oobRead), From 42527b4c5efbc19e02e0ed1533fe7b4a7ace9a06 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 2 Apr 2023 02:59:11 -0700 Subject: [PATCH 135/290] Util: More image creation functions --- include/mgba-util/image.h | 2 ++ src/util/image.c | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index 214d24c85..a226da914 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -92,6 +92,8 @@ struct mImage { struct VFile; struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format); +struct mImage* mImageCreateWithStride(unsigned width, unsigned height, unsigned stride, enum mColorFormat format); +struct mImage* mImageCreateFromConstBuffer(unsigned width, unsigned height, unsigned stride, enum mColorFormat format, const void* pixels); struct mImage* mImageLoad(const char* path); struct mImage* mImageLoadVF(struct VFile* vf); struct mImage* mImageConvertToFormat(const struct mImage*, enum mColorFormat format); diff --git a/src/util/image.c b/src/util/image.c index e38cf786d..26cbaf01e 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -14,6 +14,10 @@ #define ROW(IM, Y) PIXEL(IM, 0, Y) struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format) { + return mImageCreateWithStride(width, height, width, format); +} + +struct mImage* mImageCreateWithStride(unsigned width, unsigned height, unsigned stride, enum mColorFormat format) { if (width < 1 || height < 1) { return NULL; } @@ -23,7 +27,7 @@ struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat f } image->width = width; image->height = height; - image->stride = width; + image->stride = stride; image->format = format; image->depth = mColorFormatBytes(format); image->data = calloc(width * height, image->depth); @@ -34,6 +38,15 @@ struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat f return image; } +struct mImage* mImageCreateFromConstBuffer(unsigned width, unsigned height, unsigned stride, enum mColorFormat format, const void* pixels) { + struct mImage* image = mImageCreateWithStride(width, height, stride, format); + if (!image) { + return NULL; + } + memcpy(image->data, pixels, height * stride * image->depth); + return image; +} + struct mImage* mImageLoad(const char* path) { struct VFile* vf = VFileOpen(path, O_RDONLY); if (!vf) { From ed69b9f741aab4abeb808ec79fdd42d7eda23e8a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 2 Apr 2023 03:32:20 -0700 Subject: [PATCH 136/290] Scripting: Separate object freeing and deiniting --- include/mgba/script/types.h | 3 ++- src/script/types.c | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index a771e42c0..faf4ca750 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -164,7 +164,8 @@ enum mScriptClassInitType { }; enum { - mSCRIPT_VALUE_FLAG_FREE_BUFFER = 1 + mSCRIPT_VALUE_FLAG_FREE_BUFFER = 1, + mSCRIPT_VALUE_FLAG_DEINIT = 2, }; struct mScriptType; diff --git a/src/script/types.c b/src/script/types.c index 7382d0411..34529a0df 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -1568,7 +1568,7 @@ void mScriptObjectFree(struct mScriptValue* value) { if (value->type->base != mSCRIPT_TYPE_OBJECT) { return; } - if (value->flags & mSCRIPT_VALUE_FLAG_FREE_BUFFER) { + if (value->flags & (mSCRIPT_VALUE_FLAG_DEINIT | mSCRIPT_VALUE_FLAG_FREE_BUFFER)) { mScriptClassInit(value->type->details.cls); if (value->type->details.cls->free) { struct mScriptValue deinitMember; @@ -1584,6 +1584,8 @@ void mScriptObjectFree(struct mScriptValue* value) { mScriptFrameDeinit(&frame); } } + } + if (value->flags & mSCRIPT_VALUE_FLAG_FREE_BUFFER) { free(value->value.opaque); } } From 2fca2f4395a35ebb1879ad4dad9c317913c562f8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 2 Apr 2023 02:59:48 -0700 Subject: [PATCH 137/290] Scripting: Export current image API --- include/mgba/script/base.h | 4 ++ src/script/CMakeLists.txt | 2 + src/script/docgen.c | 8 +-- src/script/image.c | 73 ++++++++++++++++++++++++ src/script/test/image.c | 111 +++++++++++++++++++++++++++++++++++++ 5 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 src/script/image.c create mode 100644 src/script/test/image.c diff --git a/include/mgba/script/base.h b/include/mgba/script/base.h index de8eef033..4e8bbf168 100644 --- a/include/mgba/script/base.h +++ b/include/mgba/script/base.h @@ -11,8 +11,12 @@ CXX_GUARD_START #include +#include + +mSCRIPT_DECLARE_STRUCT(mImage) struct mScriptContext; +void mScriptContextAttachImage(struct mScriptContext* context); void mScriptContextAttachStdlib(struct mScriptContext* context); void mScriptContextAttachSocket(struct mScriptContext* context); diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index f741b9c05..a451ff6d8 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -2,6 +2,7 @@ include(ExportDirectory) set(SOURCE_FILES context.c input.c + image.c socket.c stdlib.c types.c) @@ -18,6 +19,7 @@ if(USE_LUA) list(APPEND SOURCE_FILES engines/lua.c) list(APPEND TEST_FILES test/context.c + test/image.c test/input.c test/lua.c test/stdlib.c) diff --git a/src/script/docgen.c b/src/script/docgen.c index a81e59422..7e35adbbd 100644 --- a/src/script/docgen.c +++ b/src/script/docgen.c @@ -7,10 +7,7 @@ #include #include #include -#include -#include -#include -#include +#include #include struct mScriptContext context; @@ -473,9 +470,10 @@ int main(int argc, char* argv[]) { mScriptContextInit(&context); mScriptContextAttachStdlib(&context); + mScriptContextAttachImage(&context); + mScriptContextAttachInput(&context); mScriptContextAttachSocket(&context); mScriptContextAttachStorage(&context); - mScriptContextAttachInput(&context); mScriptContextSetTextBufferFactory(&context, NULL, NULL); initTypes(); diff --git a/src/script/image.c b/src/script/image.c new file mode 100644 index 000000000..35575b9ae --- /dev/null +++ b/src/script/image.c @@ -0,0 +1,73 @@ +/* Copyright (c) 2013-2023 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 + +static struct mScriptValue* _mImageNew(unsigned width, unsigned height) { + // For various reasons, it's probably a good idea to limit the maximum image size scripts can make + if (width >= 10000 || height >= 10000) { + return NULL; + } + struct mImage* image = mImageCreate(width, height, mCOLOR_ABGR8); + if (!image) { + return NULL; + } + struct mScriptValue* result = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mImage)); + result->value.opaque = image; + result->flags = mSCRIPT_VALUE_FLAG_DEINIT; + return result; +} + +static struct mScriptValue* _mImageLoad(const char* path) { + struct mImage* image = mImageLoad(path); + if (!image) { + return NULL; + } + struct mScriptValue* result = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mImage)); + result->value.opaque = image; + result->flags = mSCRIPT_VALUE_FLAG_DEINIT; + return result; +} + +mSCRIPT_DECLARE_STRUCT_C_METHOD(mImage, U32, getPixel, mImageGetPixel, 2, U32, x, U32, y); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mImage, setPixel, mImageSetPixel, 3, U32, x, U32, y, U32, color); +mSCRIPT_DECLARE_STRUCT_C_METHOD_WITH_DEFAULTS(mImage, BOOL, save, mImageSave, 2, CHARP, path, CHARP, format); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mImage, _deinit, mImageDestroy, 0); + +mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mImage, save) + mSCRIPT_NO_DEFAULT, + mSCRIPT_CHARP("PNG") +mSCRIPT_DEFINE_DEFAULTS_END; + +mSCRIPT_DEFINE_STRUCT(mImage) + mSCRIPT_DEFINE_CLASS_DOCSTRING( + "A single, static image." + ) + mSCRIPT_DEFINE_STRUCT_DEINIT(mImage) + mSCRIPT_DEFINE_DOCSTRING("Save the image to a file. Currently, only `PNG` format is supported") + mSCRIPT_DEFINE_STRUCT_METHOD(mImage, save) + mSCRIPT_DEFINE_DOCSTRING("Get the ARGB value of the pixel at a given coordinate") + mSCRIPT_DEFINE_STRUCT_METHOD(mImage, getPixel) + mSCRIPT_DEFINE_DOCSTRING("Set the ARGB value of the pixel at a given coordinate") + mSCRIPT_DEFINE_STRUCT_METHOD(mImage, setPixel) + mSCRIPT_DEFINE_DOCSTRING("The width of the image, in pixels") + mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mImage, U32, width) + mSCRIPT_DEFINE_DOCSTRING("The height of the image, in pixels") + mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mImage, U32, height) +mSCRIPT_DEFINE_END; + +mSCRIPT_BIND_FUNCTION(mImageNew_Binding, W(mImage), _mImageNew, 2, U32, width, U32, height); +mSCRIPT_BIND_FUNCTION(mImageLoad_Binding, W(mImage), _mImageLoad, 1, CHARP, path); + +void mScriptContextAttachImage(struct mScriptContext* context) { + mScriptContextExportNamespace(context, "image", (struct mScriptKVPair[]) { + mSCRIPT_KV_PAIR(new, &mImageNew_Binding), + mSCRIPT_KV_PAIR(load, &mImageLoad_Binding), + mSCRIPT_KV_SENTINEL + }); + mScriptContextSetDocstring(context, "image", "Methods for creating struct::mImage instances"); + mScriptContextSetDocstring(context, "image.new", "Create a new image with the given dimensions"); + mScriptContextSetDocstring(context, "image.load", "Load an image from a path. Currently, only `PNG` format is supported"); +} diff --git a/src/script/test/image.c b/src/script/test/image.c new file mode 100644 index 000000000..541f7a210 --- /dev/null +++ b/src/script/test/image.c @@ -0,0 +1,111 @@ +/* Copyright (c) 2013-2023 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 "util/test/suite.h" + +#include +#include + +#include "script/test.h" + +#define SETUP_LUA \ + struct mScriptContext context; \ + mScriptContextInit(&context); \ + struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA); \ + mScriptContextAttachStdlib(&context); \ + mScriptContextAttachImage(&context) + +M_TEST_SUITE_SETUP(mScriptImage) { + if (mSCRIPT_ENGINE_LUA->init) { + mSCRIPT_ENGINE_LUA->init(mSCRIPT_ENGINE_LUA); + } + return 0; +} + +M_TEST_SUITE_TEARDOWN(mScriptImage) { + if (mSCRIPT_ENGINE_LUA->deinit) { + mSCRIPT_ENGINE_LUA->deinit(mSCRIPT_ENGINE_LUA); + } + return 0; +} + +M_TEST_DEFINE(members) { + SETUP_LUA; + + TEST_PROGRAM("assert(image)"); + TEST_PROGRAM("assert(image.new)"); + TEST_PROGRAM("assert(image.load)"); + TEST_PROGRAM("im = image.new(1, 1)"); + TEST_PROGRAM("assert(im)"); + TEST_PROGRAM("assert(im.width == 1)"); + TEST_PROGRAM("assert(im.height == 1)"); + TEST_PROGRAM("assert(im.save)"); + TEST_PROGRAM("assert(im.save)"); + TEST_PROGRAM("assert(im.getPixel)"); + TEST_PROGRAM("assert(im.setPixel)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(zeroDim) { + SETUP_LUA; + + TEST_PROGRAM("im = image.new(0, 0)"); + TEST_PROGRAM("assert(not im)"); + TEST_PROGRAM("im = image.new(1, 0)"); + TEST_PROGRAM("assert(not im)"); + TEST_PROGRAM("im = image.new(0, 1)"); + TEST_PROGRAM("assert(not im)"); + TEST_PROGRAM("im = image.new(1, 1)"); + TEST_PROGRAM("assert(im)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(pixelColorDefault) { + SETUP_LUA; + + TEST_PROGRAM("im = image.new(1, 1)"); + TEST_PROGRAM("assert(im:getPixel(0, 0) == 0)"); + + mScriptContextDeinit(&context); +} + +M_TEST_DEFINE(pixelColorRoundTrip) { + SETUP_LUA; + + TEST_PROGRAM("im = image.new(1, 1)"); + TEST_PROGRAM("im:setPixel(0, 0, 0xFF123456)"); + TEST_PROGRAM("assert(im:getPixel(0, 0) == 0xFF123456)"); + + mScriptContextDeinit(&context); +} + +#ifdef USE_PNG +M_TEST_DEFINE(saveLoadRoundTrip) { + SETUP_LUA; + + unlink("tmp.png"); + TEST_PROGRAM("im = image.new(1, 1)"); + TEST_PROGRAM("im:setPixel(0, 0, 0xFF123456)"); + TEST_PROGRAM("assert(im:save('tmp.png'))"); + TEST_PROGRAM("im = image.load('tmp.png')"); + TEST_PROGRAM("assert(im)"); + TEST_PROGRAM("assert(im:getPixel(0, 0) == 0xFF123456)"); + unlink("tmp.png"); + + mScriptContextDeinit(&context); +} +#endif + +M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptImage, + cmocka_unit_test(members), + cmocka_unit_test(zeroDim), + cmocka_unit_test(pixelColorDefault), + cmocka_unit_test(pixelColorRoundTrip), +#ifdef USE_PNG + cmocka_unit_test(saveLoadRoundTrip), +#endif +) From 2d07a269fc76a729e6a49fdf445385e4b6c9523b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 2 Apr 2023 03:40:39 -0700 Subject: [PATCH 138/290] Core: Add screenshotToImage scripting binding --- src/core/scripting.c | 30 +++++++++++++++++++++++++++++- src/core/test/scripting.c | 19 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/core/scripting.c b/src/core/scripting.c index a67297598..ba0549dae 100644 --- a/src/core/scripting.c +++ b/src/core/scripting.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -399,6 +400,7 @@ static int _mScriptCoreLoadStateFile(struct mCore* core, const char* path, int f vf->close(vf); return ok; } + static void _mScriptCoreTakeScreenshot(struct mCore* core, const char* filename) { if (filename) { struct VFile* vf = VFileOpen(filename, O_WRONLY | O_CREAT | O_TRUNC); @@ -412,6 +414,29 @@ static void _mScriptCoreTakeScreenshot(struct mCore* core, const char* filename) } } +static struct mScriptValue* _mScriptCoreTakeScreenshotToImage(struct mCore* core) { + size_t stride; + const void* pixels = 0; + unsigned width, height; + core->currentVideoSize(core, &width, &height); + core->getPixels(core, &pixels, &stride); + if (!pixels) { + return NULL; + } +#ifndef COLOR_16_BIT + struct mImage* image = mImageCreateFromConstBuffer(width, height, stride, mCOLOR_XBGR8, pixels); +#elif COLOR_5_6_5 + struct mImage* image = mImageCreateFromConstBuffer(width, height, stride, mCOLOR_RGB565, pixels); +#else + struct mImage* image = mImageCreateFromConstBuffer(width, height, stride, mCOLOR_BGR5, pixels); +#endif + + struct mScriptValue* result = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mImage)); + result->value.opaque = image; + result->flags = mSCRIPT_VALUE_FLAG_DEINIT; + return result; +} + // Loading functions mSCRIPT_DECLARE_STRUCT_METHOD(mCore, BOOL, loadFile, mCoreLoadFile, 1, CHARP, path); mSCRIPT_DECLARE_STRUCT_METHOD(mCore, BOOL, autoloadSave, mCoreAutoloadSave, 0); @@ -464,6 +489,7 @@ mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mCore, BOOL, loadStateFile, _mScript // Miscellaneous functions mSCRIPT_DECLARE_STRUCT_VOID_METHOD_WITH_DEFAULTS(mCore, screenshot, _mScriptCoreTakeScreenshot, 1, CHARP, filename); +mSCRIPT_DECLARE_STRUCT_METHOD(mCore, W(mImage), screenshotToImage, _mScriptCoreTakeScreenshotToImage, 0); mSCRIPT_DEFINE_STRUCT(mCore) mSCRIPT_DEFINE_CLASS_DOCSTRING( @@ -549,8 +575,10 @@ mSCRIPT_DEFINE_STRUCT(mCore) mSCRIPT_DEFINE_DOCSTRING("Load state from the given path. See C.SAVESTATE for possible values for `flags`") mSCRIPT_DEFINE_STRUCT_METHOD(mCore, loadStateFile) - mSCRIPT_DEFINE_DOCSTRING("Save a screenshot") + mSCRIPT_DEFINE_DOCSTRING("Save a screenshot to a file") mSCRIPT_DEFINE_STRUCT_METHOD(mCore, screenshot) + mSCRIPT_DEFINE_DOCSTRING("Get a screenshot in an struct::mImage") + mSCRIPT_DEFINE_STRUCT_METHOD(mCore, screenshotToImage) mSCRIPT_DEFINE_END; mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, checksum) diff --git a/src/core/test/scripting.c b/src/core/test/scripting.c index b9cfa03e8..4ed2c2abd 100644 --- a/src/core/test/scripting.c +++ b/src/core/test/scripting.c @@ -310,6 +310,24 @@ M_TEST_DEFINE(logging) { mScriptContextDeinit(&context); } +M_TEST_DEFINE(screenshot) { + SETUP_LUA; + CREATE_CORE; + color_t* buffer = malloc(240 * 160 * sizeof(color_t)); + core->setVideoBuffer(core, buffer, 240); + core->reset(core); + core->runFrame(core); + + TEST_PROGRAM("im = emu:screenshotToImage()"); + TEST_PROGRAM("assert(im)"); + TEST_PROGRAM("assert(im.width >= 160)"); + TEST_PROGRAM("assert(im.height >= 144)"); + + TEARDOWN_CORE; + free(buffer); + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore, cmocka_unit_test(globals), cmocka_unit_test(infoFuncs), @@ -318,4 +336,5 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore, cmocka_unit_test(memoryRead), cmocka_unit_test(memoryWrite), cmocka_unit_test(logging), + cmocka_unit_test(screenshot), ) From 329159bddccd9bcd5fec929a0b0e4435b85bf15e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 2 Apr 2023 23:23:54 -0700 Subject: [PATCH 139/290] Util: Refactor some things that will be used later --- src/util/image.c | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/src/util/image.c b/src/util/image.c index 26cbaf01e..106fbef08 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -13,6 +13,34 @@ #define ROW(IM, Y) PIXEL(IM, 0, Y) +#ifdef __BIG_ENDIAN__ +#define SHIFT_IN(COLOR, DEPTH) \ + if ((DEPTH) < 4) { \ + (COLOR) >>= (32 - 8 * (DEPTH)); \ + } + +#define SHIFT_OUT(COLOR, DEPTH) \ + if ((DEPTH) < 4) { \ + (COLOR) <<= (32 - 8 *(DEPTH)); \ + } +#else +#define SHIFT_IN(COLOR, DEPTH) +#define SHIFT_OUT(COLOR, DEPTH) +#endif + +#define GET_PIXEL(DST, SRC, DEPTH) do { \ + uint32_t _color = 0; \ + memcpy(&_color, (void*) (SRC), (DEPTH)); \ + SHIFT_IN(_color, (DEPTH)); \ + (DST) = _color; \ +} while (0) + +#define PUT_PIXEL(SRC, DST, DEPTH) do { \ + uint32_t _color = (SRC); \ + SHIFT_OUT(_color, (DEPTH)); \ + memcpy((void*) (DST), &_color, (DEPTH)); \ +} while (0); + struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format) { return mImageCreateWithStride(width, height, width, format); } @@ -147,20 +175,10 @@ struct mImage* mImageConvertToFormat(const struct mImage* image, enum mColorForm uintptr_t src = (uintptr_t) ROW(image, y); uintptr_t dst = (uintptr_t) ROW(newImage, y); for (x = 0; x < newImage->width; ++x, src += image->depth, dst += newImage->depth) { - uint32_t color = 0; - memcpy(&color, (void*) src, image->depth); -#ifdef __BIG_ENDIAN__ - if (image->depth < 4) { - color >>= (32 - 8 * image->depth); - } -#endif + uint32_t color; + GET_PIXEL(color, src, image->depth); color = mColorConvert(color, image->format, format); -#ifdef __BIG_ENDIAN__ - if (newImage->depth < 4) { - color <<= (32 - 8 * newImage->depth); - } -#endif - memcpy((void*) dst, &color, newImage->depth); + PUT_PIXEL(color, dst, newImage->depth); } } return newImage; From 5b18089e851dae2eda5affc3d452d3fec707afa0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 3 Apr 2023 00:39:53 -0700 Subject: [PATCH 140/290] Util: Add mRectangleIntersection --- include/mgba-util/geometry.h | 1 + src/util/geometry.c | 58 ++ src/util/test/geometry.c | 1241 ++++++++++++++++++++++++++++++++++ 3 files changed, 1300 insertions(+) diff --git a/include/mgba-util/geometry.h b/include/mgba-util/geometry.h index 5626a1aa0..aa7d65e94 100644 --- a/include/mgba-util/geometry.h +++ b/include/mgba-util/geometry.h @@ -23,6 +23,7 @@ struct mRectangle { }; void mRectangleUnion(struct mRectangle* dst, const struct mRectangle* add); +bool mRectangleIntersection(struct mRectangle* dst, const struct mRectangle* add); void mRectangleCenter(const struct mRectangle* ref, struct mRectangle* rect); CXX_GUARD_END diff --git a/src/util/geometry.c b/src/util/geometry.c index f0e41380e..17c523afe 100644 --- a/src/util/geometry.c +++ b/src/util/geometry.c @@ -30,6 +30,64 @@ void mRectangleUnion(struct mRectangle* dst, const struct mRectangle* add) { dst->height = y1 - y0; } +bool mRectangleIntersection(struct mRectangle* dst, const struct mRectangle* add) { + int x[3]; + int y[3]; + + if (dst == add) { + return true; + } + +#define SORT(Z, M) \ + if (dst->Z < add->Z) { \ + Z[0] = dst->Z; \ + Z[1] = add->Z; \ + } else { \ + Z[0] = add->Z; \ + Z[1] = dst->Z; \ + } \ + if (dst->Z + dst->M < add->Z + add->M) { \ + /* dst is entirely before add */ \ + if (dst->Z + dst->M <= add->Z) { \ + return false; \ + } \ + if (dst->Z + dst->M < Z[1]) { \ + Z[2] = Z[1]; \ + Z[1] = dst->Z + dst->M; \ + } else { \ + Z[2] = dst->Z + dst->M; \ + } \ + if (add->Z + add->M < Z[2]) { \ + Z[2] = add->Z + add->M; \ + } \ + } else { \ + /* dst is after before add */ \ + if (dst->Z >= add->Z + add->M) { \ + return false; \ + } \ + if (add->Z + add->M < Z[1]) { \ + Z[2] = Z[1]; \ + Z[1] = add->Z + add->M; \ + } else { \ + Z[2] = add->Z + add->M; \ + } \ + if (dst->Z + dst->M < Z[2]) { \ + Z[2] = dst->Z + dst->M; \ + } \ + } + + SORT(x, width); + SORT(y, height); + +#undef SORT + + dst->x = x[1]; + dst->width = x[2] - x[1]; + dst->y = y[1]; + dst->height = y[2] - y[1]; + return true; +} + void mRectangleCenter(const struct mRectangle* ref, struct mRectangle* rect) { rect->x = ref->x + (ref->width - rect->width) / 2; rect->y = ref->y + (ref->height - rect->height) / 2; diff --git a/src/util/test/geometry.c b/src/util/test/geometry.c index fac85e34a..419a1b1da 100644 --- a/src/util/test/geometry.c +++ b/src/util/test/geometry.c @@ -127,6 +127,1202 @@ M_TEST_DEFINE(unionRectNegativeOrigin) { assert_int_equal(a.height, 2); } +M_TEST_DEFINE(intersectRectNWNo) { + /* + * A..... + * ...... + * ..RR.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 0, + .y = 0, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectNWCorner0) { + /* + * ...... + * .A.... + * ..RR.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 1, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectNWCorner1) { + /* + * ...... + * .AA... + * .AXR.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 1, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectNWFullN) { + /* + * ...... + * .AAA.. + * .AXX.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 1, + .width = 3, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectNWFullW) { + /* + * ...... + * .AA... + * .AXR.. + * .AXR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 1, + .width = 2, + .height = 3 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectNNo) { + /* + * ..AA.. + * ...... + * ..RR.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 0, + .width = 2, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectN0) { + /* + * ...... + * ..AA.. + * ..RR.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 1, + .width = 2, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectN1) { + /* + * ...... + * ..AA.. + * ..XX.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 1, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectNFull) { + /* + * ...... + * ..AA.. + * ..XX.. + * ..XX.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 1, + .width = 2, + .height = 3 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectNENo) { + /* + * .....A + * ...... + * ..RR.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 5, + .y = 0, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectNECorner0) { + /* + * ...... + * ....A. + * ..RR.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 4, + .y = 0, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectNECorner1) { + /* + * ...... + * ..,AA. + * ..RXA. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 3, + .y = 1, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 3); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectNEFullN) { + /* + * ...... + * ..AAA. + * ..XXA. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 1, + .width = 3, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectNEFullE) { + /* + * ...... + * ...AA. + * ..RXA. + * ..RXA. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 3, + .y = 1, + .width = 2, + .height = 3 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 3); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectWNo) { + /* + * ...... + * ...... + * A.RR.. + * A.RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 0, + .y = 2, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectW0) { + /* + * ...... + * ...... + * .ARR.. + * .ARR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 2, + .width = 1, + .height = 2 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectW1) { + /* + * ...... + * ...... + * .AXR.. + * .AXR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 2, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectWFull) { + /* + * ...... + * ...... + * .AXX.. + * .AXX.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 2, + .width = 3, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectSWNo) { + /* + * ...... + * ...... + * ..RR.. + * ..RR.. + * ...... + * A..... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 0, + .y = 5, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectSWCorner0) { + /* + * ...... + * ...... + * ..RR.. + * ..RR.. + * .A.... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 4, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectSWCorner1) { + /* + * ...... + * ...... + * ..RR.. + * .AXR.. + * .AA... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 3, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 3); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectSWFullS) { + /* + * ...... + * ...... + * ..RR.. + * .AXX.. + * .AAA.. + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 3, + .width = 3, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 3); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectSWFullW) { + /* + * ...... + * ...... + * .AXR.. + * .AXR.. + * .AA... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 1, + .y = 2, + .width = 2, + .height = 3 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectSNo) { + /* + * ...... + * ...... + * ..RR.. + * ..RR.. + * ...... + * ..AA.. + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 5, + .width = 2, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectS0) { + /* + * ...... + * ...... + * ..RR.. + * ..RR.. + * ..AA.. + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 4, + .width = 2, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectS1) { + /* + * ...... + * ...... + * ..RR.. + * ..XX.. + * ..AA.. + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 3, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 3); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectSFull) { + /* + * ...... + * ...... + * ..XX.. + * ..XX.. + * ..AA.. + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 2, + .width = 2, + .height = 3 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectSENo) { + /* + * ...... + * ...... + * ..RR.. + * ..RR.. + * ...... + * .....A + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 5, + .y = 5, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectSECorner0) { + /* + * ...... + * ...... + * ..RR.. + * ..RR.. + * ....A. + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 4, + .y = 4, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectSECorner1) { + /* + * ...... + * ..,... + * ..RR.. + * ..RXA. + * ...AA. + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 3, + .y = 3, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 3); + assert_int_equal(ref.y, 3); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectSEFullS) { + /* + * ...... + * ...... + * ..RR.. + * ..XXA. + * ..AAA. + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 3, + .width = 3, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 3); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectSEFullE) { + /* + * ...... + * ...... + * ..RXA. + * ..RXA. + * ...AA. + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 3, + .y = 2, + .width = 2, + .height = 3 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 3); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectENo) { + /* + * ...... + * ...... + * ..RR.A + * ..RR.A + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 5, + .y = 2, + .width = 1, + .height = 1 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectE0) { + /* + * ...... + * ...... + * ..RRA. + * ..RRA. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 4, + .y = 2, + .width = 1, + .height = 2 + }; + assert_false(mRectangleIntersection(&ref, &a)); +} + +M_TEST_DEFINE(intersectRectE1) { + /* + * ...... + * ...... + * ..RXA. + * ..RXA. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 3, + .y = 2, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 3); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectEFull) { + /* + * ...... + * ...... + * ..XXA. + * ..XXA. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 2, + .width = 3, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectInternalNE) { + /* + * ...... + * ...... + * ..XR.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 2, + .width = 1, + .height = 1 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectInternalNW) { + /* + * ...... + * ...... + * ..RX.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 3, + .y = 2, + .width = 1, + .height = 1 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 3); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectInternalSE) { + /* + * ...... + * ...... + * ..RR.. + * ..XR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 3, + .width = 1, + .height = 1 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 3); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectInternalSW) { + /* + * ...... + * ...... + * ..RR.. + * ..RX.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 3, + .y = 3, + .width = 1, + .height = 1 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 3); + assert_int_equal(ref.y, 3); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectInternalN) { + /* + * ...... + * ...... + * ..XX.. + * ..RR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 2, + .width = 2, + .height = 1 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectInternalW) { + /* + * ...... + * ...... + * ..XR.. + * ..XR.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 2, + .width = 1, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectInternalS) { + /* + * ...... + * ...... + * ..RR.. + * ..XX.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 3, + .width = 2, + .height = 1 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 3); + assert_int_equal(ref.height, 1); + assert_int_equal(ref.width, 2); +} + +M_TEST_DEFINE(intersectRectInternalE) { + /* + * ...... + * ...... + * ..RX.. + * ..RX.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 3, + .y = 2, + .width = 1, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 3); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 1); +} + +M_TEST_DEFINE(intersectRectEqual) { + /* + * ...... + * ...... + * ..XX.. + * ..XX.. + * ...... + * ...... + */ + struct mRectangle ref = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + struct mRectangle a = { + .x = 2, + .y = 2, + .width = 2, + .height = 2 + }; + assert_true(mRectangleIntersection(&ref, &a)); + assert_int_equal(ref.x, 2); + assert_int_equal(ref.y, 2); + assert_int_equal(ref.height, 2); + assert_int_equal(ref.width, 2); +} + M_TEST_DEFINE(centerRectBasic) { struct mRectangle ref = { .x = 0, @@ -206,6 +1402,51 @@ M_TEST_SUITE_DEFINE(Geometry, cmocka_unit_test(unionRectOverlapping), cmocka_unit_test(unionRectSubRect), cmocka_unit_test(unionRectNegativeOrigin), + cmocka_unit_test(intersectRectNWNo), + cmocka_unit_test(intersectRectNWCorner0), + cmocka_unit_test(intersectRectNWCorner1), + cmocka_unit_test(intersectRectNWFullN), + cmocka_unit_test(intersectRectNWFullW), + cmocka_unit_test(intersectRectNNo), + cmocka_unit_test(intersectRectN0), + cmocka_unit_test(intersectRectN1), + cmocka_unit_test(intersectRectNFull), + cmocka_unit_test(intersectRectNENo), + cmocka_unit_test(intersectRectNECorner0), + cmocka_unit_test(intersectRectNECorner1), + cmocka_unit_test(intersectRectNEFullN), + cmocka_unit_test(intersectRectNEFullE), + cmocka_unit_test(intersectRectWNo), + cmocka_unit_test(intersectRectW0), + cmocka_unit_test(intersectRectW1), + cmocka_unit_test(intersectRectWFull), + cmocka_unit_test(intersectRectSWNo), + cmocka_unit_test(intersectRectSWCorner0), + cmocka_unit_test(intersectRectSWCorner1), + cmocka_unit_test(intersectRectSWFullS), + cmocka_unit_test(intersectRectSWFullW), + cmocka_unit_test(intersectRectSNo), + cmocka_unit_test(intersectRectS0), + cmocka_unit_test(intersectRectS1), + cmocka_unit_test(intersectRectSFull), + cmocka_unit_test(intersectRectSENo), + cmocka_unit_test(intersectRectSECorner0), + cmocka_unit_test(intersectRectSECorner1), + cmocka_unit_test(intersectRectSEFullS), + cmocka_unit_test(intersectRectSEFullE), + cmocka_unit_test(intersectRectENo), + cmocka_unit_test(intersectRectE0), + cmocka_unit_test(intersectRectE1), + cmocka_unit_test(intersectRectEFull), + cmocka_unit_test(intersectRectInternalNE), + cmocka_unit_test(intersectRectInternalNW), + cmocka_unit_test(intersectRectInternalSE), + cmocka_unit_test(intersectRectInternalSW), + cmocka_unit_test(intersectRectInternalN), + cmocka_unit_test(intersectRectInternalW), + cmocka_unit_test(intersectRectInternalS), + cmocka_unit_test(intersectRectInternalE), + cmocka_unit_test(intersectRectEqual), cmocka_unit_test(centerRectBasic), cmocka_unit_test(centerRectRoundDown), cmocka_unit_test(centerRectRoundDown2), From cfd5572fb67787280d2fc5849ce8a5a8123ab3af Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 3 Apr 2023 01:50:16 -0700 Subject: [PATCH 141/290] Util: Add basic mImage blit with no blending --- include/mgba-util/image.h | 3 + src/util/image.c | 51 +++++++ src/util/test/image.c | 282 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 336 insertions(+) diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index a226da914..83dec79a7 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -101,11 +101,14 @@ void mImageDestroy(struct mImage*); bool mImageSave(const struct mImage*, const char* path, const char* format); bool mImageSaveVF(const struct mImage*, struct VFile* vf, const char* format); + uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y); uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y); void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color); void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color); +void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y); + uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to); #ifndef PYCPARSE diff --git a/src/util/image.c b/src/util/image.c index 106fbef08..722c13857 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +#include #include #include @@ -317,6 +318,56 @@ void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color mImageSetPixelRaw(image, x, y, mColorConvert(color, mCOLOR_ARGB8, image->format)); } +void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y) { + struct mRectangle dstRect = { + .x = 0, + .y = 0, + .width = image->width, + .height = image->height + }; + struct mRectangle srcRect = { + .x = x, + .y = y, + .width = source->width, + .height = source->height + }; + if (!mRectangleIntersection(&srcRect, &dstRect)) { + return; + } + + int srcStartX; + int srcStartY; + int dstStartX; + int dstStartY; + + if (x < 0) { + dstStartX = 0; + srcStartX = -x; + } else { + srcStartX = 0; + dstStartX = srcRect.x; + } + + if (y < 0) { + dstStartY = 0; + srcStartY = -y; + } else { + srcStartY = 0; + dstStartY = srcRect.y; + } + + for (y = 0; y < srcRect.height; ++y) { + uintptr_t srcPixel = (uintptr_t) PIXEL(source, srcStartX, srcStartY + y); + uintptr_t dstPixel = (uintptr_t) PIXEL(image, dstStartX, dstStartY + y); + for (x = 0; x < srcRect.width; ++x, srcPixel += source->depth, dstPixel += image->depth) { + uint32_t color; + GET_PIXEL(color, srcPixel, source->depth); + color = mColorConvert(color, source->format, image->format); + PUT_PIXEL(color, dstPixel, image->depth); + } + } +} + uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to) { if (from == to) { return color; diff --git a/src/util/test/image.c b/src/util/test/image.c index 100d4e69f..fde76e1c5 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -703,6 +703,287 @@ M_TEST_DEFINE(convert2x2) { } } +M_TEST_DEFINE(blitBoundaries) { + static const uint32_t spriteBuffer[4] = { + 0xFF000F00, 0xFF000F01, + 0xFF000F10, 0xFF000F11 + }; + static const uint32_t canvasBuffer[9] = { + 0xFF000000, 0xFF000000, 0xFF000000, + 0xFF000000, 0xFF000000, 0xFF000000, + 0xFF000000, 0xFF000000, 0xFF000000 + }; + + struct mImage* sprite = mImageCreateFromConstBuffer(2, 2, 2, mCOLOR_XRGB8, spriteBuffer); + struct mImage* canvas; + +#define COMPARE(AA, BA, CA, AB, BB, CB, AC, BC, CC) \ + assert_int_equal(mImageGetPixel(canvas, 0, 0), 0xFF000000 | (AA)); \ + assert_int_equal(mImageGetPixel(canvas, 1, 0), 0xFF000000 | (BA)); \ + assert_int_equal(mImageGetPixel(canvas, 2, 0), 0xFF000000 | (CA)); \ + assert_int_equal(mImageGetPixel(canvas, 0, 1), 0xFF000000 | (AB)); \ + assert_int_equal(mImageGetPixel(canvas, 1, 1), 0xFF000000 | (BB)); \ + assert_int_equal(mImageGetPixel(canvas, 2, 1), 0xFF000000 | (CB)); \ + assert_int_equal(mImageGetPixel(canvas, 0, 2), 0xFF000000 | (AC)); \ + assert_int_equal(mImageGetPixel(canvas, 1, 2), 0xFF000000 | (BC)); \ + assert_int_equal(mImageGetPixel(canvas, 2, 2), 0xFF000000 | (CC)) + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -2, -2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -1, -2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 0, -2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 1, -2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 2, -2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 3, -2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -2, -1); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -1, -1); + COMPARE(0xF11, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 0, -1); + COMPARE(0xF10, 0xF11, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 1, -1); + COMPARE(0x000, 0xF10, 0xF11, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 2, -1); + COMPARE(0x000, 0x000, 0xF10, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 3, -1); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -2, 0); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -1, 0); + COMPARE(0xF01, 0x000, 0x000, + 0xF11, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 0, 0); + COMPARE(0xF00, 0xF01, 0x000, + 0xF10, 0xF11, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 1, 0); + COMPARE(0x000, 0xF00, 0xF01, + 0x000, 0xF10, 0xF11, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 2, 0); + COMPARE(0x000, 0x000, 0xF00, + 0x000, 0x000, 0xF10, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 3, 0); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -2, 1); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -1, 1); + COMPARE(0x000, 0x000, 0x000, + 0xF01, 0x000, 0x000, + 0xF11, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 0, 1); + COMPARE(0x000, 0x000, 0x000, + 0xF00, 0xF01, 0x000, + 0xF10, 0xF11, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 1, 1); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0xF00, 0xF01, + 0x000, 0xF10, 0xF11); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 2, 1); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0xF00, + 0x000, 0x000, 0xF10); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 3, 1); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -2, 2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -1, 2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0xF01, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 0, 2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0xF00, 0xF01, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 1, 2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0xF00, 0xF01); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 2, 2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0xF00); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 3, 2); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -2, 3); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, -1, 3); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 0, 3); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 1, 3); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 2, 3); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + + canvas = mImageCreateFromConstBuffer(3, 3, 3, mCOLOR_XRGB8, canvasBuffer); + mImageBlit(canvas, sprite, 3, 3); + COMPARE(0x000, 0x000, 0x000, + 0x000, 0x000, 0x000, + 0x000, 0x000, 0x000); + mImageDestroy(canvas); + +#undef COMPARE + mImageDestroy(sprite); +} + M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(zeroDim), cmocka_unit_test(pitchRead), @@ -721,4 +1002,5 @@ M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(convert2x1), cmocka_unit_test(convert1x2), cmocka_unit_test(convert2x2), + cmocka_unit_test(blitBoundaries), ) From c884560fdb3d332138637f10949f9d58e594381e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 3 Apr 2023 02:45:49 -0700 Subject: [PATCH 142/290] Util: Add alpha-based mImage compositing functions --- include/mgba-util/image.h | 52 ++++++++++++++ src/util/image.c | 140 ++++++++++++++++++++++++++++---------- src/util/test/color.c | 10 +++ 3 files changed, 166 insertions(+), 36 deletions(-) diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index 83dec79a7..6dd3cee73 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -108,6 +108,8 @@ void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color); void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y); +void mImageComposite(struct mImage* image, const struct mImage* source, int x, int y); +void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source, int x, int y, float alpha); uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to); @@ -250,6 +252,56 @@ ATTRIBUTE_UNUSED static unsigned mColorMix5Bit(int weightA, unsigned colorA, int #endif return c; } + +ATTRIBUTE_UNUSED static uint32_t mColorMixARGB8(uint32_t colorA, uint32_t colorB) { + uint32_t alpha = colorA >> 24; + if (!alpha) { + return colorB; + } + + uint32_t color = 0; + uint32_t a, b; + a = colorA & 0xFF00FF; + a *= alpha + 1; + color += (a >> 8) & 0xFF00FF; + + a = colorB & 0xFF00FF; + a *= 0x100 - alpha; + color += (a >> 8) & 0xFF00FF; + + if (color & 0x100) { + color &= ~0xFF; + color |= 0xFF; + } + if (color & 0x1000000) { + color &= ~0xFF0000; + color |= 0xFF0000; + } + + b = 0; + a = colorA & 0xFF00; + a *= alpha + 1; + b += a & 0xFF0000; + + a = colorB & 0xFF00; + a *= 0x100 - alpha; + b += a & 0xFF0000; + + if (b & 0x1000000) { + b &= ~0xFF0000; + b |= 0xFF0000; + } + color |= b >> 8; + + alpha += colorB >> 24; + if (alpha > 0xFF) { + color |= 0xFF000000; + } else { + color |= alpha << 24; + } + + return color; +} #endif CXX_GUARD_END diff --git a/src/util/image.c b/src/util/image.c index 722c13857..e37089d2a 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -318,43 +318,43 @@ void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color mImageSetPixelRaw(image, x, y, mColorConvert(color, mCOLOR_ARGB8, image->format)); } +#define COMPOSITE_BOUNDS_INIT \ + struct mRectangle dstRect = { \ + .x = 0, \ + .y = 0, \ + .width = image->width, \ + .height = image->height \ + }; \ + struct mRectangle srcRect = { \ + .x = x, \ + .y = y, \ + .width = source->width, \ + .height = source->height \ + }; \ + if (!mRectangleIntersection(&srcRect, &dstRect)) { \ + return; \ + } \ + int srcStartX; \ + int srcStartY; \ + int dstStartX; \ + int dstStartY; \ + if (x < 0) { \ + dstStartX = 0; \ + srcStartX = -x; \ + } else { \ + srcStartX = 0; \ + dstStartX = srcRect.x; \ + } \ + if (y < 0) { \ + dstStartY = 0; \ + srcStartY = -y; \ + } else { \ + srcStartY = 0; \ + dstStartY = srcRect.y; \ + } + void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y) { - struct mRectangle dstRect = { - .x = 0, - .y = 0, - .width = image->width, - .height = image->height - }; - struct mRectangle srcRect = { - .x = x, - .y = y, - .width = source->width, - .height = source->height - }; - if (!mRectangleIntersection(&srcRect, &dstRect)) { - return; - } - - int srcStartX; - int srcStartY; - int dstStartX; - int dstStartY; - - if (x < 0) { - dstStartX = 0; - srcStartX = -x; - } else { - srcStartX = 0; - dstStartX = srcRect.x; - } - - if (y < 0) { - dstStartY = 0; - srcStartY = -y; - } else { - srcStartY = 0; - dstStartY = srcRect.y; - } + COMPOSITE_BOUNDS_INIT; for (y = 0; y < srcRect.height; ++y) { uintptr_t srcPixel = (uintptr_t) PIXEL(source, srcStartX, srcStartY + y); @@ -368,6 +368,74 @@ void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y) } } +void mImageComposite(struct mImage* image, const struct mImage* source, int x, int y) { + if (!mColorFormatHasAlpha(source->format)) { + mImageBlit(image, source, x, y); + return; + } + + COMPOSITE_BOUNDS_INIT; + + for (y = 0; y < srcRect.height; ++y) { + uintptr_t srcPixel = (uintptr_t) PIXEL(source, srcStartX, srcStartY + y); + uintptr_t dstPixel = (uintptr_t) PIXEL(image, dstStartX, dstStartY + y); + for (x = 0; x < srcRect.width; ++x, srcPixel += source->depth, dstPixel += image->depth) { + uint32_t color, colorB; + GET_PIXEL(color, srcPixel, source->depth); + color = mColorConvert(color, source->format, mCOLOR_ARGB8); + if (color < 0xFF000000) { + GET_PIXEL(colorB, dstPixel, image->depth); + colorB = mColorConvert(colorB, image->format, mCOLOR_ARGB8); + color = mColorMixARGB8(color, colorB); + } + color = mColorConvert(color, mCOLOR_ARGB8, image->format); + PUT_PIXEL(color, dstPixel, image->depth); + } + } +} + +void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source, int x, int y, float alpha) { + if (alpha >= 1 && alpha < 257.f / 256.f) { + mImageComposite(image, source, x, y); + return; + } + if (alpha <= 0) { + return; + } + if (alpha > 256) { + // TODO: Add a slow path for alpha > 1, since we need to check saturation only on this path + alpha = 256; + } + + COMPOSITE_BOUNDS_INIT; + + int fixedAlpha = alpha * 0x200; + + for (y = 0; y < srcRect.height; ++y) { + uintptr_t srcPixel = (uintptr_t) PIXEL(source, srcStartX, srcStartY + y); + uintptr_t dstPixel = (uintptr_t) PIXEL(image, dstStartX, dstStartY + y); + for (x = 0; x < srcRect.width; ++x, srcPixel += source->depth, dstPixel += image->depth) { + uint32_t color, colorB; + GET_PIXEL(color, srcPixel, source->depth); + color = mColorConvert(color, source->format, mCOLOR_ARGB8); + uint32_t alpha = (color >> 24) * fixedAlpha; + alpha >>= 9; + if (alpha > 0xFF) { + alpha = 0xFF; + } + color &= 0x00FFFFFF; + color |= alpha << 24; + + GET_PIXEL(colorB, dstPixel, image->depth); + colorB = mColorConvert(colorB, image->format, mCOLOR_ARGB8); + + color = mColorMixARGB8(color, colorB); + color = mColorConvert(color, mCOLOR_ARGB8, image->format); + PUT_PIXEL(color, dstPixel, image->depth); + } + } +} + uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to) { if (from == to) { return color; diff --git a/src/util/test/color.c b/src/util/test/color.c index ab4b58ac3..f01fd2d2f 100644 --- a/src/util/test/color.c +++ b/src/util/test/color.c @@ -151,6 +151,15 @@ M_TEST_DEFINE(convertToGray) { } } +M_TEST_DEFINE(alphaBlendARGB8) { + assert_int_equal(mColorMixARGB8(0xFF012345, 0xFF987654), 0xFF012345); + assert_int_equal(mColorMixARGB8(0x00012345, 0xFF987654), 0xFF987654); + assert_int_equal(mColorMixARGB8(0x80012345, 0xFF987654), 0xFF4C4C4C); + assert_int_equal(mColorMixARGB8(0x80012345, 0x40987654), 0xC04C4C4C); + assert_int_equal(mColorMixARGB8(0x01012345, 0xFF987654), 0xFF977553); + assert_int_equal(mColorMixARGB8(0x01012345, 0xFD987654), 0xFE977553); +} + M_TEST_SUITE_DEFINE(Color, cmocka_unit_test(channelSwap32), cmocka_unit_test(channelSwap16), @@ -160,4 +169,5 @@ M_TEST_SUITE_DEFINE(Color, cmocka_unit_test(convertToAlpha), cmocka_unit_test(convertFromGray), cmocka_unit_test(convertToGray), + cmocka_unit_test(alphaBlendARGB8), ) From 6867b556f3e5289103bb9e32af9cca7e064a78cb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 3 Apr 2023 03:02:08 -0700 Subject: [PATCH 143/290] Scripting: Export image compositing functions --- src/script/image.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/script/image.c b/src/script/image.c index 35575b9ae..05ca9bcd4 100644 --- a/src/script/image.c +++ b/src/script/image.c @@ -30,17 +30,25 @@ static struct mScriptValue* _mImageLoad(const char* path) { result->flags = mSCRIPT_VALUE_FLAG_DEINIT; return result; } - mSCRIPT_DECLARE_STRUCT_C_METHOD(mImage, U32, getPixel, mImageGetPixel, 2, U32, x, U32, y); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mImage, setPixel, mImageSetPixel, 3, U32, x, U32, y, U32, color); mSCRIPT_DECLARE_STRUCT_C_METHOD_WITH_DEFAULTS(mImage, BOOL, save, mImageSave, 2, CHARP, path, CHARP, format); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mImage, _deinit, mImageDestroy, 0); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mImage, drawImageOpaque, mImageBlit, 3, CS(mImage), image, U32, x, U32, y); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD_WITH_DEFAULTS(mImage, drawImage, mImageCompositeWithAlpha, 4, CS(mImage), image, U32, x, U32, y, F32, alpha); mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mImage, save) mSCRIPT_NO_DEFAULT, mSCRIPT_CHARP("PNG") mSCRIPT_DEFINE_DEFAULTS_END; +mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mImage, drawImage) + mSCRIPT_NO_DEFAULT, + mSCRIPT_NO_DEFAULT, + mSCRIPT_NO_DEFAULT, + mSCRIPT_F32(1.0f) +mSCRIPT_DEFINE_DEFAULTS_END; + mSCRIPT_DEFINE_STRUCT(mImage) mSCRIPT_DEFINE_CLASS_DOCSTRING( "A single, static image." @@ -52,6 +60,10 @@ mSCRIPT_DEFINE_STRUCT(mImage) mSCRIPT_DEFINE_STRUCT_METHOD(mImage, getPixel) mSCRIPT_DEFINE_DOCSTRING("Set the ARGB value of the pixel at a given coordinate") mSCRIPT_DEFINE_STRUCT_METHOD(mImage, setPixel) + mSCRIPT_DEFINE_DOCSTRING("Draw another image onto this image without any alpha blending, overwriting what was already there") + mSCRIPT_DEFINE_STRUCT_METHOD(mImage, drawImageOpaque) + mSCRIPT_DEFINE_DOCSTRING("Draw another image onto this image with alpha blending as needed, optionally specifying a coefficient for adjusting the opacity") + mSCRIPT_DEFINE_STRUCT_METHOD(mImage, drawImage) mSCRIPT_DEFINE_DOCSTRING("The width of the image, in pixels") mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mImage, U32, width) mSCRIPT_DEFINE_DOCSTRING("The height of the image, in pixels") From 45762c8f9f5c5ecd73696d786dbfc6f9a869b525 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 3 Apr 2023 04:45:36 -0700 Subject: [PATCH 144/290] Util: Partially evaluate preprocessor utility macros to help MSVC chew --- include/mgba-util/macros.h | 42 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/include/mgba-util/macros.h b/include/mgba-util/macros.h index f994def47..b2844c8c3 100644 --- a/include/mgba-util/macros.h +++ b/include/mgba-util/macros.h @@ -16,13 +16,13 @@ #define _mCALL_0(FN, ...) #define _mCALL_1(FN, A) FN(A) #define _mCALL_2(FN, A, B) FN(A), FN(B) -#define _mCALL_3(FN, A, ...) FN(A), _mCALL_2(FN, __VA_ARGS__) -#define _mCALL_4(FN, A, ...) FN(A), _mCALL_3(FN, __VA_ARGS__) -#define _mCALL_5(FN, A, ...) FN(A), _mCALL_4(FN, __VA_ARGS__) -#define _mCALL_6(FN, A, ...) FN(A), _mCALL_5(FN, __VA_ARGS__) -#define _mCALL_7(FN, A, ...) FN(A), _mCALL_6(FN, __VA_ARGS__) -#define _mCALL_8(FN, A, ...) FN(A), _mCALL_7(FN, __VA_ARGS__) -#define _mCALL_9(FN, A, ...) FN(A), _mCALL_8(FN, __VA_ARGS__) +#define _mCALL_3(FN, A, B, C) FN(A), FN(B), FN(C) +#define _mCALL_4(FN, A, B, C, D) FN(A), FN(B), FN(C), FN(D) +#define _mCALL_5(FN, A, B, C, D, E) FN(A), FN(B), FN(C), FN(D), FN(E) +#define _mCALL_6(FN, A, B, C, D, E, F) FN(A), FN(B), FN(C), FN(D), FN(E), FN(F) +#define _mCALL_7(FN, A, B, C, D, E, F, G) FN(A), FN(B), FN(C), FN(D), FN(E), FN(F), FN(G) +#define _mCALL_8(FN, A, B, C, D, E, F, G, H) FN(A), FN(B), FN(C), FN(D), FN(E), FN(F), FN(G), FN(H) +#define _mCALL_9(FN, A, B, C, D, E, F, G, H, I) FN(A), FN(B), FN(C), FN(D), FN(E), FN(F), FN(G), FN(H), FN(I) #define _mCOMMA_0(N, ...) N #define _mCOMMA_1(N, ...) N, __VA_ARGS__ @@ -37,25 +37,25 @@ #define _mEVEN_0(...) #define _mEVEN_1(A, B, ...) A -#define _mEVEN_2(A, B, ...) A, _mIDENT(_mEVEN_1(__VA_ARGS__)) -#define _mEVEN_3(A, B, ...) A, _mIDENT(_mEVEN_2(__VA_ARGS__)) -#define _mEVEN_4(A, B, ...) A, _mIDENT(_mEVEN_3(__VA_ARGS__)) -#define _mEVEN_5(A, B, ...) A, _mIDENT(_mEVEN_4(__VA_ARGS__)) +#define _mEVEN_2(A, B, C, D, ...) A, C +#define _mEVEN_3(A, B, C, D, E, F, ...) A, C, E +#define _mEVEN_4(A, B, C, D, E, F, G, H, ...) A, C, E, G +#define _mEVEN_5(A, B, C, D, E, F, G, H, I, J, ...) A, C, E, G, I #define _mEVEN_6(A, B, ...) A, _mIDENT(_mEVEN_5(__VA_ARGS__)) -#define _mEVEN_7(A, B, ...) A, _mIDENT(_mEVEN_6(__VA_ARGS__)) -#define _mEVEN_8(A, B, ...) A, _mIDENT(_mEVEN_7(__VA_ARGS__)) -#define _mEVEN_9(A, B, ...) A, _mIDENT(_mEVEN_7(__VA_ARGS__)) +#define _mEVEN_7(A, B, C, D, ...) A, C, _mIDENT(_mEVEN_5(__VA_ARGS__)) +#define _mEVEN_8(A, B, C, D, E, F, ...) A, C, E, _mIDENT(_mEVEN_5(__VA_ARGS__)) +#define _mEVEN_9(A, B, C, D, E, F, G, H, ...) A, C, E, G, _mIDENT(_mEVEN_5(__VA_ARGS__)) #define _mODD_0(...) #define _mODD_1(A, B, ...) B -#define _mODD_2(A, B, ...) B, _mIDENT(_mODD_1(__VA_ARGS__)) -#define _mODD_3(A, B, ...) B, _mIDENT(_mODD_2(__VA_ARGS__)) -#define _mODD_4(A, B, ...) B, _mIDENT(_mODD_3(__VA_ARGS__)) -#define _mODD_5(A, B, ...) B, _mIDENT(_mODD_4(__VA_ARGS__)) +#define _mODD_2(A, B, C, D, ...) B, D +#define _mODD_3(A, B, C, D, E, F, ...) B, D, F +#define _mODD_4(A, B, C, D, E, F, G, H, ...) B, D, F, H +#define _mODD_5(A, B, C, D, E, F, G, H, I, J, ...) B, D, F, H, J #define _mODD_6(A, B, ...) B, _mIDENT(_mODD_5(__VA_ARGS__)) -#define _mODD_7(A, B, ...) B, _mIDENT(_mODD_6(__VA_ARGS__)) -#define _mODD_8(A, B, ...) B, _mIDENT(_mODD_7(__VA_ARGS__)) -#define _mODD_9(A, B, ...) B, _mIDENT(_mODD_7(__VA_ARGS__)) +#define _mODD_7(A, B, C, D, ...) B, D, _mIDENT(_mODD_5(__VA_ARGS__)) +#define _mODD_8(A, B, C, D, E, F, ...) B, D, F, _mIDENT(_mODD_5(__VA_ARGS__)) +#define _mODD_9(A, B, C, D, E, F, G, H, ...) B, D, F, H, _mIDENT(_mODD_5(__VA_ARGS__)) #define _mIF0_0(...) __VA_ARGS__ #define _mIF0_1(...) From 9920a609e82fc64487f01e52228a62b31981c5f6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 4 Apr 2023 02:04:59 -0700 Subject: [PATCH 145/290] OpenGL: Make backends handle non-zero origin outer frame boundaries --- src/platform/opengl/gl.c | 20 ++++++++++---------- src/platform/opengl/gles2.c | 15 ++++++++------- src/platform/opengl/gles2.h | 2 ++ src/platform/qt/DisplayGL.cpp | 22 ++++++++-------------- src/platform/sdl/gl-common.c | 4 ++-- 5 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index 832e78cee..ec477b954 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -141,13 +141,13 @@ static void _setFilter(struct VideoBackend* v) { } } -static void _setFrame(struct mRectangle* dims, int frameW, int frameH) { +static void _setFrame(struct mRectangle* dims, struct mRectangle* frame) { GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); - glScissor(viewport[0] + dims->x * viewport[2] / frameW, - viewport[1] + dims->y * viewport[3] / frameH, - dims->width * viewport[2] / frameW, - dims->height * viewport[3] / frameH); + glScissor(viewport[0] + (dims->x - frame->x) * viewport[2] / frame->width, + viewport[1] + (dims->y - frame->y) * viewport[3] / frame->height, + dims->width * viewport[2] / frame->width, + dims->height * viewport[3] / frame->height); glTranslatef(dims->x, dims->y, 0); glScalef(toPow2(dims->width), toPow2(dims->height), 1); } @@ -162,9 +162,9 @@ void mGLContextDrawFrame(struct VideoBackend* v) { glTexCoordPointer(2, GL_INT, 0, _glTexCoords); glMatrixMode(GL_PROJECTION); glLoadIdentity(); - unsigned frameW, frameH; - VideoBackendGetFrameSize(v, &frameW, &frameH); - glOrtho(0, frameW, frameH, 0, 0, 1); + struct mRectangle frame; + VideoBackendGetFrame(v, &frame); + glOrtho(frame.x, frame.x + frame.width, frame.y + frame.height, frame.y, 0, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); @@ -178,12 +178,12 @@ void mGLContextDrawFrame(struct VideoBackend* v) { glBindTexture(GL_TEXTURE_2D, context->layers[layer]); _setFilter(v); glPushMatrix(); - _setFrame(&context->layerDims[layer], frameW, frameH); + _setFrame(&context->layerDims[layer], &frame); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glPopMatrix(); } - _setFrame(&context->layerDims[VIDEO_LAYER_IMAGE], frameW, frameH); + _setFrame(&context->layerDims[VIDEO_LAYER_IMAGE], &frame); if (v->interframeBlending) { glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); glBlendColor(1, 1, 1, 0.5); diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index a2d0b6e50..2beea3a78 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -212,10 +212,9 @@ static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLa context->layerDims[layer].x = dims->x; context->layerDims[layer].y = dims->y; - unsigned newW; - unsigned newH; - VideoBackendGetFrameSize(v, &newW, &newH); - if (newW != context->width || newH != context->height) { + struct mRectangle frame; + VideoBackendGetFrame(v, &frame); + if (frame.width != context->width || frame.height != context->height) { size_t n; for (n = 0; n < context->nShaders; ++n) { if (context->shaders[n].width < 0 || context->shaders[n].height < 0) { @@ -224,9 +223,11 @@ static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLa } context->initialShader.dirty = true; context->interframeShader.dirty = true; - context->width = newW; - context->height = newH; + context->width = frame.width; + context->height = frame.height; } + context->x = frame.x; + context->y = frame.y; } static void mGLES2ContextLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct mRectangle* dims) { @@ -320,7 +321,7 @@ static void _drawShaderEx(struct mGLES2Context* context, struct mGLES2Shader* sh } if (layer >= 0 && layer < VIDEO_LAYER_MAX) { - glViewport(context->layerDims[layer].x, context->layerDims[layer].y, context->layerDims[layer].width, context->layerDims[layer].height); + glViewport(context->layerDims[layer].x - context->x, context->layerDims[layer].y - context->y, context->layerDims[layer].width, context->layerDims[layer].height); } else { glViewport(padW, padH, drawW, drawH); } diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h index dc15041d9..c81e9d8e2 100644 --- a/src/platform/opengl/gles2.h +++ b/src/platform/opengl/gles2.h @@ -84,6 +84,8 @@ struct mGLES2Context { struct mRectangle layerDims[VIDEO_LAYER_MAX]; struct mSize imageSizes[VIDEO_LAYER_MAX]; + int x; + int y; unsigned width; unsigned height; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index ca35c6bb6..1913dc27c 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -696,25 +696,19 @@ void PainterGL::recenterLayers() { if (!m_context) { return; } - const static std::initializer_list centeredLayers{VIDEO_LAYER_BACKGROUND, VIDEO_LAYER_IMAGE}; + const static std::initializer_list centeredLayers{VIDEO_LAYER_BACKGROUND}; + int width, height; mRectangle frame = {0}; + m_backend->imageSize(m_backend, VIDEO_LAYER_IMAGE, &width, &height); + frame.width = width; + frame.height = height; unsigned scale = std::max(1U, m_context->videoScale()); + for (VideoLayer l : centeredLayers) { mRectangle dims{}; - int width, height; m_backend->imageSize(m_backend, l, &width, &height); - dims.width = width; - dims.height = height; - if (l != VIDEO_LAYER_IMAGE) { - dims.width *= scale; - dims.height *= scale; - m_backend->setLayerDimensions(m_backend, l, &dims); - } - mRectangleUnion(&frame, &dims); - } - for (VideoLayer l : centeredLayers) { - mRectangle dims; - m_backend->layerDimensions(m_backend, l, &dims); + dims.width = width * scale; + dims.height = height * scale; mRectangleCenter(&frame, &dims); m_backend->setLayerDimensions(m_backend, l, &dims); } diff --git a/src/platform/sdl/gl-common.c b/src/platform/sdl/gl-common.c index f62e04a6b..076429f6b 100644 --- a/src/platform/sdl/gl-common.c +++ b/src/platform/sdl/gl-common.c @@ -137,9 +137,9 @@ void mSDLGLCommonRunloop(struct mSDLRenderer* renderer, void* user) { renderer->player.windowUpdated = true; struct mRectangle frame; - VideoBackendGetFrame(v, &frame); + v->layerDimensions(v, VIDEO_LAYER_IMAGE, &frame); int i; - for (i = 0; i <= VIDEO_LAYER_IMAGE; ++i) { + for (i = 0; i < VIDEO_LAYER_IMAGE; ++i) { struct mRectangle dims; v->layerDimensions(v, i, &dims); mRectangleCenter(&frame, &dims); From 2f54d98ff6295621f1c7400b36980bca28e4c551 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 4 Apr 2023 02:20:06 -0700 Subject: [PATCH 146/290] Scripting: Export some system information --- src/script/stdlib.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/script/stdlib.c b/src/script/stdlib.c index ee6ebfd93..8a64ac0c1 100644 --- a/src/script/stdlib.c +++ b/src/script/stdlib.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #ifdef M_CORE_GBA @@ -169,4 +170,27 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) { mScriptContextSetDocstring(context, "util", "Basic utility library"); mScriptContextSetDocstring(context, "util.makeBitmask", "Compile a list of bit indices into a bitmask"); mScriptContextSetDocstring(context, "util.expandBitmask", "Expand a bitmask into a list of bit indices"); + + struct mScriptValue* systemVersion = mScriptStringCreateFromUTF8(projectVersion); + struct mScriptValue* systemProgram = mScriptStringCreateFromUTF8(projectName); + struct mScriptValue* systemBranch = mScriptStringCreateFromUTF8(gitBranch); + struct mScriptValue* systemCommit = mScriptStringCreateFromUTF8(gitCommit); + struct mScriptValue* systemRevision = mScriptValueAlloc(mSCRIPT_TYPE_MS_S32); + systemRevision->value.s32 = gitRevision; + + mScriptContextExportNamespace(context, "system", (struct mScriptKVPair[]) { + mSCRIPT_KV_PAIR(version, systemVersion), + mSCRIPT_KV_PAIR(program, systemProgram), + mSCRIPT_KV_PAIR(branch, systemBranch), + mSCRIPT_KV_PAIR(commit, systemCommit), + mSCRIPT_KV_PAIR(revision, systemRevision), + mSCRIPT_KV_SENTINEL + }); + + mScriptContextSetDocstring(context, "system", "Information about the system the script is running under"); + mScriptContextSetDocstring(context, "system.version", "The current version of this build of the program"); + mScriptContextSetDocstring(context, "system.program", "The name of the program. Generally this will be \"mGBA\", but forks may change it to differentiate"); + mScriptContextSetDocstring(context, "system.branch", "The current git branch of this build of the program, if known"); + mScriptContextSetDocstring(context, "system.commit", "The current git commit hash of this build of the program, if known"); + mScriptContextSetDocstring(context, "system.revision", "The current git revision number of this build of the program, or -1 if unknown"); } From 8c55769afc7259a26026bc6ea3c6afe5e591d817 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 5 Apr 2023 04:50:26 -0700 Subject: [PATCH 147/290] Scripting: Fix some Lua string handling --- src/script/engines/lua.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index d4386c4da..2a03ba052 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -894,7 +894,9 @@ bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFi luaContext->lastError = NULL; } char name[PATH_MAX + 1]; - char dirname[PATH_MAX] = {0}; + char dirname[PATH_MAX]; + name[0] = '\0'; + dirname[0] = '\0'; if (filename) { if (*filename == '*') { snprintf(name, sizeof(name), "=%s", filename + 1); @@ -909,7 +911,7 @@ bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFi lastSlash = lastBackslash; } if (lastSlash) { - strncpy(dirname, filename, lastSlash - filename); + strlcpy(dirname, filename, lastSlash - filename); } snprintf(name, sizeof(name), "@%s", filename); } From efcdd291093ab481fb30c8c3df47996babb1925b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 5 Apr 2023 04:54:51 -0700 Subject: [PATCH 148/290] GBA e-Reader: Improve error handling in image loading --- src/gba/cart/ereader.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/gba/cart/ereader.c b/src/gba/cart/ereader.c index 5edfe193e..9da25196c 100644 --- a/src/gba/cart/ereader.c +++ b/src/gba/cart/ereader.c @@ -888,7 +888,11 @@ struct EReaderScan* EReaderScanLoadImagePNG(const char* filename) { } png_infop info = png_create_info_struct(png); png_infop end = png_create_info_struct(png); - PNGReadHeader(png, info); + if (!PNGReadHeader(png, info)) { + PNGReadClose(png, info, end); + vf->close(vf); + return NULL; + } unsigned height = png_get_image_height(png, info); unsigned width = png_get_image_width(png, info); int type = png_get_color_type(png, info); @@ -900,19 +904,34 @@ struct EReaderScan* EReaderScanLoadImagePNG(const char* filename) { break; } image = malloc(height * width * 3); - PNGReadPixels(png, info, image, width, height, width); + if (!image) { + goto out; + } + if (!PNGReadPixels(png, info, image, width, height, width)) { + free(image); + image = NULL; + goto out; + } break; case PNG_COLOR_TYPE_RGBA: if (depth != 8) { break; } image = malloc(height * width * 4); - PNGReadPixelsA(png, info, image, width, height, width); + if (!image) { + goto out; + } + if (!PNGReadPixelsA(png, info, image, width, height, width)) { + free(image); + image = NULL; + goto out; + } break; default: break; } PNGReadFooter(png, end); +out: PNGReadClose(png, info, end); vf->close(vf); if (!image) { From a69f95bcb69c69f783abb9da28db651c187786cb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 5 Apr 2023 04:56:26 -0700 Subject: [PATCH 149/290] Util: Placate coverity a bit --- src/util/image.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/util/image.c b/src/util/image.c index e37089d2a..ed070f658 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -277,6 +277,9 @@ uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y) { color |= ((const uint8_t*) pixel)[2] << 16; #endif break; + default: + // This should never be reached + abort(); } return color; } From 03d1ed77786eb33a807d799e31792b384d2523e8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 5 Apr 2023 05:00:16 -0700 Subject: [PATCH 150/290] Qt: More coverity placating --- src/platform/qt/FrameView.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/platform/qt/FrameView.cpp b/src/platform/qt/FrameView.cpp index 630d0ecd8..be2d55d40 100644 --- a/src/platform/qt/FrameView.cpp +++ b/src/platform/qt/FrameView.cpp @@ -548,6 +548,11 @@ void FrameView::newVl() { m_vl->deinit(m_vl); } m_vl = mCoreFindVF(m_currentFrame); + if (!m_vl) { + m_currentFrame->close(m_currentFrame); + m_currentFrame = nullptr; + return; + } m_vl->init(m_vl); m_vl->loadROM(m_vl, m_currentFrame); m_currentFrame = nullptr; From a039e2fbefd2ca5e5921d26702e6de2614cd9b6d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 5 Apr 2023 05:06:08 -0700 Subject: [PATCH 151/290] Core: Fix memory leaks in mCacheSet --- src/core/cache-set.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/cache-set.c b/src/core/cache-set.c index a638c786e..04d42a233 100644 --- a/src/core/cache-set.c +++ b/src/core/cache-set.c @@ -34,12 +34,15 @@ void mCacheSetDeinit(struct mCacheSet* cache) { for (i = 0; i < mMapCacheSetSize(&cache->maps); ++i) { mMapCacheDeinit(mMapCacheSetGetPointer(&cache->maps, i)); } + mMapCacheSetDeinit(&cache->maps); for (i = 0; i < mBitmapCacheSetSize(&cache->bitmaps); ++i) { mBitmapCacheDeinit(mBitmapCacheSetGetPointer(&cache->bitmaps, i)); } + mBitmapCacheSetDeinit(&cache->bitmaps); for (i = 0; i < mTileCacheSetSize(&cache->tiles); ++i) { mTileCacheDeinit(mTileCacheSetGetPointer(&cache->tiles, i)); } + mTileCacheSetDeinit(&cache->tiles); } void mCacheSetAssignVRAM(struct mCacheSet* cache, void* vram) { From b68c714455a5f2b4d1daf7f0555afbbd5bb9a950 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 7 Apr 2023 00:49:56 -0700 Subject: [PATCH 152/290] FFmpeg: Fix buffer size rounding for audio encoding --- src/feature/ffmpeg/ffmpeg-encoder.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index f1a26ec32..88f4f78c2 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -881,7 +881,7 @@ void FFmpegEncoderSetInputSampleRate(struct FFmpegEncoder* encoder, int sampleRa } void _ffmpegOpenResampleContext(struct FFmpegEncoder* encoder) { - encoder->audioBufferSize = av_rescale_q(encoder->audioFrame->nb_samples, (AVRational) { 4, encoder->sampleRate }, (AVRational) { 1, encoder->isampleRate }); + encoder->audioBufferSize = av_rescale_q(encoder->audioFrame->nb_samples, (AVRational) { 1, encoder->sampleRate }, (AVRational) { 1, encoder->isampleRate }) * 4; encoder->audioBuffer = av_malloc(encoder->audioBufferSize); #ifdef USE_LIBAVRESAMPLE encoder->resampleContext = avresample_alloc_context(); From dfb6055ae42ffce9130f74c13719913b8f9bcebb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 7 Apr 2023 00:58:28 -0700 Subject: [PATCH 153/290] FFmpeg: Force lower sample rate for codecs not supporting high rates (fixes #2869) --- CHANGES | 1 + src/feature/ffmpeg/ffmpeg-encoder.c | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 041f3f4ce..03d763dcc 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,7 @@ Emulation fixes: - 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) + - FFmpeg: Force lower sample rate for codecs not supporting high rates (fixes mgba.io/i/2869) - Qt: Fix crash when attempting to use OpenGL 2.1 to 3.1 (fixes mgba.io/i/2794) - Qt: Disable sync while running scripts from main thread (fixes mgba.io/i/2738) - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index 88f4f78c2..9d61e5846 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -154,19 +154,35 @@ bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, un } encoder->sampleRate = encoder->isampleRate; if (codec->supported_samplerates) { + bool gotSampleRate = false; + int highestSampleRate = 0; for (i = 0; codec->supported_samplerates[i]; ++i) { + if (codec->supported_samplerates[i] > highestSampleRate) { + highestSampleRate = codec->supported_samplerates[i]; + } if (codec->supported_samplerates[i] < encoder->isampleRate) { continue; } - if (encoder->sampleRate == encoder->isampleRate || encoder->sampleRate > codec->supported_samplerates[i]) { + if (!gotSampleRate || encoder->sampleRate > codec->supported_samplerates[i]) { encoder->sampleRate = codec->supported_samplerates[i]; + gotSampleRate = true; } } + if (!gotSampleRate) { + // There are no available sample rates that are higher than the input sample rate + // Let's use the highest available instead + encoder->sampleRate = highestSampleRate; + } } else if (codec->id == AV_CODEC_ID_FLAC) { // HACK: FLAC doesn't support > 65535Hz unless it's divisible by 10 if (encoder->sampleRate >= 65535) { encoder->sampleRate -= encoder->isampleRate % 10; } + } else if (codec->id == AV_CODEC_ID_VORBIS) { + // HACK: FLAC doesn't support > 48000Hz but doesn't tell us + if (encoder->sampleRate > 48000) { + encoder->sampleRate = 48000; + } } else if (codec->id == AV_CODEC_ID_AAC) { // HACK: AAC doesn't support 32768Hz (it rounds to 32000), but libfaac doesn't tell us that encoder->sampleRate = 48000; From 3f108aae75cdfaba68895d973ffeccf1c884506e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 10 Apr 2023 03:02:51 -0700 Subject: [PATCH 154/290] Scripting: Re-fix some Lua string handling (fixes #2877) --- src/script/engines/lua.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 2a03ba052..c37bd607e 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -911,7 +911,11 @@ bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFi lastSlash = lastBackslash; } if (lastSlash) { - strlcpy(dirname, filename, lastSlash - filename); + size_t len = lastSlash - filename + 1; + if (sizeof(dirname) < len) { + len = sizeof(dirname); + } + strlcpy(dirname, filename, len); } snprintf(name, sizeof(name), "@%s", filename); } From 1b85fb3de59ab132fd7b1b282e02b6c393351aa9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 11 Apr 2023 16:26:17 -0700 Subject: [PATCH 155/290] Scripting: Fix early freeing of coerced list contents (fixes #2881) --- src/script/engines/lua.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index c37bd607e..25d3b621b 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -630,6 +630,14 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext, mScriptValueDeref(list); return table; } + for (i = 0; i < mScriptListSize(list->value.list); ++i) { + struct mScriptValue* value = mScriptListGetPointer(list->value.list, i); + if (value->type->base != mSCRIPT_TYPE_WRAPPER) { + continue; + } + value = mScriptValueUnwrap(value); + mScriptValueRef(value); + } mScriptValueDeref(table); return list; } From e8ef801a3eb85e4f028218de89653caff7f2695b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 12 Apr 2023 03:33:39 -0700 Subject: [PATCH 156/290] Vita: Work around broken mktime implementation in Vita SDK (fixes #2876) --- CHANGES | 1 + src/gba/savedata.c | 41 +++++++++++++++++++++++++++++++- src/platform/psp2/CMakeLists.txt | 1 + 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 03d763dcc..3601b0fd9 100644 --- a/CHANGES +++ b/CHANGES @@ -33,6 +33,7 @@ Other fixes: - Scripting: Fix receiving packets for client sockets - Scripting: Fix empty receive calls returning unknown error on Windows - Scripting: Return proper callback ID from socket.add + - Vita: Work around broken mktime implementation in Vita SDK (fixes mgba.io/i/2876) Misc: - Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826) - GB Serialize: Add missing savestate support for MBC6 and NT (newer) diff --git a/src/gba/savedata.c b/src/gba/savedata.c index 2d377a875..512af02c9 100644 --- a/src/gba/savedata.c +++ b/src/gba/savedata.c @@ -13,6 +13,10 @@ #include #include +#ifdef PSP2 +#include +#endif + #include #include @@ -637,6 +641,9 @@ void GBASavedataRTCRead(struct GBASavedata* savedata) { } LOAD_64LE(savedata->gpio->rtc.lastLatch, 0, &buffer.lastLatch); + time_t rtcTime; + +#ifndef PSP2 struct tm date; date.tm_year = _unBCD(savedata->gpio->rtc.time[0]) + 100; date.tm_mon = _unBCD(savedata->gpio->rtc.time[1]) - 1; @@ -645,8 +652,40 @@ void GBASavedataRTCRead(struct GBASavedata* savedata) { date.tm_min = _unBCD(savedata->gpio->rtc.time[5]); date.tm_sec = _unBCD(savedata->gpio->rtc.time[6]); date.tm_isdst = -1; + rtcTime = mktime(&date); +#else + struct SceDateTime date; + date.year = _unBCD(savedata->gpio->rtc.time[0]) + 2000; + date.month = _unBCD(savedata->gpio->rtc.time[1]); + date.day = _unBCD(savedata->gpio->rtc.time[2]); + date.hour = _unBCD(savedata->gpio->rtc.time[4]); + date.minute = _unBCD(savedata->gpio->rtc.time[5]); + date.second = _unBCD(savedata->gpio->rtc.time[6]); + date.microsecond = 0; - savedata->gpio->rtc.offset = savedata->gpio->rtc.lastLatch - mktime(&date); + struct SceRtcTick tick; + int res; + res = sceRtcConvertDateTimeToTick(&date, &tick); + if (res < 0) { + mLOG(GBA_SAVE, ERROR, "sceRtcConvertDateTimeToTick %lx", res); + } + res = sceRtcConvertLocalTimeToUtc(&tick, &tick); + if (res < 0) { + mLOG(GBA_SAVE, ERROR, "sceRtcConvertUtcToLocalTime %lx", res); + } + res = sceRtcConvertTickToDateTime(&tick, &date); + if (res < 0) { + mLOG(GBA_SAVE, ERROR, "sceRtcConvertTickToDateTime %lx", res); + } + res = sceRtcConvertDateTimeToTime_t(&date, &rtcTime); + if (res < 0) { + mLOG(GBA_SAVE, ERROR, "sceRtcConvertDateTimeToTime_t %lx", res); + } +#endif + + savedata->gpio->rtc.offset = savedata->gpio->rtc.lastLatch - rtcTime; + + mLOG(GBA_SAVE, ERROR, "Savegame time offset set to %li", savedata->gpio->rtc.offset); } void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state) { diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index fa9847f14..3e638aa64 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -28,6 +28,7 @@ set(OS_LIB -lvita2d -l${M_LIBRARY} -lScePgf_stub -lScePhotoExport_stub -lScePower_stub + -lSceRtc_stub -lSceSysmodule_stub -lSceTouch_stub) set(OS_LIB ${OS_LIB} PARENT_SCOPE) From cd720fc6d870f3ca625cd67f2247217ae722f8b6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 12 Apr 2023 22:31:07 -0700 Subject: [PATCH 157/290] .gitignore: Update --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index 0e5d801be..999fdd673 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,15 @@ +# Generic files *.user* *~ *.swp *.pyc +# Build directories /build /build-* /.vs +# Build files *.a *.dylib *.dll @@ -18,4 +21,9 @@ CMakeCache.txt CMakeFiles CMakeSettings.json cmake_install.cmake +hle-bios.bin version.c + +# Runtime generated cruft +*.sav +*.ss0 From fca0505f38cb8564a2d8b1f9fe955d00e3f209fb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 12 Apr 2023 22:45:22 -0700 Subject: [PATCH 158/290] 3DS: Code cleanup --- src/platform/3ds/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index e0dd2a7dc..470c50db7 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -872,7 +872,7 @@ int main(int argc, char* argv[]) { u8 model = 0; cfguInit(); CFGU_GetSystemModel(&model); - if (model != 3 /* o2DS */) { + if (model != CFG_MODEL_2DS) { gfxSetWide(true); } cfguExit(); From f40222f0ee680d2ea094c753546b985ea0b81893 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 13 Apr 2023 01:54:06 -0700 Subject: [PATCH 159/290] Core: Code cleanup --- src/core/config.c | 3 +-- src/core/serialize.c | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core/config.c b/src/core/config.c index b9f0167ec..c0892d33e 100644 --- a/src/core/config.c +++ b/src/core/config.c @@ -279,8 +279,7 @@ void mCoreConfigDirectory(char* out, size_t outLength) { void mCoreConfigPortablePath(char* out, size_t outLength) { #ifdef _WIN32 wchar_t wpath[MAX_PATH]; - HMODULE hModule = GetModuleHandleW(NULL); - GetModuleFileNameW(hModule, wpath, MAX_PATH); + GetModuleFileNameW(NULL, wpath, MAX_PATH); PathRemoveFileSpecW(wpath); if (PATH_SEP[0] != '\\') { WCHAR* pathSep; diff --git a/src/core/serialize.c b/src/core/serialize.c index fc64c437e..1e0c72ddf 100644 --- a/src/core/serialize.c +++ b/src/core/serialize.c @@ -453,7 +453,7 @@ bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) { UNUSED(flags); #endif vf->truncate(vf, stateSize); - struct GBASerializedState* state = vf->map(vf, stateSize, MAP_WRITE); + void* state = vf->map(vf, stateSize, MAP_WRITE); if (!state) { mStateExtdataDeinit(&extdata); if (cheatVf) { From 77a4fbf439685b8f344c63bda02eb720a0c6e1cc Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 13 Apr 2023 02:04:19 -0700 Subject: [PATCH 160/290] Res: Code cleanup --- res/scripts/pokemon.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/scripts/pokemon.lua b/res/scripts/pokemon.lua index d909396d0..c89fb8e75 100644 --- a/res/scripts/pokemon.lua +++ b/res/scripts/pokemon.lua @@ -174,7 +174,7 @@ function Generation1En._readPartyMon(game, address, nameAddress, otAddress) return mon end -function Generation2En._readBoxMon(game, address, nameAddress, otAddress) +function Generation2En._readBoxMon(game, address, nameAddress, otAddress) local mon = {} mon.species = emu:read8(address + 0) mon.item = emu:read8(address + 1) From edc0828437adc79ed877a943cde95cd2a50bc10b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 13 Apr 2023 02:16:39 -0700 Subject: [PATCH 161/290] CMake: Actually bother using -fwrapv --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5869e2840..af45bd9b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ if(NOT MSVC) # mingw32 likes to complain about using the "wrong" format strings despite them actually working set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-format") endif() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration -Werror=implicit-int") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration -Werror=implicit-int -fwrapv") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS} -Woverloaded-virtual") else() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146 /wd4267 /Zc:preprocessor-") From b876f13cb2e6419a6cf0dc933b14a8a12c5b3f6a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 13 Apr 2023 02:29:27 -0700 Subject: [PATCH 162/290] VFS: Actually fflush sync when doing writeback --- src/util/vfs/vfs-file.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/util/vfs/vfs-file.c b/src/util/vfs/vfs-file.c index d3adc3177..4a63e53f8 100644 --- a/src/util/vfs/vfs-file.c +++ b/src/util/vfs/vfs-file.c @@ -151,7 +151,9 @@ static bool _vffSync(struct VFile* vf, void* buffer, size_t size) { fseek(vff->file, 0, SEEK_SET); size_t res = fwrite(buffer, size, 1, vff->file); fseek(vff->file, pos, SEEK_SET); - return res == 1; + if (res != 1) { + return false; + } } return fflush(vff->file) == 0; } From 5bf048e3807e99744e37ffadad1c574bb988e3c7 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 13 Apr 2023 02:56:12 -0700 Subject: [PATCH 163/290] GB Serialize: Add missing Pocket Cam state to savestates --- CHANGES | 1 + include/mgba/internal/gb/serialize.h | 4 ++++ src/gb/memory.c | 8 ++++++++ 3 files changed, 13 insertions(+) diff --git a/CHANGES b/CHANGES index 3601b0fd9..923643ce4 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,7 @@ Features: - New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81 - Debugger: Add range watchpoints Emulation fixes: + - GB Serialize: Add missing Pocket Cam state to savestates - GB Video: Implement DMG-style sprite ordering - GBA Audio: Fix improperly deserializing GB audio registers (fixes mgba.io/i/2793) - GBA Audio: Clear GB audio state when disabled diff --git a/include/mgba/internal/gb/serialize.h b/include/mgba/internal/gb/serialize.h index f4433c495..e76d0accb 100644 --- a/include/mgba/internal/gb/serialize.h +++ b/include/mgba/internal/gb/serialize.h @@ -419,6 +419,9 @@ struct GBSerializedState { uint8_t locked; uint8_t bank0; } mmm01; + struct { + uint8_t registersActive; + } pocketCam; struct { uint64_t lastLatch; uint8_t reg; @@ -484,6 +487,7 @@ struct GBSerializedState { union { uint8_t huc3Registers[0x80]; + uint8_t pocketCamRegisters[0x36]; struct { uint8_t registers[4]; uint8_t reserved[4]; diff --git a/src/gb/memory.c b/src/gb/memory.c index 325761c1c..e41050784 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -799,6 +799,10 @@ void GBMemorySerialize(const struct GB* gb, struct GBSerializedState* state) { state->huc3Registers[i] |= memory->mbcState.huc3.registers[i * 2 + 1] << 4; } break; + case GB_POCKETCAM: + state->memory.pocketCam.registersActive = memory->mbcState.pocketCam.registersActive; + memcpy(state->pocketCamRegisters, memory->mbcState.pocketCam.registers, sizeof(memory->mbcState.pocketCam.registers)); + break; case GB_MMM01: state->memory.mmm01.locked = memory->mbcState.mmm01.locked; state->memory.mmm01.bank0 = memory->mbcState.mmm01.currentBank0; @@ -950,6 +954,10 @@ void GBMemoryDeserialize(struct GB* gb, const struct GBSerializedState* state) { memory->mbcState.huc3.registers[i * 2 + 1] = state->huc3Registers[i] >> 4; } break; + case GB_POCKETCAM: + memory->mbcState.pocketCam.registersActive = state->memory.pocketCam.registersActive; + memcpy(memory->mbcState.pocketCam.registers, state->pocketCamRegisters, sizeof(memory->mbcState.pocketCam.registers)); + break; case GB_MMM01: memory->mbcState.mmm01.locked = state->memory.mmm01.locked; memory->mbcState.mmm01.currentBank0 = state->memory.mmm01.bank0; From fd0f24d01eb3057dd7c881cd274c3f732f989c39 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 14 Apr 2023 18:22:30 -0700 Subject: [PATCH 164/290] macOS: Declare camera usage in Info.plist --- res/info.plist.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/info.plist.in b/res/info.plist.in index eb625299c..f4b9bba80 100644 --- a/res/info.plist.in +++ b/res/info.plist.in @@ -32,6 +32,8 @@ ${MACOSX_BUNDLE_COPYRIGHT} NSSupportsAutomaticGraphicsSwitching + NSCameraUsageDescription + Only used when Game Boy Camera is selected and a physical camera is set CFBundleDocumentTypes From 225456a39cc77345aaf80eae1e2c3a68e91b8913 Mon Sep 17 00:00:00 2001 From: David Spickett Date: Fri, 14 Apr 2023 11:59:50 +0100 Subject: [PATCH 165/290] Debugger: Send flags information for cpsr register There is a feature of target XML called flags. It allows you to describe what a register contains. https://sourceware.org/gdb/onlinedocs/gdb/Target-Description-Format.html GDB has supported this for a long time and I recently added support in LLDB: https://github.com/llvm/llvm-project/commit/e07a421dd587f596b3b34ac2f79081402089f878 This change adds this flags information for the cpsr register of the ARM7TDMI. Based on the information in https://developer.arm.com/documentation/ddi0210/c/. This is what it looks like when using GDB: ``` (gdb) info registers r0 0x0 0 <...> cpsr 0x6000001f [ Z C M=31 ] ``` And LLDB: ``` (lldb) register read cpsr cpsr = 0x6000001f = (N = 0, Z = 1, C = 1, V = 0, I = 0, F = 0, T = 0, M=31) ``` (the format is up to the debugger, lldb is a lot more verbose at the moment) To enable this I have increased the GDB stub's outgoing buffer to 1400 bytes. The target XML is just above 130 bytes with the flags added. --- include/mgba/internal/debugger/gdb-stub.h | 2 +- src/debugger/gdb-stub.c | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/mgba/internal/debugger/gdb-stub.h b/include/mgba/internal/debugger/gdb-stub.h index b76b52452..1086f7b43 100644 --- a/include/mgba/internal/debugger/gdb-stub.h +++ b/include/mgba/internal/debugger/gdb-stub.h @@ -14,7 +14,7 @@ CXX_GUARD_START #include -#define GDB_STUB_MAX_LINE 1200 +#define GDB_STUB_MAX_LINE 1400 #define GDB_STUB_INTERVAL 32 enum GDBStubAckState { diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index 6c318179c..34b7f2000 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -50,7 +50,17 @@ static const char* TARGET_XML = "" "" "" "" - "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" "" ""; From 65f04ee408c45166077f31e65d812a8ba59c9bbc Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 17 Apr 2023 02:03:15 -0700 Subject: [PATCH 166/290] Util: PNGWrite*A is dead, long live PNGWrite* --- include/mgba-util/image.h | 8 + include/mgba-util/image/png-io.h | 8 +- src/core/core.c | 4 +- src/core/serialize.c | 4 +- src/platform/python/mgba/png.py | 8 +- src/platform/test/cinema-main.c | 4 +- src/util/image.c | 25 +- src/util/image/png-io.c | 415 +++++++++++++++++++++++++------ src/util/test/image.c | 46 ++++ 9 files changed, 412 insertions(+), 110 deletions(-) diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index 6dd3cee73..0a1b8d7b0 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -81,6 +81,14 @@ enum mColorFormat { mCOLOR_ANY = -1 }; +#ifndef COLOR_16_BIT +#define mCOLOR_NATIVE mCOLOR_XBGR8 +#elif !defined(COLOR_5_6_5) +#define mCOLOR_NATIVE mCOLOR_BGR5 +#else +#define mCOLOR_NATIVE mCOLOR_RGB565 +#endif + struct mImage { void* data; unsigned width; diff --git a/include/mgba-util/image/png-io.h b/include/mgba-util/image/png-io.h index 4156b5d20..b33d64ce7 100644 --- a/include/mgba-util/image/png-io.h +++ b/include/mgba-util/image/png-io.h @@ -12,6 +12,8 @@ CXX_GUARD_START #ifdef USE_PNG +#include + // png.h defines its own version of restrict which conflicts with mGBA's. #ifdef restrict #undef restrict @@ -25,12 +27,10 @@ enum { }; png_structp PNGWriteOpen(struct VFile* source); -png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height); -png_infop PNGWriteHeaderA(png_structp png, unsigned width, unsigned height); +png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height, enum mColorFormat); png_infop PNGWriteHeader8(png_structp png, unsigned width, unsigned height); bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, unsigned entries); -bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels); -bool PNGWritePixelsA(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels); +bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels, enum mColorFormat); bool PNGWritePixels8(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels); bool PNGWriteCustomChunk(png_structp png, const char* name, size_t size, void* data); void PNGWriteClose(png_structp png, png_infop info); diff --git a/src/core/core.c b/src/core/core.c index 8091599bf..61f29d71f 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -364,8 +364,8 @@ bool mCoreTakeScreenshotVF(struct mCore* core, struct VFile* vf) { core->currentVideoSize(core, &width, &height); core->getPixels(core, &pixels, &stride); png_structp png = PNGWriteOpen(vf); - png_infop info = PNGWriteHeader(png, width, height); - bool success = PNGWritePixels(png, width, height, stride, pixels); + png_infop info = PNGWriteHeader(png, width, height, mCOLOR_NATIVE); + bool success = PNGWritePixels(png, width, height, stride, pixels, mCOLOR_NATIVE); PNGWriteClose(png, info); return success; #else diff --git a/src/core/serialize.c b/src/core/serialize.c index 1e0c72ddf..b721cdafa 100644 --- a/src/core/serialize.c +++ b/src/core/serialize.c @@ -178,13 +178,13 @@ static bool _savePNGState(struct mCore* core, struct VFile* vf, struct mStateExt unsigned width, height; core->currentVideoSize(core, &width, &height); png_structp png = PNGWriteOpen(vf); - png_infop info = PNGWriteHeader(png, width, height); + png_infop info = PNGWriteHeader(png, width, height, mCOLOR_NATIVE); if (!png || !info) { PNGWriteClose(png, info); free(buffer); return false; } - PNGWritePixels(png, width, height, stride, pixels); + PNGWritePixels(png, width, height, stride, pixels, mCOLOR_NATIVE); PNGWriteCustomChunk(png, "gbAs", len, buffer); if (extdata) { uint32_t i; diff --git a/src/platform/python/mgba/png.py b/src/platform/python/mgba/png.py index ac8b79b86..3051ca197 100644 --- a/src/platform/python/mgba/png.py +++ b/src/platform/python/mgba/png.py @@ -21,18 +21,18 @@ class PNG: def write_header(self, image): self._png = lib.PNGWriteOpen(self._vfile.handle) if self.mode == MODE_RGB: - self._info = lib.PNGWriteHeader(self._png, image.width, image.height) + self._info = lib.PNGWriteHeader(self._png, image.width, image.height, lib.mCOLOR_XBGR8) if self.mode == MODE_RGBA: - self._info = lib.PNGWriteHeaderA(self._png, image.width, image.height) + self._info = lib.PNGWriteHeader(self._png, image.width, image.height, lib.mCOLOR_ABGR8) if self.mode == MODE_INDEX: self._info = lib.PNGWriteHeader8(self._png, image.width, image.height) return self._info != ffi.NULL def write_pixels(self, image): if self.mode == MODE_RGB: - return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer) + return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer, lib.mCOLOR_XBGR8) if self.mode == MODE_RGBA: - return lib.PNGWritePixelsA(self._png, image.width, image.height, image.stride, image.buffer) + return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer, lib.mCOLOR_ABGR8) if self.mode == MODE_INDEX: return lib.PNGWritePixels8(self._png, image.width, image.height, image.stride, image.buffer) return False diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index 416e8bb81..0644c4408 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -731,8 +731,8 @@ static struct VDir* _makeOutDir(const char* testName) { static void _writeImage(struct VFile* vf, const struct CInemaImage* image) { png_structp png = PNGWriteOpen(vf); - png_infop info = PNGWriteHeader(png, image->width, image->height); - if (!PNGWritePixels(png, image->width, image->height, image->stride, image->data)) { + png_infop info = PNGWriteHeader(png, image->width, image->height, mCOLOR_NATIVE); + if (!PNGWritePixels(png, image->width, image->height, image->stride, image->data, mCOLOR_NATIVE)) { CIerr(0, "Could not write output image\n"); } PNGWriteClose(png, info); diff --git a/src/util/image.c b/src/util/image.c index ed070f658..fa239cb90 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -208,32 +208,13 @@ bool mImageSave(const struct mImage* image, const char* path, const char* format #ifdef USE_PNG bool mImageSavePNG(const struct mImage* image, struct VFile* vf) { - if (image->format != mCOLOR_XBGR8 && image->format != mCOLOR_ABGR8) { - struct mImage* newImage; - if (mColorFormatHasAlpha(image->format)) { - newImage = mImageConvertToFormat(image, mCOLOR_ABGR8); - } else { - newImage = mImageConvertToFormat(image, mCOLOR_XBGR8); - } - bool ret = mImageSavePNG(newImage, vf); - mImageDestroy(newImage); - return ret; - } - png_structp png = PNGWriteOpen(vf); png_infop info = NULL; bool ok = false; if (png) { - if (image->format == mCOLOR_XBGR8) { - info = PNGWriteHeader(png, image->width, image->height); - if (info) { - ok = PNGWritePixels(png, image->width, image->height, image->stride, image->data); - } - } else { - info = PNGWriteHeaderA(png, image->width, image->height); - if (info) { - ok = PNGWritePixelsA(png, image->width, image->height, image->stride, image->data); - } + info = PNGWriteHeader(png, image->width, image->height, image->format); + if (info) { + ok = PNGWritePixels(png, image->width, image->height, image->stride, image->data, image->format); } PNGWriteClose(png, info); } diff --git a/src/util/image/png-io.c b/src/util/image/png-io.c index 9af634a5c..065acdd31 100644 --- a/src/util/image/png-io.c +++ b/src/util/image/png-io.c @@ -51,12 +51,37 @@ static png_infop _pngWriteHeader(png_structp png, unsigned width, unsigned heigh return info; } -png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height) { - return _pngWriteHeader(png, width, height, PNG_COLOR_TYPE_RGB); -} - -png_infop PNGWriteHeaderA(png_structp png, unsigned width, unsigned height) { - return _pngWriteHeader(png, width, height, PNG_COLOR_TYPE_RGB_ALPHA); +png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height, enum mColorFormat fmt) { + int type; + switch (fmt) { + case mCOLOR_XBGR8: + case mCOLOR_XRGB8: + case mCOLOR_BGRX8: + case mCOLOR_RGBX8: + case mCOLOR_RGB5: + case mCOLOR_BGR5: + case mCOLOR_RGB565: + case mCOLOR_BGR565: + case mCOLOR_RGB8: + case mCOLOR_BGR8: + type = PNG_COLOR_TYPE_RGB; + break; + case mCOLOR_ABGR8: + case mCOLOR_ARGB8: + case mCOLOR_BGRA8: + case mCOLOR_RGBA8: + case mCOLOR_ARGB5: + case mCOLOR_ABGR5: + case mCOLOR_RGBA5: + case mCOLOR_BGRA5: + case mCOLOR_ANY: + type = PNG_COLOR_TYPE_RGB_ALPHA; + break; + case mCOLOR_L8: + type = PNG_COLOR_TYPE_GRAY; + break; + } + return _pngWriteHeader(png, width, height, type); } png_infop PNGWriteHeader8(png_structp png, unsigned width, unsigned height) { @@ -85,51 +110,258 @@ bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, u return true; } -bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels) { - png_bytep row = malloc(sizeof(png_byte) * width * 3); - if (!row) { - return false; - } - const png_byte* pixelData = pixels; - if (setjmp(png_jmpbuf(png))) { - free(row); - return false; - } - unsigned i; - for (i = 0; i < height; ++i) { - unsigned x; - for (x = 0; x < width; ++x) { -#ifdef COLOR_16_BIT - uint16_t c = ((uint16_t*) pixelData)[stride * i + x]; -#ifdef COLOR_5_6_5 - row[x * 3] = (c >> 8) & 0xF8; - row[x * 3 + 1] = (c >> 3) & 0xFC; - row[x * 3 + 2] = (c << 3) & 0xF8; -#else - row[x * 3] = (c >> 7) & 0xF8; - row[x * 3 + 1] = (c >> 2) & 0xF8; - row[x * 3 + 2] = (c << 3) & 0xF8; -#endif -#else +static void _convertRowXBGR8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { #ifdef __BIG_ENDIAN__ - row[x * 3] = pixelData[stride * i * 4 + x * 4 + 3]; - row[x * 3 + 1] = pixelData[stride * i * 4 + x * 4 + 2]; - row[x * 3 + 2] = pixelData[stride * i * 4 + x * 4 + 1]; + row[x * 3] = pixelData[x * 4 + 3]; + row[x * 3 + 1] = pixelData[x * 4 + 2]; + row[x * 3 + 2] = pixelData[x * 4 + 1]; #else - row[x * 3] = pixelData[stride * i * 4 + x * 4]; - row[x * 3 + 1] = pixelData[stride * i * 4 + x * 4 + 1]; - row[x * 3 + 2] = pixelData[stride * i * 4 + x * 4 + 2]; + row[x * 3] = pixelData[x * 4]; + row[x * 3 + 1] = pixelData[x * 4 + 1]; + row[x * 3 + 2] = pixelData[x * 4 + 2]; #endif -#endif - } - png_write_row(png, row); } - free(row); - return true; } -bool PNGWritePixelsA(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels) { - png_bytep row = malloc(sizeof(png_byte) * width * 4); +static void _convertRowXRGB8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 3] = pixelData[x * 4 + 1]; + row[x * 3 + 1] = pixelData[x * 4 + 2]; + row[x * 3 + 2] = pixelData[x * 4 + 3]; +#else + row[x * 3] = pixelData[x * 4 + 2]; + row[x * 3 + 1] = pixelData[x * 4 + 1]; + row[x * 3 + 2] = pixelData[x * 4]; +#endif + } +} + +static void _convertRowBGRX8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 3] = pixelData[x * 4 + 2]; + row[x * 3 + 1] = pixelData[x * 4 + 1]; + row[x * 3 + 2] = pixelData[x * 4]; +#else + row[x * 3] = pixelData[x * 4 + 1]; + row[x * 3 + 1] = pixelData[x * 4 + 2]; + row[x * 3 + 2] = pixelData[x * 4 + 3]; +#endif + } +} + +static void _convertRowRGBX8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 3] = pixelData[x * 4]; + row[x * 3 + 1] = pixelData[x * 4 + 1]; + row[x * 3 + 2] = pixelData[x * 4 + 2]; +#else + row[x * 3] = pixelData[x * 4 + 3]; + row[x * 3 + 1] = pixelData[x * 4 + 2]; + row[x * 3 + 2] = pixelData[x * 4 + 1]; +#endif + } +} + +static void _convertRowABGR8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 4] = pixelData[x * 4 + 3]; + row[x * 4 + 1] = pixelData[x * 4 + 2]; + row[x * 4 + 2] = pixelData[x * 4 + 1]; + row[x * 4 + 3] = pixelData[x * 4]; +#else + row[x * 4] = pixelData[x * 4]; + row[x * 4 + 1] = pixelData[x * 4 + 1]; + row[x * 4 + 2] = pixelData[x * 4 + 2]; + row[x * 4 + 3] = pixelData[x * 4 + 3]; +#endif + } +} + +static void _convertRowARGB8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 4] = pixelData[x * 4 + 1]; + row[x * 4 + 1] = pixelData[x * 4 + 2]; + row[x * 4 + 2] = pixelData[x * 4 + 3]; + row[x * 4 + 3] = pixelData[x * 4]; +#else + row[x * 4] = pixelData[x * 4 + 2]; + row[x * 4 + 1] = pixelData[x * 4 + 1]; + row[x * 4 + 2] = pixelData[x * 4]; + row[x * 4 + 3] = pixelData[x * 4 + 3]; +#endif + } +} + +static void _convertRowBGRA8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 4] = pixelData[x * 4 + 2]; + row[x * 4 + 1] = pixelData[x * 4 + 1]; + row[x * 4 + 2] = pixelData[x * 4]; + row[x * 4 + 3] = pixelData[x * 4 + 3]; +#else + row[x * 4] = pixelData[x * 4 + 1]; + row[x * 4 + 1] = pixelData[x * 4 + 2]; + row[x * 4 + 2] = pixelData[x * 4 + 3]; + row[x * 4 + 3] = pixelData[x * 4]; +#endif + } +} + +static void _convertRowRGBA8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 4] = pixelData[x * 4]; + row[x * 4 + 1] = pixelData[x * 4 + 1]; + row[x * 4 + 2] = pixelData[x * 4 + 2]; + row[x * 4 + 3] = pixelData[x * 4 + 3]; +#else + row[x * 4] = pixelData[x * 4 + 3]; + row[x * 4 + 1] = pixelData[x * 4 + 2]; + row[x * 4 + 2] = pixelData[x * 4 + 1]; + row[x * 4 + 3] = pixelData[x * 4]; +#endif + } +} + +static void _convertRowRGB5(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { + uint16_t c = ((uint16_t*) pixelData)[x]; + row[x * 3] = (c >> 7) & 0xF8; + row[x * 3 + 1] = (c >> 2) & 0xF8; + row[x * 3 + 2] = (c << 3) & 0xF8; + } +} + +static void _convertRowBGR5(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { + uint16_t c = ((uint16_t*) pixelData)[x]; + row[x * 3] = (c << 3) & 0xF8; + row[x * 3 + 1] = (c >> 2) & 0xF8; + row[x * 3 + 2] = (c >> 7) & 0xF8; + } +} + +static void _convertRowARGB5(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { + uint16_t c = ((uint16_t*) pixelData)[x]; + row[x * 4] = (c >> 7) & 0xF8; + row[x * 4 + 1] = (c >> 2) & 0xF8; + row[x * 4 + 2] = (c << 3) & 0xF8; + row[x * 4 + 3] = (c >> 15) * 0xFF; + } +} + +static void _convertRowABGR5(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { + uint16_t c = ((uint16_t*) pixelData)[x]; + row[x * 4] = (c << 3) & 0xF8; + row[x * 4 + 1] = (c >> 2) & 0xF8; + row[x * 4 + 2] = (c >> 7) & 0xF8; + row[x * 4 + 3] = (c >> 15) * 0xFF; + } +} + +static void _convertRowRGBA5(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { + uint16_t c = ((uint16_t*) pixelData)[x]; + row[x * 4] = (c >> 8) & 0xF8; + row[x * 4 + 1] = (c >> 3) & 0xF8; + row[x * 4 + 2] = (c << 2) & 0xF8; + row[x * 4 + 3] = (c & 1) * 0xFF; + } +} + +static void _convertRowBGRA5(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { + uint16_t c = ((uint16_t*) pixelData)[x]; + row[x * 4] = (c << 2) & 0xF8; + row[x * 4 + 1] = (c >> 3) & 0xF8; + row[x * 4 + 2] = (c >> 8) & 0xF8; + row[x * 4 + 3] = (c & 1) * 0xFF; + } +} + +static void _convertRowRGB565(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { + uint16_t c = ((uint16_t*) pixelData)[x]; + row[x * 3] = (c >> 8) & 0xF8; + row[x * 3 + 1] = (c >> 3) & 0xFC; + row[x * 3 + 2] = (c << 3) & 0xF8; + } +} + +static void _convertRowBGR565(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { + uint16_t c = ((uint16_t*) pixelData)[x]; + row[x * 3] = (c << 3) & 0xF8; + row[x * 3 + 1] = (c >> 3) & 0xFC; + row[x * 3 + 2] = (c >> 8) & 0xF8; + } +} + +static void _convertRowBGR8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 3] = pixelData[x * 3 + 2]; + row[x * 3 + 1] = pixelData[x * 3 + 1]; + row[x * 3 + 2] = pixelData[x * 3]; +#else + row[x * 3] = pixelData[x * 3]; + row[x * 3 + 1] = pixelData[x * 3 + 1]; + row[x * 3 + 2] = pixelData[x * 3 + 2]; +#endif + } +} + +static void _convertRowRGB8(png_bytep row, const png_byte* pixelData, unsigned width) { + unsigned x; + for (x = 0; x < width; ++x) { +#ifdef __BIG_ENDIAN__ + row[x * 3] = pixelData[x * 3]; + row[x * 3 + 1] = pixelData[x * 3 + 1]; + row[x * 3 + 2] = pixelData[x * 3 + 2]; +#else + row[x * 3] = pixelData[x * 3 + 2]; + row[x * 3 + 1] = pixelData[x * 3 + 1]; + row[x * 3 + 2] = pixelData[x * 3]; +#endif + } +} + +bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels, enum mColorFormat fmt) { + int depth; + if (fmt == mCOLOR_L8) { + depth = 1; + } else if (mColorFormatHasAlpha(fmt)) { + depth = 4; + } else { + depth = 3; + } + png_bytep row = malloc(sizeof(png_byte) * width * depth); if (!row) { return false; } @@ -138,36 +370,71 @@ bool PNGWritePixelsA(png_structp png, unsigned width, unsigned height, unsigned free(row); return false; } + const png_byte* pixelRow = pixelData; + stride *= mColorFormatBytes(fmt); unsigned i; - for (i = 0; i < height; ++i) { - unsigned x; - for (x = 0; x < width; ++x) { -#ifdef COLOR_16_BIT - uint16_t c = ((uint16_t*) pixelData)[stride * i + x]; -#ifdef COLOR_5_6_5 - row[x * 4] = (c >> 8) & 0xF8; - row[x * 4 + 1] = (c >> 3) & 0xFC; - row[x * 4 + 2] = (c << 3) & 0xF8; - row[x * 4 + 3] = 0xFF; -#else - row[x * 4] = (c >> 7) & 0xF8; - row[x * 4 + 1] = (c >> 2) & 0xF8; - row[x * 4 + 2] = (c << 3) & 0xF8; - row[x * 4 + 3] = (c >> 15) * 0xFF; -#endif -#else -#ifdef __BIG_ENDIAN__ - row[x * 4] = pixelData[stride * i * 4 + x * 4 + 3]; - row[x * 4 + 1] = pixelData[stride * i * 4 + x * 4 + 2]; - row[x * 4 + 2] = pixelData[stride * i * 4 + x * 4 + 1]; - row[x * 4 + 3] = pixelData[stride * i * 4 + x * 4]; -#else - row[x * 4] = pixelData[stride * i * 4 + x * 4]; - row[x * 4 + 1] = pixelData[stride * i * 4 + x * 4 + 1]; - row[x * 4 + 2] = pixelData[stride * i * 4 + x * 4 + 2]; - row[x * 4 + 3] = pixelData[stride * i * 4 + x * 4 + 3]; -#endif -#endif + for (i = 0; i < height; ++i, pixelRow += stride) { + switch (fmt) { + case mCOLOR_XBGR8: + _convertRowXBGR8(row, pixelRow, width); + break; + case mCOLOR_XRGB8: + _convertRowXRGB8(row, pixelRow, width); + break; + case mCOLOR_BGRX8: + _convertRowBGRX8(row, pixelRow, width); + break; + case mCOLOR_RGBX8: + _convertRowRGBX8(row, pixelRow, width); + break; + case mCOLOR_ABGR8: + _convertRowABGR8(row, pixelRow, width); + break; + case mCOLOR_ARGB8: + _convertRowARGB8(row, pixelRow, width); + break; + case mCOLOR_BGRA8: + _convertRowBGRA8(row, pixelRow, width); + break; + case mCOLOR_RGBA8: + _convertRowRGBA8(row, pixelRow, width); + break; + case mCOLOR_RGB5: + _convertRowRGB5(row, pixelRow, width); + break; + case mCOLOR_BGR5: + _convertRowBGR5(row, pixelRow, width); + break; + case mCOLOR_ARGB5: + _convertRowARGB5(row, pixelRow, width); + break; + case mCOLOR_ABGR5: + _convertRowABGR5(row, pixelRow, width); + break; + case mCOLOR_RGBA5: + _convertRowRGBA5(row, pixelRow, width); + break; + case mCOLOR_BGRA5: + _convertRowBGRA5(row, pixelRow, width); + break; + case mCOLOR_RGB565: + _convertRowRGB565(row, pixelRow, width); + break; + case mCOLOR_BGR565: + _convertRowBGR565(row, pixelRow, width); + break; + case mCOLOR_BGR8: + _convertRowBGR8(row, pixelRow, width); + break; + case mCOLOR_RGB8: + _convertRowRGB8(row, pixelRow, width); + break; + case mCOLOR_L8: + memcpy(row, pixelRow, width); + break; + case mCOLOR_ANY: + // Invalid value + longjmp(png_jmpbuf(png), 1); } png_write_row(png, row); } diff --git a/src/util/test/image.c b/src/util/test/image.c index fde76e1c5..8ed1effee 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -551,6 +551,51 @@ M_TEST_DEFINE(savePngNonNative) { assert_int_equal(mImageGetPixel(image, 0, 0), 0x01234567); mImageDestroy(image); } + +M_TEST_DEFINE(savePngRoundTrip) { + const enum mColorFormat formats[] = { + mCOLOR_XBGR8, mCOLOR_XRGB8, + mCOLOR_BGRX8, mCOLOR_RGBX8, + mCOLOR_ABGR8, mCOLOR_ARGB8, + mCOLOR_BGRA8, mCOLOR_RGBA8, + mCOLOR_RGB5, mCOLOR_BGR5, + mCOLOR_ARGB5, mCOLOR_ABGR5, + mCOLOR_RGBA5, mCOLOR_BGRA5, + mCOLOR_RGB565, mCOLOR_BGR565, + mCOLOR_RGB8, mCOLOR_BGR8, + 0 + }; + + int i; + for (i = 0; formats[i]; ++i) { + struct mImage* image = mImageCreate(2, 2, formats[i]); + mImageSetPixel(image, 0, 0, 0xFF181008); + mImageSetPixel(image, 1, 0, 0xFF100818); + mImageSetPixel(image, 0, 1, 0xFF081810); + mImageSetPixel(image, 1, 1, 0xFF181008); + assert_int_equal(mImageGetPixel(image, 0, 0), 0xFF181008); + assert_int_equal(mImageGetPixel(image, 1, 0), 0xFF100818); + assert_int_equal(mImageGetPixel(image, 0, 1), 0xFF081810); + assert_int_equal(mImageGetPixel(image, 1, 1), 0xFF181008); + + struct VFile* vf = VFileMemChunk(NULL, 0); + assert_true(mImageSaveVF(image, vf, "png")); + mImageDestroy(image); + + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + assert_true(isPNG(vf)); + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + + image = mImageLoadVF(vf); + vf->close(vf); + assert_non_null(image); + assert_int_equal(mImageGetPixel(image, 0, 0), 0xFF181008); + assert_int_equal(mImageGetPixel(image, 1, 0), 0xFF100818); + assert_int_equal(mImageGetPixel(image, 0, 1), 0xFF081810); + assert_int_equal(mImageGetPixel(image, 1, 1), 0xFF181008); + mImageDestroy(image); + } +} #endif M_TEST_DEFINE(convert1x1) { @@ -997,6 +1042,7 @@ M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(loadPng32), cmocka_unit_test(savePngNative), cmocka_unit_test(savePngNonNative), + cmocka_unit_test(savePngRoundTrip), #endif cmocka_unit_test(convert1x1), cmocka_unit_test(convert2x1), From f8b923015bfa28ee552dc238aae40ff7848a8045 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 17 Apr 2023 02:10:16 -0700 Subject: [PATCH 167/290] Util: Add grayscale mImage loading --- src/util/image.c | 14 ++++++++++++++ src/util/test/image.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/util/image.c b/src/util/image.c index fa239cb90..f149ce68e 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -132,6 +132,20 @@ static struct mImage* mImageLoadPNG(struct VFile* vf) { return NULL; } break; + case 1: + if (png_get_color_type(png, info) == PNG_COLOR_TYPE_GRAY) { + image->format = mCOLOR_L8; + image->depth = 1; + image->data = malloc(width * height); + if (!PNGReadPixels8(png, info, image->data, width, height, width)) { + free(image->data); + free(image); + PNGReadClose(png, info, end); + return NULL; + } + break; + } + // Fall through default: // Not supported yet free(image); diff --git a/src/util/test/image.c b/src/util/test/image.c index 8ed1effee..f909da910 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -596,6 +596,35 @@ M_TEST_DEFINE(savePngRoundTrip) { mImageDestroy(image); } } + +M_TEST_DEFINE(savePngL8) { + struct mImage* image = mImageCreate(2, 2, mCOLOR_L8); + mImageSetPixel(image, 0, 0, 0xFF000000); + mImageSetPixel(image, 1, 0, 0xFF555555); + mImageSetPixel(image, 0, 1, 0xFFAAAAAA); + mImageSetPixel(image, 1, 1, 0xFFFFFFFF); + assert_int_equal(mImageGetPixel(image, 0, 0), 0xFF000000); + assert_int_equal(mImageGetPixel(image, 1, 0), 0xFF555555); + assert_int_equal(mImageGetPixel(image, 0, 1), 0xFFAAAAAA); + assert_int_equal(mImageGetPixel(image, 1, 1), 0xFFFFFFFF); + + struct VFile* vf = VFileMemChunk(NULL, 0); + assert_true(mImageSaveVF(image, vf, "png")); + mImageDestroy(image); + + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + assert_true(isPNG(vf)); + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + + image = mImageLoadVF(vf); + vf->close(vf); + assert_non_null(image); + assert_int_equal(mImageGetPixel(image, 0, 0), 0xFF000000); + assert_int_equal(mImageGetPixel(image, 1, 0), 0xFF555555); + assert_int_equal(mImageGetPixel(image, 0, 1), 0xFFAAAAAA); + assert_int_equal(mImageGetPixel(image, 1, 1), 0xFFFFFFFF); + mImageDestroy(image); +} #endif M_TEST_DEFINE(convert1x1) { @@ -1043,6 +1072,7 @@ M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(savePngNative), cmocka_unit_test(savePngNonNative), cmocka_unit_test(savePngRoundTrip), + cmocka_unit_test(savePngL8), #endif cmocka_unit_test(convert1x1), cmocka_unit_test(convert2x1), From 618a51cabb84367667e5b148c4a22117ad5c6ebd Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 17 Apr 2023 03:00:00 -0700 Subject: [PATCH 168/290] Util: Preliminary palette support --- include/mgba-util/image.h | 5 +++ src/util/image.c | 95 ++++++++++++++++++++++++++++++++------- src/util/test/image.c | 41 ++++++++++++++--- 3 files changed, 118 insertions(+), 23 deletions(-) diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index 0a1b8d7b0..0e0ee55bb 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -77,6 +77,7 @@ enum mColorFormat { mCOLOR_RGB8 = 0x10000, mCOLOR_BGR8 = 0x20000, mCOLOR_L8 = 0x40000, + mCOLOR_PAL8 = 0x80000, mCOLOR_ANY = -1 }; @@ -91,10 +92,12 @@ enum mColorFormat { struct mImage { void* data; + uint32_t* palette; unsigned width; unsigned height; unsigned stride; unsigned depth; + unsigned palSize; enum mColorFormat format; }; @@ -146,6 +149,7 @@ static inline unsigned mColorFormatBytes(enum mColorFormat format) { case mCOLOR_BGR8: return 3; case mCOLOR_L8: + case mCOLOR_PAL8: return 1; case mCOLOR_ANY: break; @@ -175,6 +179,7 @@ static inline bool mColorFormatHasAlpha(enum mColorFormat format) { case mCOLOR_ABGR5: case mCOLOR_RGBA5: case mCOLOR_BGRA5: + case mCOLOR_PAL8: return true; case mCOLOR_ANY: break; diff --git a/src/util/image.c b/src/util/image.c index f149ce68e..c686c297a 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -135,17 +135,51 @@ static struct mImage* mImageLoadPNG(struct VFile* vf) { case 1: if (png_get_color_type(png, info) == PNG_COLOR_TYPE_GRAY) { image->format = mCOLOR_L8; - image->depth = 1; - image->data = malloc(width * height); - if (!PNGReadPixels8(png, info, image->data, width, height, width)) { - free(image->data); - free(image); - PNGReadClose(png, info, end); + } else { + png_colorp palette; + png_bytep trns; + int count; + int trnsCount = 0; + image->format = mCOLOR_PAL8; + if (png_get_PLTE(png, info, &palette, &count) == 0) { return NULL; } - break; + if (count > 256) { + count = 256; +#ifndef NDEBUG + abort(); +#endif + } + image->palette = malloc(1024); + image->palSize = count; + png_get_tRNS(png, info, &trns, &trnsCount, NULL); + + int i; + for (i = 0; i < count; ++i) { + uint32_t color = palette[i].red << 16; + color |= palette[i].green << 8; + color |= palette[i].blue; + + if (i < trnsCount) { + color |= trns[i] << 24; + } else { + color |= 0xFF000000; + } + image->palette[i] = color; + } } - // Fall through + image->depth = 1; + image->data = malloc(width * height); + if (!PNGReadPixels8(png, info, image->data, width, height, width)) { + if (image->palette) { + free(image->palette); + } + free(image->data); + free(image); + PNGReadClose(png, info, end); + return NULL; + } + break; default: // Not supported yet free(image); @@ -169,6 +203,10 @@ struct mImage* mImageLoadVF(struct VFile* vf) { } struct mImage* mImageConvertToFormat(const struct mImage* image, enum mColorFormat format) { + if (format == mCOLOR_PAL8) { + // Quantization shouldn't be handled here + return NULL; + } struct mImage* newImage = calloc(1, sizeof(*newImage)); newImage->width = image->width; newImage->height = image->height; @@ -186,20 +224,36 @@ struct mImage* mImageConvertToFormat(const struct mImage* image, enum mColorForm // TODO: Implement more specializations, e.g. alpha narrowing/widening, channel swapping size_t x, y; - for (y = 0; y < newImage->height; ++y) { - uintptr_t src = (uintptr_t) ROW(image, y); - uintptr_t dst = (uintptr_t) ROW(newImage, y); - for (x = 0; x < newImage->width; ++x, src += image->depth, dst += newImage->depth) { - uint32_t color; - GET_PIXEL(color, src, image->depth); - color = mColorConvert(color, image->format, format); - PUT_PIXEL(color, dst, newImage->depth); + if (image->format == mCOLOR_PAL8) { + for (y = 0; y < newImage->height; ++y) { + uintptr_t src = (uintptr_t) ROW(image, y); + uintptr_t dst = (uintptr_t) ROW(newImage, y); + for (x = 0; x < newImage->width; ++x, src += image->depth, dst += newImage->depth) { + uint32_t color; + GET_PIXEL(color, src, image->depth); + color = image->palette[color]; + PUT_PIXEL(color, dst, newImage->depth); + } + } + } else { + for (y = 0; y < newImage->height; ++y) { + uintptr_t src = (uintptr_t) ROW(image, y); + uintptr_t dst = (uintptr_t) ROW(newImage, y); + for (x = 0; x < newImage->width; ++x, src += image->depth, dst += newImage->depth) { + uint32_t color; + GET_PIXEL(color, src, image->depth); + color = mColorConvert(color, image->format, format); + PUT_PIXEL(color, dst, newImage->depth); + } } } return newImage; } void mImageDestroy(struct mImage* image) { + if (image->palette) { + free(image->palette); + } free(image->data); free(image); } @@ -280,7 +334,12 @@ uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y) { } uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y) { - return mColorConvert(mImageGetPixelRaw(image, x, y), image->format, mCOLOR_ARGB8); + uint32_t raw = mImageGetPixelRaw(image, x, y); + if (image->format == mCOLOR_PAL8) { + return image->palette[raw]; + } else { + return mColorConvert(raw, image->format, mCOLOR_ARGB8); + } } void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color) { @@ -531,6 +590,7 @@ uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat b = color; break; + case mCOLOR_PAL8: case mCOLOR_ANY: return 0; } @@ -619,6 +679,7 @@ uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat // sRGB primaries in fixed point, roughly fudged to saturate to 0xFFFF color = (55 * r + 184 * g + 18 * b) >> 8; break; + case mCOLOR_PAL8: case mCOLOR_ANY: return 0; } diff --git a/src/util/test/image.c b/src/util/test/image.c index f909da910..41c29bf6d 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -464,9 +464,8 @@ M_TEST_DEFINE(loadPng24) { 0x51, 0xc0, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; - size_t len = 75; - struct VFile* vf = VFileFromConstMemory(data, len); + struct VFile* vf = VFileFromConstMemory(data, sizeof(data)); struct mImage* image = mImageLoadVF(vf); vf->close(vf); @@ -483,9 +482,8 @@ M_TEST_DEFINE(loadPng24) { mImageDestroy(image); } - M_TEST_DEFINE(loadPng32) { - unsigned char data[] = { + const uint8_t data[] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x08, 0x06, 0x00, 0x00, 0x00, 0x72, 0xb6, 0x0d, 0x24, 0x00, 0x00, 0x00, @@ -494,9 +492,8 @@ M_TEST_DEFINE(loadPng32) { 0xa7, 0x5a, 0x78, 0x58, 0x7b, 0x07, 0xac, 0xe9, 0x00, 0x3d, 0x95, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; - unsigned int len = 83; - struct VFile* vf = VFileFromConstMemory(data, len); + struct VFile* vf = VFileFromConstMemory(data, sizeof(data)); struct mImage* image = mImageLoadVF(vf); vf->close(vf); @@ -513,6 +510,37 @@ M_TEST_DEFINE(loadPng32) { mImageDestroy(image); } +M_TEST_DEFINE(loadPngPalette) { + const uint8_t data[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, + 0x08, 0x03, 0x00, 0x00, 0x00, 0x45, 0x68, 0xfd, 0x16, 0x00, 0x00, 0x00, + 0x0c, 0x50, 0x4c, 0x54, 0x45, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, + 0xff, 0x00, 0x00, 0x00, 0xff, 0x9b, 0xc0, 0x13, 0xdc, 0x00, 0x00, 0x00, + 0x04, 0x74, 0x52, 0x4e, 0x53, 0x00, 0xc0, 0x80, 0x40, 0x6f, 0x63, 0x29, + 0x01, 0x00, 0x00, 0x00, 0x0e, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x63, + 0x60, 0x60, 0x64, 0x60, 0x62, 0x06, 0x00, 0x00, 0x11, 0x00, 0x07, 0x69, + 0xe2, 0x2a, 0x44, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, + 0x42, 0x60, 0x82 + }; + + struct VFile* vf = VFileFromConstMemory(data, sizeof(data)); + struct mImage* image = mImageLoadVF(vf); + vf->close(vf); + + assert_non_null(image); + assert_int_equal(image->width, 2); + assert_int_equal(image->height, 2); + assert_int_equal(image->format, mCOLOR_PAL8); + + assert_int_equal(mImageGetPixel(image, 0, 0), 0x00000000); + assert_int_equal(mImageGetPixel(image, 1, 0), 0xC0FF0000); + assert_int_equal(mImageGetPixel(image, 0, 1), 0x8000FF00); + assert_int_equal(mImageGetPixel(image, 1, 1), 0x400000FF); + + mImageDestroy(image); +} + M_TEST_DEFINE(savePngNative) { struct mImage* image = mImageCreate(1, 1, mCOLOR_ABGR8); mImageSetPixel(image, 0, 0, 0x01234567); @@ -1069,6 +1097,7 @@ M_TEST_SUITE_DEFINE(Image, #ifdef USE_PNG cmocka_unit_test(loadPng24), cmocka_unit_test(loadPng32), + cmocka_unit_test(loadPngPalette), cmocka_unit_test(savePngNative), cmocka_unit_test(savePngNonNative), cmocka_unit_test(savePngRoundTrip), From 9fa607b30fae77a1f46c300d938cd65464ee5b13 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 17 Apr 2023 04:36:05 -0700 Subject: [PATCH 169/290] Util: More palette support --- include/mgba-util/image.h | 4 ++ include/mgba-util/image/png-io.h | 5 +- src/platform/python/mgba/png.py | 4 -- src/util/image.c | 108 +++++++++++++++++++++---------- src/util/image/png-io.c | 35 ++++++---- src/util/test/image.c | 58 +++++++++++++++++ 6 files changed, 163 insertions(+), 51 deletions(-) diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index 0e0ee55bb..8ec91b16a 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -118,11 +118,15 @@ uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y); void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color); void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color); +void mImageSetPaletteSize(struct mImage* image, unsigned count); +void mImageSetPaletteEntry(struct mImage* image, unsigned index, uint32_t color); + void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y); void mImageComposite(struct mImage* image, const struct mImage* source, int x, int y); void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source, int x, int y, float alpha); uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to); +uint32_t mImageColorConvert(uint32_t color, const struct mImage* from, enum mColorFormat to); #ifndef PYCPARSE static inline unsigned mColorFormatBytes(enum mColorFormat format) { diff --git a/include/mgba-util/image/png-io.h b/include/mgba-util/image/png-io.h index b33d64ce7..b9b82c0ac 100644 --- a/include/mgba-util/image/png-io.h +++ b/include/mgba-util/image/png-io.h @@ -28,10 +28,9 @@ enum { png_structp PNGWriteOpen(struct VFile* source); png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height, enum mColorFormat); -png_infop PNGWriteHeader8(png_structp png, unsigned width, unsigned height); -bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, unsigned entries); +png_infop PNGWriteHeaderPalette(png_structp png, unsigned width, unsigned height, const uint32_t* palette, unsigned entries); bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels, enum mColorFormat); -bool PNGWritePixels8(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels); +bool PNGWritePixelsPalette(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels); bool PNGWriteCustomChunk(png_structp png, const char* name, size_t size, void* data); void PNGWriteClose(png_structp png, png_infop info); diff --git a/src/platform/python/mgba/png.py b/src/platform/python/mgba/png.py index 3051ca197..3df4d3cd9 100644 --- a/src/platform/python/mgba/png.py +++ b/src/platform/python/mgba/png.py @@ -24,8 +24,6 @@ class PNG: self._info = lib.PNGWriteHeader(self._png, image.width, image.height, lib.mCOLOR_XBGR8) if self.mode == MODE_RGBA: self._info = lib.PNGWriteHeader(self._png, image.width, image.height, lib.mCOLOR_ABGR8) - if self.mode == MODE_INDEX: - self._info = lib.PNGWriteHeader8(self._png, image.width, image.height) return self._info != ffi.NULL def write_pixels(self, image): @@ -33,8 +31,6 @@ class PNG: return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer, lib.mCOLOR_XBGR8) if self.mode == MODE_RGBA: return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer, lib.mCOLOR_ABGR8) - if self.mode == MODE_INDEX: - return lib.PNGWritePixels8(self._png, image.width, image.height, image.stride, image.buffer) return False def write_close(self): diff --git a/src/util/image.c b/src/util/image.c index c686c297a..a4f597c2b 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -64,6 +64,15 @@ struct mImage* mImageCreateWithStride(unsigned width, unsigned height, unsigned free(image); return NULL; } + if (format == mCOLOR_PAL8) { + image->palette = malloc(1024); + if (!image->palette) { + free(image->data); + free(image); + return NULL; + } + image->palSize = 1; + } return image; } @@ -224,27 +233,14 @@ struct mImage* mImageConvertToFormat(const struct mImage* image, enum mColorForm // TODO: Implement more specializations, e.g. alpha narrowing/widening, channel swapping size_t x, y; - if (image->format == mCOLOR_PAL8) { - for (y = 0; y < newImage->height; ++y) { - uintptr_t src = (uintptr_t) ROW(image, y); - uintptr_t dst = (uintptr_t) ROW(newImage, y); - for (x = 0; x < newImage->width; ++x, src += image->depth, dst += newImage->depth) { - uint32_t color; - GET_PIXEL(color, src, image->depth); - color = image->palette[color]; - PUT_PIXEL(color, dst, newImage->depth); - } - } - } else { - for (y = 0; y < newImage->height; ++y) { - uintptr_t src = (uintptr_t) ROW(image, y); - uintptr_t dst = (uintptr_t) ROW(newImage, y); - for (x = 0; x < newImage->width; ++x, src += image->depth, dst += newImage->depth) { - uint32_t color; - GET_PIXEL(color, src, image->depth); - color = mColorConvert(color, image->format, format); - PUT_PIXEL(color, dst, newImage->depth); - } + for (y = 0; y < newImage->height; ++y) { + uintptr_t src = (uintptr_t) ROW(image, y); + uintptr_t dst = (uintptr_t) ROW(newImage, y); + for (x = 0; x < newImage->width; ++x, src += image->depth, dst += newImage->depth) { + uint32_t color; + GET_PIXEL(color, src, image->depth); + color = mImageColorConvert(color, image, format); + PUT_PIXEL(color, dst, newImage->depth); } } return newImage; @@ -280,9 +276,16 @@ bool mImageSavePNG(const struct mImage* image, struct VFile* vf) { png_infop info = NULL; bool ok = false; if (png) { - info = PNGWriteHeader(png, image->width, image->height, image->format); - if (info) { - ok = PNGWritePixels(png, image->width, image->height, image->stride, image->data, image->format); + if (image->format == mCOLOR_PAL8) { + info = PNGWriteHeaderPalette(png, image->width, image->height, image->palette, image->palSize); + if (info) { + ok = PNGWritePixelsPalette(png, image->width, image->height, image->stride, image->data); + } + } else { + info = PNGWriteHeader(png, image->width, image->height, image->format); + if (info) { + ok = PNGWritePixels(png, image->width, image->height, image->stride, image->data, image->format); + } } PNGWriteClose(png, info); } @@ -334,12 +337,7 @@ uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y) { } uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y) { - uint32_t raw = mImageGetPixelRaw(image, x, y); - if (image->format == mCOLOR_PAL8) { - return image->palette[raw]; - } else { - return mColorConvert(raw, image->format, mCOLOR_ARGB8); - } + return mImageColorConvert(mImageGetPixelRaw(image, x, y), image, mCOLOR_ARGB8); } void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color) { @@ -375,6 +373,26 @@ void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color mImageSetPixelRaw(image, x, y, mColorConvert(color, mCOLOR_ARGB8, image->format)); } +void mImageSetPaletteSize(struct mImage* image, unsigned count) { + if (image->format != mCOLOR_PAL8) { + return; + } + if (count > 256) { + count = 256; + } + image->palSize = count; +} + +void mImageSetPaletteEntry(struct mImage* image, unsigned index, uint32_t color) { + if (image->format != mCOLOR_PAL8) { + return; + } + if (index > 256) { + return; + } + image->palette[index] = color; +} + #define COMPOSITE_BOUNDS_INIT \ struct mRectangle dstRect = { \ .x = 0, \ @@ -411,6 +429,11 @@ void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color } void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y) { + if (image->format == mCOLOR_PAL8) { + // Can't blit to paletted image + return; + } + COMPOSITE_BOUNDS_INIT; for (y = 0; y < srcRect.height; ++y) { @@ -419,7 +442,7 @@ void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y) for (x = 0; x < srcRect.width; ++x, srcPixel += source->depth, dstPixel += image->depth) { uint32_t color; GET_PIXEL(color, srcPixel, source->depth); - color = mColorConvert(color, source->format, image->format); + color = mImageColorConvert(color, source, image->format); PUT_PIXEL(color, dstPixel, image->depth); } } @@ -431,6 +454,11 @@ void mImageComposite(struct mImage* image, const struct mImage* source, int x, i return; } + if (image->format == mCOLOR_PAL8) { + // Can't blit to paletted image + return; + } + COMPOSITE_BOUNDS_INIT; for (y = 0; y < srcRect.height; ++y) { @@ -439,7 +467,7 @@ void mImageComposite(struct mImage* image, const struct mImage* source, int x, i for (x = 0; x < srcRect.width; ++x, srcPixel += source->depth, dstPixel += image->depth) { uint32_t color, colorB; GET_PIXEL(color, srcPixel, source->depth); - color = mColorConvert(color, source->format, mCOLOR_ARGB8); + color = mImageColorConvert(color, source, mCOLOR_ARGB8); if (color < 0xFF000000) { GET_PIXEL(colorB, dstPixel, image->depth); colorB = mColorConvert(colorB, image->format, mCOLOR_ARGB8); @@ -456,6 +484,10 @@ void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source, mImageComposite(image, source, x, y); return; } + if (image->format == mCOLOR_PAL8) { + // Can't blit to paletted image + return; + } if (alpha <= 0) { return; } @@ -474,7 +506,7 @@ void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source, for (x = 0; x < srcRect.width; ++x, srcPixel += source->depth, dstPixel += image->depth) { uint32_t color, colorB; GET_PIXEL(color, srcPixel, source->depth); - color = mColorConvert(color, source->format, mCOLOR_ARGB8); + color = mImageColorConvert(color, source, mCOLOR_ARGB8); uint32_t alpha = (color >> 24) * fixedAlpha; alpha >>= 9; if (alpha > 0xFF) { @@ -686,3 +718,13 @@ uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat return color; } + +uint32_t mImageColorConvert(uint32_t color, const struct mImage* from, enum mColorFormat to) { + if (from->format != mCOLOR_PAL8) { + return mColorConvert(color, from->format, to); + } + if (color < from->palSize) { + color = from->palette[color]; + } + return mColorConvert(color, mCOLOR_ARGB8, to); +} diff --git a/src/util/image/png-io.c b/src/util/image/png-io.c index 065acdd31..e7df1d64f 100644 --- a/src/util/image/png-io.c +++ b/src/util/image/png-io.c @@ -9,6 +9,8 @@ #include +static bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, unsigned entries); + static void _pngWrite(png_structp png, png_bytep buffer, png_size_t size) { struct VFile* vf = png_get_io_ptr(png); size_t written = vf->write(vf, buffer, size); @@ -38,15 +40,23 @@ png_structp PNGWriteOpen(struct VFile* source) { return png; } -static png_infop _pngWriteHeader(png_structp png, unsigned width, unsigned height, int type) { +static png_infop _pngWriteHeader(png_structp png, unsigned width, unsigned height, const uint32_t* palette, unsigned entries, int type) { png_infop info = png_create_info_struct(png); if (!info) { - return 0; + return NULL; } if (setjmp(png_jmpbuf(png))) { - return 0; + return NULL; } png_set_IHDR(png, info, width, height, 8, type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + if (type == PNG_COLOR_TYPE_PALETTE) { + if (!palette) { + return NULL; + } + if (!PNGWritePalette(png, info, palette, entries)) { + return NULL; + } + } png_write_info(png, info); return info; } @@ -80,15 +90,18 @@ png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height, enum case mCOLOR_L8: type = PNG_COLOR_TYPE_GRAY; break; + case mCOLOR_PAL8: + type = PNG_COLOR_TYPE_PALETTE; + break; } - return _pngWriteHeader(png, width, height, type); + return _pngWriteHeader(png, width, height, NULL, 0, type); } -png_infop PNGWriteHeader8(png_structp png, unsigned width, unsigned height) { - return _pngWriteHeader(png, width, height, PNG_COLOR_TYPE_PALETTE); +png_infop PNGWriteHeaderPalette(png_structp png, unsigned width, unsigned height, const uint32_t* palette, unsigned entries) { + return _pngWriteHeader(png, width, height, palette, entries, PNG_COLOR_TYPE_PALETTE); } -bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, unsigned entries) { +static bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, unsigned entries) { if (!palette || !entries) { return false; } @@ -99,14 +112,13 @@ bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, u png_byte trans[256]; unsigned i; for (i = 0; i < entries && i < 256; ++i) { - colors[i].red = palette[i]; + colors[i].red = palette[i] >> 16; colors[i].green = palette[i] >> 8; - colors[i].blue = palette[i] >> 16; + colors[i].blue = palette[i]; trans[i] = palette[i] >> 24; } png_set_PLTE(png, info, colors, entries); png_set_tRNS(png, info, trans, entries, NULL); - png_write_info(png, info); return true; } @@ -430,6 +442,7 @@ bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned s _convertRowRGB8(row, pixelRow, width); break; case mCOLOR_L8: + case mCOLOR_PAL8: memcpy(row, pixelRow, width); break; case mCOLOR_ANY: @@ -442,7 +455,7 @@ bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned s return true; } -bool PNGWritePixels8(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels) { +bool PNGWritePixelsPalette(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels) { UNUSED(width); const png_byte* pixelData = pixels; if (setjmp(png_jmpbuf(png))) { diff --git a/src/util/test/image.c b/src/util/test/image.c index 41c29bf6d..a7955110a 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -453,6 +453,22 @@ M_TEST_DEFINE(oobWrite) { assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF, 0xFF, 0xFF, 0xFF }), sizeof(buffer)); } +M_TEST_DEFINE(paletteAccess) { + struct mImage* image = mImageCreate(1, 1, mCOLOR_PAL8); + mImageSetPaletteSize(image, 1); + + mImageSetPaletteEntry(image, 0, 0xFF00FF00); + mImageSetPixelRaw(image, 0, 0, 0); + assert_int_equal(mImageGetPixelRaw(image, 0, 0), 0); + assert_int_equal(mImageGetPixel(image, 0, 0), 0xFF00FF00); + + mImageSetPaletteEntry(image, 0, 0x01234567); + assert_int_equal(mImageGetPixelRaw(image, 0, 0), 0); + assert_int_equal(mImageGetPixel(image, 0, 0), 0x01234567); + + mImageDestroy(image); +} + #ifdef USE_PNG M_TEST_DEFINE(loadPng24) { const uint8_t data[] = { @@ -653,6 +669,46 @@ M_TEST_DEFINE(savePngL8) { assert_int_equal(mImageGetPixel(image, 1, 1), 0xFFFFFFFF); mImageDestroy(image); } + +M_TEST_DEFINE(savePngPal8) { + struct mImage* image = mImageCreate(2, 2, mCOLOR_PAL8); + mImageSetPaletteSize(image, 4); + mImageSetPaletteEntry(image, 0, 0x00000000); + mImageSetPaletteEntry(image, 1, 0x40FF0000); + mImageSetPaletteEntry(image, 2, 0x8000FF00); + mImageSetPaletteEntry(image, 3, 0xC00000FF); + + mImageSetPixelRaw(image, 0, 0, 0); + mImageSetPixelRaw(image, 1, 0, 1); + mImageSetPixelRaw(image, 0, 1, 2); + mImageSetPixelRaw(image, 1, 1, 3); + assert_int_equal(mImageGetPixel(image, 0, 0), 0x00000000); + assert_int_equal(mImageGetPixel(image, 1, 0), 0x40FF0000); + assert_int_equal(mImageGetPixel(image, 0, 1), 0x8000FF00); + assert_int_equal(mImageGetPixel(image, 1, 1), 0xC00000FF); + + struct VFile* vf = VFileMemChunk(NULL, 0); + assert_true(mImageSaveVF(image, vf, "png")); + mImageDestroy(image); + + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + assert_true(isPNG(vf)); + assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0); + + image = mImageLoadVF(vf); + vf->close(vf); + assert_non_null(image); + assert_int_equal(image->format, mCOLOR_PAL8); + assert_int_equal(mImageGetPixelRaw(image, 0, 0), 0); + assert_int_equal(mImageGetPixelRaw(image, 1, 0), 1); + assert_int_equal(mImageGetPixelRaw(image, 0, 1), 2); + assert_int_equal(mImageGetPixelRaw(image, 1, 1), 3); + assert_int_equal(mImageGetPixel(image, 0, 0), 0x00000000); + assert_int_equal(mImageGetPixel(image, 1, 0), 0x40FF0000); + assert_int_equal(mImageGetPixel(image, 0, 1), 0x8000FF00); + assert_int_equal(mImageGetPixel(image, 1, 1), 0xC00000FF); + mImageDestroy(image); +} #endif M_TEST_DEFINE(convert1x1) { @@ -1094,6 +1150,7 @@ M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(pitchWrite), cmocka_unit_test(strideWrite), cmocka_unit_test(oobWrite), + cmocka_unit_test(paletteAccess), #ifdef USE_PNG cmocka_unit_test(loadPng24), cmocka_unit_test(loadPng32), @@ -1102,6 +1159,7 @@ M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(savePngNonNative), cmocka_unit_test(savePngRoundTrip), cmocka_unit_test(savePngL8), + cmocka_unit_test(savePngPal8), #endif cmocka_unit_test(convert1x1), cmocka_unit_test(convert2x1), From 80a8074608833fb7974a90229e4286f7d00637e7 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 17 Apr 2023 22:23:54 -0700 Subject: [PATCH 170/290] GBA Video: Fix interpolation issues with OpenGL renderer --- CHANGES | 1 + src/gba/renderers/gl.c | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 923643ce4..58a465c51 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,7 @@ Emulation fixes: - 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) + - GBA Video: Fix interpolation issues with OpenGL renderer Other fixes: - Core: Allow sending thread requests to a crashed core (fixes mgba.io/i/2784) - FFmpeg: Force lower sample rate for codecs not supporting high rates (fixes mgba.io/i/2869) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index b6ffa2aac..4a751c706 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -289,12 +289,12 @@ static const char* const _renderMode2 = " }\n" " loadAffine(int(incoord.y), mat, offset);\n" " float y = fract(incoord.y);\n" - " float start = 0.75;\n" + " float start = 2. / 3.;\n" " if (int(incoord.y) - range.x < 4) {\n" " y = incoord.y - float(range.x);\n" - " start = 0.;\n" + " start -= 1.;\n" " }\n" - " float lin = start + y * 0.25;\n" + " float lin = start + y / 3.;\n" " vec2 mixedTransform = interpolate(mat, lin);\n" " vec2 mixedOffset = interpolate(offset, lin);\n" " int paletteEntry = fetchTile(ivec2(mixedTransform * incoord.x + mixedOffset));\n" @@ -340,12 +340,12 @@ static const char* const _renderMode35 = " }\n" " loadAffine(int(incoord.y), mat, offset);\n" " float y = fract(incoord.y);\n" - " float start = 0.75;\n" + " float start = 2. / 3.;\n" " if (int(incoord.y) - range.x < 4) {\n" " y = incoord.y - float(range.x);\n" - " start = 0.;\n" + " start -= 1.;\n" " }\n" - " float lin = start + y * 0.25;\n" + " float lin = start + y / 3.;\n" " vec2 mixedTransform = interpolate(mat, lin);\n" " vec2 mixedOffset = interpolate(offset, lin);\n" " ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n" @@ -401,12 +401,12 @@ static const char* const _renderMode4 = " }\n" " loadAffine(int(incoord.y), mat, offset);\n" " float y = fract(incoord.y);\n" - " float start = 0.75;\n" + " float start = 2. / 3.;\n" " if (int(incoord.y) - range.x < 4) {\n" " y = incoord.y - float(range.x);\n" - " start = 0.;\n" + " start -= 1.;\n" " }\n" - " float lin = start + y * 0.25;\n" + " float lin = start + y / 3.;\n" " vec2 mixedTransform = interpolate(mat, lin);\n" " vec2 mixedOffset = interpolate(offset, lin);\n" " ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n" From 2c846893453a8fdaaa5bd9a9c6198ef4722530ed Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 17 Apr 2023 22:31:03 -0700 Subject: [PATCH 171/290] Util: Improve mImageLoadPNG memory cleanup --- src/util/image.c | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/util/image.c b/src/util/image.c index a4f597c2b..0a4b6e68d 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -113,6 +113,11 @@ static struct mImage* mImageLoadPNG(struct VFile* vf) { unsigned height = png_get_image_height(png, info); struct mImage* image = calloc(1, sizeof(*image)); + if (!image) { + PNGReadClose(png, info, end); + return NULL; + } + bool ok = true; image->width = width; image->height = height; @@ -124,10 +129,7 @@ static struct mImage* mImageLoadPNG(struct VFile* vf) { image->depth = 4; image->data = malloc(width * height * 4); if (!PNGReadPixels(png, info, image->data, width, height, width)) { - free(image->data); - free(image); - PNGReadClose(png, info, end); - return NULL; + ok = false; } break; case 4: @@ -135,10 +137,7 @@ static struct mImage* mImageLoadPNG(struct VFile* vf) { image->depth = 4; image->data = malloc(width * height * 4); if (!PNGReadPixelsA(png, info, image->data, width, height, width)) { - free(image->data); - free(image); - PNGReadClose(png, info, end); - return NULL; + ok = false; } break; case 1: @@ -151,7 +150,8 @@ static struct mImage* mImageLoadPNG(struct VFile* vf) { int trnsCount = 0; image->format = mCOLOR_PAL8; if (png_get_PLTE(png, info, &palette, &count) == 0) { - return NULL; + ok = false; + break; } if (count > 256) { count = 256; @@ -180,22 +180,20 @@ static struct mImage* mImageLoadPNG(struct VFile* vf) { image->depth = 1; image->data = malloc(width * height); if (!PNGReadPixels8(png, info, image->data, width, height, width)) { - if (image->palette) { - free(image->palette); - } - free(image->data); - free(image); - PNGReadClose(png, info, end); - return NULL; + ok = false; } break; default: // Not supported yet - free(image); - PNGReadClose(png, info, end); - return NULL; + ok = false; + break; } + PNGReadClose(png, info, end); + if (!ok) { + mImageDestroy(image); + image = NULL; + } return image; } #endif From 133ed11cab090a8fc34eb1664878861e9d32603d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 18 Apr 2023 02:18:49 -0700 Subject: [PATCH 172/290] GBA Video: Don't repeat yourself --- src/gba/renderers/gl.c | 95 +++++++++++++----------------------------- 1 file changed, 29 insertions(+), 66 deletions(-) diff --git a/src/gba/renderers/gl.c b/src/gba/renderers/gl.c index 4a751c706..e4d6415d6 100644 --- a/src/gba/renderers/gl.c +++ b/src/gba/renderers/gl.c @@ -233,6 +233,29 @@ static const char* const _interpolate = " aff[2] = transform[start + 2].zw;\n" " mat[3] = transform[start + 3].xy;\n" " aff[3] = transform[start + 3].zw;\n" + "}\n" + + "ivec2 affineInterpolate() {\n" + " ivec2 mat[4];\n" + " ivec2 offset[4];\n" + " vec2 incoord = texCoord;\n" + " if (mosaic.x > 1) {\n" + " incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n" + " }\n" + " if (mosaic.y > 1) {\n" + " incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n" + " }\n" + " loadAffine(int(incoord.y), mat, offset);\n" + " float y = fract(incoord.y);\n" + " float start = 2. / 3.;\n" + " if (int(incoord.y) - range.x < 4) {\n" + " y = incoord.y - float(range.x);\n" + " start -= 1.;\n" + " }\n" + " float lin = start + y / 3.;\n" + " vec2 mixedTransform = interpolate(mat, lin);\n" + " vec2 mixedOffset = interpolate(offset, lin);\n" + " return ivec2(mixedTransform * incoord.x + mixedOffset);\n" "}\n"; static const char* const _renderMode2 = @@ -250,8 +273,7 @@ static const char* const _renderMode2 = "OUT(0) out vec4 color;\n" "int fetchTile(ivec2 coord);\n" - "vec2 interpolate(ivec2 arr[4], float x);\n" - "void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n" + "ivec2 affineInterpolate();\n" "int renderTile(ivec2 coord) {\n" " int map = (coord.x >> 11) + (((coord.y >> 7) & 0x7F0) << size);\n" @@ -278,26 +300,7 @@ static const char* const _renderMode2 = "}\n" "void main() {\n" - " ivec2 mat[4];\n" - " ivec2 offset[4];\n" - " vec2 incoord = texCoord;\n" - " if (mosaic.x > 1) {\n" - " incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n" - " }\n" - " if (mosaic.y > 1) {\n" - " incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n" - " }\n" - " loadAffine(int(incoord.y), mat, offset);\n" - " float y = fract(incoord.y);\n" - " float start = 2. / 3.;\n" - " if (int(incoord.y) - range.x < 4) {\n" - " y = incoord.y - float(range.x);\n" - " start -= 1.;\n" - " }\n" - " float lin = start + y / 3.;\n" - " vec2 mixedTransform = interpolate(mat, lin);\n" - " vec2 mixedOffset = interpolate(offset, lin);\n" - " int paletteEntry = fetchTile(ivec2(mixedTransform * incoord.x + mixedOffset));\n" + " int paletteEntry = fetchTile(affineInterpolate());\n" " color = texelFetch(palette, ivec2(paletteEntry, int(texCoord.y)), 0);\n" "}"; @@ -325,30 +328,10 @@ static const char* const _renderMode35 = "uniform ivec2 mosaic;\n" "OUT(0) out vec4 color;\n" - "vec2 interpolate(ivec2 arr[4], float x);\n" - "void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n" + "ivec2 affineInterpolate();\n" "void main() {\n" - " ivec2 mat[4];\n" - " ivec2 offset[4];\n" - " vec2 incoord = texCoord;\n" - " if (mosaic.x > 1) {\n" - " incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n" - " }\n" - " if (mosaic.y > 1) {\n" - " incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n" - " }\n" - " loadAffine(int(incoord.y), mat, offset);\n" - " float y = fract(incoord.y);\n" - " float start = 2. / 3.;\n" - " if (int(incoord.y) - range.x < 4) {\n" - " y = incoord.y - float(range.x);\n" - " start -= 1.;\n" - " }\n" - " float lin = start + y / 3.;\n" - " vec2 mixedTransform = interpolate(mat, lin);\n" - " vec2 mixedOffset = interpolate(offset, lin);\n" - " ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n" + " ivec2 coord = affineInterpolate();\n" " if (coord.x < 0 || coord.x >= (size.x << 8)) {\n" " discard;\n" " }\n" @@ -386,30 +369,10 @@ static const char* const _renderMode4 = "uniform ivec2 mosaic;\n" "OUT(0) out vec4 color;\n" - "vec2 interpolate(ivec2 arr[4], float x);\n" - "void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n" + "ivec2 affineInterpolate();\n" "void main() {\n" - " ivec2 mat[4];\n" - " ivec2 offset[4];\n" - " vec2 incoord = texCoord;\n" - " if (mosaic.x > 1) {\n" - " incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n" - " }\n" - " if (mosaic.y > 1) {\n" - " incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n" - " }\n" - " loadAffine(int(incoord.y), mat, offset);\n" - " float y = fract(incoord.y);\n" - " float start = 2. / 3.;\n" - " if (int(incoord.y) - range.x < 4) {\n" - " y = incoord.y - float(range.x);\n" - " start -= 1.;\n" - " }\n" - " float lin = start + y / 3.;\n" - " vec2 mixedTransform = interpolate(mat, lin);\n" - " vec2 mixedOffset = interpolate(offset, lin);\n" - " ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n" + " ivec2 coord = affineInterpolate();\n" " if (coord.x < 0 || coord.x >= (size.x << 8)) {\n" " discard;\n" " }\n" From 7337edb82a5a574ac9ad77a27a9ad571889f8d51 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 18 Apr 2023 17:47:33 -0700 Subject: [PATCH 173/290] Qt: Manually toggle swap interval as needed --- CMakeLists.txt | 7 +++++ src/platform/qt/Display.cpp | 1 + src/platform/qt/Display.h | 1 + src/platform/qt/DisplayGL.cpp | 48 +++++++++++++++++++++++++++++++++++ src/platform/qt/DisplayGL.h | 3 +++ src/platform/qt/DisplayQt.h | 1 + src/platform/qt/Window.cpp | 9 ++++++- 7 files changed, 69 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index af45bd9b6..c80e2350e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -422,6 +422,13 @@ if(BUILD_GL) elseif(UNIX AND NOT APPLE AND TARGET OpenGL::GL) set(OPENGL_LIBRARY OpenGL::GL) endif() + if(OpenGL_GLX_FOUND) + list(APPEND FEATURES GLX) + endif() + if(OpenGL_EGL_FOUND) + list(APPEND FEATURES EGL) + list(APPEND OPENGL_LIBRARY ${OPENGL_egl_LIBRARY}) + endif() endif() if(BUILD_GL) list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gl.c) diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index fcbec86bc..f8b803558 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -112,6 +112,7 @@ void QGBA::Display::configure(ConfigController* config) { filter(opts->resampleVideo); config->updateOption("showOSD"); config->updateOption("showFrameCounter"); + config->updateOption("videoSync"); #if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(BUILD_GLES3) if (opts->shader && supportsShaders()) { struct VDir* shader = VDirOpen(opts->shader); diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index 4fc100799..6830e8515 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -76,6 +76,7 @@ public slots: virtual void showOSDMessages(bool enable); virtual void showFrameCounter(bool enable); virtual void filter(bool filter); + virtual void swapInterval(int interval) = 0; virtual void framePosted() = 0; virtual void setShaders(struct VDir*) = 0; virtual void clearShaders() = 0; diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 1913dc27c..f04629d8a 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -38,6 +38,21 @@ using QOpenGLFunctions_Baseline = QOpenGLFunctions_3_2_Core; #endif #endif +#ifdef _WIN32 +#include +#elif defined(Q_OS_MAC) +#include +#endif +#ifdef USE_GLX +#define GLX_GLXEXT_PROTOTYPES +typedef struct _XDisplay Display; +#include +#include +#endif +#ifdef USE_EGL +#include +#endif + #ifdef _WIN32 #define OVERHEAD_NSEC 1000000 #else @@ -416,6 +431,10 @@ void DisplayGL::filter(bool filter) { QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter)); } +void DisplayGL::swapInterval(int interval) { + QMetaObject::invokeMethod(m_painter.get(), "swapInterval", Q_ARG(int, interval)); +} + void DisplayGL::framePosted() { m_painter->enqueue(m_context->drawContext()); QMetaObject::invokeMethod(m_painter.get(), "draw"); @@ -755,6 +774,32 @@ void PainterGL::filter(bool filter) { } } +void PainterGL::swapInterval(int interval) { + if (!m_started) { + return; + } + m_swapInterval = interval; +#ifdef Q_OS_WIN + wglSwapIntervalEXT(interval); +#elif defined(Q_OS_MAC) + CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &interval); +#else +#ifdef USE_GLX + if (QGuiApplication::platformName() == "xcb") { + ::Display* display = glXGetCurrentDisplay(); + GLXDrawable drawable = glXGetCurrentDrawable(); + glXSwapIntervalEXT(display, drawable, interval); + } +#endif +#ifdef USE_EGL + if (QGuiApplication::platformName().contains("egl")) { + EGLDisplay display = eglGetCurrentDisplay(); + eglSwapInterval(display, interval); + } +#endif +#endif +} + #ifndef GL_DEBUG_OUTPUT_SYNCHRONOUS #define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 #endif @@ -811,6 +856,9 @@ void PainterGL::draw() { } return; } + if (m_swapInterval != !!sync->videoFrameWait) { + swapInterval(!!sync->videoFrameWait); + } dequeue(); bool forceRedraw = true; if (!m_delayTimer.isValid()) { diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 759789609..0190eb30a 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -109,6 +109,7 @@ public slots: void showOSDMessages(bool enable) override; void showFrameCounter(bool enable) override; void filter(bool filter) override; + void swapInterval(int interval) override; void framePosted() override; void setShaders(struct VDir*) override; void clearShaders() override; @@ -186,6 +187,7 @@ public slots: void showOSD(bool enable); void showFrameCounter(bool enable); void filter(bool filter); + void swapInterval(int interval); void resizeContext(); void updateFramebufferHandle(); void setBackgroundImage(const QImage&); @@ -248,6 +250,7 @@ private: MessagePainter* m_messagePainter = nullptr; QElapsedTimer m_delayTimer; std::shared_ptr m_videoProxy; + int m_swapInterval = -1; }; } diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index 8ea6cc80a..78deb2042 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -32,6 +32,7 @@ public slots: void lockAspectRatio(bool lock) override; void lockIntegerScaling(bool lock) override; void interframeBlending(bool enable) override; + void swapInterval(int) override {}; void filter(bool filter) override; void framePosted() override; void setShaders(struct VDir*) override {} diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index bb97bf5c3..4da4c33ab 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1759,7 +1759,14 @@ void Window::setupMenu(QMenuBar* menubar) { void Window::setupOptions() { ConfigOption* videoSync = m_config->addOption("videoSync"); - videoSync->connect([this](const QVariant&) { + videoSync->connect([this](const QVariant& variant) { + if (m_display) { + bool ok; + int interval = variant.toInt(&ok); + if (ok) { + m_display->swapInterval(interval); + } + } reloadConfig(); }, this); From 727ba5b2f2dabbcb77d94409f8ffb23c188f80bb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 18 Apr 2023 20:15:57 -0700 Subject: [PATCH 174/290] Qt: Rip out OpenGL proxy thread --- src/platform/qt/DisplayGL.cpp | 154 +++++----------------------------- src/platform/qt/DisplayGL.h | 18 ---- 2 files changed, 20 insertions(+), 152 deletions(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index f04629d8a..02d22cb89 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -63,11 +63,6 @@ typedef struct _XDisplay Display; using namespace QGBA; -enum ThreadStartFrom { - START = 1, - PROXY = 2, -}; - QHash DisplayGL::s_supports; uint qHash(const QSurfaceFormat& format, uint seed) { @@ -221,11 +216,6 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent) m_drawThread.setObjectName("Painter Thread"); m_painter->setThread(&m_drawThread); - m_proxyThread.setObjectName("OpenGL Proxy Thread"); - m_proxyContext = std::make_unique(); - m_proxyContext->setFormat(format); - connect(m_painter.get(), &PainterGL::created, this, &DisplayGL::setupProxyThread); - connect(&m_drawThread, &QThread::started, m_painter.get(), &PainterGL::create); connect(m_painter.get(), &PainterGL::started, this, [this] { m_hasStarted = true; @@ -240,11 +230,6 @@ DisplayGL::~DisplayGL() { QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection); m_drawThread.exit(); m_drawThread.wait(); - - if (m_proxyThread.isRunning()) { - m_proxyThread.exit(); - m_proxyThread.wait(); - } } bool DisplayGL::supportsShaders() const { @@ -265,6 +250,9 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { m_painter->setContext(controller); m_painter->setMessagePainter(messagePainter()); m_context = controller; + if (videoProxy()) { + videoProxy()->moveToThread(&m_drawThread); + } lockAspectRatio(isAspectRatioLocked()); lockIntegerScaling(isIntegerScalingLocked()); @@ -279,15 +267,6 @@ void DisplayGL::startDrawing(std::shared_ptr controller) { messagePainter()->resize(size(), devicePixelRatio()); #endif - startThread(ThreadStartFrom::START); -} - -void DisplayGL::startThread(int from) { - m_threadStartPending |= from; - if (m_threadStartPending < 3) { - return; - } - CoreController::Interrupter interrupter(m_context); QMetaObject::invokeMethod(m_painter.get(), "start"); if (!m_gl) { @@ -365,7 +344,6 @@ void DisplayGL::stopDrawing() { hide(); } setUpdatesEnabled(true); - m_threadStartPending &= ~1; } m_context.reset(); } @@ -490,35 +468,11 @@ bool DisplayGL::shouldDisableUpdates() { void DisplayGL::setVideoProxy(std::shared_ptr proxy) { Display::setVideoProxy(proxy); if (proxy) { - proxy->moveToThread(&m_proxyThread); + proxy->moveToThread(&m_drawThread); } m_painter->setVideoProxy(proxy); } -void DisplayGL::setupProxyThread() { - m_proxyContext->moveToThread(&m_proxyThread); - m_proxySurface.create(); - connect(&m_proxyThread, &QThread::started, m_proxyContext.get(), [this]() { - m_proxyContext->setShareContext(m_painter->shareContext()); - m_proxyContext->create(); - m_proxyContext->makeCurrent(&m_proxySurface); -#if defined(_WIN32) && defined(USE_EPOXY) - epoxy_handle_external_wglMakeCurrent(); -#endif - QMetaObject::invokeMethod(this, "startThread", Q_ARG(int, ThreadStartFrom::PROXY)); - }); - connect(m_painter.get(), &PainterGL::texSwapped, m_proxyContext.get(), [this]() { - if (!m_context->hardwareAccelerated()) { - return; - } - if (videoProxy()) { - videoProxy()->processData(); - } - m_painter->updateFramebufferHandle(); - }, Qt::BlockingQueuedConnection); - m_proxyThread.start(); -} - void DisplayGL::updateContentSize() { QMetaObject::invokeMethod(m_painter.get(), "contentSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QSize, m_cachedContentSize)); } @@ -593,12 +547,6 @@ void PainterGL::create() { gl2Backend = static_cast(malloc(sizeof(mGLES2Context))); mGLES2ContextCreate(gl2Backend); m_backend = &gl2Backend->d; - QOpenGLFunctions* fn = m_gl->functions(); - fn->glGenTextures(m_bridgeTexes.size(), m_bridgeTexes.data()); - for (auto tex : m_bridgeTexes) { - m_freeTex.enqueue(tex); - } - m_bridgeTexIn = m_freeTex.dequeue(); } #endif @@ -666,11 +614,9 @@ void PainterGL::destroy() { } makeCurrent(); #if defined(BUILD_GLES2) || defined(BUILD_GLES3) - QOpenGLFunctions* fn = m_gl->functions(); if (m_shader.passes) { mGLES2ShaderFree(&m_shader); } - fn->glDeleteTextures(m_bridgeTexes.size(), m_bridgeTexes.data()); #endif m_backend->deinit(m_backend); m_gl->doneCurrent(); @@ -821,7 +767,6 @@ void PainterGL::start() { } #endif resizeContext(); - m_context->addFrameAction(std::bind(&PainterGL::swapTex, this)); m_buffer = nullptr; m_active = true; @@ -830,7 +775,7 @@ void PainterGL::start() { } void PainterGL::draw() { - if (!m_started || (m_queue.isEmpty() && m_queueTex.isEmpty())) { + if (!m_started || m_queue.isEmpty()) { return; } @@ -916,6 +861,11 @@ void PainterGL::doStop() { } m_backend->clear(m_backend); m_backend->swap(m_backend); + if (m_videoProxy) { + m_videoProxy->reset(); + m_videoProxy->moveToThread(m_window->thread()); + m_videoProxy.reset(); + } } void PainterGL::pause() { @@ -943,33 +893,22 @@ void PainterGL::performDraw() { } void PainterGL::enqueue(const uint32_t* backing) { - if (!backing) { - return; - } QMutexLocker locker(&m_mutex); uint32_t* buffer = nullptr; - if (m_free.isEmpty()) { - buffer = m_queue.dequeue(); - } else { - buffer = m_free.takeLast(); - } - if (buffer) { - QSize size = m_context->screenDimensions(); - memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL); + if (backing) { + if (m_free.isEmpty()) { + buffer = m_queue.dequeue(); + } else { + buffer = m_free.takeLast(); + } + if (buffer) { + QSize size = m_context->screenDimensions(); + memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL); + } } m_queue.enqueue(buffer); } -void PainterGL::enqueue(GLuint tex) { - QMutexLocker locker(&m_mutex); - if (m_freeTex.isEmpty()) { - m_bridgeTexIn = m_queueTex.dequeue(); - } else { - m_bridgeTexIn = m_freeTex.takeLast(); - } - m_queueTex.enqueue(tex); -} - void PainterGL::dequeue() { QMutexLocker locker(&m_mutex); if (!m_queue.isEmpty()) { @@ -979,19 +918,6 @@ void PainterGL::dequeue() { } m_buffer = buffer; } - - if (!m_queueTex.isEmpty()) { - if (m_bridgeTexOut != std::numeric_limits::max()) { - m_freeTex.enqueue(m_bridgeTexOut); - } - m_bridgeTexOut = m_queueTex.dequeue(); -#if defined(BUILD_GLES2) || defined(BUILD_GLES3) - if (supportsShaders()) { - mGLES2Context* gl2Backend = reinterpret_cast(m_backend); - gl2Backend->tex[VIDEO_LAYER_IMAGE] = m_bridgeTexOut; - } -#endif - } } void PainterGL::dequeueAll(bool keep) { @@ -1012,19 +938,6 @@ void PainterGL::dequeueAll(bool keep) { m_free.append(m_buffer); m_buffer = nullptr; } - - m_queueTex.clear(); - m_freeTex.clear(); - for (auto tex : m_bridgeTexes) { - if (keep && tex == m_bridgeTexIn) { - continue; - } - m_freeTex.enqueue(tex); - } - if (!keep) { - m_bridgeTexIn = m_freeTex.dequeue(); - m_bridgeTexOut = std::numeric_limits::max(); - } } void PainterGL::setVideoProxy(std::shared_ptr proxy) { @@ -1112,23 +1025,6 @@ QOpenGLContext* PainterGL::shareContext() { } } -void PainterGL::updateFramebufferHandle() { - QOpenGLFunctions* fn = m_gl->functions(); - // TODO: Figure out why glFlush doesn't work here on Intel/Windows - if (glContextHasBug(OpenGLBug::CROSS_THREAD_FLUSH)) { - fn->glFinish(); - } else { - fn->glFlush(); - } - - CoreController::Interrupter interrupter(m_context); - if (!m_context->hardwareAccelerated()) { - return; - } - enqueue(m_bridgeTexIn); - m_context->setFramebufferHandle(m_bridgeTexIn); -} - void PainterGL::setBackgroundImage(const QImage& image) { if (!m_started) { makeCurrent(); @@ -1150,14 +1046,4 @@ void PainterGL::setBackgroundImage(const QImage& image) { } } -void PainterGL::swapTex() { - if (!m_started) { - return; - } - - CoreController::Interrupter interrupter(m_context); - emit texSwapped(); - m_context->addFrameAction(std::bind(&PainterGL::swapTex, this)); -} - #endif diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index 0190eb30a..c770ae76c 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -122,8 +122,6 @@ protected: virtual void resizeEvent(QResizeEvent*) override; private slots: - void startThread(int); - void setupProxyThread(); void updateContentSize(); private: @@ -134,14 +132,10 @@ private: bool m_isDrawing = false; bool m_hasStarted = false; - int m_threadStartPending = 0; std::unique_ptr m_painter; QThread m_drawThread; - QThread m_proxyThread; std::shared_ptr m_context; mGLWidget* m_gl; - QOffscreenSurface m_proxySurface; - std::unique_ptr m_proxyContext; QSize m_cachedContentSize; }; @@ -156,7 +150,6 @@ public: void setContext(std::shared_ptr); void setMessagePainter(MessagePainter*); void enqueue(const uint32_t* backing); - void enqueue(GLuint tex); void stop(); @@ -168,9 +161,6 @@ public: void setVideoProxy(std::shared_ptr); void interrupt(); - // Run on main thread - void swapTex(); - public slots: void create(); void destroy(); @@ -189,7 +179,6 @@ public slots: void filter(bool filter); void swapInterval(int interval); void resizeContext(); - void updateFramebufferHandle(); void setBackgroundImage(const QImage&); void setShaders(struct VDir*); @@ -217,13 +206,6 @@ private: QQueue m_queue; uint32_t* m_buffer = nullptr; - std::array m_bridgeTexes; - QQueue m_freeTex; - QQueue m_queueTex; - - GLuint m_bridgeTexIn = std::numeric_limits::max(); - GLuint m_bridgeTexOut = std::numeric_limits::max(); - QPainter m_painter; QMutex m_mutex; QWindow* m_window; From 5f6948351bcfa6b12074b7d6be9d7413f9c23bd6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 19 Apr 2023 05:35:36 -0700 Subject: [PATCH 175/290] Feature: Move video-backend.c to a sensible place --- CMakeLists.txt | 5 ----- {src/platform => include/mgba/feature}/video-backend.h | 0 src/feature/CMakeLists.txt | 1 + src/{platform => feature}/video-backend.c | 2 +- src/platform/opengl/gl.h | 2 +- src/platform/opengl/gles2.h | 2 +- src/platform/qt/DisplayGL.h | 2 +- src/platform/qt/ShaderSelector.cpp | 2 +- 8 files changed, 6 insertions(+), 10 deletions(-) rename {src/platform => include/mgba/feature}/video-backend.h (100%) rename src/{platform => feature}/video-backend.c (94%) diff --git a/CMakeLists.txt b/CMakeLists.txt index c80e2350e..4766eb521 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -747,11 +747,6 @@ elseif(BUILD_GLES2) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libgles2") endif() -if(USE_EPOXY OR BUILD_GL OR BUILD_GLES2) - # This file should probably go somewhere else - list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/video-backend.c) -endif() - if(WIN32 AND NOT (LIBMGBA_ONLY OR SKIP_LIBRARY OR USE_EPOXY)) message(FATAL_ERROR "Windows requires epoxy module!") endif() diff --git a/src/platform/video-backend.h b/include/mgba/feature/video-backend.h similarity index 100% rename from src/platform/video-backend.h rename to include/mgba/feature/video-backend.h diff --git a/src/feature/CMakeLists.txt b/src/feature/CMakeLists.txt index ffb469953..f18028ff0 100644 --- a/src/feature/CMakeLists.txt +++ b/src/feature/CMakeLists.txt @@ -3,6 +3,7 @@ set(SOURCE_FILES commandline.c thread-proxy.c updater.c + video-backend.c video-logger.c) set(GUI_FILES diff --git a/src/platform/video-backend.c b/src/feature/video-backend.c similarity index 94% rename from src/platform/video-backend.c rename to src/feature/video-backend.c index d8e5826ea..ffe916edc 100644 --- a/src/platform/video-backend.c +++ b/src/feature/video-backend.c @@ -3,7 +3,7 @@ * 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 "video-backend.h" +#include void VideoBackendGetFrame(const struct VideoBackend* v, struct mRectangle* frame) { memset(frame, 0, sizeof(*frame)); diff --git a/src/platform/opengl/gl.h b/src/platform/opengl/gl.h index 5e4873b3d..28bb5249d 100644 --- a/src/platform/opengl/gl.h +++ b/src/platform/opengl/gl.h @@ -21,7 +21,7 @@ CXX_GUARD_START #include #endif -#include "platform/video-backend.h" +#include struct mGLContext { struct VideoBackend d; diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h index c81e9d8e2..498cbdc86 100644 --- a/src/platform/opengl/gles2.h +++ b/src/platform/opengl/gles2.h @@ -24,7 +24,7 @@ CXX_GUARD_START #include #endif -#include "platform/video-backend.h" +#include union mGLES2UniformValue { GLfloat f; diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index c770ae76c..d94a68621 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -37,7 +37,7 @@ #include "CoreController.h" #include "VideoProxy.h" -#include "platform/video-backend.h" +#include class QOpenGLPaintDevice; class QOpenGLWidget; diff --git a/src/platform/qt/ShaderSelector.cpp b/src/platform/qt/ShaderSelector.cpp index 80a8fcd9d..885692edf 100644 --- a/src/platform/qt/ShaderSelector.cpp +++ b/src/platform/qt/ShaderSelector.cpp @@ -19,8 +19,8 @@ #include #include +#include #include -#include "platform/video-backend.h" #if defined(BUILD_GL) || defined(BUILD_GLES2) From 420da428bd2779444fd9498ecafcfb6f3892722e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 20 Apr 2023 02:43:06 -0700 Subject: [PATCH 176/290] Qt: Cleaner type punning --- src/platform/qt/CoreController.cpp | 10 ++--- src/platform/qt/CoreController.h | 3 +- src/platform/qt/DebuggerConsoleController.cpp | 20 +++++----- src/platform/qt/DebuggerConsoleController.h | 3 +- src/platform/qt/VideoProxy.cpp | 39 ++++++++++--------- src/platform/qt/VideoProxy.h | 9 ++--- 6 files changed, 41 insertions(+), 43 deletions(-) diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 5bdc997c2..7cc8d7e1f 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -998,9 +998,9 @@ void CoreController::attachPrinter() { } GB* gb = static_cast(m_threadContext.core->board); clearMultiplayerController(); - GBPrinterCreate(&m_printer.d); + GBPrinterCreate(&m_printer); m_printer.parent = this; - m_printer.d.print = [](GBPrinter* printer, int height, const uint8_t* data) { + m_printer.print = [](GBPrinter* printer, int height, const uint8_t* data) { QGBPrinter* qPrinter = reinterpret_cast(printer); QImage image(GB_VIDEO_HORIZONTAL_PIXELS, height, QImage::Format_Indexed8); QVector colors; @@ -1021,7 +1021,7 @@ void CoreController::attachPrinter() { QMetaObject::invokeMethod(qPrinter->parent, "imagePrinted", Q_ARG(const QImage&, image)); }; Interrupter interrupter(this); - GBSIOSetDriver(&gb->sio, &m_printer.d.d); + GBSIOSetDriver(&gb->sio, &m_printer.d); } void CoreController::detachPrinter() { @@ -1030,7 +1030,7 @@ void CoreController::detachPrinter() { } Interrupter interrupter(this); GB* gb = static_cast(m_threadContext.core->board); - GBPrinterDonePrinting(&m_printer.d); + GBPrinterDonePrinting(&m_printer); GBSIOSetDriver(&gb->sio, nullptr); } @@ -1039,7 +1039,7 @@ void CoreController::endPrint() { return; } Interrupter interrupter(this); - GBPrinterDonePrinting(&m_printer.d); + GBPrinterDonePrinting(&m_printer); } #endif diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index 9e64bb838..74d9a7b05 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -314,8 +314,7 @@ private: VFile* m_vlVf = nullptr; #ifdef M_CORE_GB - struct QGBPrinter { - GBPrinter d; + struct QGBPrinter : public GBPrinter { CoreController* parent; } m_printer; #endif diff --git a/src/platform/qt/DebuggerConsoleController.cpp b/src/platform/qt/DebuggerConsoleController.cpp index 3a9446b15..bacc97104 100644 --- a/src/platform/qt/DebuggerConsoleController.cpp +++ b/src/platform/qt/DebuggerConsoleController.cpp @@ -19,18 +19,18 @@ using namespace QGBA; DebuggerConsoleController::DebuggerConsoleController(QObject* parent) : DebuggerController(&m_cliDebugger.d, parent) { - m_backend.d.printf = printf; - m_backend.d.init = init; - m_backend.d.deinit = deinit; - m_backend.d.readline = readLine; - m_backend.d.lineAppend = lineAppend; - m_backend.d.historyLast = historyLast; - m_backend.d.historyAppend = historyAppend; - m_backend.d.interrupt = interrupt; + m_backend.printf = printf; + m_backend.init = init; + m_backend.deinit = deinit; + m_backend.readline = readLine; + m_backend.lineAppend = lineAppend; + m_backend.historyLast = historyLast; + m_backend.historyAppend = historyAppend; + m_backend.interrupt = interrupt; m_backend.self = this; CLIDebuggerCreate(&m_cliDebugger); - CLIDebuggerAttachBackend(&m_cliDebugger, &m_backend.d); + CLIDebuggerAttachBackend(&m_cliDebugger, &m_backend); } void DebuggerConsoleController::enterLine(const QString& line) { @@ -60,7 +60,7 @@ void DebuggerConsoleController::attachInternal() { CoreController::Interrupter interrupter(m_gameController); QMutexLocker lock(&m_mutex); mCore* core = m_gameController->thread()->core; - CLIDebuggerAttachBackend(&m_cliDebugger, &m_backend.d); + CLIDebuggerAttachBackend(&m_cliDebugger, &m_backend); CLIDebuggerAttachSystem(&m_cliDebugger, core->cliDebuggerSystem(core)); } diff --git a/src/platform/qt/DebuggerConsoleController.h b/src/platform/qt/DebuggerConsoleController.h index fdf183c08..c3d98d35c 100644 --- a/src/platform/qt/DebuggerConsoleController.h +++ b/src/platform/qt/DebuggerConsoleController.h @@ -56,8 +56,7 @@ private: QStringList m_lines; QByteArray m_last; - struct Backend { - CLIDebuggerBackend d; + struct Backend : public CLIDebuggerBackend { DebuggerConsoleController* self; } m_backend; }; diff --git a/src/platform/qt/VideoProxy.cpp b/src/platform/qt/VideoProxy.cpp index 7c6366229..cf674e1e8 100644 --- a/src/platform/qt/VideoProxy.cpp +++ b/src/platform/qt/VideoProxy.cpp @@ -12,39 +12,40 @@ using namespace QGBA; VideoProxy::VideoProxy() { - mVideoLoggerRendererCreate(&m_logger.d, false); - m_logger.d.block = true; - m_logger.d.waitOnFlush = true; + mVideoLoggerRendererCreate(&m_logger, false); + m_logger.p = this; + m_logger.block = true; + m_logger.waitOnFlush = true; - m_logger.d.init = &cbind<&VideoProxy::init>; - m_logger.d.reset = &cbind<&VideoProxy::reset>; - m_logger.d.deinit = &cbind<&VideoProxy::deinit>; - m_logger.d.lock = &cbind<&VideoProxy::lock>; - m_logger.d.unlock = &cbind<&VideoProxy::unlock>; - m_logger.d.wait = &cbind<&VideoProxy::wait>; - m_logger.d.wake = &callback::func<&VideoProxy::wake>; + m_logger.init = &cbind<&VideoProxy::init>; + m_logger.reset = &cbind<&VideoProxy::reset>; + m_logger.deinit = &cbind<&VideoProxy::deinit>; + m_logger.lock = &cbind<&VideoProxy::lock>; + m_logger.unlock = &cbind<&VideoProxy::unlock>; + m_logger.wait = &cbind<&VideoProxy::wait>; + m_logger.wake = &callback::func<&VideoProxy::wake>; - m_logger.d.writeData = &callback::func<&VideoProxy::writeData>; - m_logger.d.readData = &callback::func<&VideoProxy::readData>; - m_logger.d.postEvent = &callback::func<&VideoProxy::postEvent>; + m_logger.writeData = &callback::func<&VideoProxy::writeData>; + m_logger.readData = &callback::func<&VideoProxy::readData>; + m_logger.postEvent = &callback::func<&VideoProxy::postEvent>; connect(this, &VideoProxy::dataAvailable, this, &VideoProxy::processData); } void VideoProxy::attach(CoreController* controller) { CoreController::Interrupter interrupter(controller); - controller->thread()->core->videoLogger = &m_logger.d; + controller->thread()->core->videoLogger = &m_logger; } void VideoProxy::detach(CoreController* controller) { CoreController::Interrupter interrupter(controller); - if (controller->thread()->core->videoLogger == &m_logger.d) { + if (controller->thread()->core->videoLogger == &m_logger) { controller->thread()->core->videoLogger = nullptr; } } void VideoProxy::processData() { - mVideoLoggerRendererRun(&m_logger.d, false); + mVideoLoggerRendererRun(&m_logger, false); m_fromThreadCond.wakeAll(); } @@ -67,7 +68,7 @@ bool VideoProxy::writeData(const void* data, size_t length) { while (!RingFIFOWrite(&m_dirtyQueue, data, length)) { if (QThread::currentThread() == thread()) { // We're on the main thread - mVideoLoggerRendererRun(&m_logger.d, false); + mVideoLoggerRendererRun(&m_logger, false); } else { emit dataAvailable(); m_mutex.lock(); @@ -105,7 +106,7 @@ void VideoProxy::postEvent(enum mVideoLoggerEvent event) { void VideoProxy::handleEvent(int event) { m_mutex.lock(); - m_logger.d.handleEvent(&m_logger.d, static_cast(event)); + m_logger.handleEvent(&m_logger, static_cast(event)); m_mutex.unlock(); } @@ -122,7 +123,7 @@ void VideoProxy::wait() { while (RingFIFOSize(&m_dirtyQueue)) { if (QThread::currentThread() == thread()) { // We're on the main thread - mVideoLoggerRendererRun(&m_logger.d, false); + mVideoLoggerRendererRun(&m_logger, false); } else { emit dataAvailable(); m_toThreadCond.wakeAll(); diff --git a/src/platform/qt/VideoProxy.h b/src/platform/qt/VideoProxy.h index 3ba845617..4e16c1f90 100644 --- a/src/platform/qt/VideoProxy.h +++ b/src/platform/qt/VideoProxy.h @@ -24,7 +24,7 @@ public: void attach(CoreController*); void detach(CoreController*); - void setBlocking(bool block) { m_logger.d.waitOnFlush = block; } + void setBlocking(bool block) { m_logger.waitOnFlush = block; } signals: void dataAvailable(); @@ -51,17 +51,16 @@ private: using type = T (VideoProxy::*)(A...); template static T func(mVideoLogger* logger, A... args) { - VideoProxy* proxy = reinterpret_cast(logger)->p; + VideoProxy* proxy = static_cast(logger)->p; return (proxy->*F)(args...); } }; template static void cbind(mVideoLogger* logger) { callback::func(logger); } - struct Logger { - mVideoLogger d; + struct Logger : public mVideoLogger { VideoProxy* p; - } m_logger = {{}, this}; + } m_logger; RingFIFO m_dirtyQueue; QMutex m_mutex; From 8739b22fbc90fdf0b4f6612ef9c0520f0ba44a51 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 20 Apr 2023 20:12:53 -0700 Subject: [PATCH 177/290] Qt: Detect Wayland as EGL --- src/platform/qt/DisplayGL.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 02d22cb89..3b5e222b8 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -738,7 +738,7 @@ void PainterGL::swapInterval(int interval) { } #endif #ifdef USE_EGL - if (QGuiApplication::platformName().contains("egl")) { + if (QGuiApplication::platformName().contains("egl") || QGuiApplication::platformName() == "wayland") { EGLDisplay display = eglGetCurrentDisplay(); eglSwapInterval(display, interval); } From 03dd7d70b4d08f19b10489ab9b7e1c53b42b569a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 21 Apr 2023 01:45:15 -0700 Subject: [PATCH 178/290] Res: Update Patrons for April --- res/patrons.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/res/patrons.txt b/res/patrons.txt index c9346ce64..5126cc891 100644 --- a/res/patrons.txt +++ b/res/patrons.txt @@ -8,9 +8,8 @@ gocha Jaime J. Denizard MichaelK__ Miras Absar +Nic Losby Petru-Sebastian Toader Stevoisiak -Tyler Jenkins William K. Leung -Zach Zhongchao Qian From 85aaa6c3c54960752dbe33cf1a121fb04ff22e06 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 22 Apr 2023 22:27:22 -0700 Subject: [PATCH 179/290] GB, GBA Audio: Clamp audio buffer size to 8192 --- src/gb/audio.c | 3 +++ src/gba/audio.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/gb/audio.c b/src/gb/audio.c index 078f93358..acd57d6d6 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -138,6 +138,9 @@ void GBAudioReset(struct GBAudio* audio) { } void GBAudioResizeBuffer(struct GBAudio* audio, size_t samples) { + if (samples > BLIP_BUFFER_SIZE / 2) { + samples = BLIP_BUFFER_SIZE / 2; + } mCoreSyncLockAudio(audio->p->sync); audio->samples = samples; blip_clear(audio->left); diff --git a/src/gba/audio.c b/src/gba/audio.c index 83c20da7d..9cbf1dc24 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -106,6 +106,9 @@ void GBAAudioDeinit(struct GBAAudio* audio) { } void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) { + if (samples > 0x2000) { + samples = 0x2000; + } mCoreSyncLockAudio(audio->p->sync); audio->samples = samples; blip_clear(audio->psg.left); From ae75abb8face3c504dad6fccba95f21c297906d4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 23 Apr 2023 20:08:48 -0700 Subject: [PATCH 180/290] Qt: Only disable swapInterval when fast-forwarding --- src/platform/qt/DisplayGL.cpp | 8 +++++--- src/platform/qt/Window.cpp | 7 ------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index 3b5e222b8..b9bd72a19 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -771,6 +771,7 @@ void PainterGL::start() { m_buffer = nullptr; m_active = true; m_started = true; + swapInterval(1); emit started(); } @@ -801,15 +802,16 @@ void PainterGL::draw() { } return; } - if (m_swapInterval != !!sync->videoFrameWait) { - swapInterval(!!sync->videoFrameWait); + int wantSwap = sync->audioWait || sync->videoFrameWait; + if (m_swapInterval != wantSwap) { + swapInterval(wantSwap); } dequeue(); bool forceRedraw = true; if (!m_delayTimer.isValid()) { m_delayTimer.start(); } else { - if (sync->audioWait || sync->videoFrameWait) { + if (wantSwap) { while (m_delayTimer.nsecsElapsed() + OVERHEAD_NSEC < 1000000000 / sync->fpsTarget) { QThread::usleep(500); } diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 4da4c33ab..f10bdebb6 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1760,13 +1760,6 @@ void Window::setupMenu(QMenuBar* menubar) { void Window::setupOptions() { ConfigOption* videoSync = m_config->addOption("videoSync"); videoSync->connect([this](const QVariant& variant) { - if (m_display) { - bool ok; - int interval = variant.toInt(&ok); - if (ok) { - m_display->swapInterval(interval); - } - } reloadConfig(); }, this); From bd880335a882261fb145d2c431a711da5e640e08 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 23 Apr 2023 21:15:00 -0700 Subject: [PATCH 181/290] Res: Update no-intro --- res/nointro.dat | 5309 +++++++++++++++++++++++++++++------------------ 1 file changed, 3262 insertions(+), 2047 deletions(-) diff --git a/res/nointro.dat b/res/nointro.dat index ac844f883..cb73775b6 100644 --- a/res/nointro.dat +++ b/res/nointro.dat @@ -1,8 +1,8 @@ clrmamepro ( name "Nintendo - Game Boy Advance" description "Nintendo - Game Boy Advance" - version 20220828-205130 - author "aci68, Arctic Circle System, Aringon, Bent, BigFred, bikerspade, C. V. Reynolds, chillerecke, DeadSkullzJr, Densetsu, DeriLoko3, einstein95, ElBarto, Enker, Flashfire42, fuzzball, Gefflon, Hiccup, hking0036, hydr0x, InternalLoss, Jack, jimmsu, kazumi213, Larsenv, Lesserkuma, Madeline, MeguCocoa, Money_114, NESBrew12, niemand, nnssxx, norkmetnoil577, omonim2007, Powerpuff, PPLToast, relax, RetroGamer, Rifu, sCZther, SonGoku, Tauwasser, togemet2, ufocrossing, Vallaine01, Whovian9369, xuom2, zg" + version 20230422-084331 + author "aci68, Arctic Circle System, Aringon, Bent, BigFred, bikerspade, C. V. Reynolds, chillerecke, DeadSkullzJr, Densetsu, DeriLoko3, einstein95, ElBarto, Enker, Flashfire42, fuzzball, Gefflon, Hiccup, hking0036, hydr0x, InternalLoss, Jack, jimmsu, kazumi213, Larsenv, Lesserkuma, Madeline, MeguCocoa, Money_114, NESBrew12, niemand, nnssxx, norkmetnoil577, NovaAurora, omonim2007, Powerpuff, PPLToast, rarenight, relax, RetroGamer, Rifu, sCZther, SonGoku, Tauwasser, Tescu, togemet2, ufocrossing, Vallaine01, Whovian9369, xprism, xuom2, zg" homepage No-Intro url "https://www.no-intro.org" forcenodump required @@ -45,7 +45,7 @@ game ( game ( name "007 - NightFire (USA, Europe) (En,Fr,De)" description "007 - NightFire (USA, Europe) (En,Fr,De)" - rom ( name "007 - NightFire (USA, Europe) (En,Fr,De).gba" size 8388608 crc 56c83c16 sha1 f4363923181b71448ddd6e28ac72d30b3ecfc019 ) + rom ( name "007 - NightFire (USA, Europe) (En,Fr,De).gba" size 8388608 crc 56c83c16 sha1 f4363923181b71448ddd6e28ac72d30b3ecfc019 flags verified ) ) game ( @@ -651,7 +651,7 @@ game ( game ( name "3 Game Pack! - Candy Land + Chutes and Ladders + Original Memory Game (USA)" description "3 Game Pack! - Candy Land + Chutes and Ladders + Original Memory Game (USA)" - rom ( name "3 Game Pack! - Candy Land + Chutes and Ladders + Original Memory Game (USA).gba" size 4194304 crc 628a8e32 sha1 946ccbf8d21adc7a5587ef85a9759a2ded23875d ) + rom ( name "3 Game Pack! - Candy Land + Chutes and Ladders + Original Memory Game (USA).gba" size 4194304 crc 628a8e32 sha1 946ccbf8d21adc7a5587ef85a9759a2ded23875d flags verified ) ) game ( @@ -765,7 +765,7 @@ game ( game ( name "Ace Combat Advance (USA, Europe)" description "Ace Combat Advance (USA, Europe)" - rom ( name "Ace Combat Advance (USA, Europe).gba" size 4194304 crc 43f5e157 sha1 856a08e8f60f817b96add5bf2f6db186bea832ef ) + rom ( name "Ace Combat Advance (USA, Europe).gba" size 4194304 crc 43f5e157 sha1 856a08e8f60f817b96add5bf2f6db186bea832ef flags verified ) ) game ( @@ -843,7 +843,7 @@ game ( game ( name "Activision Anthology (USA)" description "Activision Anthology (USA)" - rom ( name "Activision Anthology (USA).gba" size 8388608 crc 14a28d68 sha1 5125bbbbf1df7782590d99273735826636a2f9ba ) + rom ( name "Activision Anthology (USA).gba" size 8388608 crc 14a28d68 sha1 5125bbbbf1df7782590d99273735826636a2f9ba flags verified ) ) game ( @@ -903,7 +903,7 @@ game ( game ( name "Advance Wars (USA) (Rev 1)" description "Advance Wars (USA) (Rev 1)" - rom ( name "Advance Wars (USA) (Rev 1).gba" size 4194304 crc 26fd0fc9 sha1 15053499d5b3f49128a941d7f2d84876f5424d0c ) + rom ( name "Advance Wars (USA) (Rev 1).gba" size 4194304 crc 26fd0fc9 sha1 15053499d5b3f49128a941d7f2d84876f5424d0c flags verified ) ) game ( @@ -985,9 +985,15 @@ game ( ) game ( - name "Aero the Acro-Bat (USA) (Beta)" - description "Aero the Acro-Bat (USA) (Beta)" - rom ( name "Aero the Acro-Bat (USA) (Beta).gba" size 1030244 crc beed50f0 sha1 68c0a00275c7135ca1a116ccced1ecede75819b8 ) + name "Adventures of Mr. Bean (Europe) (Demo)" + description "Adventures of Mr. Bean (Europe) (Demo)" + rom ( name "Adventures of Mr. Bean (Europe) (Demo).gba" size 2097152 crc 5e3b686d sha1 158856b0b438217ddf8b900ba9c5dca3d6457c7f ) +) + +game ( + name "Aero the Acro-Bat (USA) (Beta 1)" + description "Aero the Acro-Bat (USA) (Beta 1)" + rom ( name "Aero the Acro-Bat (USA) (Beta 1).gba" size 1030244 crc beed50f0 sha1 68c0a00275c7135ca1a116ccced1ecede75819b8 ) ) game ( @@ -997,9 +1003,9 @@ game ( ) game ( - name "Aero the Acro-Bat - Rascal Rival Revenge (Europe) (Beta)" - description "Aero the Acro-Bat - Rascal Rival Revenge (Europe) (Beta)" - rom ( name "Aero the Acro-Bat - Rascal Rival Revenge (Europe) (Beta).gba" size 4194304 crc 6f3ea564 sha1 3f88084c501fb15820f6ad9a9c87ac21af3b59eb ) + name "Aero the Acro-Bat - Rascal Rival Revenge (USA) (Beta 2) (2002-03-27)" + description "Aero the Acro-Bat - Rascal Rival Revenge (USA) (Beta 2) (2002-03-27)" + rom ( name "Aero the Acro-Bat - Rascal Rival Revenge (USA) (Beta 2) (2002-03-27).gba" size 4194304 crc 6f3ea564 sha1 3f88084c501fb15820f6ad9a9c87ac21af3b59eb ) ) game ( @@ -1020,6 +1026,12 @@ game ( rom ( name "Agassi Tennis Generation (USA).gba" size 4194304 crc 8ba179b8 sha1 1797251886d165e136ce6ba564ad8c8ec865d829 ) ) +game ( + name "AGB Aging Cartridge (World) (v1.0) (Test Program)" + description "AGB Aging Cartridge (World) (v1.0) (Test Program)" + rom ( name "AGB Aging Cartridge (World) (v1.0) (Test Program).gba" size 2097152 crc bf553530 sha1 2445bf11a5f905695b00011d77d18c99e754b633 ) +) + game ( name "AGB-Parallel Interface Cartridge (Japan) (En) (Program)" description "AGB-Parallel Interface Cartridge (Japan) (En) (Program)" @@ -1045,9 +1057,9 @@ game ( ) game ( - name "AGS Aging Cartridge (World) (v7.0) (Test Program)" - description "AGS Aging Cartridge (World) (v7.0) (Test Program)" - rom ( name "AGS Aging Cartridge (World) (v7.0) (Test Program).gba" size 2097152 crc bbb6a960 sha1 c67e0a5e26ea5eba2bc11c99d003027a96e44060 flags verified ) + name "AGS Aging Cartridge (World) (Rev 1, v7.0) (Test Program)" + description "AGS Aging Cartridge (World) (Rev 1, v7.0) (Test Program)" + rom ( name "AGS Aging Cartridge (World) (Rev 1, v7.0) (Test Program).gba" size 2097152 crc bbb6a960 sha1 c67e0a5e26ea5eba2bc11c99d003027a96e44060 flags verified ) ) game ( @@ -1063,9 +1075,9 @@ game ( ) game ( - name "AGS Aging Cartridge (World) (v9.0) (Test Program)" - description "AGS Aging Cartridge (World) (v9.0) (Test Program)" - rom ( name "AGS Aging Cartridge (World) (v9.0) (Test Program).gba" size 2097152 crc 2e69686d sha1 03d3a486482b61128872519fd755d0c072c12d93 ) + name "AGS Aging Cartridge (World) (Rev 3, v9.0) (Test Program)" + description "AGS Aging Cartridge (World) (Rev 3, v9.0) (Test Program)" + rom ( name "AGS Aging Cartridge (World) (Rev 3, v9.0) (Test Program).gba" size 2097152 crc 2e69686d sha1 03d3a486482b61128872519fd755d0c072c12d93 flags verified ) ) game ( @@ -1083,7 +1095,7 @@ game ( game ( name "AirForce Delta Storm (USA) (En,Ja,Fr,De)" description "AirForce Delta Storm (USA) (En,Ja,Fr,De)" - rom ( name "AirForce Delta Storm (USA) (En,Ja,Fr,De).gba" size 4194304 crc ebf757b8 sha1 7bc53480a43ada2aad32d798ab00b6e761726728 ) + rom ( name "AirForce Delta Storm (USA) (En,Ja,Fr,De).gba" size 4194304 crc ebf757b8 sha1 7bc53480a43ada2aad32d798ab00b6e761726728 flags verified ) ) game ( @@ -1146,12 +1158,6 @@ game ( rom ( name "Aleck Bordon Adventure - Tower & Shaft Advance (Japan).gba" size 4194304 crc e068728f sha1 2def9d7ea29a0ed5845b354e433e0a55e979f636 ) ) -game ( - name "Alex Ferguson's Player Manager 2002 ~ Total Soccer Manager (Europe) (En,Fr,De,Es,It,Nl)" - description "Alex Ferguson's Player Manager 2002 ~ Total Soccer Manager (Europe) (En,Fr,De,Es,It,Nl)" - rom ( name "Alex Ferguson's Player Manager 2002 ~ Total Soccer Manager (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc 92f99295 sha1 a870e1321a8ad3317cd695fcc0c713441c25919f ) -) - game ( name "Alex Rider - Stormbreaker (USA)" description "Alex Rider - Stormbreaker (USA)" @@ -1173,7 +1179,7 @@ game ( game ( name "Alienators - Evolution Continues (USA, Europe)" description "Alienators - Evolution Continues (USA, Europe)" - rom ( name "Alienators - Evolution Continues (USA, Europe).gba" size 4194304 crc 0d694ca4 sha1 fb691c5e21fa388d75497e9090db82dec881e422 ) + rom ( name "Alienators - Evolution Continues (USA, Europe).gba" size 4194304 crc 0d694ca4 sha1 fb691c5e21fa388d75497e9090db82dec881e422 flags verified ) ) game ( @@ -1209,7 +1215,7 @@ game ( game ( name "Altered Beast - Guardian of the Realms (USA)" description "Altered Beast - Guardian of the Realms (USA)" - rom ( name "Altered Beast - Guardian of the Realms (USA).gba" size 8388608 crc c4955f69 sha1 194dc1fff5578dcd8a2a6914647f1b67334f2a8a ) + rom ( name "Altered Beast - Guardian of the Realms (USA).gba" size 8388608 crc c4955f69 sha1 194dc1fff5578dcd8a2a6914647f1b67334f2a8a flags verified ) ) game ( @@ -1273,9 +1279,69 @@ game ( ) game ( - name "Anguna - Warriors of Virtue (World) (Aftermarket) (Homebrew)" - description "Anguna - Warriors of Virtue (World) (Aftermarket) (Homebrew)" - rom ( name "Anguna - Warriors of Virtue (World) (Aftermarket) (Homebrew).gba" size 1710888 crc 3346891f sha1 270c426705df767a4ad2dc69d039842442f779b2 ) + name "Anguna - Warriors of Virtue (World) (v0.95) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (v0.95) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (v0.95) (Aftermarket) (Unl).gba" size 1710888 crc 3346891f sha1 270c426705df767a4ad2dc69d039842442f779b2 flags verified ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (2021-02-28) (Patreon) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (2021-02-28) (Patreon) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (2021-02-28) (Patreon) (Aftermarket) (Unl).gba" size 1729120 crc a354d555 sha1 d7cd0ab9d622187d4ce55bf0c7f14a24ee781710 ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (v0.95) (Itch.io) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (v0.95) (Itch.io) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (v0.95) (Itch.io) (Aftermarket) (Unl).gba" size 1775856 crc 41ea5b0b sha1 e351bc6a9046ec002fc2dfdee061047908bd4350 ) +) + +game ( + name "Anguna - Warriors of Virtue (Unknown) (Retro-Bit Generations) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (Unknown) (Retro-Bit Generations) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (Unknown) (Retro-Bit Generations) (Aftermarket) (Unl).gba" size 1775660 crc a234813c sha1 ad904624bf198a164ba580eea326cd30fffebac8 ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (Demo) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (Demo) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (Demo) (Aftermarket) (Unl).gba" size 597104 crc 0a9098b4 sha1 0992fb6f38e49426adc834fb3831748a1caa1bc0 ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (v2.0) (Demo) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (v2.0) (Demo) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (v2.0) (Demo) (Aftermarket) (Unl).gba" size 811840 crc 63c1cc2c sha1 faca8225ee20b8e5edb14e005af77b63c6f51446 ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (v0.91) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (v0.91) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (v0.91) (Aftermarket) (Unl).gba" size 1714000 crc eebb016f sha1 008a9d06f78bccc540ca4ece4d1982de76f95901 ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (v0.92) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (v0.92) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (v0.92) (Aftermarket) (Unl).gba" size 1710624 crc ba271987 sha1 04f1e79e79e894d8f45ed1ea2ff5aa67053f0ab3 ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (v0.93) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (v0.93) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (v0.93) (Aftermarket) (Unl).gba" size 1710624 crc df7108e3 sha1 bd267fd7e762acb8cd595e54bfd51a139864759e ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (v0.93) (Aftermarket) (Unl) (Alt)" + description "Anguna - Warriors of Virtue (World) (v0.93) (Aftermarket) (Unl) (Alt)" + rom ( name "Anguna - Warriors of Virtue (World) (v0.93) (Aftermarket) (Unl) (Alt).gba" size 1714000 crc 54ff2e56 sha1 0386315fc140db2a43a6f927c04de658bd800da9 ) +) + +game ( + name "Anguna - Warriors of Virtue (World) (v0.94) (Aftermarket) (Unl)" + description "Anguna - Warriors of Virtue (World) (v0.94) (Aftermarket) (Unl)" + rom ( name "Anguna - Warriors of Virtue (World) (v0.94) (Aftermarket) (Unl).gba" size 1710952 crc cb4e2768 sha1 a7297cf4ecffc1f93f86edde68e1ff42e8099f3b ) ) game ( @@ -1293,7 +1359,7 @@ game ( game ( name "Animal Snap - Rescue Them 2 by 2 (USA)" description "Animal Snap - Rescue Them 2 by 2 (USA)" - rom ( name "Animal Snap - Rescue Them 2 by 2 (USA).gba" size 4194304 crc 7304dca4 sha1 899b9158f622ab145c6787b2af65abda902e6a95 ) + rom ( name "Animal Snap - Rescue Them 2 by 2 (USA).gba" size 4194304 crc 7304dca4 sha1 899b9158f622ab145c6787b2af65abda902e6a95 flags verified ) ) game ( @@ -1315,9 +1381,15 @@ game ( ) game ( - name "Another World (World) (En,Fr) (Unl)" - description "Another World (World) (En,Fr) (Unl)" - rom ( name "Another World (World) (En,Fr) (Unl).gba" size 2010358 crc 86c4f772 sha1 41d39a0c34f72469dd3fbcc90190605b8ada93e6 ) + name "Another World (Europe) (En,Fr) (v2.1) (Unl)" + description "Another World (Europe) (En,Fr) (v2.1) (Unl)" + rom ( name "Another World (Europe) (En,Fr) (v2.1) (Unl).gba" size 2010358 crc 86c4f772 sha1 41d39a0c34f72469dd3fbcc90190605b8ada93e6 ) +) + +game ( + name "Another World (Europe) (Fr) (v1.2) (Unl)" + description "Another World (Europe) (Fr) (v1.2) (Unl)" + rom ( name "Another World (Europe) (Fr) (v1.2) (Unl).gba" size 1823864 crc 1a1397de sha1 5bcc5c9a633e2226411dd41f1a191b9fcc793d92 ) ) game ( @@ -1350,6 +1422,12 @@ game ( rom ( name "Ao-Zora to Nakama-tachi - Yume no Bouken (Japan).gba" size 4194304 crc ad9af125 sha1 0e6c92477793ce495caa400899effb4f87384f3c ) ) +game ( + name "Apotris (World) (v3.4.5) (Aftermarket) (Unl)" + description "Apotris (World) (v3.4.5) (Aftermarket) (Unl)" + rom ( name "Apotris (World) (v3.4.5) (Aftermarket) (Unl).gba" size 4194304 crc 55ae4312 sha1 fb7142bcc30f71f187cc51b7fcbc5a3958374c6c ) +) + game ( name "Archer Maclean's 3D Pool (USA)" description "Archer Maclean's 3D Pool (USA)" @@ -1365,25 +1443,25 @@ game ( game ( name "Army Men - Operation Green (USA) (En,Fr,De,Es,It)" description "Army Men - Operation Green (USA) (En,Fr,De,Es,It)" - rom ( name "Army Men - Operation Green (USA) (En,Fr,De,Es,It).gba" size 4194304 crc 6d174e28 sha1 b0fa4769cddbc66bc333ad45658524a4aeeec755 ) + rom ( name "Army Men - Operation Green (USA) (En,Fr,De,Es,It).gba" size 4194304 crc 6d174e28 sha1 b0fa4769cddbc66bc333ad45658524a4aeeec755 flags verified ) ) game ( name "Army Men - Turf Wars (USA)" description "Army Men - Turf Wars (USA)" - rom ( name "Army Men - Turf Wars (USA).gba" size 8388608 crc 65ac74cc sha1 43bfc86185a06d9d9c27686ad8cb650288b716ae ) + rom ( name "Army Men - Turf Wars (USA).gba" size 8388608 crc 65ac74cc sha1 43bfc86185a06d9d9c27686ad8cb650288b716ae flags verified ) ) game ( name "Army Men Advance (USA, Europe) (En,Fr,De,Es,It)" description "Army Men Advance (USA, Europe) (En,Fr,De,Es,It)" - rom ( name "Army Men Advance (USA, Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 9a4a509f sha1 43cd22c8e5832790b0cdbdcf0e11859021747342 ) + rom ( name "Army Men Advance (USA, Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 9a4a509f sha1 43cd22c8e5832790b0cdbdcf0e11859021747342 flags verified ) ) game ( name "Around the World in 80 Days (USA)" description "Around the World in 80 Days (USA)" - rom ( name "Around the World in 80 Days (USA).gba" size 4194304 crc c2c22af2 sha1 a3649682ef8a2378767154b37ca854d615305646 ) + rom ( name "Around the World in 80 Days (USA).gba" size 4194304 crc c2c22af2 sha1 a3649682ef8a2378767154b37ca854d615305646 flags verified ) ) game ( @@ -1395,7 +1473,7 @@ game ( game ( name "Arthur and the Invisibles (USA) (En,Fr,Es)" description "Arthur and the Invisibles (USA) (En,Fr,Es)" - rom ( name "Arthur and the Invisibles (USA) (En,Fr,Es).gba" size 16777216 crc 87c25055 sha1 6173ea7e00497e7a908a97e64cb4bc86396e8459 ) + rom ( name "Arthur and the Invisibles (USA) (En,Fr,Es).gba" size 16777216 crc 87c25055 sha1 6173ea7e00497e7a908a97e64cb4bc86396e8459 flags verified ) ) game ( @@ -1449,7 +1527,7 @@ game ( game ( name "Astro Boy - Tetsuwan Atom - Atom Heart no Himitsu (Japan)" description "Astro Boy - Tetsuwan Atom - Atom Heart no Himitsu (Japan)" - rom ( name "Astro Boy - Tetsuwan Atom - Atom Heart no Himitsu (Japan).gba" size 8388608 crc e2d94b0d sha1 ca16dc4044b9ae98a26c826bb3cc19a7e8315315 ) + rom ( name "Astro Boy - Tetsuwan Atom - Atom Heart no Himitsu (Japan).gba" size 8388608 crc e2d94b0d sha1 ca16dc4044b9ae98a26c826bb3cc19a7e8315315 flags verified ) ) game ( @@ -1476,6 +1554,12 @@ game ( rom ( name "Atlantis - The Lost Empire (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc b3948dbc sha1 93624eaa9a80cdb791bd14955b55dd7c58c4abbd ) ) +game ( + name "Atomic Betty (USA, Europe)" + description "Atomic Betty (USA, Europe)" + rom ( name "Atomic Betty (USA, Europe).gba" size 8388608 crc 8919d82c sha1 59e7400802ab634065b9674de3f437ddf8309d6e flags verified ) +) + game ( name "Atomic Betty (USA, Europe) (Beta)" description "Atomic Betty (USA, Europe) (Beta)" @@ -1483,9 +1567,9 @@ game ( ) game ( - name "Atomic Betty (USA, Europe)" - description "Atomic Betty (USA, Europe)" - rom ( name "Atomic Betty (USA, Europe).gba" size 8388608 crc 8919d82c sha1 59e7400802ab634065b9674de3f437ddf8309d6e ) + name "ATV - Quad Power Racing (USA, Europe)" + description "ATV - Quad Power Racing (USA, Europe)" + rom ( name "ATV - Quad Power Racing (USA, Europe).gba" size 4194304 crc 9c1a7dcb sha1 43a2c71b1f3b4085adee648e5a409be4517cd7bb flags verified ) ) game ( @@ -1494,12 +1578,6 @@ game ( rom ( name "ATV - Quad Power Racing (Europe) (En,Fr,De,Es,It) (Rev 1).gba" size 4194304 crc 4b4e8bc7 sha1 2234f23928c871b00bd48bdcdd5273362d4e0cdc ) ) -game ( - name "ATV - Quad Power Racing (USA, Europe)" - description "ATV - Quad Power Racing (USA, Europe)" - rom ( name "ATV - Quad Power Racing (USA, Europe).gba" size 4194304 crc 9c1a7dcb sha1 43a2c71b1f3b4085adee648e5a409be4517cd7bb ) -) - game ( name "ATV - Thunder Ridge Riders (USA)" description "ATV - Thunder Ridge Riders (USA)" @@ -1515,7 +1593,7 @@ game ( game ( name "Avatar - The Last Airbender (USA)" description "Avatar - The Last Airbender (USA)" - rom ( name "Avatar - The Last Airbender (USA).gba" size 8388608 crc 946787c0 sha1 9d864e9b3ccce4e5e1b1c566afa0d06088e88dfd ) + rom ( name "Avatar - The Last Airbender (USA).gba" size 8388608 crc 946787c0 sha1 9d864e9b3ccce4e5e1b1c566afa0d06088e88dfd flags verified ) ) game ( @@ -1575,7 +1653,7 @@ game ( game ( name "Back to Stone (USA) (En,Fr)" description "Back to Stone (USA) (En,Fr)" - rom ( name "Back to Stone (USA) (En,Fr).gba" size 4194304 crc 4c29c9c8 sha1 db1fe7581858053b58412a97df262b46838a7be7 ) + rom ( name "Back to Stone (USA) (En,Fr).gba" size 4194304 crc 4c29c9c8 sha1 db1fe7581858053b58412a97df262b46838a7be7 flags verified ) ) game ( @@ -1611,7 +1689,7 @@ game ( game ( name "Backyard Baseball (USA)" description "Backyard Baseball (USA)" - rom ( name "Backyard Baseball (USA).gba" size 4194304 crc 9f36b4e5 sha1 657a639ab3ffbfc85c7a0da51d640ac2e60ec430 ) + rom ( name "Backyard Baseball (USA).gba" size 4194304 crc 9f36b4e5 sha1 657a639ab3ffbfc85c7a0da51d640ac2e60ec430 flags verified ) ) game ( @@ -1653,7 +1731,7 @@ game ( game ( name "Backyard Sports - Baseball 2007 (USA)" description "Backyard Sports - Baseball 2007 (USA)" - rom ( name "Backyard Sports - Baseball 2007 (USA).gba" size 4194304 crc 0ee82569 sha1 cde84171b11a767338158c61ee2ad608d4eb8889 ) + rom ( name "Backyard Sports - Baseball 2007 (USA).gba" size 4194304 crc 0ee82569 sha1 cde84171b11a767338158c61ee2ad608d4eb8889 flags verified ) ) game ( @@ -1764,18 +1842,18 @@ game ( rom ( name "Banjo-Pilot (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc b98de3a4 sha1 5203f49c3b372b49dcb5c23f0dcbbb8432336081 flags verified ) ) -game ( - name "Banjo-Pilot (Europe) (En,Fr,De,Es,It) (Beta 2) (2004-09-29)" - description "Banjo-Pilot (Europe) (En,Fr,De,Es,It) (Beta 2) (2004-09-29)" - rom ( name "Banjo-Pilot (Europe) (En,Fr,De,Es,It) (Beta 2) (2004-09-29).gba" size 16777216 crc 3168a9a4 sha1 d0d26e986f73300b52f2a91b0bd57f92d05dda31 ) -) - game ( name "Banjo-Pilot (Europe) (Beta 1)" description "Banjo-Pilot (Europe) (Beta 1)" rom ( name "Banjo-Pilot (Europe) (Beta 1).gba" size 15545696 crc 817fd31d sha1 51b7fe0af316f4c25e08b2c8348b579e86697e3f ) ) +game ( + name "Banjo-Pilot (Europe) (En,Fr,De,Es,It) (Beta 2) (2004-09-29)" + description "Banjo-Pilot (Europe) (En,Fr,De,Es,It) (Beta 2) (2004-09-29)" + rom ( name "Banjo-Pilot (Europe) (En,Fr,De,Es,It) (Beta 2) (2004-09-29).gba" size 16777216 crc 3168a9a4 sha1 d0d26e986f73300b52f2a91b0bd57f92d05dda31 ) +) + game ( name "Barbie - The Princess and the Pauper (USA)" description "Barbie - The Princess and the Pauper (USA)" @@ -1801,9 +1879,9 @@ game ( ) game ( - name "Barbie as the Island Princess (USA)" - description "Barbie as the Island Princess (USA)" - rom ( name "Barbie as the Island Princess (USA).gba" size 8388608 crc db6160eb sha1 4ac1502b9b292584beecb8c7baf1abbcdb3a2d75 ) + name "Barbie as The Island Princess (USA)" + description "Barbie as The Island Princess (USA)" + rom ( name "Barbie as The Island Princess (USA).gba" size 8388608 crc db6160eb sha1 4ac1502b9b292584beecb8c7baf1abbcdb3a2d75 ) ) game ( @@ -1818,18 +1896,6 @@ game ( rom ( name "Barbie Diaries, The - High School Mystery (Europe).gba" size 8388608 crc 4e8a650c sha1 1a89dc89d8cffc119643b99228d4cc567d5421b3 ) ) -game ( - name "Barbie Groovy Games (USA)" - description "Barbie Groovy Games (USA)" - rom ( name "Barbie Groovy Games (USA).gba" size 4194304 crc aa017567 sha1 055db9f8f3ba18358162a1411cf24300a6de6046 ) -) - -game ( - name "Barbie Groovy Games (Europe) (En,Fr,De,Es,It)" - description "Barbie Groovy Games (Europe) (En,Fr,De,Es,It)" - rom ( name "Barbie Groovy Games (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 749339b2 sha1 e41751c52d5cd84b204ac4d73eefc5b360f56f1d ) -) - game ( name "Barbie Horse Adventures (Europe) (En,Fr,De,Es,It,Nl)" description "Barbie Horse Adventures (Europe) (En,Fr,De,Es,It,Nl)" @@ -1851,7 +1917,19 @@ game ( game ( name "Barbie in the 12 Dancing Princesses (Europe) (En,Fr,De,Es,It)" description "Barbie in the 12 Dancing Princesses (Europe) (En,Fr,De,Es,It)" - rom ( name "Barbie in the 12 Dancing Princesses (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 7ca7aa12 sha1 3feb77f60ad71d4dbb83c7ea0712759e50ccc088 ) + rom ( name "Barbie in the 12 Dancing Princesses (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 7ca7aa12 sha1 3feb77f60ad71d4dbb83c7ea0712759e50ccc088 flags verified ) +) + +game ( + name "Barbie Software - Groovy Games (USA)" + description "Barbie Software - Groovy Games (USA)" + rom ( name "Barbie Software - Groovy Games (USA).gba" size 4194304 crc aa017567 sha1 055db9f8f3ba18358162a1411cf24300a6de6046 ) +) + +game ( + name "Barbie Software - Groovy Games (Europe) (En,Fr,De,Es,It)" + description "Barbie Software - Groovy Games (Europe) (En,Fr,De,Es,It)" + rom ( name "Barbie Software - Groovy Games (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 749339b2 sha1 e41751c52d5cd84b204ac4d73eefc5b360f56f1d ) ) game ( @@ -1927,69 +2005,69 @@ game ( ) game ( - name "Battle Network Rockman EXE (Japan)" - description "Battle Network Rockman EXE (Japan)" - rom ( name "Battle Network Rockman EXE (Japan).gba" size 8388608 crc d9516e50 sha1 6e42dbd5cdee25851fb55dba060b28e28e4f4e5f ) + name "Battle Network - Rockman EXE (Japan) (Virtual Console)" + description "Battle Network - Rockman EXE (Japan) (Virtual Console)" + rom ( name "Battle Network - Rockman EXE (Japan) (Virtual Console).gba" size 8388608 crc 109f133b sha1 7872a276d0059ff0bcff86fd3f843cace3cc8840 ) ) game ( - name "Battle Network Rockman EXE (Japan) (Virtual Console)" - description "Battle Network Rockman EXE (Japan) (Virtual Console)" - rom ( name "Battle Network Rockman EXE (Japan) (Virtual Console).gba" size 8388608 crc 109f133b sha1 7872a276d0059ff0bcff86fd3f843cace3cc8840 ) + name "Battle Network - Rockman EXE (Japan)" + description "Battle Network - Rockman EXE (Japan)" + rom ( name "Battle Network - Rockman EXE (Japan).gba" size 8388608 crc d9516e50 sha1 6e42dbd5cdee25851fb55dba060b28e28e4f4e5f ) ) game ( - name "Battle Network Rockman EXE 2 (Japan) (Rev 1) (Virtual Console)" - description "Battle Network Rockman EXE 2 (Japan) (Rev 1) (Virtual Console)" - rom ( name "Battle Network Rockman EXE 2 (Japan) (Rev 1) (Virtual Console).gba" size 8388608 crc 44d44721 sha1 1c2416dfb86936752c5f68861f6339feac21458f ) + name "Battle Network - Rockman EXE 2 (Japan)" + description "Battle Network - Rockman EXE 2 (Japan)" + rom ( name "Battle Network - Rockman EXE 2 (Japan).gba" size 8388608 crc 98e4f096 sha1 6ed31ea56328673ba9d87186a7d506c701508e28 ) ) game ( - name "Battle Network Rockman EXE 2 (Japan) (Rev 1)" - description "Battle Network Rockman EXE 2 (Japan) (Rev 1)" - rom ( name "Battle Network Rockman EXE 2 (Japan) (Rev 1).gba" size 8388608 crc 41576087 sha1 9cb4d57bdedee5a760e98a3068c0e39a293b447c ) + name "Battle Network - Rockman EXE 2 (Japan) (Rev 1)" + description "Battle Network - Rockman EXE 2 (Japan) (Rev 1)" + rom ( name "Battle Network - Rockman EXE 2 (Japan) (Rev 1).gba" size 8388608 crc 41576087 sha1 9cb4d57bdedee5a760e98a3068c0e39a293b447c ) ) game ( - name "Battle Network Rockman EXE 2 (Japan)" - description "Battle Network Rockman EXE 2 (Japan)" - rom ( name "Battle Network Rockman EXE 2 (Japan).gba" size 8388608 crc 98e4f096 sha1 6ed31ea56328673ba9d87186a7d506c701508e28 ) + name "Battle Network - Rockman EXE 2 (Japan) (Rev 1) (Virtual Console)" + description "Battle Network - Rockman EXE 2 (Japan) (Rev 1) (Virtual Console)" + rom ( name "Battle Network - Rockman EXE 2 (Japan) (Rev 1) (Virtual Console).gba" size 8388608 crc 44d44721 sha1 1c2416dfb86936752c5f68861f6339feac21458f ) ) game ( - name "Battle Network Rockman EXE 3 (Japan)" - description "Battle Network Rockman EXE 3 (Japan)" - rom ( name "Battle Network Rockman EXE 3 (Japan).gba" size 8388608 crc 1c57724e sha1 2a381543f84dacc0f8310d4516fde0c33b5feca0 ) + name "Battle Network - Rockman EXE 3 (Japan) (Rev 1) (Virtual Console)" + description "Battle Network - Rockman EXE 3 (Japan) (Rev 1) (Virtual Console)" + rom ( name "Battle Network - Rockman EXE 3 (Japan) (Rev 1) (Virtual Console).gba" size 8388608 crc 47ccb9b8 sha1 e2a7c4abba66d17e073281e9ea44b6a5760b53c6 ) ) game ( - name "Battle Network Rockman EXE 3 (Japan) (Rev 1) (Virtual Console)" - description "Battle Network Rockman EXE 3 (Japan) (Rev 1) (Virtual Console)" - rom ( name "Battle Network Rockman EXE 3 (Japan) (Rev 1) (Virtual Console).gba" size 8388608 crc 47ccb9b8 sha1 e2a7c4abba66d17e073281e9ea44b6a5760b53c6 ) + name "Battle Network - Rockman EXE 3 (Japan)" + description "Battle Network - Rockman EXE 3 (Japan)" + rom ( name "Battle Network - Rockman EXE 3 (Japan).gba" size 8388608 crc 1c57724e sha1 2a381543f84dacc0f8310d4516fde0c33b5feca0 ) ) game ( - name "Battle Network Rockman EXE 3 (Japan) (Rev 1)" - description "Battle Network Rockman EXE 3 (Japan) (Rev 1)" - rom ( name "Battle Network Rockman EXE 3 (Japan) (Rev 1).gba" size 8388608 crc e48e6bc9 sha1 87e0ab10541eaaa5e9c01f7fad822a3e1bf52278 ) + name "Battle Network - Rockman EXE 3 (Japan) (Rev 1)" + description "Battle Network - Rockman EXE 3 (Japan) (Rev 1)" + rom ( name "Battle Network - Rockman EXE 3 (Japan) (Rev 1).gba" size 8388608 crc e48e6bc9 sha1 87e0ab10541eaaa5e9c01f7fad822a3e1bf52278 ) ) game ( - name "Battle Network Rockman EXE 3 - Black (Japan) (Promo)" - description "Battle Network Rockman EXE 3 - Black (Japan) (Promo)" - rom ( name "Battle Network Rockman EXE 3 - Black (Japan) (Promo).gba" size 8388608 crc 1f13c41f sha1 ff65af8fea15ecf5a556595efe414d1211a9ab4e ) + name "Battle Network - Rockman EXE 3 - Black (Japan) (Promo)" + description "Battle Network - Rockman EXE 3 - Black (Japan) (Promo)" + rom ( name "Battle Network - Rockman EXE 3 - Black (Japan) (Promo).gba" size 8388608 crc 1f13c41f sha1 ff65af8fea15ecf5a556595efe414d1211a9ab4e ) ) game ( - name "Battle Network Rockman EXE 3 - Black (Japan) (Rev 1)" - description "Battle Network Rockman EXE 3 - Black (Japan) (Rev 1)" - rom ( name "Battle Network Rockman EXE 3 - Black (Japan) (Rev 1).gba" size 8388608 crc fd57493b sha1 e089a2254496a4791666c8122585cb785e3012fc ) + name "Battle Network - Rockman EXE 3 - Black (Japan) (Rev 1) (Virtual Console)" + description "Battle Network - Rockman EXE 3 - Black (Japan) (Rev 1) (Virtual Console)" + rom ( name "Battle Network - Rockman EXE 3 - Black (Japan) (Rev 1) (Virtual Console).gba" size 8388608 crc 93e89735 sha1 42ed01e9c8fdc0ea7c0703c821322bd196c66be4 ) ) game ( - name "Battle Network Rockman EXE 3 - Black (Japan) (Rev 1) (Virtual Console)" - description "Battle Network Rockman EXE 3 - Black (Japan) (Rev 1) (Virtual Console)" - rom ( name "Battle Network Rockman EXE 3 - Black (Japan) (Rev 1) (Virtual Console).gba" size 8388608 crc 93e89735 sha1 42ed01e9c8fdc0ea7c0703c821322bd196c66be4 ) + name "Battle Network - Rockman EXE 3 - Black (Japan) (Rev 1)" + description "Battle Network - Rockman EXE 3 - Black (Japan) (Rev 1)" + rom ( name "Battle Network - Rockman EXE 3 - Black (Japan) (Rev 1).gba" size 8388608 crc fd57493b sha1 e089a2254496a4791666c8122585cb785e3012fc ) ) game ( @@ -2148,6 +2226,12 @@ game ( rom ( name "Bionicle - Matoran Adventures (USA, Europe) (En,Fr,De,Es,It,Nl,Sv,Da).gba" size 4194304 crc daec2264 sha1 a478f5880c484a70a5fdefc42f73aae2eb948168 flags verified ) ) +game ( + name "Bionicle - Maze of Shadows (Europe) (En,De)" + description "Bionicle - Maze of Shadows (Europe) (En,De)" + rom ( name "Bionicle - Maze of Shadows (Europe) (En,De).gba" size 8388608 crc bce2d68e sha1 7ff9811e2bd40b24da02be194213d41a0885aa34 ) +) + game ( name "Bionicle - Maze of Shadows (USA)" description "Bionicle - Maze of Shadows (USA)" @@ -2160,12 +2244,6 @@ game ( rom ( name "Bionicle - Maze of Shadows (Europe) (En,De) (Rev 1).gba" size 8388608 crc 9d66ec5e sha1 430c7dac6f7dd989294a8ac1cfdabd9e74b3e682 ) ) -game ( - name "Bionicle - Maze of Shadows (Europe) (En,De)" - description "Bionicle - Maze of Shadows (Europe) (En,De)" - rom ( name "Bionicle - Maze of Shadows (Europe) (En,De).gba" size 8388608 crc bce2d68e sha1 7ff9811e2bd40b24da02be194213d41a0885aa34 ) -) - game ( name "Bionicle Heroes (USA) (En,Fr,De,Es,It,Da)" description "Bionicle Heroes (USA) (En,Fr,De,Es,It,Da)" @@ -2262,6 +2340,12 @@ game ( rom ( name "Bleach Advance - Kurenai ni Somaru Soul Society (Japan).gba" size 33554432 crc 9de5cd08 sha1 29d24c38d3ec8bbe9d81df2f5ff61c4a2dadcee4 ) ) +game ( + name "Blender Bros. (World) (Aftermarket) (Unl)" + description "Blender Bros. (World) (Aftermarket) (Unl)" + rom ( name "Blender Bros. (World) (Aftermarket) (Unl).gba" size 8388608 crc 440f2f06 sha1 8f9ca62306b7ab56d8da45673d7b7d0eb4c349c5 ) +) + game ( name "Blender Bros. (USA)" description "Blender Bros. (USA)" @@ -2283,7 +2367,7 @@ game ( game ( name "BMX Trick Racer (USA)" description "BMX Trick Racer (USA)" - rom ( name "BMX Trick Racer (USA).gba" size 16777216 crc b6d79476 sha1 3a42d3331e81e92d49e285b36ae1a6ed5db6a2ea ) + rom ( name "BMX Trick Racer (USA).gba" size 16777216 crc b6d79476 sha1 3a42d3331e81e92d49e285b36ae1a6ed5db6a2ea flags verified ) ) game ( @@ -2322,12 +2406,6 @@ game ( rom ( name "Boboboubo Boubobo - Ougi 87.5 Bakuretsu Hanage Shinken (Japan).gba" size 8388608 crc 58105f89 sha1 c93fd22bb10e26cb986161ce1cdc4c2acb38add9 ) ) -game ( - name "Boktai - The Sun Is in Your Hand (USA) (Sample)" - description "Possibly a version given out to press at E3 or an E3 demo" - rom ( name "Boktai - The Sun Is in Your Hand (USA) (Sample).gba" size 16777216 crc cf692572 sha1 f91126cd3a1bf7bf5f770d3a70229171d0d5a6ee ) -) - game ( name "Boktai - The Sun Is in Your Hand (USA)" description "Boktai - The Sun Is in Your Hand (USA)" @@ -2340,6 +2418,12 @@ game ( rom ( name "Boktai - The Sun Is in Your Hand (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc 9686c36b sha1 64f7bf0f0560f6e94da33b549d3206678b29f557 flags verified ) ) +game ( + name "Boktai - The Sun Is in Your Hand (USA) (Sample)" + description "Possibly a version given out to press at E3 or an E3 demo" + rom ( name "Boktai - The Sun Is in Your Hand (USA) (Sample).gba" size 16777216 crc cf692572 sha1 f91126cd3a1bf7bf5f770d3a70229171d0d5a6ee ) +) + game ( name "Boktai 2 - Solar Boy Django (USA)" description "Boktai 2 - Solar Boy Django (USA)" @@ -2445,7 +2529,7 @@ game ( game ( name "Bouken Yuuki Pluster World - Densetsu no Plust Gate (Japan)" description "Bouken Yuuki Pluster World - Densetsu no Plust Gate (Japan)" - rom ( name "Bouken Yuuki Pluster World - Densetsu no Plust Gate (Japan).gba" size 8388608 crc 57438e39 sha1 bb779de3aa9c08b82ffcb3cb2eb70f151a8a3b13 ) + rom ( name "Bouken Yuuki Pluster World - Densetsu no Plust Gate (Japan).gba" size 8388608 crc 57438e39 sha1 bb779de3aa9c08b82ffcb3cb2eb70f151a8a3b13 flags verified ) ) game ( @@ -2595,7 +2679,7 @@ game ( game ( name "Breath of Fire (Europe)" description "Breath of Fire (Europe)" - rom ( name "Breath of Fire (Europe).gba" size 4194304 crc a1c3165d sha1 f47870d25665588d19b75e90d0bd32a759e64918 ) + rom ( name "Breath of Fire (Europe).gba" size 4194304 crc a1c3165d sha1 f47870d25665588d19b75e90d0bd32a759e64918 flags verified ) ) game ( @@ -2790,18 +2874,18 @@ game ( rom ( name "Cabela's Big Game Hunter - 2005 Adventures (USA, Europe).gba" size 4194304 crc bd054567 sha1 aff3c5bc948c2c868be7da8a327aee25beaa027c flags verified ) ) -game ( - name "Caesars Palace Advance - Millennium Gold Edition (USA) (Beta)" - description "Caesars Palace Advance - Millennium Gold Edition (USA) (Beta)" - rom ( name "Caesars Palace Advance - Millennium Gold Edition (USA) (Beta).gba" size 4194304 crc 13dc0731 sha1 1a89cecd9bf89df42ae4ef35d17d437b17e53c80 ) -) - game ( name "Caesars Palace Advance - Millennium Gold Edition (USA, Europe)" description "Caesars Palace Advance - Millennium Gold Edition (USA, Europe)" rom ( name "Caesars Palace Advance - Millennium Gold Edition (USA, Europe).gba" size 8388608 crc 5d54ece5 sha1 cd5dbb8b8361ca5d003ec496a99351020dabc329 ) ) +game ( + name "Caesars Palace Advance - Millennium Gold Edition (USA, Europe) (Beta)" + description "Caesars Palace Advance - Millennium Gold Edition (USA, Europe) (Beta)" + rom ( name "Caesars Palace Advance - Millennium Gold Edition (USA, Europe) (Beta).gba" size 4194304 crc 13dc0731 sha1 1a89cecd9bf89df42ae4ef35d17d437b17e53c80 ) +) + game ( name "Calciobit (Japan)" description "Calciobit (Japan)" @@ -2875,15 +2959,15 @@ game ( ) game ( - name "Care Bears - The Care Quests (Europe) (En,Fr,De,Es,It,Nl,Pt,Da)" - description "Care Bears - The Care Quests (Europe) (En,Fr,De,Es,It,Nl,Pt,Da)" - rom ( name "Care Bears - The Care Quests (Europe) (En,Fr,De,Es,It,Nl,Pt,Da).gba" size 4194304 crc 6111ed1e sha1 606ad547286fdf14cc0fe60e5c34f9db83a059dc ) + name "Care Bears - The Care Quest (Europe) (En,Fr,De,Es,It,Nl,Pt,Da)" + description "Care Bears - The Care Quest (Europe) (En,Fr,De,Es,It,Nl,Pt,Da)" + rom ( name "Care Bears - The Care Quest (Europe) (En,Fr,De,Es,It,Nl,Pt,Da).gba" size 4194304 crc 6111ed1e sha1 606ad547286fdf14cc0fe60e5c34f9db83a059dc ) ) game ( - name "Care Bears - The Care Quests (USA) (En,Fr,Es)" - description "Care Bears - The Care Quests (USA) (En,Fr,Es)" - rom ( name "Care Bears - The Care Quests (USA) (En,Fr,Es).gba" size 4194304 crc f848c327 sha1 750eb05960ace3bd38c6e362a0f6970ac3371964 ) + name "Care Bears - The Care Quest (USA) (En,Fr,Es)" + description "Care Bears - The Care Quest (USA) (En,Fr,Es)" + rom ( name "Care Bears - The Care Quest (USA) (En,Fr,Es).gba" size 4194304 crc f848c327 sha1 750eb05960ace3bd38c6e362a0f6970ac3371964 ) ) game ( @@ -3145,21 +3229,21 @@ game ( ) game ( - name "Celeste Classic (World) (v1.0) (Aftermarket) (Homebrew)" - description "Celeste Classic (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Celeste Classic (World) (v1.0) (Aftermarket) (Homebrew).gba" size 5416932 crc f79b0d53 sha1 756f02396a150698e695ad4afd24445e2af70576 ) + name "Celeste Classic (World) (v1.0) (Aftermarket) (Unl)" + description "Celeste Classic (World) (v1.0) (Aftermarket) (Unl)" + rom ( name "Celeste Classic (World) (v1.0) (Aftermarket) (Unl).gba" size 5416932 crc f79b0d53 sha1 756f02396a150698e695ad4afd24445e2af70576 ) ) game ( - name "Celeste Classic (World) (v1.1) (Aftermarket) (Homebrew)" - description "Celeste Classic (World) (v1.1) (Aftermarket) (Homebrew)" - rom ( name "Celeste Classic (World) (v1.1) (Aftermarket) (Homebrew).gba" size 5418248 crc f5d36ad2 sha1 95a86dff641b11b4c2ed0d0fe8152c33b462d927 ) + name "Celeste Classic (World) (v1.1) (Aftermarket) (Unl)" + description "Celeste Classic (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Celeste Classic (World) (v1.1) (Aftermarket) (Unl).gba" size 5418248 crc f5d36ad2 sha1 95a86dff641b11b4c2ed0d0fe8152c33b462d927 ) ) game ( - name "Celeste Classic (World) (v1.2) (Aftermarket) (Homebrew)" - description "Celeste Classic (World) (v1.2) (Aftermarket) (Homebrew)" - rom ( name "Celeste Classic (World) (v1.2) (Aftermarket) (Homebrew).gba" size 5418424 crc ff0e8ada sha1 08512605e5c02e81a72e24c2c8d4959d8e84e1f4 ) + name "Celeste Classic (World) (v1.2) (Aftermarket) (Unl)" + description "Celeste Classic (World) (v1.2) (Aftermarket) (Unl)" + rom ( name "Celeste Classic (World) (v1.2) (Aftermarket) (Unl).gba" size 5418424 crc ff0e8ada sha1 08512605e5c02e81a72e24c2c8d4959d8e84e1f4 ) ) game ( @@ -3375,7 +3459,7 @@ game ( game ( name "Cinderella - Magical Dreams (USA) (En,Fr,De,Es,It)" description "Cinderella - Magical Dreams (USA) (En,Fr,De,Es,It)" - rom ( name "Cinderella - Magical Dreams (USA) (En,Fr,De,Es,It).gba" size 8388608 crc 22f19169 sha1 1682d023933bf758c78ce2c18b503efd4f0803b9 ) + rom ( name "Cinderella - Magical Dreams (USA) (En,Fr,De,Es,It).gba" size 8388608 crc 22f19169 sha1 1682d023933bf758c78ce2c18b503efd4f0803b9 flags verified ) ) game ( @@ -3423,7 +3507,7 @@ game ( game ( name "Classic NES Series - Dr. Mario (USA, Europe)" description "Classic NES Series - Dr. Mario (USA, Europe)" - rom ( name "Classic NES Series - Dr. Mario (USA, Europe).gba" size 1048576 crc 934e1f1d sha1 fc396f0eae55cf19e573aa322f525427e03d3854 flags verified ) + rom ( name "Classic NES Series - Dr. Mario (USA, Europe).gba" size 1048576 crc 934e1f1d sha1 fc396f0eae55cf19e573aa322f525427e03d3854 ) ) game ( @@ -3645,7 +3729,7 @@ game ( game ( name "Crash Bandicoot - The Huge Adventure (USA)" description "Crash Bandicoot - The Huge Adventure (USA)" - rom ( name "Crash Bandicoot - The Huge Adventure (USA).gba" size 8388608 crc 034d2d4b sha1 5a2651c78bb8a1d707e3c3af1f46fb64e2104198 ) + rom ( name "Crash Bandicoot - The Huge Adventure (USA).gba" size 8388608 crc 034d2d4b sha1 5a2651c78bb8a1d707e3c3af1f46fb64e2104198 flags verified ) ) game ( @@ -3717,7 +3801,7 @@ game ( game ( name "Crash Nitro Kart (Europe) (En,Fr,De,Es,It,Nl)" description "Crash Nitro Kart (Europe) (En,Fr,De,Es,It,Nl)" - rom ( name "Crash Nitro Kart (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 03925772 sha1 91904f5ba5c501b761fc01a48e4efc41ee5b8e41 ) + rom ( name "Crash Nitro Kart (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 03925772 sha1 91904f5ba5c501b761fc01a48e4efc41ee5b8e41 flags verified ) ) game ( @@ -3859,9 +3943,9 @@ game ( ) game ( - name "Cruis'n Velocity (USA) (Beta)" - description "Cruis'n Velocity (USA) (Beta)" - rom ( name "Cruis'n Velocity (USA) (Beta).gba" size 8388608 crc 5436d5da sha1 d1716d4603dd8db7ae8715d5142a60230d17e1d2 ) + name "Cruis'n Velocity (USA, Europe) (Beta)" + description "Cruis'n Velocity (USA, Europe) (Beta)" + rom ( name "Cruis'n Velocity (USA, Europe) (Beta).gba" size 8388608 crc 5436d5da sha1 d1716d4603dd8db7ae8715d5142a60230d17e1d2 ) ) game ( @@ -3900,18 +3984,18 @@ game ( rom ( name "CT Special Forces - Back to Hell (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 863cbf31 sha1 b782dac774932d93c313b122ca2c7c072ca84840 ) ) -game ( - name "CT Special Forces - Bioterror (Europe) (En,Fr,De,Es,It,Nl)" - description "CT Special Forces - Bioterror (Europe) (En,Fr,De,Es,It,Nl)" - rom ( name "CT Special Forces - Bioterror (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 7dc40eda sha1 0ac478e4568f5886fce546b903fd50424a059438 ) -) - game ( name "CT Special Forces 2 - Back in the Trenches (USA) (En,Fr,De,Es,It,Nl)" description "CT Special Forces 2 - Back in the Trenches (USA) (En,Fr,De,Es,It,Nl)" rom ( name "CT Special Forces 2 - Back in the Trenches (USA) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc d2b58a33 sha1 cedca3aac39309dc122909d11457e306c81e16be ) ) +game ( + name "CT Special Forces 3 - Bioterror (Europe) (En,Fr,De,Es,It,Nl)" + description "CT Special Forces 3 - Bioterror (Europe) (En,Fr,De,Es,It,Nl)" + rom ( name "CT Special Forces 3 - Bioterror (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 7dc40eda sha1 0ac478e4568f5886fce546b903fd50424a059438 ) +) + game ( name "Cubix - Robots for Everyone - Clash 'N Bash (USA)" description "Cubix - Robots for Everyone - Clash 'N Bash (USA)" @@ -3948,6 +4032,12 @@ game ( rom ( name "Dai-mahjong. (Japan).gba" size 4194304 crc c6ccca05 sha1 35e4c69a6a30362d873e3383942f86932a216ecf ) ) +game ( + name "Daigassou! Band-Brothers - Request Selection (Japan) (DS Expansion Cartridge)" + description "Daigassou! Band-Brothers - Request Selection (Japan) (DS Expansion Cartridge)" + rom ( name "Daigassou! Band-Brothers - Request Selection (Japan) (DS Expansion Cartridge).gba" size 1048576 crc 374de320 sha1 5cb5af28b3b6a0fd7a0bff4a800f28c9e3727194 ) +) + game ( name "Daisenryaku for Game Boy Advance (Japan)" description "Daisenryaku for Game Boy Advance (Japan)" @@ -4011,7 +4101,7 @@ game ( game ( name "Daredevil (USA, Europe)" description "Daredevil (USA, Europe)" - rom ( name "Daredevil (USA, Europe).gba" size 4194304 crc d438347e sha1 db2bb397b47f1ff247dad5cfa6e35866b1048a7b ) + rom ( name "Daredevil (USA, Europe).gba" size 4194304 crc d438347e sha1 db2bb397b47f1ff247dad5cfa6e35866b1048a7b flags verified ) ) game ( @@ -4065,7 +4155,7 @@ game ( game ( name "Dave Mirra Freestyle BMX 3 (USA, Europe)" description "Dave Mirra Freestyle BMX 3 (USA, Europe)" - rom ( name "Dave Mirra Freestyle BMX 3 (USA, Europe).gba" size 8388608 crc 8d8f26a5 sha1 dcce47b536ace90a918d94d06698c92538d7e4d1 ) + rom ( name "Dave Mirra Freestyle BMX 3 (USA, Europe).gba" size 8388608 crc 8d8f26a5 sha1 dcce47b536ace90a918d94d06698c92538d7e4d1 flags verified ) ) game ( @@ -4149,7 +4239,7 @@ game ( game ( name "DemiKids - Light Version (USA)" description "DemiKids - Light Version (USA)" - rom ( name "DemiKids - Light Version (USA).gba" size 8388608 crc dc4357c4 sha1 8bc80eb5f08bfa83597483390b18092d9a12700d ) + rom ( name "DemiKids - Light Version (USA).gba" size 8388608 crc dc4357c4 sha1 8bc80eb5f08bfa83597483390b18092d9a12700d flags verified ) ) game ( @@ -4269,7 +4359,7 @@ game ( game ( name "Di Gi Charat - DigiCommunication (Japan)" description "Di Gi Charat - DigiCommunication (Japan)" - rom ( name "Di Gi Charat - DigiCommunication (Japan).gba" size 8388608 crc a1288427 sha1 35f9fa0305de3382aa6fb01f14baa9419ffa5349 ) + rom ( name "Di Gi Charat - DigiCommunication (Japan).gba" size 8388608 crc a1288427 sha1 35f9fa0305de3382aa6fb01f14baa9419ffa5349 flags verified ) ) game ( @@ -4548,6 +4638,18 @@ game ( rom ( name "DK - King of Swing (USA) (Demo) (Kiosk).gba" size 8388608 crc 049626d1 sha1 d704ea9311c6ac9773bb70181de86ba0843583fd ) ) +game ( + name "Dog Trainer (Europe) (DS Cheat Cartridge) (Unl)" + description "Dog Trainer (Europe) (DS Cheat Cartridge) (Unl)" + rom ( name "Dog Trainer (Europe) (DS Cheat Cartridge) (Unl).gba" size 1048576 crc 6b3961fb sha1 6994b43605a14f64b7b1ad2f4ba93f216342b9e1 ) +) + +game ( + name "Dog Trainer 2 (Europe) (DS Cheat Cartridge) (Unl)" + description "Dog Trainer 2 (Europe) (DS Cheat Cartridge) (Unl)" + rom ( name "Dog Trainer 2 (Europe) (DS Cheat Cartridge) (Unl).gba" size 1048576 crc b2100fed sha1 812475fcb025a7a216f4544605e9110759d8b02e ) +) + game ( name "Dogz (USA)" description "Dogz (USA)" @@ -4656,6 +4758,12 @@ game ( rom ( name "Domo-kun no Fushigi Television (Japan).gba" size 8388608 crc e743c611 sha1 c5c70840c6222c52a2e814d106ab1f2478cd5cab ) ) +game ( + name "Don-chan Puzzle - Hanabi de Doon! Advance (Japan)" + description "Don-chan Puzzle - Hanabi de Doon! Advance (Japan)" + rom ( name "Don-chan Puzzle - Hanabi de Doon! Advance (Japan).gba" size 8388608 crc 500922e8 sha1 5598e6143dd0dab1793507317624973557f35568 ) +) + game ( name "Donald Duck Advance (Europe) (En,Fr,De,Es,It)" description "Donald Duck Advance (Europe) (En,Fr,De,Es,It)" @@ -4674,30 +4782,24 @@ game ( rom ( name "Donald Duck Advance (USA).gba" size 8388608 crc 936daab2 sha1 020081ad9dda023adf604b790ddfc95fcf2a7835 ) ) -game ( - name "Donchan Puzzle - Hanabi de Dohn! Advance (Japan)" - description "Donchan Puzzle - Hanabi de Dohn! Advance (Japan)" - rom ( name "Donchan Puzzle - Hanabi de Dohn! Advance (Japan).gba" size 8388608 crc 500922e8 sha1 5598e6143dd0dab1793507317624973557f35568 ) -) - game ( name "Donkey Kong 2 (USA) (Unl)" description "Donkey Kong 2 (USA) (Unl)" rom ( name "Donkey Kong 2 (USA) (Unl).gba" size 33554432 crc 006a6afa sha1 b62e1625416f67981cb834c8428603f7800fee5b ) ) -game ( - name "Donkey Kong Country (Europe) (En,Fr,De,Es,It)" - description "Donkey Kong Country (Europe) (En,Fr,De,Es,It)" - rom ( name "Donkey Kong Country (Europe) (En,Fr,De,Es,It).gba" size 8388608 crc 41d277fe sha1 8995f0be99a9cff66474a8975b8499bd69fb4c45 flags verified ) -) - game ( name "Donkey Kong Country (USA)" description "Donkey Kong Country (USA)" rom ( name "Donkey Kong Country (USA).gba" size 8388608 crc 12f7a968 sha1 fcc62356a3b7157ca7dda1398c9bf1af1dd31265 flags verified ) ) +game ( + name "Donkey Kong Country (Europe) (En,Fr,De,Es,It)" + description "Donkey Kong Country (Europe) (En,Fr,De,Es,It)" + rom ( name "Donkey Kong Country (Europe) (En,Fr,De,Es,It).gba" size 8388608 crc 41d277fe sha1 8995f0be99a9cff66474a8975b8499bd69fb4c45 flags verified ) +) + game ( name "Donkey Kong Country 2 (Europe) (En,Fr,De,Es,It)" description "Donkey Kong Country 2 (Europe) (En,Fr,De,Es,It)" @@ -4707,7 +4809,7 @@ game ( game ( name "Donkey Kong Country 2 (USA)" description "Donkey Kong Country 2 (USA)" - rom ( name "Donkey Kong Country 2 (USA).gba" size 8388608 crc 11417fc1 sha1 b0a4d59447c8d7c321bea4dc7253b0f581129ede ) + rom ( name "Donkey Kong Country 2 (USA).gba" size 8388608 crc 11417fc1 sha1 b0a4d59447c8d7c321bea4dc7253b0f581129ede flags verified ) ) game ( @@ -4728,6 +4830,12 @@ game ( rom ( name "Donkey Kong Country 3 (USA).gba" size 16777216 crc fe03e5af sha1 c50982b4c26e25ba3538be97b585d95737d7ade7 flags verified ) ) +game ( + name "Donsol (World) (Aftermarket) (Unl)" + description "Donsol (World) (Aftermarket) (Unl)" + rom ( name "Donsol (World) (Aftermarket) (Unl).gba" size 52520 crc aec144b3 sha1 751675b0d77c341f2265b0edd38858b0dbdb7b50 ) +) + game ( name "Doom (USA, Europe)" description "Doom (USA, Europe)" @@ -4737,7 +4845,7 @@ game ( game ( name "Doom II (USA)" description "Doom II (USA)" - rom ( name "Doom II (USA).gba" size 16777216 crc c885d9e9 sha1 2feeffc96386cf2cc0b2076928b010f18e9e9748 ) + rom ( name "Doom II (USA).gba" size 16777216 crc c885d9e9 sha1 2feeffc96386cf2cc0b2076928b010f18e9e9748 flags verified ) ) game ( @@ -4920,18 +5028,18 @@ game ( rom ( name "Dragon Ball - Advanced Adventure (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc 6c135820 sha1 e49ae836b14f84dc8cd817bf912fcdce82d8a587 ) ) -game ( - name "Dragon Ball - Advanced Adventure (Europe) (En,Fr,De,Es,It) (Beta) (2005-02-25)" - description "Dragon Ball - Advanced Adventure (Europe) (En,Fr,De,Es,It) (Beta) (2005-02-25)" - rom ( name "Dragon Ball - Advanced Adventure (Europe) (En,Fr,De,Es,It) (Beta) (2005-02-25).gba" size 16777216 crc 2b136906 sha1 fe190755f05994f1c796286ad767d7da49c36385 ) -) - game ( name "Dragon Ball - Advanced Adventure (USA)" description "Dragon Ball - Advanced Adventure (USA)" rom ( name "Dragon Ball - Advanced Adventure (USA).gba" size 16777216 crc 7d7306df sha1 1338584d8cfa57402603197e65d2e2ff0184d24f ) ) +game ( + name "Dragon Ball - Advanced Adventure (Europe) (En,Fr,De,Es,It) (Beta) (2005-02-25)" + description "Dragon Ball - Advanced Adventure (Europe) (En,Fr,De,Es,It) (Beta) (2005-02-25)" + rom ( name "Dragon Ball - Advanced Adventure (Europe) (En,Fr,De,Es,It) (Beta) (2005-02-25).gba" size 16777216 crc 2b136906 sha1 fe190755f05994f1c796286ad767d7da49c36385 ) +) + game ( name "Dragon Ball GT - Transformation (USA)" description "Dragon Ball GT - Transformation (USA)" @@ -4995,7 +5103,7 @@ game ( game ( name "Dragon Ball Z - Taiketsu (USA)" description "Dragon Ball Z - Taiketsu (USA)" - rom ( name "Dragon Ball Z - Taiketsu (USA).gba" size 8388608 crc 64ec07e7 sha1 1fac4e187b8cf0aeb218ca307f10955f432d258d ) + rom ( name "Dragon Ball Z - Taiketsu (USA).gba" size 8388608 crc 64ec07e7 sha1 1fac4e187b8cf0aeb218ca307f10955f432d258d flags verified ) ) game ( @@ -6067,9 +6175,9 @@ game ( ) game ( - name "Fantastic Maerchen - Cake-ya-san Monogatari (Japan)" - description "Fantastic Maerchen - Cake-ya-san Monogatari (Japan)" - rom ( name "Fantastic Maerchen - Cake-ya-san Monogatari (Japan).gba" size 8388608 crc ec60d573 sha1 826c247ef4e5b831aa0d75e97cfdfe06c48ad7af ) + name "Fantastic Maerchen - Cake-ya-san Monogatari + Doubutsu Chara Navi Uranai - Kosei Shinri Gaku (Japan)" + description "Fantastic Maerchen - Cake-ya-san Monogatari + Doubutsu Chara Navi Uranai - Kosei Shinri Gaku (Japan)" + rom ( name "Fantastic Maerchen - Cake-ya-san Monogatari + Doubutsu Chara Navi Uranai - Kosei Shinri Gaku (Japan).gba" size 8388608 crc ec60d573 sha1 826c247ef4e5b831aa0d75e97cfdfe06c48ad7af ) ) game ( @@ -6085,9 +6193,9 @@ game ( ) game ( - name "Fear Factor Unleashed (USA)" - description "Fear Factor Unleashed (USA)" - rom ( name "Fear Factor Unleashed (USA).gba" size 8388608 crc 1289639c sha1 bf933c51bdcb52ae54d518e80129c687b751f836 ) + name "Fear Factor - Unleashed (USA)" + description "Fear Factor - Unleashed (USA)" + rom ( name "Fear Factor - Unleashed (USA).gba" size 8388608 crc 1289639c sha1 bf933c51bdcb52ae54d518e80129c687b751f836 ) ) game ( @@ -6441,7 +6549,7 @@ game ( game ( name "Fire Emblem - Rekka no Ken (Japan)" description "Fire Emblem - Rekka no Ken (Japan)" - rom ( name "Fire Emblem - Rekka no Ken (Japan).gba" size 16777216 crc f0c10e72 sha1 037702b1febd5c9535262165bf030551d153de81 ) + rom ( name "Fire Emblem - Rekka no Ken (Japan).gba" size 16777216 crc f0c10e72 sha1 037702b1febd5c9535262165bf030551d153de81 flags verified ) ) game ( @@ -7110,6 +7218,12 @@ game ( rom ( name "Ghost Trap (Japan).gba" size 8388608 crc 81ea54e2 sha1 00efb5ed50127f91e2a2827926cf2d4491e4b1b3 ) ) +game ( + name "Glacia Dungeon (World) (En,Es,Ru,Ro) (v1.5.2) (Aftermarket) (Unl)" + description "Glacia Dungeon (World) (En,Es,Ru,Ro) (v1.5.2) (Aftermarket) (Unl)" + rom ( name "Glacia Dungeon (World) (En,Es,Ru,Ro) (v1.5.2) (Aftermarket) (Unl).gba" size 1210608 crc ab5ae65b sha1 e35037ff9cc24f8a7043d9a9d378e86d2ef80f9b ) +) + game ( name "Global Star - Sudoku Fever (USA)" description "Global Star - Sudoku Fever (USA)" @@ -7167,7 +7281,7 @@ game ( game ( name "Golden Sun (France)" description "Golden Sun (France)" - rom ( name "Golden Sun (France).gba" size 8388608 crc f6521161 sha1 42f3b262c16cfc5bde1fa2b25016fb74046de1b3 ) + rom ( name "Golden Sun (France).gba" size 8388608 crc f6521161 sha1 42f3b262c16cfc5bde1fa2b25016fb74046de1b3 flags verified ) ) game ( @@ -7197,7 +7311,7 @@ game ( game ( name "Golden Sun - L'Age Perdu (France)" description "Golden Sun - L'Age Perdu (France)" - rom ( name "Golden Sun - L'Age Perdu (France).gba" size 16777216 crc 1090bd33 sha1 f14855c0f8c87a95cc52189e8e3b2bd9df186322 ) + rom ( name "Golden Sun - L'Age Perdu (France).gba" size 16777216 crc 1090bd33 sha1 f14855c0f8c87a95cc52189e8e3b2bd9df186322 flags verified ) ) game ( @@ -7326,18 +7440,18 @@ game ( rom ( name "Gremlins - Stripe vs Gizmo (Europe) (En,Fr,De,Es,It,Pt) (Beta).gba" size 4194304 crc 68ae6bbb sha1 6aa43d0624af03214473f329fda6334dc9c63009 ) ) -game ( - name "Gremlins - Stripe vs Gizmo (USA)" - description "Gremlins - Stripe vs Gizmo (USA)" - rom ( name "Gremlins - Stripe vs Gizmo (USA).gba" size 4194304 crc 5e72899a sha1 ee32e704598d2b6a21b3db271f9cf94578b94744 ) -) - game ( name "Gremlins - Stripe vs Gizmo (Europe) (En,Fr,De,Es,It,Pt)" description "Gremlins - Stripe vs Gizmo (Europe) (En,Fr,De,Es,It,Pt)" rom ( name "Gremlins - Stripe vs Gizmo (Europe) (En,Fr,De,Es,It,Pt).gba" size 4194304 crc b6225186 sha1 a2c4bf97785e717ef45e4b73a6c66a2d4ae18192 ) ) +game ( + name "Gremlins - Stripe vs Gizmo (USA)" + description "Gremlins - Stripe vs Gizmo (USA)" + rom ( name "Gremlins - Stripe vs Gizmo (USA).gba" size 4194304 crc 5e72899a sha1 ee32e704598d2b6a21b3db271f9cf94578b94744 ) +) + game ( name "Grim Adventures of Billy & Mandy, The (USA)" description "Grim Adventures of Billy & Mandy, The (USA)" @@ -7417,15 +7531,9 @@ game ( ) game ( - name "Guilty Gear X - Advance Edition (Japan)" - description "Guilty Gear X - Advance Edition (Japan)" - rom ( name "Guilty Gear X - Advance Edition (Japan).gba" size 8388608 crc 160903ee sha1 0801b7e39462527d5b650e57fcce908711258a0c ) -) - -game ( - name "Guilty Gear X - Advance Edition (Europe)" - description "Guilty Gear X - Advance Edition (Europe)" - rom ( name "Guilty Gear X - Advance Edition (Europe).gba" size 8388608 crc ba95861d sha1 8236a650a18dfedc22da7c00b5affd3e752ec5de ) + name "Guilty Gear X - Advance Edition (Japan) (Beta)" + description "Guilty Gear X - Advance Edition (Japan) (Beta)" + rom ( name "Guilty Gear X - Advance Edition (Japan) (Beta).gba" size 8388608 crc 4506ada8 sha1 85915ecf10ce73afe9fffbbc8cd7a449dfe802c4 ) ) game ( @@ -7435,9 +7543,15 @@ game ( ) game ( - name "Guilty Gear X - Advance Edition (Japan) (Beta)" - description "Guilty Gear X - Advance Edition (Japan) (Beta)" - rom ( name "Guilty Gear X - Advance Edition (Japan) (Beta).gba" size 8388608 crc 4506ada8 sha1 85915ecf10ce73afe9fffbbc8cd7a449dfe802c4 ) + name "Guilty Gear X - Advance Edition (Japan)" + description "Guilty Gear X - Advance Edition (Japan)" + rom ( name "Guilty Gear X - Advance Edition (Japan).gba" size 8388608 crc 160903ee sha1 0801b7e39462527d5b650e57fcce908711258a0c ) +) + +game ( + name "Guilty Gear X - Advance Edition (Europe)" + description "Guilty Gear X - Advance Edition (Europe)" + rom ( name "Guilty Gear X - Advance Edition (Europe).gba" size 8388608 crc ba95861d sha1 8236a650a18dfedc22da7c00b5affd3e752ec5de ) ) game ( @@ -7530,12 +7644,6 @@ game ( rom ( name "Gyakuten Saiban 3 (Japan).gba" size 8388608 crc 51b6cf22 sha1 70944b396da3f9ce039cc96bc1661826c37b0aa2 flags verified ) ) -game ( - name "Ha Li Bo Te IV (Taiwan) (Unl)" - description "Ha Li Bo Te IV (Taiwan) (Unl)" - rom ( name "Ha Li Bo Te IV (Taiwan) (Unl).gba" size 33554432 crc 892347d4 sha1 80c0a695e23fa5d867da0e39203a52adaab2601d ) -) - game ( name "Hachiemon (Japan)" description "Hachiemon (Japan)" @@ -7560,6 +7668,12 @@ game ( rom ( name "Hajime no Ippo - The Fighting! (Japan).gba" size 8388608 crc 782dc7eb sha1 4b39dde92aa2e26433a9f0c90ca6235a508ba87c ) ) +game ( + name "Hali Bote IV (Taiwan) (Unl)" + description "Hali Bote IV (Taiwan) (Unl)" + rom ( name "Hali Bote IV (Taiwan) (Unl).gba" size 33554432 crc 892347d4 sha1 80c0a695e23fa5d867da0e39203a52adaab2601d ) +) + game ( name "Hamepane - Tokyo Mew Mew (Japan)" description "Hamepane - Tokyo Mew Mew (Japan)" @@ -7629,7 +7743,7 @@ game ( game ( name "Hamtaro - Ham-Ham Heartbreak (Europe) (En,Fr,De,Es,It)" description "Hamtaro - Ham-Ham Heartbreak (Europe) (En,Fr,De,Es,It)" - rom ( name "Hamtaro - Ham-Ham Heartbreak (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc ecbd80ea sha1 b016328e4880f0413b9335c95758ba9c09e53710 ) + rom ( name "Hamtaro - Ham-Ham Heartbreak (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc ecbd80ea sha1 b016328e4880f0413b9335c95758ba9c09e53710 flags verified ) ) game ( @@ -7944,18 +8058,18 @@ game ( rom ( name "Hikaru no Go (Japan) (Rev 1).gba" size 8388608 crc 53116fd7 sha1 c61227f6ccfdc196ef810c1986322b0b1988e508 ) ) -game ( - name "Hikaru no Go (Japan) (Demo) (Promo)" - description "Hikaru no Go (Japan) (Demo) (Promo)" - rom ( name "Hikaru no Go (Japan) (Demo) (Promo).gba" size 4194304 crc 4955fa1b sha1 636c3247f455263c41fc0dee85fd4532246b60c2 ) -) - game ( name "Hikaru no Go (Japan)" description "Hikaru no Go (Japan)" rom ( name "Hikaru no Go (Japan).gba" size 8388608 crc 5a6c7537 sha1 a96299f55026b959ed806bff0fb4a440d33e65af ) ) +game ( + name "Hikaru no Go - Taikenban (Japan) (Demo)" + description "Hikaru no Go - Taikenban (Japan) (Demo)" + rom ( name "Hikaru no Go - Taikenban (Japan) (Demo).gba" size 4194304 crc 4955fa1b sha1 636c3247f455263c41fc0dee85fd4532246b60c2 ) +) + game ( name "Hikaru no Go 2 (Japan)" description "Hikaru no Go 2 (Japan)" @@ -8035,9 +8149,9 @@ game ( ) game ( - name "Hoshi no Kirby - Kagami no Daimeikyuu (Japan) (Virtual Console)" - description "Hoshi no Kirby - Kagami no Daimeikyuu (Japan) (Virtual Console)" - rom ( name "Hoshi no Kirby - Kagami no Daimeikyuu (Japan) (Virtual Console).gba" size 16777216 crc 3da3603b sha1 700006a8b919d7ee4b7dd972dbf6c429d1adca71 ) + name "Hoshi no Kirby - Kagami no Daimeikyuu (Japan) (Rev 1) (Virtual Console)" + description "Hoshi no Kirby - Kagami no Daimeikyuu (Japan) (Rev 1) (Virtual Console)" + rom ( name "Hoshi no Kirby - Kagami no Daimeikyuu (Japan) (Rev 1) (Virtual Console).gba" size 16777216 crc 3da3603b sha1 700006a8b919d7ee4b7dd972dbf6c429d1adca71 ) ) game ( @@ -8055,7 +8169,7 @@ game ( game ( name "Hot Potato! (USA)" description "Hot Potato! (USA)" - rom ( name "Hot Potato! (USA).gba" size 4194304 crc 5acb7a95 sha1 6d07c27f7d7858f177cf3c1466ea5a8858df4f92 ) + rom ( name "Hot Potato! (USA).gba" size 4194304 crc 5acb7a95 sha1 6d07c27f7d7858f177cf3c1466ea5a8858df4f92 flags verified ) ) game ( @@ -8262,6 +8376,18 @@ game ( rom ( name "Ignition Collection - Volume 1 (Europe).gba" size 16777216 crc 164a75ac sha1 62e598f9c6ea47626954619295107955ad7c371a ) ) +game ( + name "IK+ (USA)" + description "IK+ (USA)" + rom ( name "IK+ (USA).gba" size 4194304 crc 31c4f03b sha1 99075504699400430e9817feb4192a6e153fbc9a ) +) + +game ( + name "IK+ (Europe)" + description "IK+ (Europe)" + rom ( name "IK+ (Europe).gba" size 4194304 crc 1052a0ea sha1 844395914641af431c58bbe82ab2513c5f8a60ce ) +) + game ( name "Incredibili, Gli - Una 'Normale' Famiglia di Supereroi (Italy)" description "Incredibili, Gli - Una 'Normale' Famiglia di Supereroi (Italy)" @@ -8340,18 +8466,6 @@ game ( rom ( name "International Karate Advanced (Europe).gba" size 4194304 crc d33a14af sha1 bb8b12d0a446ed097fb74a9a4496980fb65164ef flags verified ) ) -game ( - name "International Karate Plus (Europe)" - description "International Karate Plus (Europe)" - rom ( name "International Karate Plus (Europe).gba" size 4194304 crc 1052a0ea sha1 844395914641af431c58bbe82ab2513c5f8a60ce ) -) - -game ( - name "International Karate Plus (USA)" - description "International Karate Plus (USA)" - rom ( name "International Karate Plus (USA).gba" size 4194304 crc 31c4f03b sha1 99075504699400430e9817feb4192a6e153fbc9a ) -) - game ( name "International Superstar Soccer (Europe)" description "International Superstar Soccer (Europe)" @@ -8401,9 +8515,9 @@ game ( ) game ( - name "Iridion 3D (World) (Aftermarket) (Unl)" - description "Iridion 3D (World) (Aftermarket) (Unl)" - rom ( name "Iridion 3D (World) (Aftermarket) (Unl).gba" size 4194304 crc 4c7ebe42 sha1 6f1c77ab88351d2d50da412e4788fc7ca8a6714d ) + name "Iridion 3D (USA) (Aftermarket) (Unl)" + description "Iridion 3D (USA) (Aftermarket) (Unl)" + rom ( name "Iridion 3D (USA) (Aftermarket) (Unl).gba" size 4194304 crc 4c7ebe42 sha1 6f1c77ab88351d2d50da412e4788fc7ca8a6714d ) ) game ( @@ -8431,9 +8545,9 @@ game ( ) game ( - name "Iridion II (World) (Aftermarket) (Unl)" - description "Iridion II (World) (Aftermarket) (Unl)" - rom ( name "Iridion II (World) (Aftermarket) (Unl).gba" size 8388608 crc b371f070 sha1 cc788b38a047ff5ec8c445ce11efcd1852ad7c4d ) + name "Iridion II (USA) (Aftermarket) (Unl)" + description "Iridion II (USA) (Aftermarket) (Unl)" + rom ( name "Iridion II (USA) (Aftermarket) (Unl).gba" size 8388608 crc b371f070 sha1 cc788b38a047ff5ec8c445ce11efcd1852ad7c4d ) ) game ( @@ -8544,12 +8658,6 @@ game ( rom ( name "JGTO Kounin Golf Master Mobile - Japan Golf Tour Game (Japan).gba" size 8388608 crc b863dae1 sha1 975cff59783cd8f5ec30caaa9e6bef130a6e3529 ) ) -game ( - name "Ji Xie Ren Da Zhan - Zhong Jie Ban (Taiwan) (Unl)" - description "Ji Xie Ren Da Zhan - Zhong Jie Ban (Taiwan) (Unl)" - rom ( name "Ji Xie Ren Da Zhan - Zhong Jie Ban (Taiwan) (Unl).gba" size 8388608 crc 81462aba sha1 75e94c4b91638dcb82a6a6778394ec8dc7940091 ) -) - game ( name "Jikkyou World Soccer Pocket (Japan)" description "Jikkyou World Soccer Pocket (Japan)" @@ -8563,15 +8671,15 @@ game ( ) game ( - name "Jimmy Neutron Boy Genius (USA)" - description "Jimmy Neutron Boy Genius (USA)" - rom ( name "Jimmy Neutron Boy Genius (USA).gba" size 4194304 crc d3ee0c51 sha1 31ed7659e2e072d1c00baf95c0ee9c770e949900 ) + name "Jimmy Neutron - Boy Genius (USA)" + description "Jimmy Neutron - Boy Genius (USA)" + rom ( name "Jimmy Neutron - Boy Genius (USA).gba" size 4194304 crc d3ee0c51 sha1 31ed7659e2e072d1c00baf95c0ee9c770e949900 ) ) game ( - name "Jimmy Neutron Boy Genius (Europe) (En,Fr,De,Es)" - description "Jimmy Neutron Boy Genius (Europe) (En,Fr,De,Es)" - rom ( name "Jimmy Neutron Boy Genius (Europe) (En,Fr,De,Es).gba" size 4194304 crc a9423234 sha1 0923b6186739ba697627822ebce04edaa721a70f ) + name "Jimmy Neutron - Boy Genius (Europe) (En,Fr,De,Es)" + description "Jimmy Neutron - Boy Genius (Europe) (En,Fr,De,Es)" + rom ( name "Jimmy Neutron - Boy Genius (Europe) (En,Fr,De,Es).gba" size 4194304 crc a9423234 sha1 0923b6186739ba697627822ebce04edaa721a70f ) ) game ( @@ -8604,6 +8712,12 @@ game ( rom ( name "Jisu F-Zero Weilai Saiche (China).gba" size 4194304 crc 0e5c38b7 sha1 2bf6622398655a16a5d6b3d94241b72e63a71de9 ) ) +game ( + name "Jixie Ren Dazhan - Zhongjie Ban (Taiwan) (Unl)" + description "Jixie Ren Dazhan - Zhongjie Ban (Taiwan) (Unl)" + rom ( name "Jixie Ren Dazhan - Zhongjie Ban (Taiwan) (Unl).gba" size 8388608 crc 81462aba sha1 75e94c4b91638dcb82a6a6778394ec8dc7940091 ) +) + game ( name "Jonny Moseley Mad Trix (USA, Europe) (En,Fr,De,Es,It)" description "Jonny Moseley Mad Trix (USA, Europe) (En,Fr,De,Es,It)" @@ -8631,7 +8745,7 @@ game ( game ( name "Jungle Book 2, The (Europe) (En,Fr,De,Es,It,Nl)" description "Jungle Book 2, The (Europe) (En,Fr,De,Es,It,Nl)" - rom ( name "Jungle Book 2, The (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 9898258b sha1 4dd4fbf9ecd755c37dfb5f6eb0094873c72aabc2 ) + rom ( name "Jungle Book 2, The (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 9898258b sha1 4dd4fbf9ecd755c37dfb5f6eb0094873c72aabc2 flags verified ) ) game ( @@ -8779,15 +8893,15 @@ game ( ) game ( - name "Kao the Kangaroo (USA) (En,Fr,De,Es,It,Nl)" - description "Kao the Kangaroo (USA) (En,Fr,De,Es,It,Nl)" - rom ( name "Kao the Kangaroo (USA) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc f6667b3e sha1 772d323145cb7707cb8792bdfd5c7ef5e1e27c46 ) + name "KAO the Kangaroo (USA) (En,Fr,De,Es,It,Nl)" + description "KAO the Kangaroo (USA) (En,Fr,De,Es,It,Nl)" + rom ( name "KAO the Kangaroo (USA) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc f6667b3e sha1 772d323145cb7707cb8792bdfd5c7ef5e1e27c46 ) ) game ( - name "Kao the Kangaroo (Europe) (En,Fr,De,Es,It,Nl)" - description "Kao the Kangaroo (Europe) (En,Fr,De,Es,It,Nl)" - rom ( name "Kao the Kangaroo (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc 51e7522c sha1 5a337fcc321eaa0c350644c026767824add338f3 ) + name "KAO the Kangaroo (Europe) (En,Fr,De,Es,It,Nl)" + description "KAO the Kangaroo (Europe) (En,Fr,De,Es,It,Nl)" + rom ( name "KAO the Kangaroo (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc 51e7522c sha1 5a337fcc321eaa0c350644c026767824add338f3 ) ) game ( @@ -8799,7 +8913,7 @@ game ( game ( name "Karnaaj Rally (USA, Europe)" description "Karnaaj Rally (USA, Europe)" - rom ( name "Karnaaj Rally (USA, Europe).gba" size 8388608 crc 63fe3dfe sha1 31bb73186bb98f3d2b80089490f2eeca2861fe5e ) + rom ( name "Karnaaj Rally (USA, Europe).gba" size 8388608 crc 63fe3dfe sha1 31bb73186bb98f3d2b80089490f2eeca2861fe5e flags verified ) ) game ( @@ -8877,7 +8991,7 @@ game ( game ( name "Kelly Slater's Pro Surfer (USA, Europe)" description "Kelly Slater's Pro Surfer (USA, Europe)" - rom ( name "Kelly Slater's Pro Surfer (USA, Europe).gba" size 8388608 crc 31f85dbe sha1 73328bfd274d1ec77c9a67351c026e22f09eeff5 ) + rom ( name "Kelly Slater's Pro Surfer (USA, Europe).gba" size 8388608 crc 31f85dbe sha1 73328bfd274d1ec77c9a67351c026e22f09eeff5 flags verified ) ) game ( @@ -9201,7 +9315,7 @@ game ( game ( name "Klonoa - Empire of Dreams (USA)" description "Klonoa - Empire of Dreams (USA)" - rom ( name "Klonoa - Empire of Dreams (USA).gba" size 4194304 crc f74e1036 sha1 a0a298d9dba1ba15d04a42fc2eb35893d1a9569b ) + rom ( name "Klonoa - Empire of Dreams (USA).gba" size 4194304 crc f74e1036 sha1 a0a298d9dba1ba15d04a42fc2eb35893d1a9569b flags verified ) ) game ( @@ -9396,6 +9510,12 @@ game ( rom ( name "Konjiki no Gashbell!! - Unare! Yuujou no Zakeru 2 (Japan).gba" size 16777216 crc b7827f20 sha1 854a76c49f6e83d8ee9dd42098ce1fc7236af855 flags verified ) ) +game ( + name "Konjiki no Gashbell!! - Unare! Yuujou no Zakeru 2 (Japan) (Rev 1)" + description "Konjiki no Gashbell!! - Unare! Yuujou no Zakeru 2 (Japan) (Rev 1)" + rom ( name "Konjiki no Gashbell!! - Unare! Yuujou no Zakeru 2 (Japan) (Rev 1).gba" size 16777216 crc f40e19a9 sha1 a8a68c714497c8ba68ba47636d8156182a5c3b7c ) +) + game ( name "Konjiki no Gashbell!! The Card Battle for GBA (Japan)" description "Konjiki no Gashbell!! The Card Battle for GBA (Japan)" @@ -9409,9 +9529,9 @@ game ( ) game ( - name "Koro Koro Puzzle - Happy Panechu! (Japan)" - description "Koro Koro Puzzle - Happy Panechu! (Japan)" - rom ( name "Koro Koro Puzzle - Happy Panechu! (Japan).gba" size 4194304 crc 0bfe46e9 sha1 40cb751d119a49be0cd44cf0491c93ebc8795ef0 ) + name "Korokoro Puzzle - Happy Panecchu! (Japan)" + description "Korokoro Puzzle - Happy Panecchu! (Japan)" + rom ( name "Korokoro Puzzle - Happy Panecchu! (Japan).gba" size 4194304 crc 0bfe46e9 sha1 40cb751d119a49be0cd44cf0491c93ebc8795ef0 ) ) game ( @@ -9451,9 +9571,9 @@ game ( ) game ( - name "Koutetsu Teikoku from HOT-B (Japan)" - description "Koutetsu Teikoku from HOT-B (Japan)" - rom ( name "Koutetsu Teikoku from HOT-B (Japan).gba" size 4194304 crc cdfac4ee sha1 7253d2d036429b478e4132de4901417dd840ae1a ) + name "Koutetsu Teikoku (Japan)" + description "Koutetsu Teikoku (Japan)" + rom ( name "Koutetsu Teikoku (Japan).gba" size 4194304 crc cdfac4ee sha1 7253d2d036429b478e4132de4901417dd840ae1a ) ) game ( @@ -9577,9 +9697,9 @@ game ( ) game ( - name "Legend of Dynamic Goushouden - Houkai no Rondo (Japan)" - description "Legend of Dynamic Goushouden - Houkai no Rondo (Japan)" - rom ( name "Legend of Dynamic Goushouden - Houkai no Rondo (Japan).gba" size 8388608 crc 67f18f8e sha1 8a037eff3a44b1667ec5e81ca8c1ff03537366b2 ) + name "Legend of Dynamic - Goushouden - Houkai no Rondo (Japan)" + description "Legend of Dynamic - Goushouden - Houkai no Rondo (Japan)" + rom ( name "Legend of Dynamic - Goushouden - Houkai no Rondo (Japan).gba" size 8388608 crc 67f18f8e sha1 8a037eff3a44b1667ec5e81ca8c1ff03537366b2 ) ) game ( @@ -9702,12 +9822,6 @@ game ( rom ( name "LEGO Racers 2 (Europe) (En,Fr,De,Es,It,Nl,Sv,Da).gba" size 8388608 crc 7a1dc458 sha1 a4da0237646a4f56296465b92c659f3e7438ad98 ) ) -game ( - name "LEGO Soccer Mania (USA, Europe) (En,Fr,De,Es,It,Nl,Sv,Da)" - description "LEGO Soccer Mania (USA, Europe) (En,Fr,De,Es,It,Nl,Sv,Da)" - rom ( name "LEGO Soccer Mania (USA, Europe) (En,Fr,De,Es,It,Nl,Sv,Da).gba" size 8388608 crc 73ef3a35 sha1 05799b99395aba04dfe5ae8af019e4d70cb8e61b ) -) - game ( name "LEGO Star Wars - The Video Game (USA, Europe) (En,Fr,De,Es,It,Nl,Da)" description "LEGO Star Wars - The Video Game (USA, Europe) (En,Fr,De,Es,It,Nl,Da)" @@ -9801,13 +9915,13 @@ game ( game ( name "Lilo & Stitch (Europe) (En,Fr,De,Es,It,Nl) (Rev 1)" description "Lilo & Stitch (Europe) (En,Fr,De,Es,It,Nl) (Rev 1)" - rom ( name "Lilo & Stitch (Europe) (En,Fr,De,Es,It,Nl) (Rev 1).gba" size 8388608 crc e7bc4ef1 sha1 a4e86401593c1763fdd70c6e1f7b4ccec7101db7 flags verified ) + rom ( name "Lilo & Stitch (Europe) (En,Fr,De,Es,It,Nl) (Rev 1).gba" size 8388608 crc e7bc4ef1 sha1 a4e86401593c1763fdd70c6e1f7b4ccec7101db7 ) ) game ( name "Lilo & Stitch (USA)" description "Lilo & Stitch (USA)" - rom ( name "Lilo & Stitch (USA).gba" size 8388608 crc 542aa4ac sha1 b86e22f7009ae20c0d8efc51ab7c61c0cee21a0c ) + rom ( name "Lilo & Stitch (USA).gba" size 8388608 crc 542aa4ac sha1 b86e22f7009ae20c0d8efc51ab7c61c0cee21a0c flags verified ) ) game ( @@ -10047,7 +10161,7 @@ game ( game ( name "Madagascar (Europe)" description "Madagascar (Europe)" - rom ( name "Madagascar (Europe).gba" size 8388608 crc 394f2126 sha1 30e85193a92ae6fa4dd1c54ea76d629276789657 ) + rom ( name "Madagascar (Europe).gba" size 8388608 crc 394f2126 sha1 30e85193a92ae6fa4dd1c54ea76d629276789657 flags verified ) ) game ( @@ -10191,7 +10305,7 @@ game ( game ( name "Magical Quest 2 Starring Mickey & Minnie (Europe) (En,Fr,De)" description "Magical Quest 2 Starring Mickey & Minnie (Europe) (En,Fr,De)" - rom ( name "Magical Quest 2 Starring Mickey & Minnie (Europe) (En,Fr,De).gba" size 4194304 crc f9498038 sha1 1cac78eb9f6a9079f1a02d48dcd1ce4c7a81350b ) + rom ( name "Magical Quest 2 Starring Mickey & Minnie (Europe) (En,Fr,De).gba" size 4194304 crc f9498038 sha1 1cac78eb9f6a9079f1a02d48dcd1ce4c7a81350b flags verified ) ) game ( @@ -10309,15 +10423,15 @@ game ( ) game ( - name "Manga-ka Debut Monogatari (Japan) (Rev 1)" - description "Manga-ka Debut Monogatari (Japan) (Rev 1)" - rom ( name "Manga-ka Debut Monogatari (Japan) (Rev 1).gba" size 4194304 crc f0c22c62 sha1 78c57d1062830074166da3192b01d0d0ae9afaeb ) + name "Manga-ka Debut Monogatari - Akogare! Manga Ka Ikusei Game! (Japan) (Rev 1)" + description "Manga-ka Debut Monogatari - Akogare! Manga Ka Ikusei Game! (Japan) (Rev 1)" + rom ( name "Manga-ka Debut Monogatari - Akogare! Manga Ka Ikusei Game! (Japan) (Rev 1).gba" size 4194304 crc f0c22c62 sha1 78c57d1062830074166da3192b01d0d0ae9afaeb ) ) game ( - name "Manga-ka Debut Monogatari (Japan)" - description "Manga-ka Debut Monogatari (Japan)" - rom ( name "Manga-ka Debut Monogatari (Japan).gba" size 4194304 crc 61a602d7 sha1 5f7c1b88b8c7caa57a0af81cd3ae74171abe50d1 ) + name "Manga-ka Debut Monogatari - Akogare! Manga Ka Ikusei Game! (Japan)" + description "Manga-ka Debut Monogatari - Akogare! Manga Ka Ikusei Game! (Japan)" + rom ( name "Manga-ka Debut Monogatari - Akogare! Manga Ka Ikusei Game! (Japan).gba" size 4194304 crc 61a602d7 sha1 5f7c1b88b8c7caa57a0af81cd3ae74171abe50d1 ) ) game ( @@ -10512,6 +10626,18 @@ game ( rom ( name "Mario Kart Advance (Japan).gba" size 4194304 crc 30e99fcd sha1 88ffde363b05264a99a4d5ada0c80a00196a94d7 flags verified ) ) +game ( + name "Mario Kart XXL (Europe) (Demo)" + description "Mario Kart XXL (Europe) (Demo)" + rom ( name "Mario Kart XXL (Europe) (Demo).gba" size 4194304 crc e3075601 sha1 ba1e75f780c41e737922f090dcb17526f1fd7a01 ) +) + +game ( + name "Mario Party Advance (Europe) (En,Fr,De,Es,It) (Virtual Console)" + description "Mario Party Advance (Europe) (En,Fr,De,Es,It) (Virtual Console)" + rom ( name "Mario Party Advance (Europe) (En,Fr,De,Es,It) (Virtual Console).gba" size 16777216 crc 46c66f40 sha1 34d13fd8cca31a77eeebf9c864a4388969f975c4 ) +) + game ( name "Mario Party Advance (USA) (Virtual Console)" description "Mario Party Advance (USA) (Virtual Console)" @@ -10543,9 +10669,9 @@ game ( ) game ( - name "Mario Party Advance (Europe) (En,Fr,De,Es,It) (Virtual Console)" - description "Mario Party Advance (Europe) (En,Fr,De,Es,It) (Virtual Console)" - rom ( name "Mario Party Advance (Europe) (En,Fr,De,Es,It) (Virtual Console).gba" size 16777216 crc 46c66f40 sha1 34d13fd8cca31a77eeebf9c864a4388969f975c4 ) + name "Mario Pinball Land (USA, Australia)" + description "Mario Pinball Land (USA, Australia)" + rom ( name "Mario Pinball Land (USA, Australia).gba" size 8388608 crc 70a6d2c1 sha1 3fdcd3bb30d61b4dd6829dbdc1a0ac116618b87d flags verified ) ) game ( @@ -10561,9 +10687,9 @@ game ( ) game ( - name "Mario Pinball Land (USA, Australia)" - description "Mario Pinball Land (USA, Australia)" - rom ( name "Mario Pinball Land (USA, Australia).gba" size 8388608 crc 70a6d2c1 sha1 3fdcd3bb30d61b4dd6829dbdc1a0ac116618b87d flags verified ) + name "Mario Power Tennis (Europe) (En,Fr,De,Es,It) (Virtual Console)" + description "Mario Power Tennis (Europe) (En,Fr,De,Es,It) (Virtual Console)" + rom ( name "Mario Power Tennis (Europe) (En,Fr,De,Es,It) (Virtual Console).gba" size 16777216 crc 851a44cb sha1 24087080476ce04853455fd82f09c4d8c65604d1 ) ) game ( @@ -10572,12 +10698,6 @@ game ( rom ( name "Mario Power Tennis (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc c8db4f60 sha1 d61990974040d405b5bf8436ac8e1e0beb0f7964 flags verified ) ) -game ( - name "Mario Power Tennis (Europe) (En,Fr,De,Es,It) (Virtual Console)" - description "Mario Power Tennis (Europe) (En,Fr,De,Es,It) (Virtual Console)" - rom ( name "Mario Power Tennis (Europe) (En,Fr,De,Es,It) (Virtual Console).gba" size 16777216 crc 851a44cb sha1 24087080476ce04853455fd82f09c4d8c65604d1 ) -) - game ( name "Mario Tennis - Power Tour (USA, Australia) (En,Fr,De,Es,It)" description "Mario Tennis - Power Tour (USA, Australia) (En,Fr,De,Es,It)" @@ -10608,6 +10728,12 @@ game ( rom ( name "Mario vs. Donkey Kong (Japan) (Demo) (Kiosk, GameCube).gba" size 16777216 crc b3642431 sha1 e7389b36573f7e32d53ae9acfdba744389c2f6c2 flags verified ) ) +game ( + name "Mario vs. Donkey Kong (USA) (Demo) (Kiosk, GameCube)" + description "Mario vs. Donkey Kong (USA) (Demo) (Kiosk, GameCube)" + rom ( name "Mario vs. Donkey Kong (USA) (Demo) (Kiosk, GameCube).gba" size 16777216 crc 25505164 sha1 68eebe6dbcf8973ed7426a793185db202a01288f flags verified ) +) + game ( name "Mario vs. Donkey Kong (USA, Australia)" description "Mario vs. Donkey Kong (USA, Australia)" @@ -10626,12 +10752,6 @@ game ( rom ( name "Mario vs. Donkey Kong (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc ca030d61 sha1 c645798ea9cf94e2fd8826ea96a5971a7aeb52b7 flags verified ) ) -game ( - name "Mario vs. Donkey Kong (USA) (Demo) (Kiosk, GameCube)" - description "Mario vs. Donkey Kong (USA) (Demo) (Kiosk, GameCube)" - rom ( name "Mario vs. Donkey Kong (USA) (Demo) (Kiosk, GameCube).gba" size 16777216 crc 25505164 sha1 68eebe6dbcf8973ed7426a793185db202a01288f flags verified ) -) - game ( name "Marvel - Ultimate Alliance (USA)" description "Marvel - Ultimate Alliance (USA)" @@ -10665,7 +10785,7 @@ game ( game ( name "Mat Hoffman's Pro BMX (USA, Europe)" description "Mat Hoffman's Pro BMX (USA, Europe)" - rom ( name "Mat Hoffman's Pro BMX (USA, Europe).gba" size 4194304 crc a333fa51 sha1 df0345c31ca3cad51cef7adae8c6fdadf81b3a97 ) + rom ( name "Mat Hoffman's Pro BMX (USA, Europe).gba" size 4194304 crc a333fa51 sha1 df0345c31ca3cad51cef7adae8c6fdadf81b3a97 flags verified ) ) game ( @@ -10680,18 +10800,18 @@ game ( rom ( name "Mat Hoffman's Pro BMX 2 (USA, Europe).gba" size 8388608 crc e7fa71c6 sha1 d12bc188404bfb6d012b9ce1cf7302762d934832 ) ) -game ( - name "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan) (Rev 1)" - description "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan) (Rev 1)" - rom ( name "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan) (Rev 1).gba" size 8388608 crc d647ab18 sha1 3489569fa8a3c6a750c5cb0048915da78c5f40ab ) -) - game ( name "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan)" description "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan)" rom ( name "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan).gba" size 8388608 crc 4ebdad86 sha1 9e1d0d5d5454d544ab85b34ad0e50fad7b54487c flags verified ) ) +game ( + name "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan) (Rev 1)" + description "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan) (Rev 1)" + rom ( name "Matantei Loki Ragnarok - Gensou no Labyrinth (Japan) (Rev 1).gba" size 8388608 crc d647ab18 sha1 3489569fa8a3c6a750c5cb0048915da78c5f40ab ) +) + game ( name "Matchbox Cross Town Heroes (USA)" description "Matchbox Cross Town Heroes (USA)" @@ -10770,6 +10890,12 @@ game ( rom ( name "Medabots - Metabee (Europe) (Virtual Console).gba" size 8388608 crc 3524f206 sha1 5a4d269998829d7e41fdb014ffa11584ea1f8aa6 ) ) +game ( + name "Medabots - Metabee (Europe)" + description "Medabots - Metabee (Europe)" + rom ( name "Medabots - Metabee (Europe).gba" size 8388608 crc 50927f3e sha1 cd3d674e88f40a0707b150c4293588a659001d29 ) +) + game ( name "Medabots - Metabee (Spain)" description "Medabots - Metabee (Spain)" @@ -10789,9 +10915,9 @@ game ( ) game ( - name "Medabots - Metabee (Europe)" - description "Medabots - Metabee (Europe)" - rom ( name "Medabots - Metabee (Europe).gba" size 8388608 crc 50927f3e sha1 cd3d674e88f40a0707b150c4293588a659001d29 ) + name "Medabots - Rokusho (USA)" + description "Medabots - Rokusho (USA)" + rom ( name "Medabots - Rokusho (USA).gba" size 8388608 crc e144ded2 sha1 c4572428ea97b302f699a3b4eba2a1f0e87c1c9c ) ) game ( @@ -10800,12 +10926,6 @@ game ( rom ( name "Medabots - Rokusho (Europe).gba" size 8388608 crc a519feb5 sha1 fc44b80f3e71effc33d8e05a74888cb885c32eb0 flags verified ) ) -game ( - name "Medabots - Rokusho (Spain)" - description "Medabots - Rokusho (Spain)" - rom ( name "Medabots - Rokusho (Spain).gba" size 8388608 crc 046d86c4 sha1 90fe7f2927c592aabc9b33d0df00d92046c5cb92 ) -) - game ( name "Medabots - Rokusho (USA) (Virtual Console)" description "Medabots - Rokusho (USA) (Virtual Console)" @@ -10819,15 +10939,9 @@ game ( ) game ( - name "Medabots - Rokusho (USA)" - description "Medabots - Rokusho (USA)" - rom ( name "Medabots - Rokusho (USA).gba" size 8388608 crc e144ded2 sha1 c4572428ea97b302f699a3b4eba2a1f0e87c1c9c ) -) - -game ( - name "Medabots AX - Metabee Ver. (Europe) (En,Fr,De,Es,It)" - description "Medabots AX - Metabee Ver. (Europe) (En,Fr,De,Es,It)" - rom ( name "Medabots AX - Metabee Ver. (Europe) (En,Fr,De,Es,It).gba" size 8388608 crc 5f1e5a48 sha1 130c908d24ba3e422fd8db84e683bacc87235e78 flags verified ) + name "Medabots - Rokusho (Spain)" + description "Medabots - Rokusho (Spain)" + rom ( name "Medabots - Rokusho (Spain).gba" size 8388608 crc 046d86c4 sha1 90fe7f2927c592aabc9b33d0df00d92046c5cb92 ) ) game ( @@ -10836,6 +10950,12 @@ game ( rom ( name "Medabots AX - Metabee Ver. (USA).gba" size 8388608 crc 03294511 sha1 80c024df6d40e499776665d7f0c494a252973048 ) ) +game ( + name "Medabots AX - Metabee Ver. (Europe) (En,Fr,De,Es,It)" + description "Medabots AX - Metabee Ver. (Europe) (En,Fr,De,Es,It)" + rom ( name "Medabots AX - Metabee Ver. (Europe) (En,Fr,De,Es,It).gba" size 8388608 crc 5f1e5a48 sha1 130c908d24ba3e422fd8db84e683bacc87235e78 flags verified ) +) + game ( name "Medabots AX - Metabee Ver. (USA) (Virtual Console)" description "Medabots AX - Metabee Ver. (USA) (Virtual Console)" @@ -10878,6 +10998,12 @@ game ( rom ( name "Medal of Honor - Infiltrator (USA, Europe) (En,Fr,De).gba" size 16777216 crc f23150a4 sha1 47761911475e9548c81fed78e3d3336ddae89a58 flags verified ) ) +game ( + name "Medal of Honor - Underground (Europe) (En,Fr,Es,It) (Zoo Digital)" + description "Medal of Honor - Underground (Europe) (En,Fr,Es,It) (Zoo Digital)" + rom ( name "Medal of Honor - Underground (Europe) (En,Fr,Es,It) (Zoo Digital).gba" size 8388608 crc 72b4cf20 sha1 abb26d759ef729d21e41a913fc6c1ce4ab77149f ) +) + game ( name "Medal of Honor - Underground (USA)" description "Medal of Honor - Underground (USA)" @@ -10890,12 +11016,6 @@ game ( rom ( name "Medal of Honor - Underground (Europe) (En,Fr,Es,It) (Ubi Soft).gba" size 8388608 crc 9db145b8 sha1 e43aaba3f925443cfccf9294b870024a9c71a9a9 ) ) -game ( - name "Medal of Honor - Underground (Europe) (En,Fr,Es,It) (Zoo Digital)" - description "Medal of Honor - Underground (Europe) (En,Fr,Es,It) (Zoo Digital)" - rom ( name "Medal of Honor - Underground (Europe) (En,Fr,Es,It) (Zoo Digital).gba" size 8388608 crc 72b4cf20 sha1 abb26d759ef729d21e41a913fc6c1ce4ab77149f ) -) - game ( name "Medal of Honor Advance (Japan)" description "Medal of Honor Advance (Japan)" @@ -10993,375 +11113,375 @@ game ( ) game ( - name "Mega Man & Bass (USA) (Virtual Console)" - description "Mega Man & Bass (USA) (Virtual Console)" - rom ( name "Mega Man & Bass (USA) (Virtual Console).gba" size 8388608 crc b61f99d4 sha1 37db963a52aecec8018057cef3811860c3e889ed ) + name "Megaman - Battle Chip Challenge (USA) (Virtual Console)" + description "Megaman - Battle Chip Challenge (USA) (Virtual Console)" + rom ( name "Megaman - Battle Chip Challenge (USA) (Virtual Console).gba" size 8388608 crc 59fc1ef6 sha1 e936db20e9cf923dc10c6c1dc14bc7ed6b220080 ) ) game ( - name "Mega Man & Bass (USA)" - description "Mega Man & Bass (USA)" - rom ( name "Mega Man & Bass (USA).gba" size 8388608 crc eea68c2e sha1 7610847b331870d4338e5ac894b36e55e2bec5a0 ) + name "Megaman - Battle Chip Challenge (Europe) (Virtual Console)" + description "Megaman - Battle Chip Challenge (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Chip Challenge (Europe) (Virtual Console).gba" size 8388608 crc 24046bf2 sha1 f0776ad33eb01a47207a076d95b534145223371d ) ) game ( - name "Mega Man & Bass (Europe)" - description "Mega Man & Bass (Europe)" - rom ( name "Mega Man & Bass (Europe).gba" size 8388608 crc 01b4d95e sha1 5d6f8fb1f52803a54e9857e53d0b88173cf8f48a flags verified ) + name "Megaman - Battle Chip Challenge (USA)" + description "Megaman - Battle Chip Challenge (USA)" + rom ( name "Megaman - Battle Chip Challenge (USA).gba" size 8388608 crc 26be44fd sha1 72309736f3820470c6f372d6a05ad1f16bc5a946 ) ) game ( - name "Mega Man & Bass (Europe) (Virtual Console)" - description "Mega Man & Bass (Europe) (Virtual Console)" - rom ( name "Mega Man & Bass (Europe) (Virtual Console).gba" size 8388608 crc 6e140bfa sha1 37a188a250ff553a71144f8bbe92098c85c8006d ) + name "Megaman - Battle Chip Challenge (Europe)" + description "Megaman - Battle Chip Challenge (Europe)" + rom ( name "Megaman - Battle Chip Challenge (Europe).gba" size 8388608 crc 5b4631f9 sha1 f54486c8a0bb22cf0ece4297fb052d0a0f11e56d ) ) game ( - name "Mega Man Battle Chip Challenge (USA) (Virtual Console)" - description "Mega Man Battle Chip Challenge (USA) (Virtual Console)" - rom ( name "Mega Man Battle Chip Challenge (USA) (Virtual Console).gba" size 8388608 crc 59fc1ef6 sha1 e936db20e9cf923dc10c6c1dc14bc7ed6b220080 ) + name "Megaman - Battle Network (USA) (Virtual Console)" + description "Megaman - Battle Network (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network (USA) (Virtual Console).gba" size 8388608 crc 1d5d0cb6 sha1 a371765e107c30dc4ac37c2f114a785305f47a4c ) ) game ( - name "Mega Man Battle Chip Challenge (Europe) (Virtual Console)" - description "Mega Man Battle Chip Challenge (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Chip Challenge (Europe) (Virtual Console).gba" size 8388608 crc 24046bf2 sha1 f0776ad33eb01a47207a076d95b534145223371d ) + name "Megaman - Battle Network (Europe) (Virtual Console)" + description "Megaman - Battle Network (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network (Europe) (Virtual Console).gba" size 8388608 crc 1a16c13d sha1 0dcbc43d5d50401c0eab8bb5989055523ce90b10 ) ) game ( - name "Mega Man Battle Chip Challenge (USA)" - description "Mega Man Battle Chip Challenge (USA)" - rom ( name "Mega Man Battle Chip Challenge (USA).gba" size 8388608 crc 26be44fd sha1 72309736f3820470c6f372d6a05ad1f16bc5a946 ) + name "Megaman - Battle Network (USA)" + description "Megaman - Battle Network (USA)" + rom ( name "Megaman - Battle Network (USA).gba" size 8388608 crc 1d347971 sha1 a4fbae389654a6611d0597b1e9109cbbd32a132f ) ) game ( - name "Mega Man Battle Chip Challenge (Europe)" - description "Mega Man Battle Chip Challenge (Europe)" - rom ( name "Mega Man Battle Chip Challenge (Europe).gba" size 8388608 crc 5b4631f9 sha1 f54486c8a0bb22cf0ece4297fb052d0a0f11e56d ) + name "Megaman - Battle Network (Europe)" + description "Megaman - Battle Network (Europe)" + rom ( name "Megaman - Battle Network (Europe).gba" size 8388608 crc 1a7fb4fa sha1 b017b6054ffafc012be9adee785819b17706cabc flags verified ) ) game ( - name "Mega Man Battle Network (USA)" - description "Mega Man Battle Network (USA)" - rom ( name "Mega Man Battle Network (USA).gba" size 8388608 crc 1d347971 sha1 a4fbae389654a6611d0597b1e9109cbbd32a132f ) + name "Megaman - Battle Network 2 (USA)" + description "Megaman - Battle Network 2 (USA)" + rom ( name "Megaman - Battle Network 2 (USA).gba" size 8388608 crc 6d961f82 sha1 601b5012f77001d2c5c11b31304afafc45a70d0b ) ) game ( - name "Mega Man Battle Network (Europe)" - description "Mega Man Battle Network (Europe)" - rom ( name "Mega Man Battle Network (Europe).gba" size 8388608 crc 1a7fb4fa sha1 b017b6054ffafc012be9adee785819b17706cabc ) + name "Megaman - Battle Network 2 (Europe)" + description "Megaman - Battle Network 2 (Europe)" + rom ( name "Megaman - Battle Network 2 (Europe).gba" size 8388608 crc 66341f3b sha1 13d8c1978cbcd9ca2a127168544fda176e0a4d6c ) ) game ( - name "Mega Man Battle Network (USA) (Virtual Console)" - description "Mega Man Battle Network (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network (USA) (Virtual Console).gba" size 8388608 crc 1d5d0cb6 sha1 a371765e107c30dc4ac37c2f114a785305f47a4c ) + name "Megaman - Battle Network 2 (USA) (Debug Version)" + description "Megaman - Battle Network 2 (USA) (Debug Version)" + rom ( name "Megaman - Battle Network 2 (USA) (Debug Version).gba" size 8388608 crc c3aabd70 sha1 d8968ac6c33f376d8f6ce6205b53756434a8d2ac ) ) game ( - name "Mega Man Battle Network (Europe) (Virtual Console)" - description "Mega Man Battle Network (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network (Europe) (Virtual Console).gba" size 8388608 crc 1a16c13d sha1 0dcbc43d5d50401c0eab8bb5989055523ce90b10 ) + name "Megaman - Battle Network 2 (USA, Europe) (Virtual Console)" + description "Megaman - Battle Network 2 (USA, Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 2 (USA, Europe) (Virtual Console).gba" size 8388608 crc 5e88c34b sha1 d6b574203cc393f292a9825fcec64e72b3b2a11b flags verified ) ) game ( - name "Mega Man Battle Network 2 (USA) (Debug Version)" - description "Mega Man Battle Network 2 (USA) (Debug Version)" - rom ( name "Mega Man Battle Network 2 (USA) (Debug Version).gba" size 33554432 crc fbdadaa7 sha1 3bc2a627a859431db0f40f755fbc2857936e23b2 ) + name "Megaman - Battle Network 3 - Blue Version (USA) (Virtual Console)" + description "Megaman - Battle Network 3 - Blue Version (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network 3 - Blue Version (USA) (Virtual Console).gba" size 8388608 crc edd7106e sha1 6e30310f3994803e56170f6d67c5f154a5d91ccb flags verified ) ) game ( - name "Mega Man Battle Network 2 (USA, Europe) (Virtual Console)" - description "Mega Man Battle Network 2 (USA, Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 2 (USA, Europe) (Virtual Console).gba" size 8388608 crc 5e88c34b sha1 d6b574203cc393f292a9825fcec64e72b3b2a11b flags verified ) + name "Megaman - Battle Network 3 - Blue Version (Europe) (Virtual Console)" + description "Megaman - Battle Network 3 - Blue Version (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 3 - Blue Version (Europe) (Virtual Console).gba" size 8388608 crc 3213fc2d sha1 a9fe51d3ffd7488739821b895b45cf91c10a3108 ) ) game ( - name "Mega Man Battle Network 2 (USA)" - description "Mega Man Battle Network 2 (USA)" - rom ( name "Mega Man Battle Network 2 (USA).gba" size 8388608 crc 6d961f82 sha1 601b5012f77001d2c5c11b31304afafc45a70d0b ) + name "Megaman - Battle Network 3 - Blue Version (USA)" + description "Megaman - Battle Network 3 - Blue Version (USA)" + rom ( name "Megaman - Battle Network 3 - Blue Version (USA).gba" size 8388608 crc c0c780f9 sha1 3d21905b6e860d39a00ba643779776de4c73c411 flags verified ) ) game ( - name "Mega Man Battle Network 2 (Europe)" - description "Mega Man Battle Network 2 (Europe)" - rom ( name "Mega Man Battle Network 2 (Europe).gba" size 8388608 crc 66341f3b sha1 13d8c1978cbcd9ca2a127168544fda176e0a4d6c ) + name "Megaman - Battle Network 3 - Blue Version (Europe)" + description "Megaman - Battle Network 3 - Blue Version (Europe)" + rom ( name "Megaman - Battle Network 3 - Blue Version (Europe).gba" size 8388608 crc 1f036cba sha1 9cb052728cae18864a012e7598119ca7b93eea67 ) ) game ( - name "Mega Man Battle Network 3 - Blue (Europe)" - description "Mega Man Battle Network 3 - Blue (Europe)" - rom ( name "Mega Man Battle Network 3 - Blue (Europe).gba" size 8388608 crc 1f036cba sha1 9cb052728cae18864a012e7598119ca7b93eea67 ) + name "Megaman - Battle Network 3 - White Version (USA)" + description "Megaman - Battle Network 3 - White Version (USA)" + rom ( name "Megaman - Battle Network 3 - White Version (USA).gba" size 8388608 crc 0be4410a sha1 ff45038ae6d01cde4eae25a02dcb8bed29e07a6f flags verified ) ) game ( - name "Mega Man Battle Network 3 - Blue (Europe) (Virtual Console)" - description "Mega Man Battle Network 3 - Blue (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 3 - Blue (Europe) (Virtual Console).gba" size 8388608 crc 3213fc2d sha1 a9fe51d3ffd7488739821b895b45cf91c10a3108 ) + name "Megaman - Battle Network 3 - White Version (Europe)" + description "Megaman - Battle Network 3 - White Version (Europe)" + rom ( name "Megaman - Battle Network 3 - White Version (Europe).gba" size 8388608 crc 23d0a981 sha1 2942a890c369569e163a60f831150305ca0828fc ) ) game ( - name "Mega Man Battle Network 3 - Blue Version (USA) (Virtual Console)" - description "Mega Man Battle Network 3 - Blue Version (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network 3 - Blue Version (USA) (Virtual Console).gba" size 8388608 crc edd7106e sha1 6e30310f3994803e56170f6d67c5f154a5d91ccb flags verified ) + name "Megaman - Battle Network 3 - White Version (USA) (Virtual Console)" + description "Megaman - Battle Network 3 - White Version (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network 3 - White Version (USA) (Virtual Console).gba" size 8388608 crc f1c5ac80 sha1 1f0762b7c817211e855f4f50c2b11585d0893be9 ) ) game ( - name "Mega Man Battle Network 3 - Blue Version (USA)" - description "Mega Man Battle Network 3 - Blue Version (USA)" - rom ( name "Mega Man Battle Network 3 - Blue Version (USA).gba" size 8388608 crc c0c780f9 sha1 3d21905b6e860d39a00ba643779776de4c73c411 flags verified ) + name "Megaman - Battle Network 3 - White Version (Europe) (Virtual Console)" + description "Megaman - Battle Network 3 - White Version (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 3 - White Version (Europe) (Virtual Console).gba" size 8388608 crc d9f1440b sha1 e0bd967c2296ca5a515e8b6231d4959336c1278d ) ) game ( - name "Mega Man Battle Network 3 - White (Europe)" - description "Mega Man Battle Network 3 - White (Europe)" - rom ( name "Mega Man Battle Network 3 - White (Europe).gba" size 8388608 crc 23d0a981 sha1 2942a890c369569e163a60f831150305ca0828fc ) + name "Megaman - Battle Network 4 - Blue Moon (USA) (Virtual Console)" + description "Megaman - Battle Network 4 - Blue Moon (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network 4 - Blue Moon (USA) (Virtual Console).gba" size 8388608 crc 739b7ec4 sha1 62a0169f0fe00e5f8184475a20a36877d1063f63 flags verified ) ) game ( - name "Mega Man Battle Network 3 - White (Europe) (Virtual Console)" - description "Mega Man Battle Network 3 - White (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 3 - White (Europe) (Virtual Console).gba" size 8388608 crc d9f1440b sha1 e0bd967c2296ca5a515e8b6231d4959336c1278d ) + name "Megaman - Battle Network 4 - Blue Moon (Europe) (Virtual Console)" + description "Megaman - Battle Network 4 - Blue Moon (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 4 - Blue Moon (Europe) (Virtual Console).gba" size 8388608 crc ad89f27d sha1 0fbf2b2f53f0a32e893e2593e2d7c542b5fcac99 ) ) game ( - name "Mega Man Battle Network 3 - White Version (USA) (Virtual Console)" - description "Mega Man Battle Network 3 - White Version (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network 3 - White Version (USA) (Virtual Console).gba" size 8388608 crc f1c5ac80 sha1 1f0762b7c817211e855f4f50c2b11585d0893be9 ) + name "Megaman - Battle Network 4 - Blue Moon (USA)" + description "Megaman - Battle Network 4 - Blue Moon (USA)" + rom ( name "Megaman - Battle Network 4 - Blue Moon (USA).gba" size 8388608 crc 758a46e9 sha1 5e017c803ddc768efb8010314b46bc17e9757f71 flags verified ) ) game ( - name "Mega Man Battle Network 3 - White Version (USA)" - description "Mega Man Battle Network 3 - White Version (USA)" - rom ( name "Mega Man Battle Network 3 - White Version (USA).gba" size 8388608 crc 0be4410a sha1 ff45038ae6d01cde4eae25a02dcb8bed29e07a6f flags verified ) + name "Megaman - Battle Network 4 - Blue Moon (Europe)" + description "Megaman - Battle Network 4 - Blue Moon (Europe)" + rom ( name "Megaman - Battle Network 4 - Blue Moon (Europe).gba" size 8388608 crc 48758316 sha1 a05e8dce26b5134001337e193d49958be2081598 ) ) game ( - name "Mega Man Battle Network 4 - Blue Moon (USA)" - description "Mega Man Battle Network 4 - Blue Moon (USA)" - rom ( name "Mega Man Battle Network 4 - Blue Moon (USA).gba" size 8388608 crc 758a46e9 sha1 5e017c803ddc768efb8010314b46bc17e9757f71 flags verified ) + name "Megaman - Battle Network 4 - Red Sun (USA)" + description "Megaman - Battle Network 4 - Red Sun (USA)" + rom ( name "Megaman - Battle Network 4 - Red Sun (USA).gba" size 8388608 crc 2120695c sha1 a97e96a7da03abd70f7953328e511b9fa29179f1 flags verified ) ) game ( - name "Mega Man Battle Network 4 - Blue Moon (Europe)" - description "Mega Man Battle Network 4 - Blue Moon (Europe)" - rom ( name "Mega Man Battle Network 4 - Blue Moon (Europe).gba" size 8388608 crc 48758316 sha1 a05e8dce26b5134001337e193d49958be2081598 ) + name "Megaman - Battle Network 4 - Red Sun (Europe)" + description "Megaman - Battle Network 4 - Red Sun (Europe)" + rom ( name "Megaman - Battle Network 4 - Red Sun (Europe).gba" size 8388608 crc 0cb136c2 sha1 21af9f7805a27729e770928f939acedd6ae27c1c ) ) game ( - name "Mega Man Battle Network 4 - Blue Moon (USA) (Virtual Console)" - description "Mega Man Battle Network 4 - Blue Moon (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network 4 - Blue Moon (USA) (Virtual Console).gba" size 8388608 crc 739b7ec4 sha1 62a0169f0fe00e5f8184475a20a36877d1063f63 flags verified ) + name "Megaman - Battle Network 4 - Red Sun (USA) (Virtual Console)" + description "Megaman - Battle Network 4 - Red Sun (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network 4 - Red Sun (USA) (Virtual Console).gba" size 8388608 crc 12e7ab52 sha1 07426328dbb4a3aea763e75c8ffe6de482ff69d0 flags verified ) ) game ( - name "Mega Man Battle Network 4 - Blue Moon (Europe) (Virtual Console)" - description "Mega Man Battle Network 4 - Blue Moon (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 4 - Blue Moon (Europe) (Virtual Console).gba" size 8388608 crc ad89f27d sha1 0fbf2b2f53f0a32e893e2593e2d7c542b5fcac99 ) + name "Megaman - Battle Network 4 - Red Sun (Europe) (Virtual Console)" + description "Megaman - Battle Network 4 - Red Sun (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 4 - Red Sun (Europe) (Virtual Console).gba" size 8388608 crc cb3bbf74 sha1 36d83a1509d28b0c09365466cc1481c919d84cb0 ) ) game ( - name "Mega Man Battle Network 4 - Red Sun (USA) (Virtual Console)" - description "Mega Man Battle Network 4 - Red Sun (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network 4 - Red Sun (USA) (Virtual Console).gba" size 8388608 crc 12e7ab52 sha1 07426328dbb4a3aea763e75c8ffe6de482ff69d0 flags verified ) + name "Megaman - Battle Network 5 - Team Colonel (USA) (Virtual Console)" + description "Megaman - Battle Network 5 - Team Colonel (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network 5 - Team Colonel (USA) (Virtual Console).gba" size 8388608 crc 78dd4edc sha1 c715f1d01e016ad6890ebc8ec90120bc3b146a7e ) ) game ( - name "Mega Man Battle Network 4 - Red Sun (Europe) (Virtual Console)" - description "Mega Man Battle Network 4 - Red Sun (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 4 - Red Sun (Europe) (Virtual Console).gba" size 8388608 crc cb3bbf74 sha1 36d83a1509d28b0c09365466cc1481c919d84cb0 ) + name "Megaman - Battle Network 5 - Team Colonel (Europe)" + description "Megaman - Battle Network 5 - Team Colonel (Europe)" + rom ( name "Megaman - Battle Network 5 - Team Colonel (Europe).gba" size 8388608 crc 8fc8cf73 sha1 d15bcb7c351252a8890c29a2eaac25ff621635c9 ) ) game ( - name "Mega Man Battle Network 4 - Red Sun (USA)" - description "Mega Man Battle Network 4 - Red Sun (USA)" - rom ( name "Mega Man Battle Network 4 - Red Sun (USA).gba" size 8388608 crc 2120695c sha1 a97e96a7da03abd70f7953328e511b9fa29179f1 ) + name "Megaman - Battle Network 5 - Team Colonel (USA)" + description "Megaman - Battle Network 5 - Team Colonel (USA)" + rom ( name "Megaman - Battle Network 5 - Team Colonel (USA).gba" size 8388608 crc a552f683 sha1 5f472f78d8de2df01d5039e045c043cb40969a39 ) ) game ( - name "Mega Man Battle Network 4 - Red Sun (Europe)" - description "Mega Man Battle Network 4 - Red Sun (Europe)" - rom ( name "Mega Man Battle Network 4 - Red Sun (Europe).gba" size 8388608 crc 0cb136c2 sha1 21af9f7805a27729e770928f939acedd6ae27c1c ) + name "Megaman - Battle Network 5 - Team Colonel (Europe) (Virtual Console)" + description "Megaman - Battle Network 5 - Team Colonel (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 5 - Team Colonel (Europe) (Virtual Console).gba" size 8388608 crc 5247772c sha1 061c291b29629d5ce44011d2d83931f2e35044ed ) ) game ( - name "Mega Man Battle Network 5 - Team Colonel (Europe)" - description "Mega Man Battle Network 5 - Team Colonel (Europe)" - rom ( name "Mega Man Battle Network 5 - Team Colonel (Europe).gba" size 8388608 crc 8fc8cf73 sha1 d15bcb7c351252a8890c29a2eaac25ff621635c9 ) + name "Megaman - Battle Network 5 - Team Protoman (Europe)" + description "Megaman - Battle Network 5 - Team Protoman (Europe)" + rom ( name "Megaman - Battle Network 5 - Team Protoman (Europe).gba" size 8388608 crc 79f45ed8 sha1 3d017ed23535e42174299ff89fff44678eb553c3 ) ) game ( - name "Mega Man Battle Network 5 - Team Colonel (USA)" - description "Mega Man Battle Network 5 - Team Colonel (USA)" - rom ( name "Mega Man Battle Network 5 - Team Colonel (USA).gba" size 8388608 crc a552f683 sha1 5f472f78d8de2df01d5039e045c043cb40969a39 ) + name "Megaman - Battle Network 5 - Team Protoman (USA)" + description "Megaman - Battle Network 5 - Team Protoman (USA)" + rom ( name "Megaman - Battle Network 5 - Team Protoman (USA).gba" size 8388608 crc a73e83a4 sha1 b3774e96b1f107bb8b1db79b216be41b9bc5bac0 ) ) game ( - name "Mega Man Battle Network 5 - Team Colonel (Europe) (Virtual Console)" - description "Mega Man Battle Network 5 - Team Colonel (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 5 - Team Colonel (Europe) (Virtual Console).gba" size 8388608 crc 5247772c sha1 061c291b29629d5ce44011d2d83931f2e35044ed ) + name "Megaman - Battle Network 5 - Team Protoman (USA) (Virtual Console)" + description "Megaman - Battle Network 5 - Team Protoman (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network 5 - Team Protoman (USA) (Virtual Console).gba" size 8388608 crc a0911541 sha1 70ee16615f7d9d6bb13382d70dd2f213b918063b ) ) game ( - name "Mega Man Battle Network 5 - Team Colonel (USA) (Virtual Console)" - description "Mega Man Battle Network 5 - Team Colonel (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network 5 - Team Colonel (USA) (Virtual Console).gba" size 8388608 crc 78dd4edc sha1 c715f1d01e016ad6890ebc8ec90120bc3b146a7e ) + name "Megaman - Battle Network 5 - Team Protoman (Europe) (Virtual Console)" + description "Megaman - Battle Network 5 - Team Protoman (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 5 - Team Protoman (Europe) (Virtual Console).gba" size 8388608 crc 7e5bc83d sha1 b68b310e3106e7fa6a47d4155239be2e43959da4 ) ) game ( - name "Mega Man Battle Network 5 - Team Proto Man (USA) (Virtual Console)" - description "Mega Man Battle Network 5 - Team Proto Man (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network 5 - Team Proto Man (USA) (Virtual Console).gba" size 8388608 crc a0911541 sha1 70ee16615f7d9d6bb13382d70dd2f213b918063b ) + name "Megaman - Battle Network 6 - Cybeast Falzar (Europe)" + description "Megaman - Battle Network 6 - Cybeast Falzar (Europe)" + rom ( name "Megaman - Battle Network 6 - Cybeast Falzar (Europe).gba" size 8388608 crc 13183967 sha1 1f3037b33878fc66b79b4e2dcf1bb83202fa1b90 ) ) game ( - name "Mega Man Battle Network 5 - Team Proto Man (Europe) (Virtual Console)" - description "Mega Man Battle Network 5 - Team Proto Man (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 5 - Team Proto Man (Europe) (Virtual Console).gba" size 8388608 crc 7e5bc83d sha1 b68b310e3106e7fa6a47d4155239be2e43959da4 ) + name "Megaman - Battle Network 6 - Cybeast Falzar (USA)" + description "Megaman - Battle Network 6 - Cybeast Falzar (USA)" + rom ( name "Megaman - Battle Network 6 - Cybeast Falzar (USA).gba" size 8388608 crc dee6f2a9 sha1 0676ecd4d58a976af3346caebb44b9b6489ad099 flags verified ) ) game ( - name "Mega Man Battle Network 5 - Team Protoman (Europe)" - description "Mega Man Battle Network 5 - Team Protoman (Europe)" - rom ( name "Mega Man Battle Network 5 - Team Protoman (Europe).gba" size 8388608 crc 79f45ed8 sha1 3d017ed23535e42174299ff89fff44678eb553c3 ) + name "Megaman - Battle Network 6 - Cybeast Falzar (USA) (Virtual Console)" + description "Megaman - Battle Network 6 - Cybeast Falzar (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network 6 - Cybeast Falzar (USA) (Virtual Console).gba" size 16777216 crc c2a21be3 sha1 c477367c5c9b81cbca2f3ba0cf6a820d489a3b7e ) ) game ( - name "Mega Man Battle Network 5 - Team Protoman (USA)" - description "Mega Man Battle Network 5 - Team Protoman (USA)" - rom ( name "Mega Man Battle Network 5 - Team Protoman (USA).gba" size 8388608 crc a73e83a4 sha1 b3774e96b1f107bb8b1db79b216be41b9bc5bac0 ) + name "Megaman - Battle Network 6 - Cybeast Falzar (Europe) (Virtual Console)" + description "Megaman - Battle Network 6 - Cybeast Falzar (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 6 - Cybeast Falzar (Europe) (Virtual Console).gba" size 16777216 crc 8104e85a sha1 e2dbec1cb65065ed716fa9851237677efe27ae1c ) ) game ( - name "Mega Man Battle Network 6 - Cybeast Falzar (Europe)" - description "Mega Man Battle Network 6 - Cybeast Falzar (Europe)" - rom ( name "Mega Man Battle Network 6 - Cybeast Falzar (Europe).gba" size 8388608 crc 13183967 sha1 1f3037b33878fc66b79b4e2dcf1bb83202fa1b90 ) + name "Megaman - Battle Network 6 - Cybeast Gregar (USA)" + description "Megaman - Battle Network 6 - Cybeast Gregar (USA)" + rom ( name "Megaman - Battle Network 6 - Cybeast Gregar (USA).gba" size 8388608 crc 79452182 sha1 89fe0bac4fd3d2ab1d2ca35e87ef8b1294a84cd6 ) ) game ( - name "Mega Man Battle Network 6 - Cybeast Falzar (USA)" - description "Mega Man Battle Network 6 - Cybeast Falzar (USA)" - rom ( name "Mega Man Battle Network 6 - Cybeast Falzar (USA).gba" size 8388608 crc dee6f2a9 sha1 0676ecd4d58a976af3346caebb44b9b6489ad099 flags verified ) + name "Megaman - Battle Network 6 - Cybeast Gregar (Europe)" + description "Megaman - Battle Network 6 - Cybeast Gregar (Europe)" + rom ( name "Megaman - Battle Network 6 - Cybeast Gregar (Europe).gba" size 8388608 crc 25c29efb sha1 4d2e441b1bcb8438c0bef2ae61d937de7d04af02 ) ) game ( - name "Mega Man Battle Network 6 - Cybeast Falzar (USA) (Virtual Console)" - description "Mega Man Battle Network 6 - Cybeast Falzar (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network 6 - Cybeast Falzar (USA) (Virtual Console).gba" size 16777216 crc c2a21be3 sha1 c477367c5c9b81cbca2f3ba0cf6a820d489a3b7e ) + name "Megaman - Battle Network 6 - Cybeast Gregar (USA) (Virtual Console)" + description "Megaman - Battle Network 6 - Cybeast Gregar (USA) (Virtual Console)" + rom ( name "Megaman - Battle Network 6 - Cybeast Gregar (USA) (Virtual Console).gba" size 8388608 crc e7e546fe sha1 0a8de8417b9939573daa04f95cdecdeaaf58a79e ) ) game ( - name "Mega Man Battle Network 6 - Cybeast Falzar (Europe) (Virtual Console)" - description "Mega Man Battle Network 6 - Cybeast Falzar (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 6 - Cybeast Falzar (Europe) (Virtual Console).gba" size 16777216 crc 8104e85a sha1 e2dbec1cb65065ed716fa9851237677efe27ae1c ) + name "Megaman - Battle Network 6 - Cybeast Gregar (Europe) (Virtual Console)" + description "Megaman - Battle Network 6 - Cybeast Gregar (Europe) (Virtual Console)" + rom ( name "Megaman - Battle Network 6 - Cybeast Gregar (Europe) (Virtual Console).gba" size 8388608 crc bb62f987 sha1 7106dc0469898cb5768ec76c99029b24039d7605 ) ) game ( - name "Mega Man Battle Network 6 - Cybeast Gregar (Europe) (Virtual Console)" - description "Mega Man Battle Network 6 - Cybeast Gregar (Europe) (Virtual Console)" - rom ( name "Mega Man Battle Network 6 - Cybeast Gregar (Europe) (Virtual Console).gba" size 8388608 crc bb62f987 sha1 7106dc0469898cb5768ec76c99029b24039d7605 ) + name "Megaman & Bass (USA) (Virtual Console)" + description "Megaman & Bass (USA) (Virtual Console)" + rom ( name "Megaman & Bass (USA) (Virtual Console).gba" size 8388608 crc b61f99d4 sha1 37db963a52aecec8018057cef3811860c3e889ed ) ) game ( - name "Mega Man Battle Network 6 - Cybeast Gregar (USA)" - description "Mega Man Battle Network 6 - Cybeast Gregar (USA)" - rom ( name "Mega Man Battle Network 6 - Cybeast Gregar (USA).gba" size 8388608 crc 79452182 sha1 89fe0bac4fd3d2ab1d2ca35e87ef8b1294a84cd6 ) + name "Megaman & Bass (Europe)" + description "Megaman & Bass (Europe)" + rom ( name "Megaman & Bass (Europe).gba" size 8388608 crc 01b4d95e sha1 5d6f8fb1f52803a54e9857e53d0b88173cf8f48a flags verified ) ) game ( - name "Mega Man Battle Network 6 - Cybeast Gregar (Europe)" - description "Mega Man Battle Network 6 - Cybeast Gregar (Europe)" - rom ( name "Mega Man Battle Network 6 - Cybeast Gregar (Europe).gba" size 8388608 crc 25c29efb sha1 4d2e441b1bcb8438c0bef2ae61d937de7d04af02 ) + name "Megaman & Bass (Europe) (Virtual Console)" + description "Megaman & Bass (Europe) (Virtual Console)" + rom ( name "Megaman & Bass (Europe) (Virtual Console).gba" size 8388608 crc 6e140bfa sha1 37a188a250ff553a71144f8bbe92098c85c8006d ) ) game ( - name "Mega Man Battle Network 6 - Cybeast Gregar (USA) (Virtual Console)" - description "Mega Man Battle Network 6 - Cybeast Gregar (USA) (Virtual Console)" - rom ( name "Mega Man Battle Network 6 - Cybeast Gregar (USA) (Virtual Console).gba" size 8388608 crc e7e546fe sha1 0a8de8417b9939573daa04f95cdecdeaaf58a79e ) + name "Megaman & Bass (USA)" + description "Megaman & Bass (USA)" + rom ( name "Megaman & Bass (USA).gba" size 8388608 crc eea68c2e sha1 7610847b331870d4338e5ac894b36e55e2bec5a0 flags verified ) ) game ( - name "Mega Man Zero (USA) (Virtual Console)" - description "Mega Man Zero (USA) (Virtual Console)" - rom ( name "Mega Man Zero (USA) (Virtual Console).gba" size 8388608 crc 9c77209c sha1 7279705e24d32895dada7f5476f68bbc33dd643b ) + name "Megaman Zero (USA, Europe)" + description "Megaman Zero (USA, Europe)" + rom ( name "Megaman Zero (USA, Europe).gba" size 8388608 crc 9707d2a1 sha1 193b14120119162518a73c70876f0b8bffdbd96e flags verified ) ) game ( - name "Mega Man Zero (USA, Europe)" - description "Mega Man Zero (USA, Europe)" - rom ( name "Mega Man Zero (USA, Europe).gba" size 8388608 crc 9707d2a1 sha1 193b14120119162518a73c70876f0b8bffdbd96e flags verified ) + name "Megaman Zero (USA) (Virtual Console)" + description "Megaman Zero (USA) (Virtual Console)" + rom ( name "Megaman Zero (USA) (Virtual Console).gba" size 8388608 crc 9c77209c sha1 7279705e24d32895dada7f5476f68bbc33dd643b ) ) game ( - name "Mega Man Zero 2 (USA) (Virtual Console)" - description "Mega Man Zero 2 (USA) (Virtual Console)" - rom ( name "Mega Man Zero 2 (USA) (Virtual Console).gba" size 8388608 crc 30d051fe sha1 d7a1edd912f8e01bc442809b922bceaf5a1f0176 ) + name "Megaman Zero 2 (USA) (Virtual Console)" + description "Megaman Zero 2 (USA) (Virtual Console)" + rom ( name "Megaman Zero 2 (USA) (Virtual Console).gba" size 8388608 crc 30d051fe sha1 d7a1edd912f8e01bc442809b922bceaf5a1f0176 ) ) game ( - name "Mega Man Zero 2 (USA)" - description "Mega Man Zero 2 (USA)" - rom ( name "Mega Man Zero 2 (USA).gba" size 8388608 crc ce1e37bb sha1 c4d93a58f0f82c526dec8a3fdbda170336303689 ) + name "Megaman Zero 2 (USA)" + description "Megaman Zero 2 (USA)" + rom ( name "Megaman Zero 2 (USA).gba" size 8388608 crc ce1e37bb sha1 c4d93a58f0f82c526dec8a3fdbda170336303689 ) ) game ( - name "Mega Man Zero 2 (Europe)" - description "Mega Man Zero 2 (Europe)" - rom ( name "Mega Man Zero 2 (Europe).gba" size 8388608 crc 29a14b59 sha1 c55eca8f2c31fdf772f2605181d0b29815ea37a0 ) + name "Megaman Zero 2 (Europe)" + description "Megaman Zero 2 (Europe)" + rom ( name "Megaman Zero 2 (Europe).gba" size 8388608 crc 29a14b59 sha1 c55eca8f2c31fdf772f2605181d0b29815ea37a0 ) ) game ( - name "Mega Man Zero 2 (Europe) (Virtual Console)" - description "Mega Man Zero 2 (Europe) (Virtual Console)" - rom ( name "Mega Man Zero 2 (Europe) (Virtual Console).gba" size 8388608 crc d76f2d1c sha1 4ae9cd23f80d190f21002d2fdf4b8847c9452592 ) + name "Megaman Zero 2 (Europe) (Virtual Console)" + description "Megaman Zero 2 (Europe) (Virtual Console)" + rom ( name "Megaman Zero 2 (Europe) (Virtual Console).gba" size 8388608 crc d76f2d1c sha1 4ae9cd23f80d190f21002d2fdf4b8847c9452592 ) ) game ( - name "Mega Man Zero 3 (Europe) (Virtual Console)" - description "Mega Man Zero 3 (Europe) (Virtual Console)" - rom ( name "Mega Man Zero 3 (Europe) (Virtual Console).gba" size 8388608 crc 87e8656e sha1 8245ecb895caf0e0a2914f46bb79e4c9c5ba8c4a ) + name "Megaman Zero 3 (Europe) (Virtual Console)" + description "Megaman Zero 3 (Europe) (Virtual Console)" + rom ( name "Megaman Zero 3 (Europe) (Virtual Console).gba" size 8388608 crc 87e8656e sha1 8245ecb895caf0e0a2914f46bb79e4c9c5ba8c4a ) ) game ( - name "Mega Man Zero 3 (USA) (Virtual Console)" - description "Mega Man Zero 3 (USA) (Virtual Console)" - rom ( name "Mega Man Zero 3 (USA) (Virtual Console).gba" size 16777216 crc cbd24ac4 sha1 de5413481d823c53973cb29507567ef3dc6af399 ) + name "Megaman Zero 3 (USA) (Virtual Console)" + description "Megaman Zero 3 (USA) (Virtual Console)" + rom ( name "Megaman Zero 3 (USA) (Virtual Console).gba" size 16777216 crc cbd24ac4 sha1 de5413481d823c53973cb29507567ef3dc6af399 ) ) game ( - name "Mega Man Zero 3 (Europe)" - description "Mega Man Zero 3 (Europe)" - rom ( name "Mega Man Zero 3 (Europe).gba" size 8388608 crc b099577f sha1 edfcb606136951374f24aa8fd7e5b4e710300301 ) + name "Megaman Zero 3 (Europe)" + description "Megaman Zero 3 (Europe)" + rom ( name "Megaman Zero 3 (Europe).gba" size 8388608 crc b099577f sha1 edfcb606136951374f24aa8fd7e5b4e710300301 ) ) game ( - name "Mega Man Zero 3 (USA)" - description "Mega Man Zero 3 (USA)" - rom ( name "Mega Man Zero 3 (USA).gba" size 8388608 crc 2784f3f2 sha1 403a78f2cad93d41e4b0f2e520ce08026531664b ) + name "Megaman Zero 3 (USA)" + description "Megaman Zero 3 (USA)" + rom ( name "Megaman Zero 3 (USA).gba" size 8388608 crc 2784f3f2 sha1 403a78f2cad93d41e4b0f2e520ce08026531664b ) ) game ( - name "Mega Man Zero 4 (Europe) (Virtual Console)" - description "Mega Man Zero 4 (Europe) (Virtual Console)" - rom ( name "Mega Man Zero 4 (Europe) (Virtual Console).gba" size 16777216 crc ffda95be sha1 4424f9e08381a447192d87bbad20dab3c77740db ) + name "Megaman Zero 4 (Europe) (Virtual Console)" + description "Megaman Zero 4 (Europe) (Virtual Console)" + rom ( name "Megaman Zero 4 (Europe) (Virtual Console).gba" size 16777216 crc ffda95be sha1 4424f9e08381a447192d87bbad20dab3c77740db ) ) game ( - name "Mega Man Zero 4 (Europe)" - description "Mega Man Zero 4 (Europe)" - rom ( name "Mega Man Zero 4 (Europe).gba" size 16777216 crc b7f022b9 sha1 8e13b9ee89a2ed665212daa401ba9331ad11bda9 ) + name "Megaman Zero 4 (Europe)" + description "Megaman Zero 4 (Europe)" + rom ( name "Megaman Zero 4 (Europe).gba" size 16777216 crc b7f022b9 sha1 8e13b9ee89a2ed665212daa401ba9331ad11bda9 ) ) game ( - name "Mega Man Zero 4 (USA)" - description "Mega Man Zero 4 (USA)" - rom ( name "Mega Man Zero 4 (USA).gba" size 16777216 crc 7ee24793 sha1 596993205a1895a6f51e80749407fb069b907628 ) + name "Megaman Zero 4 (USA)" + description "Megaman Zero 4 (USA)" + rom ( name "Megaman Zero 4 (USA).gba" size 16777216 crc 7ee24793 sha1 596993205a1895a6f51e80749407fb069b907628 ) ) game ( - name "Mega Man Zero 4 (USA) (Virtual Console)" - description "Mega Man Zero 4 (USA) (Virtual Console)" - rom ( name "Mega Man Zero 4 (USA) (Virtual Console).gba" size 16777216 crc c4838cfa sha1 24a774446c8f652c78bb4bd938fc57c612db3d6c flags verified ) + name "Megaman Zero 4 (USA) (Virtual Console)" + description "Megaman Zero 4 (USA) (Virtual Console)" + rom ( name "Megaman Zero 4 (USA) (Virtual Console).gba" size 16777216 crc c4838cfa sha1 24a774446c8f652c78bb4bd938fc57c612db3d6c flags verified ) ) game ( @@ -11664,6 +11784,12 @@ game ( rom ( name "Mini Moni. - Onegai Ohoshi-sama! (Japan).gba" size 8388608 crc f11c35cc sha1 d34795cf3679c6f259177db52a138c0d6e9fcdfd ) ) +game ( + name "Minicraft (World) (Aftermarket) (Unl)" + description "Minicraft (World) (Aftermarket) (Unl)" + rom ( name "Minicraft (World) (Aftermarket) (Unl).gba" size 131072 crc e852c9e9 sha1 06faa5be11978666db6995d8fce40ce2c3641ce8 ) +) + game ( name "Minna de Puyo Puyo (Japan) (En,Ja)" description "Minna de Puyo Puyo (Japan) (En,Ja)" @@ -11773,9 +11899,9 @@ game ( ) game ( - name "Misfortune Advance (World) (Aftermarket) (Homebrew)" - description "Misfortune Advance (World) (Aftermarket) (Homebrew)" - rom ( name "Misfortune Advance (World) (Aftermarket) (Homebrew).gba" size 5125100 crc 18f2166d sha1 68e215025ac963220f16ea3c100e51698f97c4eb ) + name "Misfortune Advance (World) (Aftermarket) (Unl)" + description "Misfortune Advance (World) (Aftermarket) (Unl)" + rom ( name "Misfortune Advance (World) (Aftermarket) (Unl).gba" size 5125100 crc 18f2166d sha1 68e215025ac963220f16ea3c100e51698f97c4eb ) ) game ( @@ -11803,15 +11929,9 @@ game ( ) game ( - name "MLB SlugFest 20-04 (USA)" - description "MLB SlugFest 20-04 (USA)" - rom ( name "MLB SlugFest 20-04 (USA).gba" size 4194304 crc a4e12d4b sha1 cd60f0aacdada987f7935d7a5fa2cd3242abc145 ) -) - -game ( - name "Mo Jie Qibing (Taiwan) (Unl)" - description "Mo Jie Qibing (Taiwan) (Unl)" - rom ( name "Mo Jie Qibing (Taiwan) (Unl).gba" size 4194304 crc 8ee0ed6f sha1 e9b68e1f7584892b366a6a4b3a94feeb8729e4c5 ) + name "MLB SlugFest 2004 (USA)" + description "MLB SlugFest 2004 (USA)" + rom ( name "MLB SlugFest 2004 (USA).gba" size 4194304 crc a4e12d4b sha1 cd60f0aacdada987f7935d7a5fa2cd3242abc145 ) ) game ( @@ -11832,6 +11952,12 @@ game ( rom ( name "Moero!! Jaleco Collection (Japan).gba" size 4194304 crc 6da36e82 sha1 52d1cbeed52de62147d13a667a9631f6d8a24865 ) ) +game ( + name "Mojie Qibing (Taiwan) (Unl)" + description "Mojie Qibing (Taiwan) (Unl)" + rom ( name "Mojie Qibing (Taiwan) (Unl).gba" size 4194304 crc 8ee0ed6f sha1 e9b68e1f7584892b366a6a4b3a94feeb8729e4c5 ) +) + game ( name "Momotarou Dentetsu G - Gold Deck o Tsukure! (Japan)" description "Momotarou Dentetsu G - Gold Deck o Tsukure! (Japan)" @@ -12030,12 +12156,30 @@ game ( rom ( name "Monsters, Inc. (Europe) (En,Es,Nl).gba" size 4194304 crc d13177e0 sha1 36645b8de074f0b27dbe61528e70ae3af28fb3b9 ) ) +game ( + name "Mooncat's Trio (World) (Aftermarket) (Unl)" + description "Mooncat's Trio (World) (Aftermarket) (Unl)" + rom ( name "Mooncat's Trio (World) (Aftermarket) (Unl).gba" size 1175680 crc b05d5339 sha1 890de7d73723d235d9762e4866d569d3554d1480 flags verified ) +) + +game ( + name "Mooncat's Trio (World) (Beta) (Aftermarket) (Unl)" + description "Mooncat's Trio (World) (Beta) (Aftermarket) (Unl)" + rom ( name "Mooncat's Trio (World) (Beta) (Aftermarket) (Unl).gba" size 504220 crc cd3328ee sha1 c3ea5247f32c428bbcfd5c087218fc56779e3106 ) +) + game ( name "Moorhen 3 - The Chicken Chase! (Europe) (En,Fr,De,Es,It)" description "Moorhen 3 - The Chicken Chase! (Europe) (En,Fr,De,Es,It)" rom ( name "Moorhen 3 - The Chicken Chase! (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc a16c10a8 sha1 b743dd4b3f1bfa169fed64f86766d5a616c5ee60 ) ) +game ( + name "Moorhuhn Jagd (Europe) (Proto)" + description "Moorhuhn Jagd (Europe) (Proto)" + rom ( name "Moorhuhn Jagd (Europe) (Proto).gba" size 8388608 crc d3f841f3 sha1 a2a698345251b9487c5387a665b8c49c3af7ccd5 ) +) + game ( name "Morita Shougi Advance (Japan)" description "Morita Shougi Advance (Japan)" @@ -12252,18 +12396,18 @@ game ( rom ( name "Mummy, The (USA) (En,Fr,De,Es,It).gba" size 4194304 crc 55bf350b sha1 cc833b1398a6ea87d31712806757c2fe52fadd31 ) ) -game ( - name "Muppet Pinball Mayhem (USA)" - description "Muppet Pinball Mayhem (USA)" - rom ( name "Muppet Pinball Mayhem (USA).gba" size 4194304 crc 58575f65 sha1 cbf381536ab8d8ea02814a77f46db13cb625c72f ) -) - game ( name "Muppet Pinball Mayhem (Europe)" description "Muppet Pinball Mayhem (Europe)" rom ( name "Muppet Pinball Mayhem (Europe).gba" size 4194304 crc 8d5034d7 sha1 1c45790873605defc999b45ee145013e7e406a5d ) ) +game ( + name "Muppet Pinball Mayhem (USA)" + description "Muppet Pinball Mayhem (USA)" + rom ( name "Muppet Pinball Mayhem (USA).gba" size 4194304 crc 58575f65 sha1 cbf381536ab8d8ea02814a77f46db13cb625c72f ) +) + game ( name "Muppets, The - On with the Show! (USA, Europe) (En,Fr,De,Es,It,Nl)" description "Muppets, The - On with the Show! (USA, Europe) (En,Fr,De,Es,It,Nl)" @@ -12342,6 +12486,12 @@ game ( rom ( name "Nakayoshi Pet Advance Series 4 - Kawaii Koinu Mini - Wanko to Asobou!! Kogata-ken (Japan).gba" size 4194304 crc ae2a69f3 sha1 0c78a24d5a8a296d05a8b47cf5623de0f031748e ) ) +game ( + name "Nakayoshi Youchien - Sukoyaka Enji Ikusei Game (Japan)" + description "Nakayoshi Youchien - Sukoyaka Enji Ikusei Game (Japan)" + rom ( name "Nakayoshi Youchien - Sukoyaka Enji Ikusei Game (Japan).gba" size 4194304 crc 1276a95c sha1 77743de7541e0a9d09e3aac864c4e3f9f1a51d2b ) +) + game ( name "Nakayoshi Youchien - Sukoyaka Enji Ikusei Game (Japan) (Rev 1)" description "Nakayoshi Youchien - Sukoyaka Enji Ikusei Game (Japan) (Rev 1)" @@ -12349,9 +12499,9 @@ game ( ) game ( - name "Nakayoshi Youchien - Sukoyaka Enji Ikusei Game (Japan)" - description "Nakayoshi Youchien - Sukoyaka Enji Ikusei Game (Japan)" - rom ( name "Nakayoshi Youchien - Sukoyaka Enji Ikusei Game (Japan).gba" size 4194304 crc 1276a95c sha1 77743de7541e0a9d09e3aac864c4e3f9f1a51d2b ) + name "Namco Museum (Europe)" + description "Namco Museum (Europe)" + rom ( name "Namco Museum (Europe).gba" size 4194304 crc bb82460a sha1 3e84508a0d2362a0abf31dede1d55b865566213c ) ) game ( @@ -12366,12 +12516,6 @@ game ( rom ( name "Namco Museum (Japan) (En).gba" size 4194304 crc 818f4e4b sha1 54d01cf5ac57f5fb454c0e188de9e60f63854a97 ) ) -game ( - name "Namco Museum (Europe)" - description "Namco Museum (Europe)" - rom ( name "Namco Museum (Europe).gba" size 4194304 crc bb82460a sha1 3e84508a0d2362a0abf31dede1d55b865566213c ) -) - game ( name "Namco Museum - 50th Anniversary (USA)" description "Namco Museum - 50th Anniversary (USA)" @@ -12492,6 +12636,12 @@ game ( rom ( name "NBA Jam 2002 (USA, Europe).gba" size 4194304 crc ca428a7a sha1 38de98758669b120b895661bec3882c3474cf47e ) ) +game ( + name "Need for Speed - Carbon - Own the City (USA, Europe) (En,Fr,De,Es,It)" + description "Need for Speed - Carbon - Own the City (USA, Europe) (En,Fr,De,Es,It)" + rom ( name "Need for Speed - Carbon - Own the City (USA, Europe) (En,Fr,De,Es,It).gba" size 8388608 crc f4c0d140 sha1 e5298b2482a769aa6458955cd6b18db3ac3f3a20 flags verified ) +) + game ( name "Need for Speed - Most Wanted (USA, Europe) (En,Fr,De,It)" description "Need for Speed - Most Wanted (USA, Europe) (En,Fr,De,It)" @@ -12513,7 +12663,7 @@ game ( game ( name "Need for Speed - Underground (USA, Europe) (En,Fr,De,It)" description "Need for Speed - Underground (USA, Europe) (En,Fr,De,It)" - rom ( name "Need for Speed - Underground (USA, Europe) (En,Fr,De,It).gba" size 8388608 crc 828020e9 sha1 ddd304481617a748aa9f37535908a37452dd2f03 ) + rom ( name "Need for Speed - Underground (USA, Europe) (En,Fr,De,It).gba" size 8388608 crc 828020e9 sha1 ddd304481617a748aa9f37535908a37452dd2f03 flags verified ) ) game ( @@ -12522,12 +12672,6 @@ game ( rom ( name "Need for Speed - Underground 2 (USA, Europe) (En,Fr,De,It).gba" size 8388608 crc 9a0c5090 sha1 f772000fbdfb84d8d5de9f69bd9c0de551389929 flags verified ) ) -game ( - name "Need for Speed Carbon - Own the City (USA, Europe) (En,Fr,De,Es,It)" - description "Need for Speed Carbon - Own the City (USA, Europe) (En,Fr,De,Es,It)" - rom ( name "Need for Speed Carbon - Own the City (USA, Europe) (En,Fr,De,Es,It).gba" size 8388608 crc f4c0d140 sha1 e5298b2482a769aa6458955cd6b18db3ac3f3a20 flags verified ) -) - game ( name "Nekketsu Monogatari Advance (Japan) (Demo) (Aftermarket) (Unl)" description "Nekketsu Monogatari Advance (Japan) (Demo) (Aftermarket) (Unl)" @@ -12559,15 +12703,15 @@ game ( ) game ( - name "NFL Blitz 20-02 (USA)" - description "NFL Blitz 20-02 (USA)" - rom ( name "NFL Blitz 20-02 (USA).gba" size 4194304 crc 8c748dcb sha1 590196c49eb4b33d933db60d58a1d0efd94b3f7b ) + name "NFL Blitz 2002 (USA)" + description "NFL Blitz 2002 (USA)" + rom ( name "NFL Blitz 2002 (USA).gba" size 4194304 crc 8c748dcb sha1 590196c49eb4b33d933db60d58a1d0efd94b3f7b ) ) game ( - name "NFL Blitz 20-03 (USA)" - description "NFL Blitz 20-03 (USA)" - rom ( name "NFL Blitz 20-03 (USA).gba" size 4194304 crc e0b137e7 sha1 6d63871abf8016248ff7f8777d762e7c75877d40 ) + name "NFL Blitz 2003 (USA)" + description "NFL Blitz 2003 (USA)" + rom ( name "NFL Blitz 2003 (USA).gba" size 4194304 crc e0b137e7 sha1 6d63871abf8016248ff7f8777d762e7c75877d40 ) ) game ( @@ -12577,9 +12721,9 @@ game ( ) game ( - name "NHL Hitz 20-03 (USA)" - description "NHL Hitz 20-03 (USA)" - rom ( name "NHL Hitz 20-03 (USA).gba" size 4194304 crc 4f1adf75 sha1 4a88ca2962fdfb7ea1b91b9e2bffe8c69ff96519 ) + name "NHL Hitz 2003 (USA)" + description "NHL Hitz 2003 (USA)" + rom ( name "NHL Hitz 2003 (USA).gba" size 4194304 crc 4f1adf75 sha1 4a88ca2962fdfb7ea1b91b9e2bffe8c69ff96519 ) ) game ( @@ -12645,7 +12789,7 @@ game ( game ( name "Nintendo MP3 Player (Europe) (En,Fr,De,Es,It)" description "Nintendo MP3 Player (Europe) (En,Fr,De,Es,It)" - rom ( name "Nintendo MP3 Player (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 867bec76 sha1 0c1eb4af684f3b428766b10cdbbe62fd726e5e27 ) + rom ( name "Nintendo MP3 Player (Europe) (En,Fr,De,Es,It).gba" size 1048576 crc 16e5d0f2 sha1 b7c517f1cb5860fec8beaa4e819bcffa8ce95a5b flags verified ) ) game ( @@ -12723,7 +12867,7 @@ game ( game ( name "Oddworld - Munch's Oddysee (USA, Europe)" description "Oddworld - Munch's Oddysee (USA, Europe)" - rom ( name "Oddworld - Munch's Oddysee (USA, Europe).gba" size 8388608 crc 97367887 sha1 cc6bf19e5d6a35c2a745bea4e3b2a210a9ebd296 flags verified ) + rom ( name "Oddworld - Munch's Oddysee (USA, Europe).gba" size 8388608 crc 97367887 sha1 cc6bf19e5d6a35c2a745bea4e3b2a210a9ebd296 ) ) game ( @@ -12853,9 +12997,9 @@ game ( ) game ( - name "Oshare Princess 2 (Japan)" - description "Oshare Princess 2 (Japan)" - rom ( name "Oshare Princess 2 (Japan).gba" size 8388608 crc 8383fcda sha1 649001f85cdcd2515183fc1364d08de52c918feb ) + name "Oshare Princess 2 + Doubutsu Kyaranabi Uranai (Japan)" + description "Oshare Princess 2 + Doubutsu Kyaranabi Uranai (Japan)" + rom ( name "Oshare Princess 2 + Doubutsu Kyaranabi Uranai (Japan).gba" size 8388608 crc 8383fcda sha1 649001f85cdcd2515183fc1364d08de52c918feb ) ) game ( @@ -12931,9 +13075,9 @@ game ( ) game ( - name "Overstorm (Unknown) (Proto)" - description "Overstorm (Unknown) (Proto)" - rom ( name "Overstorm (Unknown) (Proto).gba" size 3470916 crc ee764cf6 sha1 88129a07e9dfb556c38a5c61c6e96fbfb4d23fea ) + name "Overstorm (Europe) (Proto)" + description "Overstorm (Europe) (Proto)" + rom ( name "Overstorm (Europe) (Proto).gba" size 3470916 crc ee764cf6 sha1 88129a07e9dfb556c38a5c61c6e96fbfb4d23fea ) ) game ( @@ -13033,9 +13177,9 @@ game ( ) game ( - name "Pac-Man World 2 (USA) (Beta)" - description "Pac-Man World 2 (USA) (Beta)" - rom ( name "Pac-Man World 2 (USA) (Beta).gba" size 4194304 crc b645b5ef sha1 82fe986f205f733c29bc00cd96ddf3ec46098fc6 ) + name "Pac-Man World 2 (USA) (Beta) (2005-04-15)" + description "Pac-Man World 2 (USA) (Beta) (2005-04-15)" + rom ( name "Pac-Man World 2 (USA) (Beta) (2005-04-15).gba" size 4194304 crc b645b5ef sha1 82fe986f205f733c29bc00cd96ddf3ec46098fc6 ) ) game ( @@ -13153,9 +13297,9 @@ game ( ) game ( - name "Pferd & Pony 2 in 1 (Germany)" - description "Pferd & Pony 2 in 1 (Germany)" - rom ( name "Pferd & Pony 2 in 1 (Germany).gba" size 8388608 crc 965391ea sha1 ec0307c819b41ff0f3f5aa9212a6e333b2242675 ) + name "Pferd & Pony - Mein Pferdehof & Pferd and Pony - Lass Uns Reiten 2 (Germany)" + description "Pferd & Pony - Mein Pferdehof & Pferd and Pony - Lass Uns Reiten 2 (Germany)" + rom ( name "Pferd & Pony - Mein Pferdehof & Pferd and Pony - Lass Uns Reiten 2 (Germany).gba" size 8388608 crc 965391ea sha1 ec0307c819b41ff0f3f5aa9212a6e333b2242675 ) ) game ( @@ -13179,7 +13323,7 @@ game ( game ( name "Phantasy Star Collection (USA)" description "Phantasy Star Collection (USA)" - rom ( name "Phantasy Star Collection (USA).gba" size 8388608 crc e5a7fe17 sha1 9f2dc591c9b1526f9f965b1c375fb4ea7101fd16 ) + rom ( name "Phantasy Star Collection (USA).gba" size 8388608 crc e5a7fe17 sha1 9f2dc591c9b1526f9f965b1c375fb4ea7101fd16 flags verified ) ) game ( @@ -13323,13 +13467,13 @@ game ( game ( name "Pirates of the Caribbean - Dead Man's Chest (USA, Europe) (En,Fr,De,Es,It)" description "Pirates of the Caribbean - Dead Man's Chest (USA, Europe) (En,Fr,De,Es,It)" - rom ( name "Pirates of the Caribbean - Dead Man's Chest (USA, Europe) (En,Fr,De,Es,It).gba" size 16777216 crc d1a67be8 sha1 3a11c76edaad8fa0845b7da55e8ed26e26e09883 ) + rom ( name "Pirates of the Caribbean - Dead Man's Chest (USA, Europe) (En,Fr,De,Es,It).gba" size 16777216 crc d1a67be8 sha1 3a11c76edaad8fa0845b7da55e8ed26e26e09883 flags verified ) ) game ( name "Pirates of the Caribbean - The Curse of the Black Pearl (USA) (En,Fr,De,Es,It)" description "Pirates of the Caribbean - The Curse of the Black Pearl (USA) (En,Fr,De,Es,It)" - rom ( name "Pirates of the Caribbean - The Curse of the Black Pearl (USA) (En,Fr,De,Es,It).gba" size 8388608 crc 8f15bf4c sha1 034e5206c3ed8275e4bbb249edf305d14948bf8f ) + rom ( name "Pirates of the Caribbean - The Curse of the Black Pearl (USA) (En,Fr,De,Es,It).gba" size 8388608 crc 8f15bf4c sha1 034e5206c3ed8275e4bbb249edf305d14948bf8f flags verified ) ) game ( @@ -13416,18 +13560,6 @@ game ( rom ( name "Pocket Dogs (USA).gba" size 8388608 crc beddc67f sha1 79a90b119ea84c5812575d20ba6107be7edbfee7 ) ) -game ( - name "Pocket Monster Diamond Tomodachi to Issho Ni! Present Campaign Senyou Cartridge (Japan) [b]" - description "Pocket Monster Diamond Tomodachi to Issho Ni! Present Campaign Senyou Cartridge (Japan) [b]" - rom ( name "Pocket Monster Diamond Tomodachi to Issho Ni! Present Campaign Senyou Cartridge (Japan) [b].gba" size 2097152 crc 393a77e8 sha1 147d115e4d5d758ec386dce7b0063321a1dfd583 flags baddump ) -) - -game ( - name "Pocket Monster Pearl Tomodachi to Issho ni! Present Campaign Senyou Cartridge (Japan) [b]" - description "Pocket Monster Pearl Tomodachi to Issho ni! Present Campaign Senyou Cartridge (Japan) [b]" - rom ( name "Pocket Monster Pearl Tomodachi to Issho ni! Present Campaign Senyou Cartridge (Japan) [b].gba" size 2097152 crc 21e120be sha1 ebc831ef1e00e204975104cfbd0fd526e0f6e435 flags baddump ) -) - game ( name "Pocket Monsters - Emerald (Japan)" description "Pocket Monsters - Emerald (Japan)" @@ -13494,6 +13626,18 @@ game ( rom ( name "Pocket Monsters - Sapphire (Japan) (Rev 1).gba" size 8388608 crc 01bd60e3 sha1 01f509671445965236ac4c6b5a354fe2f1e69f13 flags verified ) ) +game ( + name "Pocket Monsters Diamond - Tomodachi to Issho ni! - Present Campaign Senyou Cartridge (Japan) [b]" + description "Pocket Monsters Diamond - Tomodachi to Issho ni! - Present Campaign Senyou Cartridge (Japan) [b]" + rom ( name "Pocket Monsters Diamond - Tomodachi to Issho ni! - Present Campaign Senyou Cartridge (Japan) [b].gba" size 2097152 crc 393a77e8 sha1 147d115e4d5d758ec386dce7b0063321a1dfd583 flags baddump ) +) + +game ( + name "Pocket Monsters Pearl - Tomodachi to Issho ni! - Present Campaign Senyou Cartridge (Japan) [b]" + description "Pocket Monsters Pearl - Tomodachi to Issho ni! - Present Campaign Senyou Cartridge (Japan) [b]" + rom ( name "Pocket Monsters Pearl - Tomodachi to Issho ni! - Present Campaign Senyou Cartridge (Japan) [b].gba" size 2097152 crc 21e120be sha1 ebc831ef1e00e204975104cfbd0fd526e0f6e435 flags baddump ) +) + game ( name "Pocket Music (Europe) (En,Fr,De,Es,It)" description "Pocket Music (Europe) (En,Fr,De,Es,It)" @@ -13611,7 +13755,7 @@ game ( game ( name "Pokemon - Edicion Rubi (Spain)" description "Pokemon - Edicion Rubi (Spain)" - rom ( name "Pokemon - Edicion Rubi (Spain).gba" size 16777216 crc eb0729cf sha1 1f49f7289253dcbfecbc4c5ba3e67aa0652ec83c ) + rom ( name "Pokemon - Edicion Rubi (Spain).gba" size 16777216 crc eb0729cf sha1 1f49f7289253dcbfecbc4c5ba3e67aa0652ec83c flags verified ) ) game ( @@ -13651,9 +13795,9 @@ game ( ) game ( - name "Pokemon - FireRed Version (USA)" - description "Pokemon - FireRed Version (USA)" - rom ( name "Pokemon - FireRed Version (USA).gba" size 16777216 crc dd88761c sha1 41cb23d8dccc8ebd7c649cd8fbb58eeace6e2fdc flags verified ) + name "Pokemon - FireRed Version (USA, Europe)" + description "Pokemon - FireRed Version (USA, Europe)" + rom ( name "Pokemon - FireRed Version (USA, Europe).gba" size 16777216 crc dd88761c sha1 41cb23d8dccc8ebd7c649cd8fbb58eeace6e2fdc flags verified ) ) game ( @@ -13669,9 +13813,9 @@ game ( ) game ( - name "Pokemon - LeafGreen Version (USA)" - description "Pokemon - LeafGreen Version (USA)" - rom ( name "Pokemon - LeafGreen Version (USA).gba" size 16777216 crc d69c96cc sha1 574fa542ffebb14be69902d1d36f1ec0a4afd71e flags verified ) + name "Pokemon - LeafGreen Version (USA, Europe)" + description "Pokemon - LeafGreen Version (USA, Europe)" + rom ( name "Pokemon - LeafGreen Version (USA, Europe).gba" size 16777216 crc d69c96cc sha1 574fa542ffebb14be69902d1d36f1ec0a4afd71e flags verified ) ) game ( @@ -13717,9 +13861,9 @@ game ( ) game ( - name "Pokemon - Ruby Version (USA)" - description "Pokemon - Ruby Version (USA)" - rom ( name "Pokemon - Ruby Version (USA).gba" size 16777216 crc f0815ee7 sha1 f28b6ffc97847e94a6c21a63cacf633ee5c8df1e flags verified ) + name "Pokemon - Ruby Version (USA, Europe)" + description "Pokemon - Ruby Version (USA, Europe)" + rom ( name "Pokemon - Ruby Version (USA, Europe).gba" size 16777216 crc f0815ee7 sha1 f28b6ffc97847e94a6c21a63cacf633ee5c8df1e flags verified ) ) game ( @@ -13747,9 +13891,9 @@ game ( ) game ( - name "Pokemon - Sapphire Version (USA)" - description "Pokemon - Sapphire Version (USA)" - rom ( name "Pokemon - Sapphire Version (USA).gba" size 16777216 crc 554dedc4 sha1 3ccbbd45f8553c36463f13b938e833f652b793e4 flags verified ) + name "Pokemon - Sapphire Version (USA, Europe)" + description "Pokemon - Sapphire Version (USA, Europe)" + rom ( name "Pokemon - Sapphire Version (USA, Europe).gba" size 16777216 crc 554dedc4 sha1 3ccbbd45f8553c36463f13b938e833f652b793e4 flags verified ) ) game ( @@ -13986,12 +14130,6 @@ game ( rom ( name "Power Poke Dash (Japan).gba" size 8388608 crc 59e7ab4c sha1 598db3dd414ec3acb8bea47192761816e401df15 ) ) -game ( - name "Power Pro Kun Pocket 1, 2 (Japan) (Promo)" - description "Power Pro Kun Pocket 1, 2 (Japan) (Promo)" - rom ( name "Power Pro Kun Pocket 1, 2 (Japan) (Promo).gba" size 8388608 crc 46493fa1 sha1 43badcefe8cad127f8f8655ee89262517399a20b ) -) - game ( name "Power Pro Kun Pocket 1, 2 (Japan)" description "Power Pro Kun Pocket 1, 2 (Japan)" @@ -14023,9 +14161,9 @@ game ( ) game ( - name "Power Pro Kun Pocket 5 (Japan) (Promo)" - description "Power Pro Kun Pocket 5 (Japan) (Promo)" - rom ( name "Power Pro Kun Pocket 5 (Japan) (Promo).gba" size 8388608 crc 969ac691 sha1 f9fd4c8ee4f274c751551f056dc868abd0355390 ) + name "Power Pro Kun Pocket 5 (Japan) (Demo) (Kiosk)" + description "Power Pro Kun Pocket 5 (Japan) (Demo) (Kiosk)" + rom ( name "Power Pro Kun Pocket 5 (Japan) (Demo) (Kiosk).gba" size 8388608 crc 969ac691 sha1 f9fd4c8ee4f274c751551f056dc868abd0355390 ) ) game ( @@ -14040,12 +14178,6 @@ game ( rom ( name "Power Pro Kun Pocket 5 (Japan).gba" size 8388608 crc 06f0f8d4 sha1 15bc24f0f47e0317c849c1c33a36357a43def74c ) ) -game ( - name "Power Pro Kun Pocket 6 (Japan) (Promo)" - description "Power Pro Kun Pocket 6 (Japan) (Promo)" - rom ( name "Power Pro Kun Pocket 6 (Japan) (Promo).gba" size 8388608 crc 023a9a75 sha1 066d2086d776c66aa566eac211a0e9b45f1942f6 ) -) - game ( name "Power Pro Kun Pocket 6 (Japan)" description "Power Pro Kun Pocket 6 (Japan)" @@ -14155,9 +14287,9 @@ game ( ) game ( - name "Prehistorik Man (USA) (En,Fr,De,Es,It,Nl) (Beta)" - description "Prehistorik Man (USA) (En,Fr,De,Es,It,Nl) (Beta)" - rom ( name "Prehistorik Man (USA) (En,Fr,De,Es,It,Nl) (Beta).gba" size 4194304 crc abb50d93 sha1 dc789663025c8e5122622ce4fc6d0953a5656bd6 ) + name "Prehistorik Man (USA, Europe) (En,Fr,De,Es,It,Nl) (Beta)" + description "Prehistorik Man (USA, Europe) (En,Fr,De,Es,It,Nl) (Beta)" + rom ( name "Prehistorik Man (USA, Europe) (En,Fr,De,Es,It,Nl) (Beta).gba" size 4194304 crc abb50d93 sha1 dc789663025c8e5122622ce4fc6d0953a5656bd6 ) ) game ( @@ -14383,9 +14515,9 @@ game ( ) game ( - name "Racing Fever (Europe) (En,De,Es,It)" - description "Racing Fever (Europe) (En,De,Es,It)" - rom ( name "Racing Fever (Europe) (En,De,Es,It).gba" size 4194304 crc e40ec737 sha1 9bb5c036bca8f0d2e06013cac7ea6148a4f7f512 ) + name "R3D Demo (Europe) (Demo)" + description "R3D Demo (Europe) (Demo)" + rom ( name "R3D Demo (Europe) (Demo).gba" size 8388608 crc 3e7f9100 sha1 48261a7984808f573a1b07be647c02b941af6247 ) ) game ( @@ -14394,6 +14526,18 @@ game ( rom ( name "Racing Fever (France).gba" size 4194304 crc 4f61dedb sha1 67eb3051c7527692cfd7515ea367796fab3a1362 ) ) +game ( + name "Racing Fever (Europe) (En,De,Es,It)" + description "Racing Fever (Europe) (En,De,Es,It)" + rom ( name "Racing Fever (Europe) (En,De,Es,It).gba" size 4194304 crc e40ec737 sha1 9bb5c036bca8f0d2e06013cac7ea6148a4f7f512 ) +) + +game ( + name "Racing Gears Advance (USA) (Beta)" + description "Racing Gears Advance (USA) (Beta)" + rom ( name "Racing Gears Advance (USA) (Beta).gba" size 16777216 crc 86a05f93 sha1 d258526ce1dcfbed51b745a762f148c9bea01154 ) +) + game ( name "Racing Gears Advance (Europe) (En,Fr,De,Es,It)" description "Racing Gears Advance (Europe) (En,Fr,De,Es,It)" @@ -14409,7 +14553,7 @@ game ( game ( name "Rampage - Puzzle Attack (USA, Europe)" description "Rampage - Puzzle Attack (USA, Europe)" - rom ( name "Rampage - Puzzle Attack (USA, Europe).gba" size 4194304 crc 8eca2b0f sha1 35c4ace4044f2171dc90a1195ec83ae57324fc83 ) + rom ( name "Rampage - Puzzle Attack (USA, Europe).gba" size 4194304 crc 8eca2b0f sha1 35c4ace4044f2171dc90a1195ec83ae57324fc83 flags verified ) ) game ( @@ -14418,12 +14562,6 @@ game ( rom ( name "Rapala Pro Fishing (USA, Europe).gba" size 4194304 crc 964d39a7 sha1 e3df6fe7a447ab30faf6fd7d4c57e88f987a5639 ) ) -game ( - name "Ratatouille (Greece) (En)" - description "Ratatouille (Greece) (En)" - rom ( name "Ratatouille (Greece) (En).gba" size 8388608 crc 3e1711c7 sha1 58f2fa1d7e531c7345b11db268e1bbc10372bebc ) -) - game ( name "Ratatouille (USA)" description "Ratatouille (USA)" @@ -14448,6 +14586,12 @@ game ( rom ( name "Ratatouille (Europe) (En,It,Sv,No,Da).gba" size 8388608 crc 82f9c596 sha1 a5c4a636979c06670a81428c778ff76ed2c65eee ) ) +game ( + name "Ratatouille (Greece) (En)" + description "Ratatouille (Greece) (En)" + rom ( name "Ratatouille (Greece) (En).gba" size 8388608 crc 3e1711c7 sha1 58f2fa1d7e531c7345b11db268e1bbc10372bebc ) +) + game ( name "Rave Master - Special Attack Force! (USA)" description "Rave Master - Special Attack Force! (USA)" @@ -14496,18 +14640,6 @@ game ( rom ( name "Rayman 3 (Europe) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi) (Beta).gba" size 8388608 crc 8f34b14f sha1 064f6b09732f4225f5964d0e8bd386affb83e3d7 ) ) -game ( - name "Rayman 3 (Europe) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi)" - description "Rayman 3 (Europe) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi)" - rom ( name "Rayman 3 (Europe) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi).gba" size 8388608 crc 29f83314 sha1 21b7296d29486ccab68bdfa45ab24b9d370d052c ) -) - -game ( - name "Rayman 3 (USA) (En,Fr,Es)" - description "Rayman 3 (USA) (En,Fr,Es)" - rom ( name "Rayman 3 (USA) (En,Fr,Es).gba" size 8388608 crc d1613266 sha1 1a8fb488aac9af4d3d42046750bdf429f48ac391 ) -) - game ( name "Rayman 3 (USA) (Beta)" description "Rayman 3 (USA) (Beta)" @@ -14515,9 +14647,15 @@ game ( ) game ( - name "Rayman Advance (Europe) (En,Fr,De,Es,It) (Beta)" - description "Rayman Advance (Europe) (En,Fr,De,Es,It) (Beta)" - rom ( name "Rayman Advance (Europe) (En,Fr,De,Es,It) (Beta).gba" size 8388608 crc 4b1b4e02 sha1 2a473e6a0664412ead33aa9889912dc25db93714 ) + name "Rayman 3 (Europe) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi)" + description "Rayman 3 (Europe) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi)" + rom ( name "Rayman 3 (Europe) (En,Fr,De,Es,It,Nl,Sv,No,Da,Fi).gba" size 8388608 crc 29f83314 sha1 21b7296d29486ccab68bdfa45ab24b9d370d052c flags verified ) +) + +game ( + name "Rayman 3 (USA) (En,Fr,Es)" + description "Rayman 3 (USA) (En,Fr,Es)" + rom ( name "Rayman 3 (USA) (En,Fr,Es).gba" size 8388608 crc d1613266 sha1 1a8fb488aac9af4d3d42046750bdf429f48ac391 ) ) game ( @@ -14532,6 +14670,12 @@ game ( rom ( name "Rayman Advance (Europe) (En,Fr,De,Es,It).gba" size 8388608 crc b43783b4 sha1 6a32d270848ae302a3e4fb873e53b663c2db4811 ) ) +game ( + name "Rayman Advance (Europe) (En,Fr,De,Es,It) (Beta)" + description "Rayman Advance (Europe) (En,Fr,De,Es,It) (Beta)" + rom ( name "Rayman Advance (Europe) (En,Fr,De,Es,It) (Beta).gba" size 8388608 crc 4b1b4e02 sha1 2a473e6a0664412ead33aa9889912dc25db93714 ) +) + game ( name "Rayman IV (USA) (Unl)" description "Rayman IV (USA) (Unl)" @@ -14583,7 +14727,13 @@ game ( game ( name "Ready 2 Rumble Boxing - Round 2 (Europe) (En,Fr,De)" description "Ready 2 Rumble Boxing - Round 2 (Europe) (En,Fr,De)" - rom ( name "Ready 2 Rumble Boxing - Round 2 (Europe) (En,Fr,De).gba" size 4194304 crc e418e962 sha1 57d06e405b281f68800b986bd9ff1257e4466939 ) + rom ( name "Ready 2 Rumble Boxing - Round 2 (Europe) (En,Fr,De).gba" size 4194304 crc e418e962 sha1 57d06e405b281f68800b986bd9ff1257e4466939 flags verified ) +) + +game ( + name "Rebelstar - Tactical Command (Europe) (En,Fr,De,Es,It) (Beta)" + description "Rebelstar - Tactical Command (Europe) (En,Fr,De,Es,It) (Beta)" + rom ( name "Rebelstar - Tactical Command (Europe) (En,Fr,De,Es,It) (Beta).gba" size 4194304 crc 2189021c sha1 b6e2167272a68bd4e3833cccd68929d50a27a426 ) ) game ( @@ -14598,12 +14748,6 @@ game ( rom ( name "Rebelstar - Tactical Command (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc e448c5d4 sha1 669c47e62f2388ce0ba676f3dc641b5ad2ddb5c5 flags verified ) ) -game ( - name "Rebelstar - Tactical Command (Europe) (En,Fr,De,Es,It) (Beta)" - description "Rebelstar - Tactical Command (Europe) (En,Fr,De,Es,It) (Beta)" - rom ( name "Rebelstar - Tactical Command (Europe) (En,Fr,De,Es,It) (Beta).gba" size 4194304 crc 2189021c sha1 b6e2167272a68bd4e3833cccd68929d50a27a426 ) -) - game ( name "Recca no Honoo - The Game (Japan)" description "Recca no Honoo - The Game (Japan)" @@ -14635,15 +14779,15 @@ game ( ) game ( - name "Ren Zhe Shen Gui 2 (Taiwan) (Unl)" - description "Ren Zhe Shen Gui 2 (Taiwan) (Unl)" - rom ( name "Ren Zhe Shen Gui 2 (Taiwan) (Unl).gba" size 33554432 crc f4641711 sha1 efe55f22035576ebe0be6012c1b9d32238bb0ca2 ) + name "Renzhe Shen Gui 2 (Taiwan) (Unl)" + description "Renzhe Shen Gui 2 (Taiwan) (Unl)" + rom ( name "Renzhe Shen Gui 2 (Taiwan) (Unl).gba" size 33554432 crc f4641711 sha1 efe55f22035576ebe0be6012c1b9d32238bb0ca2 ) ) game ( name "Rescue Heroes - Billy Blazes! (USA)" description "Rescue Heroes - Billy Blazes! (USA)" - rom ( name "Rescue Heroes - Billy Blazes! (USA).gba" size 4194304 crc 8111261c sha1 481fd8ab9e8e980039e244ba31a6dff057235210 ) + rom ( name "Rescue Heroes - Billy Blazes! (USA).gba" size 4194304 crc 8111261c sha1 481fd8ab9e8e980039e244ba31a6dff057235210 flags verified ) ) game ( @@ -14805,7 +14949,7 @@ game ( game ( name "Robots (Europe) (En,Fr,De,Es,It)" description "Robots (Europe) (En,Fr,De,Es,It)" - rom ( name "Robots (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc 2acbdb64 sha1 26be826817f35433b3609d13ee75a3872e94d616 ) + rom ( name "Robots (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc 2acbdb64 sha1 26be826817f35433b3609d13ee75a3872e94d616 flags verified ) ) game ( @@ -14886,6 +15030,18 @@ game ( rom ( name "Rockman & Forte (Japan).gba" size 8388608 crc ce2b48c4 sha1 f8eb056745e8b58c1ef4bfef995c77a27aadc57a flags verified ) ) +game ( + name "Rockman EXE - Battle Chip GP (Japan)" + description "Rockman EXE - Battle Chip GP (Japan)" + rom ( name "Rockman EXE - Battle Chip GP (Japan).gba" size 8388608 crc 9217fb18 sha1 f39992851257a1567f3bfca81dd269f37469bb67 ) +) + +game ( + name "Rockman EXE - Battle Chip GP (Japan) (Virtual Console)" + description "Rockman EXE - Battle Chip GP (Japan) (Virtual Console)" + rom ( name "Rockman EXE - Battle Chip GP (Japan) (Virtual Console).gba" size 8388608 crc f37bf038 sha1 9816fdb5f076c601287f5dfb3c7218150a75f2f6 ) +) + game ( name "Rockman EXE 4 - Tournament Blue Moon (Japan) (Rev 1) (Virtual Console)" description "Rockman EXE 4 - Tournament Blue Moon (Japan) (Rev 1) (Virtual Console)" @@ -14976,18 +15132,6 @@ game ( rom ( name "Rockman EXE 6 - Dennoujuu Gregar (Japan) (Virtual Console).gba" size 8388608 crc d0fdbefb sha1 678787c7d7cbee119eb524211487fb5dcd01426b ) ) -game ( - name "Rockman EXE Battle Chip GP (Japan) (Virtual Console)" - description "Rockman EXE Battle Chip GP (Japan) (Virtual Console)" - rom ( name "Rockman EXE Battle Chip GP (Japan) (Virtual Console).gba" size 8388608 crc f37bf038 sha1 9816fdb5f076c601287f5dfb3c7218150a75f2f6 ) -) - -game ( - name "Rockman EXE Battle Chip GP (Japan)" - description "Rockman EXE Battle Chip GP (Japan)" - rom ( name "Rockman EXE Battle Chip GP (Japan).gba" size 8388608 crc 9217fb18 sha1 f39992851257a1567f3bfca81dd269f37469bb67 ) -) - game ( name "Rockman Zero (Japan) (Virtual Console)" description "Rockman Zero (Japan) (Virtual Console)" @@ -15165,7 +15309,7 @@ game ( game ( name "Samurai Jack - The Amulet of Time (USA, Europe)" description "Samurai Jack - The Amulet of Time (USA, Europe)" - rom ( name "Samurai Jack - The Amulet of Time (USA, Europe).gba" size 8388608 crc b250366c sha1 27a5604b4aa89d2fec216aeac2b99e76165f531f ) + rom ( name "Samurai Jack - The Amulet of Time (USA, Europe).gba" size 8388608 crc b250366c sha1 27a5604b4aa89d2fec216aeac2b99e76165f531f flags verified ) ) game ( @@ -15195,7 +15339,7 @@ game ( game ( name "Santa Claus Jr. Advance (Europe)" description "Santa Claus Jr. Advance (Europe)" - rom ( name "Santa Claus Jr. Advance (Europe).gba" size 4194304 crc fe3e6769 sha1 60d6b482c4be9fb61a172f3864c9b35afc275980 ) + rom ( name "Santa Claus Jr. Advance (Europe).gba" size 4194304 crc fe3e6769 sha1 60d6b482c4be9fb61a172f3864c9b35afc275980 flags verified ) ) game ( @@ -15594,12 +15738,6 @@ game ( rom ( name "Sheep (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 75a92925 sha1 cb200350b4dc01dcdb0a52af11286aaca15cd786 ) ) -game ( - name "Shi Rui Ke II - Yu Mei Ren (Taiwan) (Unl)" - description "Shi Rui Ke II - Yu Mei Ren (Taiwan) (Unl)" - rom ( name "Shi Rui Ke II - Yu Mei Ren (Taiwan) (Unl).gba" size 33554432 crc 16d294d1 sha1 8033cc6c2c7f929844c39304b7faaf90115f1abc ) -) - game ( name "Shifting Gears - Road Trip (USA)" description "Shifting Gears - Road Trip (USA)" @@ -15618,18 +15756,18 @@ game ( rom ( name "Shikakui Atama o Maruku Suru. Advance - Kokugo, Sansuu, Shakai, Rika (Japan).gba" size 4194304 crc 07695a6c sha1 c1cf1478d79daf6c2717df6f19cad806bd7f6429 ) ) -game ( - name "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan) (Rev 1)" - description "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan) (Rev 1)" - rom ( name "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan) (Rev 1).gba" size 4194304 crc 5546b016 sha1 c7c94199bac6a1f69356413031b5a49cb30c93d1 ) -) - game ( name "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan)" description "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan)" rom ( name "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan).gba" size 4194304 crc f29e6e2d sha1 b2f100650af2c8d6ec02c0a71774eeba22d6be2f ) ) +game ( + name "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan) (Rev 1)" + description "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan) (Rev 1)" + rom ( name "Shimura Ken no Baka Tonosama - Bakushou Tenka Touitsu Game (Japan) (Rev 1).gba" size 4194304 crc 5546b016 sha1 c7c94199bac6a1f69356413031b5a49cb30c93d1 ) +) + game ( name "Shin Bokura no Taiyou - Gyakushuu no Sabata (Japan)" description "Shin Bokura no Taiyou - Gyakushuu no Sabata (Japan)" @@ -15660,6 +15798,30 @@ game ( rom ( name "Shin Megami Tensei (Japan).gba" size 8388608 crc b857c3c5 sha1 7851f7f061693d664672f47f2d2d714829cf52ac ) ) +game ( + name "Shin Megami Tensei - Devil Children - Honoo no Sho (Japan)" + description "Shin Megami Tensei - Devil Children - Honoo no Sho (Japan)" + rom ( name "Shin Megami Tensei - Devil Children - Honoo no Sho (Japan).gba" size 8388608 crc 9a0903bc sha1 8c29d6921e7db8b0e71094d32e32066ac13c500d ) +) + +game ( + name "Shin Megami Tensei - Devil Children - Koori no Sho (Japan)" + description "Shin Megami Tensei - Devil Children - Koori no Sho (Japan)" + rom ( name "Shin Megami Tensei - Devil Children - Koori no Sho (Japan).gba" size 8388608 crc ad80d5f9 sha1 8470a93bded96270d1403bb485f32c42edf1462a flags verified ) +) + +game ( + name "Shin Megami Tensei - Devil Children - Messiah Riser (Japan)" + description "Shin Megami Tensei - Devil Children - Messiah Riser (Japan)" + rom ( name "Shin Megami Tensei - Devil Children - Messiah Riser (Japan).gba" size 8388608 crc 0ec98c51 sha1 9387947baa410c748ec5fab1ce9a07ae8047e398 ) +) + +game ( + name "Shin Megami Tensei - Devil Children - Puzzle de Call! (Japan)" + description "Shin Megami Tensei - Devil Children - Puzzle de Call! (Japan)" + rom ( name "Shin Megami Tensei - Devil Children - Puzzle de Call! (Japan).gba" size 4194304 crc ea8a185a sha1 e70fc457b56c8e27ab63e491706b88232f598452 ) +) + game ( name "Shin Megami Tensei Devil Children - Hikari no Sho (Japan)" description "Shin Megami Tensei Devil Children - Hikari no Sho (Japan)" @@ -15667,27 +15829,9 @@ game ( ) game ( - name "Shin Megami Tensei Devil Children - Honoo no Sho (Japan)" - description "Shin Megami Tensei Devil Children - Honoo no Sho (Japan)" - rom ( name "Shin Megami Tensei Devil Children - Honoo no Sho (Japan).gba" size 8388608 crc 9a0903bc sha1 8c29d6921e7db8b0e71094d32e32066ac13c500d ) -) - -game ( - name "Shin Megami Tensei Devil Children - Koori no Sho (Japan)" - description "Shin Megami Tensei Devil Children - Koori no Sho (Japan)" - rom ( name "Shin Megami Tensei Devil Children - Koori no Sho (Japan).gba" size 8388608 crc ad80d5f9 sha1 8470a93bded96270d1403bb485f32c42edf1462a flags verified ) -) - -game ( - name "Shin Megami Tensei Devil Children - Messiah Riser (Japan)" - description "Shin Megami Tensei Devil Children - Messiah Riser (Japan)" - rom ( name "Shin Megami Tensei Devil Children - Messiah Riser (Japan).gba" size 8388608 crc 0ec98c51 sha1 9387947baa410c748ec5fab1ce9a07ae8047e398 ) -) - -game ( - name "Shin Megami Tensei Devil Children - Puzzle de Call! (Japan)" - description "Shin Megami Tensei Devil Children - Puzzle de Call! (Japan)" - rom ( name "Shin Megami Tensei Devil Children - Puzzle de Call! (Japan).gba" size 4194304 crc ea8a185a sha1 e70fc457b56c8e27ab63e491706b88232f598452 ) + name "Shin Megami Tensei Devil Children - Yami no Sho (Japan)" + description "Shin Megami Tensei Devil Children - Yami no Sho (Japan)" + rom ( name "Shin Megami Tensei Devil Children - Yami no Sho (Japan).gba" size 8388608 crc e0e153b7 sha1 236fd0e7c14f5d6db7fa2083766324341b9e5b39 ) ) game ( @@ -15696,12 +15840,6 @@ game ( rom ( name "Shin Megami Tensei Devil Children - Yami no Sho (Japan) (Beta).gba" size 8388608 crc 81f30ebc sha1 c2db9b977758b4f350340f981baffcc0bba0c6c2 ) ) -game ( - name "Shin Megami Tensei Devil Children - Yami no Sho (Japan)" - description "Shin Megami Tensei Devil Children - Yami no Sho (Japan)" - rom ( name "Shin Megami Tensei Devil Children - Yami no Sho (Japan).gba" size 8388608 crc e0e153b7 sha1 236fd0e7c14f5d6db7fa2083766324341b9e5b39 ) -) - game ( name "Shin Megami Tensei II (Japan)" description "Shin Megami Tensei II (Japan)" @@ -15714,18 +15852,18 @@ game ( rom ( name "Shin Nihon Pro Wrestling - Toukon Retsuden Advance (Japan).gba" size 8388608 crc 9f0b8b79 sha1 8c7d119f4e9ac6dab2c308d1dfad5eabb0837014 ) ) -game ( - name "Shin Sangoku Musou Advance (Japan)" - description "Shin Sangoku Musou Advance (Japan)" - rom ( name "Shin Sangoku Musou Advance (Japan).gba" size 16777216 crc fe1be6c1 sha1 677cd51c1ecdde73a6f40ec2cd30d35c77db459a ) -) - game ( name "Shin Sangoku Musou Advance (Japan) (Rev 1)" description "Shin Sangoku Musou Advance (Japan) (Rev 1)" rom ( name "Shin Sangoku Musou Advance (Japan) (Rev 1).gba" size 16777216 crc 7ceded10 sha1 be2bfa709478dc482e80fb476c1ee846b703bcbb ) ) +game ( + name "Shin Sangoku Musou Advance (Japan)" + description "Shin Sangoku Musou Advance (Japan)" + rom ( name "Shin Sangoku Musou Advance (Japan).gba" size 16777216 crc fe1be6c1 sha1 677cd51c1ecdde73a6f40ec2cd30d35c77db459a ) +) + game ( name "Shingata Medarot - Kabuto Version (Japan)" description "Shingata Medarot - Kabuto Version (Japan)" @@ -15828,6 +15966,12 @@ game ( rom ( name "Shiren Monsters - Netsal (Japan) (Demo) (Kiosk, GameCube).gba" size 16777216 crc 25b0b122 sha1 2ebd243709a9b0af16c6c0b0404fb02999eefffd flags verified ) ) +game ( + name "Shiruike II - Yu Meiren (Taiwan) (Unl)" + description "Shiruike II - Yu Meiren (Taiwan) (Unl)" + rom ( name "Shiruike II - Yu Meiren (Taiwan) (Unl).gba" size 33554432 crc 16d294d1 sha1 8033cc6c2c7f929844c39304b7faaf90115f1abc ) +) + game ( name "Shrek - Hassle at the Castle (USA) (En,Fr,De,Es,It,Nl)" description "Shrek - Hassle at the Castle (USA) (En,Fr,De,Es,It,Nl)" @@ -15843,7 +15987,7 @@ game ( game ( name "Shrek - Reekin' Havoc (USA) (En,Fr,De,Es,It,Nl)" description "Shrek - Reekin' Havoc (USA) (En,Fr,De,Es,It,Nl)" - rom ( name "Shrek - Reekin' Havoc (USA) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 63f49ba9 sha1 af0bc8338e49be78ce6e85b59d1ddfca8a10f290 ) + rom ( name "Shrek - Reekin' Havoc (USA) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 63f49ba9 sha1 af0bc8338e49be78ce6e85b59d1ddfca8a10f290 flags verified ) ) game ( @@ -16206,6 +16350,12 @@ game ( rom ( name "Soccer Kid (USA, Europe).gba" size 4194304 crc d3485f5a sha1 4e7dc4d47ef064131990fe3ebb340edeaed321ef flags verified ) ) +game ( + name "Soccer Mania (USA, Europe) (En,Fr,De,Es,It,Nl,Sv,Da)" + description "Soccer Mania (USA, Europe) (En,Fr,De,Es,It,Nl,Sv,Da)" + rom ( name "Soccer Mania (USA, Europe) (En,Fr,De,Es,It,Nl,Sv,Da).gba" size 8388608 crc 73ef3a35 sha1 05799b99395aba04dfe5ae8af019e4d70cb8e61b ) +) + game ( name "Sonic 3 - Fighter Sonic (USA) (Unl)" description "Sonic 3 - Fighter Sonic (USA) (Unl)" @@ -16273,9 +16423,9 @@ game ( ) game ( - name "Sonic Advance 2 (USA) (Beta)" - description "Sonic Advance 2 (USA) (Beta)" - rom ( name "Sonic Advance 2 (USA) (Beta).gba" size 16777216 crc 95ab3867 sha1 3368642fc4157824af63367e2a685b7d6ee9b09d ) + name "Sonic Advance 2 (USA) (Beta) (2002-10-25)" + description "Sonic Advance 2 (USA) (Beta) (2002-10-25)" + rom ( name "Sonic Advance 2 (USA) (Beta) (2002-10-25).gba" size 16777216 crc 95ab3867 sha1 3368642fc4157824af63367e2a685b7d6ee9b09d ) ) game ( @@ -16615,15 +16765,15 @@ game ( ) game ( - name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08) (05-16)" - description "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08) (05-16)" - rom ( name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08) (05-16).gba" size 315572 crc 49754ee4 sha1 d14787d2af88af2907f71744d4b4a933b38ee31a ) + name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08T051624)" + description "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08T051624)" + rom ( name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08T051624).gba" size 315572 crc 49754ee4 sha1 d14787d2af88af2907f71744d4b4a933b38ee31a ) ) game ( - name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08) (07-47)" - description "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08) (07-47)" - rom ( name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08) (07-47).gba" size 342160 crc b83d7e0d sha1 bc0833743e90b6cff8f51906e12f15748fed4ed6 ) + name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08T074756)" + description "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08T074756)" + rom ( name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-01-08T074756).gba" size 342160 crc b83d7e0d sha1 bc0833743e90b6cff8f51906e12f15748fed4ed6 ) ) game ( @@ -16669,15 +16819,15 @@ game ( ) game ( - name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06) (11-34)" - description "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06) (11-34)" - rom ( name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06) (11-34).gba" size 2470464 crc 40cc2e8a sha1 071e9a6c09ccd6e52d1d6cc40a0ebea3e8dd80c3 ) + name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06T113400)" + description "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06T113400)" + rom ( name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06T113400).gba" size 2470464 crc 40cc2e8a sha1 071e9a6c09ccd6e52d1d6cc40a0ebea3e8dd80c3 ) ) game ( - name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06) (11-39)" - description "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06) (11-39)" - rom ( name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06) (11-39).gba" size 3011936 crc ff4f1ee7 sha1 909a6cc0930818de20489cfcdd967280b403272b ) + name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06T113900)" + description "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06T113900)" + rom ( name "SpongeBob SquarePants - SuperSponge (USA, Europe) (Beta) (2001-06-06T113900).gba" size 3011936 crc ff4f1ee7 sha1 909a6cc0930818de20489cfcdd967280b403272b ) ) game ( @@ -16947,7 +17097,7 @@ game ( game ( name "Star Wars - The New Droid Army (Europe) (En,Fr,De,Es)" description "Star Wars - The New Droid Army (Europe) (En,Fr,De,Es)" - rom ( name "Star Wars - The New Droid Army (Europe) (En,Fr,De,Es).gba" size 8388608 crc 677854af sha1 4966ce40938df2b0ffd262c6aa6c4461b9fa8922 ) + rom ( name "Star Wars - The New Droid Army (Europe) (En,Fr,De,Es).gba" size 8388608 crc 677854af sha1 4966ce40938df2b0ffd262c6aa6c4461b9fa8922 flags verified ) ) game ( @@ -16993,9 +17143,9 @@ game ( ) game ( - name "Starsky & Hutch (USA) (Beta)" - description "Starsky & Hutch (USA) (Beta)" - rom ( name "Starsky & Hutch (USA) (Beta).gba" size 4231928 crc ab142d2e sha1 861066ddf8b52be194b30cbb70e959989f0a2f76 ) + name "Starsky & Hutch (USA) (Beta) (2003-02-11)" + description "Starsky & Hutch (USA) (Beta) (2003-02-11)" + rom ( name "Starsky & Hutch (USA) (Beta) (2003-02-11).gba" size 4231928 crc ab142d2e sha1 861066ddf8b52be194b30cbb70e959989f0a2f76 ) ) game ( @@ -17004,12 +17154,6 @@ game ( rom ( name "Steel Empire (Europe).gba" size 4194304 crc cb91922e sha1 b280937636a533020bf42591cc246b221a16f94e ) ) -game ( - name "Steven Gerrard's Total Soccer 2002 (Europe) (En,Fr,De,Es,It,Nl)" - description "Steven Gerrard's Total Soccer 2002 (Europe) (En,Fr,De,Es,It,Nl)" - rom ( name "Steven Gerrard's Total Soccer 2002 (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc b94ddc93 sha1 1f37abe2a70ff0027abe714cb4cd93f168925753 flags verified ) -) - game ( name "Strawberry Shortcake - Ice Cream Island - Riding Camp (Europe) (En,Fr,De,Es,It,Nl,Pt,Da)" description "Strawberry Shortcake - Ice Cream Island - Riding Camp (Europe) (En,Fr,De,Es,It,Nl,Pt,Da)" @@ -17205,7 +17349,7 @@ game ( game ( name "Super Bust-A-Move (Europe) (En,Fr,De,Es,It)" description "Super Bust-A-Move (Europe) (En,Fr,De,Es,It)" - rom ( name "Super Bust-A-Move (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 6bfd2915 sha1 e4dd4bb3691444fff595a926b2c06313f2e69751 ) + rom ( name "Super Bust-A-Move (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 6bfd2915 sha1 e4dd4bb3691444fff595a926b2c06313f2e69751 flags verified ) ) game ( @@ -17388,6 +17532,12 @@ game ( rom ( name "Super Mario Advance 3 - Yoshi's Island + Mario Brothers (Japan) (Virtual Console).gba" size 4194304 crc 10f9eda4 sha1 fa7486b4721925074ba1fde9b800ef85a5b5954d ) ) +game ( + name "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan) (Rev 2) (Switch Online)" + description "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan) (Rev 2) (Switch Online)" + rom ( name "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan) (Rev 2) (Switch Online).gba" size 8388608 crc aa312980 sha1 b65217bb411bf1e9aff50bf0940c4a0021789f59 ) +) + game ( name "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan) (Rev 2)" description "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan) (Rev 2)" @@ -17409,7 +17559,7 @@ game ( game ( name "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan)" description "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan)" - rom ( name "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan).gba" size 4194304 crc f3c87306 sha1 19f7928bc4ffd733d71884dae9ad9b7f4007d38d ) + rom ( name "Super Mario Advance 4 - Super Mario 3 + Mario Brothers (Japan).gba" size 4194304 crc f3c87306 sha1 19f7928bc4ffd733d71884dae9ad9b7f4007d38d flags verified ) ) game ( @@ -17448,6 +17598,30 @@ game ( rom ( name "Super Mario Advance 4 - Super Mario Bros. 3 (Europe) (En,Fr,De,Es,It) (Rev 1) (Virtual Console).gba" size 8388608 crc d4f45b01 sha1 00667ce3da4bfee3182c4445ac2f5483870be97c flags verified ) ) +game ( + name "Super Mario Advance 4 - Super Mario Bros. 3 (USA) (Rev 1) (Switch Online)" + description "Super Mario Advance 4 - Super Mario Bros. 3 (USA) (Rev 1) (Switch Online)" + rom ( name "Super Mario Advance 4 - Super Mario Bros. 3 (USA) (Rev 1) (Switch Online).gba" size 8388608 crc 22e12d0e sha1 82fa5a6cf09415c2e262931488841b78a524e2c3 flags verified ) +) + +game ( + name "Super Mario Advance 4 - Super Mario Bros. 3 (Europe) (En,Fr,De,Es,It) (Rev 1) (Switch Online)" + description "Super Mario Advance 4 - Super Mario Bros. 3 (Europe) (En,Fr,De,Es,It) (Rev 1) (Switch Online)" + rom ( name "Super Mario Advance 4 - Super Mario Bros. 3 (Europe) (En,Fr,De,Es,It) (Rev 1) (Switch Online).gba" size 8388608 crc e3847e32 sha1 cdb79f7926fb61ee7f13ee4cbd61ebe3fb01ba69 flags verified ) +) + +game ( + name "Super Mario Ball (Europe) (Virtual Console)" + description "Super Mario Ball (Europe) (Virtual Console)" + rom ( name "Super Mario Ball (Europe) (Virtual Console).gba" size 8388608 crc 3b15e886 sha1 e3ee63bf92340a50085cb905e167c03f3c60b72a ) +) + +game ( + name "Super Mario Ball (Japan) (Demo) (Kiosk, GameCube)" + description "Super Mario Ball (Japan) (Demo) (Kiosk, GameCube)" + rom ( name "Super Mario Ball (Japan) (Demo) (Kiosk, GameCube).gba" size 8388608 crc 65b1d9cb sha1 f8a356c0ba5e216c8ba408474fd74553d284b9b8 flags verified ) +) + game ( name "Super Mario Ball (Japan) (Virtual Console)" description "Super Mario Ball (Japan) (Virtual Console)" @@ -17466,18 +17640,6 @@ game ( rom ( name "Super Mario Ball (Europe).gba" size 8388608 crc 3b035681 sha1 d53fbc63e08c15bfdc045b5664fe24e8e2718469 flags verified ) ) -game ( - name "Super Mario Ball (Europe) (Virtual Console)" - description "Super Mario Ball (Europe) (Virtual Console)" - rom ( name "Super Mario Ball (Europe) (Virtual Console).gba" size 8388608 crc 3b15e886 sha1 e3ee63bf92340a50085cb905e167c03f3c60b72a ) -) - -game ( - name "Super Mario Ball (Japan) (Demo) (Kiosk, GameCube)" - description "Super Mario Ball (Japan) (Demo) (Kiosk, GameCube)" - rom ( name "Super Mario Ball (Japan) (Demo) (Kiosk, GameCube).gba" size 8388608 crc 65b1d9cb sha1 f8a356c0ba5e216c8ba408474fd74553d284b9b8 flags verified ) -) - game ( name "Super Mario Bros. (Japan) (Hot Mario Campaign)" description "Super Mario Bros. (Japan) (Hot Mario Campaign)" @@ -17805,7 +17967,7 @@ game ( game ( name "Tales of the World - Narikiri Dungeon 2 (Japan)" description "Tales of the World - Narikiri Dungeon 2 (Japan)" - rom ( name "Tales of the World - Narikiri Dungeon 2 (Japan).gba" size 8388608 crc 231b9fca sha1 2feb4cff9485c68758c1fac847c6eb907e747a01 ) + rom ( name "Tales of the World - Narikiri Dungeon 2 (Japan).gba" size 8388608 crc 231b9fca sha1 2feb4cff9485c68758c1fac847c6eb907e747a01 flags verified ) ) game ( @@ -18049,9 +18211,9 @@ game ( ) game ( - name "Texas Hold 'em Poker (USA, Europe)" - description "Texas Hold 'em Poker (USA, Europe)" - rom ( name "Texas Hold 'em Poker (USA, Europe).gba" size 4194304 crc 78b59aac sha1 5fcc9958210d8bde94a429516b15ca669427fe6d ) + name "Texas Hold 'em Poker (USA)" + description "Texas Hold 'em Poker (USA)" + rom ( name "Texas Hold 'em Poker (USA).gba" size 4194304 crc 78b59aac sha1 5fcc9958210d8bde94a429516b15ca669427fe6d ) ) game ( @@ -18099,7 +18261,7 @@ game ( game ( name "Three-in-One Pack - Sorry! + Aggravation + Scrabble Junior (USA)" description "Three-in-One Pack - Sorry! + Aggravation + Scrabble Junior (USA)" - rom ( name "Three-in-One Pack - Sorry! + Aggravation + Scrabble Junior (USA).gba" size 4194304 crc 111eb61a sha1 cbc5bb55d4951e884e65ea2cab36957ee51aaadb ) + rom ( name "Three-in-One Pack - Sorry! + Aggravation + Scrabble Junior (USA).gba" size 4194304 crc 111eb61a sha1 cbc5bb55d4951e884e65ea2cab36957ee51aaadb flags verified ) ) game ( @@ -18150,12 +18312,6 @@ game ( rom ( name "Tim Burton's The Nightmare Before Christmas - The Pumpkin King (USA, Europe) (En,Fr,De,Es,It).gba" size 8388608 crc 3c247c14 sha1 8e713fa3e3cc5aa8c1836fba9d81bbf80de20638 ) ) -game ( - name "Tiny Chao Garden (USA) (Ja) (Phantasy Star Online Episode I & II)" - description "Tiny Chao Garden (USA) (Ja) (Phantasy Star Online Episode I & II)" - rom ( name "Tiny Chao Garden (USA) (Ja) (Phantasy Star Online Episode I & II).gba" size 182840 crc c523504f sha1 55c9812e2164a618575c6dcd669fd993b60585f5 flags verified ) -) - game ( name "Tiny Toon Adventures - Buster's Bad Dream (Europe) (En,Fr,De,Es,It)" description "Tiny Toon Adventures - Buster's Bad Dream (Europe) (En,Fr,De,Es,It)" @@ -18168,36 +18324,36 @@ game ( rom ( name "Tiny Toon Adventures - Scary Dreams (USA).gba" size 4194304 crc fcd7a7c0 sha1 305c6cf66e47508deee4b9fc12c8917302f2ed39 ) ) -game ( - name "Tiny Toon Adventures - Wacky Stackers (USA)" - description "Tiny Toon Adventures - Wacky Stackers (USA)" - rom ( name "Tiny Toon Adventures - Wacky Stackers (USA).gba" size 4194304 crc e03c633e sha1 fd9d10006bd29945ac16c6f88608a27e15f8f7da ) -) - game ( name "Tiny Toon Adventures - Wacky Stackers (Europe) (En,Fr,De,Es,It)" description "Tiny Toon Adventures - Wacky Stackers (Europe) (En,Fr,De,Es,It)" rom ( name "Tiny Toon Adventures - Wacky Stackers (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 4ac770b6 sha1 cd9d810b29070d9f37df219bfd7e52ce9d52d2b0 ) ) +game ( + name "Tiny Toon Adventures - Wacky Stackers (USA)" + description "Tiny Toon Adventures - Wacky Stackers (USA)" + rom ( name "Tiny Toon Adventures - Wacky Stackers (USA).gba" size 4194304 crc e03c633e sha1 fd9d10006bd29945ac16c6f88608a27e15f8f7da ) +) + game ( name "Tir et But - Edition Champions du Monde (France)" description "Tir et But - Edition Champions du Monde (France)" rom ( name "Tir et But - Edition Champions du Monde (France).gba" size 4194304 crc 7782a204 sha1 e097aa37ab67146d544ae1438a19f8cd9e3a8f7c ) ) -game ( - name "Titeuf - Mega Compet (France) (Beta)" - description "Titeuf - Mega Compet (France) (Beta)" - rom ( name "Titeuf - Mega Compet (France) (Beta).gba" size 4194304 crc c9be7fcf sha1 1ed43c7419b22b1147bd7e3198de52f4c8d3003d ) -) - game ( name "Titeuf - Mega Compet (France)" description "Titeuf - Mega Compet (France)" rom ( name "Titeuf - Mega Compet (France).gba" size 4194304 crc ffd0a045 sha1 ff59a9810adc3270295763282c9d86d549f58a6f ) ) +game ( + name "Titeuf - Mega Compet (France) (Beta)" + description "Titeuf - Mega Compet (France) (Beta)" + rom ( name "Titeuf - Mega Compet (France) (Beta).gba" size 4194304 crc c9be7fcf sha1 1ed43c7419b22b1147bd7e3198de52f4c8d3003d ) +) + game ( name "Titeuf - Ze Gag Machine (France)" description "Titeuf - Ze Gag Machine (France)" @@ -18249,7 +18405,7 @@ game ( game ( name "Tokyo Xtreme Racer Advance (Europe) (En,Fr,De,Es,It)" description "Tokyo Xtreme Racer Advance (Europe) (En,Fr,De,Es,It)" - rom ( name "Tokyo Xtreme Racer Advance (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc bd5f5f4c sha1 15a306794efdd64651387c1447ae4713892e4b87 ) + rom ( name "Tokyo Xtreme Racer Advance (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc bd5f5f4c sha1 15a306794efdd64651387c1447ae4713892e4b87 flags verified ) ) game ( @@ -18306,18 +18462,18 @@ game ( rom ( name "Tom Clancy's Splinter Cell (USA) (En,Fr,Es).gba" size 8388608 crc 308ba69c sha1 dbd089cbdd79c26b2f0b651f74df59b6cbfafbcb ) ) -game ( - name "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl) (Beta)" - description "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl) (Beta)" - rom ( name "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl) (Beta).gba" size 16777216 crc e07ba799 sha1 3a55f6ed66a3ec7c019221fa2660e2f85954ebae ) -) - game ( name "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl)" description "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl)" rom ( name "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl).gba" size 8388608 crc 7ecd29ce sha1 d623cbd1019665eee78846a2a60d17adaf8ec0fe flags verified ) ) +game ( + name "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl) (Beta)" + description "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl) (Beta)" + rom ( name "Tom Clancy's Splinter Cell (Europe) (En,Fr,De,Es,It,Nl) (Beta).gba" size 16777216 crc e07ba799 sha1 3a55f6ed66a3ec7c019221fa2660e2f85954ebae ) +) + game ( name "Tom Clancy's Splinter Cell - Pandora Tomorrow (Europe) (En,Fr,De,Es,It,Nl)" description "Tom Clancy's Splinter Cell - Pandora Tomorrow (Europe) (En,Fr,De,Es,It,Nl)" @@ -18435,7 +18591,7 @@ game ( game ( name "Tony Hawk's Underground 2 (USA, Europe)" description "Tony Hawk's Underground 2 (USA, Europe)" - rom ( name "Tony Hawk's Underground 2 (USA, Europe).gba" size 8388608 crc bf864083 sha1 c684d839c32515a99e62b183b607e2259120cfc9 ) + rom ( name "Tony Hawk's Underground 2 (USA, Europe).gba" size 8388608 crc bf864083 sha1 c684d839c32515a99e62b183b607e2259120cfc9 flags verified ) ) game ( @@ -18498,12 +18654,24 @@ game ( rom ( name "Top Spin 2 (Europe) (En,Fr,De,Es,It).gba" size 16777216 crc a8e8618b sha1 192bb233840382e410fd242f6b6bfbd33ab24d38 ) ) +game ( + name "Total Soccer 2002 (Europe) (En,Fr,De,Es,It,Nl)" + description "Total Soccer 2002 (Europe) (En,Fr,De,Es,It,Nl)" + rom ( name "Total Soccer 2002 (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc b94ddc93 sha1 1f37abe2a70ff0027abe714cb4cd93f168925753 flags verified ) +) + game ( name "Total Soccer Advance (Japan)" description "Total Soccer Advance (Japan)" rom ( name "Total Soccer Advance (Japan).gba" size 4194304 crc 28c3e211 sha1 3a45d03ac6b098b70641c222f684abd13ef02500 ) ) +game ( + name "Total Soccer Manager (Europe) (En,Fr,De,Es,It,Nl)" + description "Total Soccer Manager (Europe) (En,Fr,De,Es,It,Nl)" + rom ( name "Total Soccer Manager (Europe) (En,Fr,De,Es,It,Nl).gba" size 4194304 crc 92f99295 sha1 a870e1321a8ad3317cd695fcc0c713441c25919f ) +) + game ( name "Totally Spies! (Europe) (En,Fr,De,Es,It,Nl)" description "Totally Spies! (Europe) (En,Fr,De,Es,It,Nl)" @@ -18577,21 +18745,15 @@ game ( ) game ( - name "Toyrobo Force (Japan)" - description "Toyrobo Force (Japan)" - rom ( name "Toyrobo Force (Japan).gba" size 4194304 crc 227fc16b sha1 6ef30145099d09cf1a1f2b3bc6618a985dabdaae ) + name "Toy Robo Force (Japan)" + description "Toy Robo Force (Japan)" + rom ( name "Toy Robo Force (Japan).gba" size 4194304 crc 227fc16b sha1 6ef30145099d09cf1a1f2b3bc6618a985dabdaae ) ) game ( - name "Travis Pastrana's Pro MotoX (USA, Europe) (Proto) (2002-12-12)" - description "Travis Pastrana's Pro MotoX (USA, Europe) (Proto) (2002-12-12)" - rom ( name "Travis Pastrana's Pro MotoX (USA, Europe) (Proto) (2002-12-12).gba" size 4194304 crc ecfe5f96 sha1 64e356f495fd58a862f272124798ecf61e20cfb7 ) -) - -game ( - name "Treasure Planet (Europe) (En,Fr,De,Es,It,Nl) (Rev 1)" - description "Treasure Planet (Europe) (En,Fr,De,Es,It,Nl) (Rev 1)" - rom ( name "Treasure Planet (Europe) (En,Fr,De,Es,It,Nl) (Rev 1).gba" size 8388608 crc 794b2602 sha1 c703edaee8a2bad09c2298e2c92a14d459fe664a ) + name "Travis Pastrana's Pro MotoX (USA) (Proto) (2002-12-12)" + description "Travis Pastrana's Pro MotoX (USA) (Proto) (2002-12-12)" + rom ( name "Travis Pastrana's Pro MotoX (USA) (Proto) (2002-12-12).gba" size 4194304 crc ecfe5f96 sha1 64e356f495fd58a862f272124798ecf61e20cfb7 ) ) game ( @@ -18607,27 +18769,33 @@ game ( ) game ( - name "Tremblay Island (World) (v.1.10) (Aftermarket) (Homebrew)" - description "Tremblay Island (World) (v.1.10) (Aftermarket) (Homebrew)" - rom ( name "Tremblay Island (World) (v.1.10) (Aftermarket) (Homebrew).gba" size 16239516 crc ce85ba07 sha1 b09b600bfc222c2d92829d8883feab700b013953 ) + name "Treasure Planet (Europe) (En,Fr,De,Es,It,Nl) (Rev 1)" + description "Treasure Planet (Europe) (En,Fr,De,Es,It,Nl) (Rev 1)" + rom ( name "Treasure Planet (Europe) (En,Fr,De,Es,It,Nl) (Rev 1).gba" size 8388608 crc 794b2602 sha1 c703edaee8a2bad09c2298e2c92a14d459fe664a ) ) game ( - name "Tremblay Island (World) (v.1.10) (Solid State Version) (Aftermarket) (Homebrew)" - description "Tremblay Island (World) (v.1.10) (Solid State Version) (Aftermarket) (Homebrew)" - rom ( name "Tremblay Island (World) (v.1.10) (Solid State Version) (Aftermarket) (Homebrew).gba" size 16229600 crc bd57344d sha1 a2fcfe94c9b8b0feff31c1245c8b033222c9f668 ) + name "Tremblay Island (World) (En) (v.1.10) (Aftermarket) (Unl)" + description "Tremblay Island (World) (En) (v.1.10) (Aftermarket) (Unl)" + rom ( name "Tremblay Island (World) (En) (v.1.10) (Aftermarket) (Unl).gba" size 16239516 crc ce85ba07 sha1 b09b600bfc222c2d92829d8883feab700b013953 ) ) game ( - name "Tremblay Island (World) (Fr) (v.1.10) (Solid State Version) (Aftermarket) (Homebrew)" - description "Tremblay Island (World) (Fr) (v.1.10) (Solid State Version) (Aftermarket) (Homebrew)" - rom ( name "Tremblay Island (World) (Fr) (v.1.10) (Solid State Version) (Aftermarket) (Homebrew).gba" size 16204024 crc fa43669e sha1 a57392fa7cee61913b20a840fe8b2ee27f11d808 ) + name "Tremblay Island (World) (En) (v.1.10) (Solid State Version) (Aftermarket) (Unl)" + description "Tremblay Island (World) (En) (v.1.10) (Solid State Version) (Aftermarket) (Unl)" + rom ( name "Tremblay Island (World) (En) (v.1.10) (Solid State Version) (Aftermarket) (Unl).gba" size 16229600 crc bd57344d sha1 a2fcfe94c9b8b0feff31c1245c8b033222c9f668 ) ) game ( - name "Tremblay Island (World) (Kickstarter Edition) (Aftermarket) (Homebrew)" - description "Tremblay Island (World) (Kickstarter Edition) (Aftermarket) (Homebrew)" - rom ( name "Tremblay Island (World) (Kickstarter Edition) (Aftermarket) (Homebrew).gba" size 16777216 crc ff5d64ab sha1 645ab8d4dacb6666d8eb549d491f77d8cd63ce97 ) + name "Tremblay Island (World) (Fr) (v.1.10) (Solid State Version) (Aftermarket) (Unl)" + description "Tremblay Island (World) (Fr) (v.1.10) (Solid State Version) (Aftermarket) (Unl)" + rom ( name "Tremblay Island (World) (Fr) (v.1.10) (Solid State Version) (Aftermarket) (Unl).gba" size 16204024 crc fa43669e sha1 a57392fa7cee61913b20a840fe8b2ee27f11d808 ) +) + +game ( + name "Tremblay Island (World) (En) (Kickstarter Edition) (Aftermarket) (Unl)" + description "Tremblay Island (World) (En) (Kickstarter Edition) (Aftermarket) (Unl)" + rom ( name "Tremblay Island (World) (En) (Kickstarter Edition) (Aftermarket) (Unl).gba" size 16777216 crc ff5d64ab sha1 645ab8d4dacb6666d8eb549d491f77d8cd63ce97 ) ) game ( @@ -18696,6 +18864,12 @@ game ( rom ( name "Tsuukin Hitofude (Japan) (Virtual Console).gba" size 4194304 crc f4c7a95b sha1 906dbf246cb40954fc27942763048bc09783b1f8 ) ) +game ( + name "Tsuukin Hitofude (Japan) (Beta)" + description "Tsuukin Hitofude (Japan) (Beta)" + rom ( name "Tsuukin Hitofude (Japan) (Beta).gba" size 1048576 crc 9542e247 sha1 24b4be7f296fce5eae93d969eb02831951f00b7b ) +) + game ( name "Turbo Turtle Adventure (USA)" description "Turbo Turtle Adventure (USA)" @@ -18942,6 +19116,18 @@ game ( rom ( name "Unglaublichen, Die (Germany).gba" size 8388608 crc bdc06e57 sha1 6bc89e6aa6cd251d56fe86e520b08cadf20ca4f4 ) ) +game ( + name "Uno - Free Fall (USA)" + description "Uno - Free Fall (USA)" + rom ( name "Uno - Free Fall (USA).gba" size 4194304 crc 19fcc78e sha1 2dacf03965ee248d414d385594cc919f896f032e ) +) + +game ( + name "Uno - Free Fall (Europe) (En,Fr,De,Es,It)" + description "Uno - Free Fall (Europe) (En,Fr,De,Es,It)" + rom ( name "Uno - Free Fall (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 414ea9f4 sha1 2ad8bdfc425f9426296c03acd433b296e40eb72e ) +) + game ( name "Uno 52 (USA)" description "Uno 52 (USA)" @@ -18954,18 +19140,6 @@ game ( rom ( name "Uno 52 (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 315360d0 sha1 f85709ec7a97c8b40504f2097497bd961531272d ) ) -game ( - name "Uno Free Fall (USA)" - description "Uno Free Fall (USA)" - rom ( name "Uno Free Fall (USA).gba" size 4194304 crc 19fcc78e sha1 2dacf03965ee248d414d385594cc919f896f032e ) -) - -game ( - name "Uno Free Fall (Europe) (En,Fr,De,Es,It)" - description "Uno Free Fall (Europe) (En,Fr,De,Es,It)" - rom ( name "Uno Free Fall (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 414ea9f4 sha1 2ad8bdfc425f9426296c03acd433b296e40eb72e ) -) - game ( name "Urban Yeti! (USA, Europe)" description "Urban Yeti! (USA, Europe)" @@ -19051,9 +19225,9 @@ game ( ) game ( - name "Viewpoint (World) (Tech Demo)" - description "Viewpoint (World) (Tech Demo)" - rom ( name "Viewpoint (World) (Tech Demo).gba" size 2097152 crc 7ad65e4b sha1 8fdcc5c207593cb196cea8416bb14bb45db37434 ) + name "Viewpoint (Europe) (Demo)" + description "Viewpoint (Europe) (Demo)" + rom ( name "Viewpoint (Europe) (Demo).gba" size 2097152 crc 7ad65e4b sha1 8fdcc5c207593cb196cea8416bb14bb45db37434 ) ) game ( @@ -19207,9 +19381,9 @@ game ( ) game ( - name "WarioWare - Twisted! (USA)" - description "WarioWare - Twisted! (USA)" - rom ( name "WarioWare - Twisted! (USA).gba" size 16777216 crc cb4e844b sha1 f0102d0d6f7596fe853d5d0a94682718278e083a flags verified ) + name "WarioWare - Twisted! (USA, Australia)" + description "WarioWare - Twisted! (USA, Australia)" + rom ( name "WarioWare - Twisted! (USA, Australia).gba" size 16777216 crc cb4e844b sha1 f0102d0d6f7596fe853d5d0a94682718278e083a flags verified ) ) game ( @@ -19495,9 +19669,9 @@ game ( ) game ( - name "World Reborn (World) (Aftermarket) (Unl)" - description "World Reborn (World) (Aftermarket) (Unl)" - rom ( name "World Reborn (World) (Aftermarket) (Unl).gba" size 4194304 crc eefb32ff sha1 c7ec2f8d7d3dec40a893cdfe2a41a8ed43ed71c4 ) + name "World Reborn (USA) (Aftermarket) (Unl)" + description "World Reborn (USA) (Aftermarket) (Unl)" + rom ( name "World Reborn (USA) (Aftermarket) (Unl).gba" size 4194304 crc eefb32ff sha1 c7ec2f8d7d3dec40a893cdfe2a41a8ed43ed71c4 ) ) game ( @@ -19512,24 +19686,24 @@ game ( rom ( name "World Tennis Stars (USA).gba" size 4194304 crc 036e30b0 sha1 c668815a2ccf0bc6c3816d0947218661437449bc ) ) +game ( + name "Worms - World Party (USA) (En,Fr,De,Es,It)" + description "Worms - World Party (USA) (En,Fr,De,Es,It)" + rom ( name "Worms - World Party (USA) (En,Fr,De,Es,It).gba" size 4194304 crc e8789c18 sha1 7b078976d9056aba7f81bb1ff33e5a36357f5f0b ) +) + +game ( + name "Worms - World Party (Europe) (En,Fr,De,Es,It)" + description "Worms - World Party (Europe) (En,Fr,De,Es,It)" + rom ( name "Worms - World Party (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 8ee32cbe sha1 5e0abe3d94d1489fadebe897df1ed5a5c0212901 ) +) + game ( name "Worms Blast (Europe) (En,Fr,De,Es,It)" description "Worms Blast (Europe) (En,Fr,De,Es,It)" rom ( name "Worms Blast (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 5223f5aa sha1 0da4ff24b61ae8b6d67ef59377770fd958517f2f ) ) -game ( - name "Worms World Party (Europe) (En,Fr,De,Es,It)" - description "Worms World Party (Europe) (En,Fr,De,Es,It)" - rom ( name "Worms World Party (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 8ee32cbe sha1 5e0abe3d94d1489fadebe897df1ed5a5c0212901 ) -) - -game ( - name "Worms World Party (USA) (En,Fr,De,Es,It)" - description "Worms World Party (USA) (En,Fr,De,Es,It)" - rom ( name "Worms World Party (USA) (En,Fr,De,Es,It).gba" size 4194304 crc e8789c18 sha1 7b078976d9056aba7f81bb1ff33e5a36357f5f0b ) -) - game ( name "WTA Tour Tennis (USA)" description "WTA Tour Tennis (USA)" @@ -19551,7 +19725,7 @@ game ( game ( name "WWE - Survivor Series (USA, Europe)" description "WWE - Survivor Series (USA, Europe)" - rom ( name "WWE - Survivor Series (USA, Europe).gba" size 4194304 crc 6694f94d sha1 bb61e289f2a09e7ce0e193267430dbc6f5583a49 ) + rom ( name "WWE - Survivor Series (USA, Europe).gba" size 4194304 crc 6694f94d sha1 bb61e289f2a09e7ce0e193267430dbc6f5583a49 flags verified ) ) game ( @@ -19887,7 +20061,7 @@ game ( game ( name "Yu-Gi-Oh! Duel Monsters 6 Expert 2 (Japan)" description "Yu-Gi-Oh! Duel Monsters 6 Expert 2 (Japan)" - rom ( name "Yu-Gi-Oh! Duel Monsters 6 Expert 2 (Japan).gba" size 16777216 crc a7054d60 sha1 e5ee6fb7a6a4eb0e33197549fd30f8fe402b595f ) + rom ( name "Yu-Gi-Oh! Duel Monsters 6 Expert 2 (Japan).gba" size 16777216 crc a7054d60 sha1 e5ee6fb7a6a4eb0e33197549fd30f8fe402b595f flags verified ) ) game ( @@ -19995,7 +20169,7 @@ game ( game ( name "Zelda no Densetsu - Fushigi no Boushi (Japan)" description "Zelda no Densetsu - Fushigi no Boushi (Japan)" - rom ( name "Zelda no Densetsu - Fushigi no Boushi (Japan).gba" size 16777216 crc 6ce771a5 sha1 6c5404a1effb17f481f352181d0f1c61a2765c5d ) + rom ( name "Zelda no Densetsu - Fushigi no Boushi (Japan).gba" size 16777216 crc 6ce771a5 sha1 6c5404a1effb17f481f352181d0f1c61a2765c5d flags verified ) ) game ( @@ -20077,9 +20251,9 @@ game ( ) game ( - name "Zidane Football Generation 2002 (Europe) (En,Fr,De,Es,It)" - description "Zidane Football Generation 2002 (Europe) (En,Fr,De,Es,It)" - rom ( name "Zidane Football Generation 2002 (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 59e2635f sha1 e18e9e8aabb8844de688ddff8843329f84e470aa ) + name "Zidane - Football Generation 2002 (Europe) (En,Fr,De,Es,It)" + description "Zidane - Football Generation 2002 (Europe) (En,Fr,De,Es,It)" + rom ( name "Zidane - Football Generation 2002 (Europe) (En,Fr,De,Es,It).gba" size 4194304 crc 59e2635f sha1 e18e9e8aabb8844de688ddff8843329f84e470aa ) ) game ( @@ -20089,9 +20263,9 @@ game ( ) game ( - name "Zoids Legacy (USA)" - description "Zoids Legacy (USA)" - rom ( name "Zoids Legacy (USA).gba" size 8388608 crc 302e75f3 sha1 460fa2158606097f6e6f63ce966d2d6ecdd58d70 ) + name "Zoids - Legacy (USA)" + description "Zoids - Legacy (USA)" + rom ( name "Zoids - Legacy (USA).gba" size 8388608 crc 302e75f3 sha1 460fa2158606097f6e6f63ce966d2d6ecdd58d70 ) ) game ( @@ -20763,10 +20937,10 @@ game ( ) clrmamepro ( - name "Nintendo - Game Boy" - description "Nintendo - Game Boy" - version 20220829-183056 - author "aci68, akubi, Arctic Circle System, Aringon, baldjared, Bent, BigFred, BitLooter, C. V. Reynolds, chillerecke, darthcloud, DeadSkullzJr, Densetsu, DeriLoko3, ElBarto, foxe, fuzzball, Gefflon, Hiccup, hking0036, InternalLoss, jimmsu, kazumi213, leekindo, Lesserkuma, Madeline, Money_114, NESBrew12, NGEfreak, nnssxx, norkmetnoil577, NovaAurora, omonim2007, Powerpuff, PPLToast, relax, RetroUprising, rpg2813, sCZther, SonGoku, Tauwasser, togemet2, UnlockerPT, xNo, xuom2" + name "Nintendo - Game Boy Advance (Video)" + description "Nintendo - Game Boy Advance (Video)" + version 20230301-094141 + author "BigFred, C. V. Reynolds, DeadSkullzJr, Hiccup, kazumi213, omonim2007, relax, SonGoku, xuom2" homepage No-Intro url "https://www.no-intro.org" forcenodump required @@ -20777,9 +20951,281 @@ emulator ( ) game ( - name "[BIOS] Nintendo Game Boy Boot ROM (Japan) (En)" - description "[BIOS] Nintendo Game Boy Boot ROM (Japan) (En)" - rom ( name "[BIOS] Nintendo Game Boy Boot ROM (Japan) (En).gb" size 256 crc c2f5cc97 sha1 8bd501e31921e9601788316dbd3ce9833a97bcbc flags verified ) + name "Game Boy Advance Video - All Grown Up! - Volume 1 (USA)" + description "Game Boy Advance Video - All Grown Up! - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - All Grown Up! - Volume 1 (USA).gba" size 33554432 crc ffbd4da9 sha1 02158627fd5a526f848077440182fa92ad87ea3f ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Edition Platinum (France)" + description "Game Boy Advance Video - Cartoon Network Collection - Edition Platinum (France)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Edition Platinum (France).gba" size 33554432 crc fc042f18 sha1 01564b86644cf43f49320e0c05854896f8c60c2f ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Edition Premium (France)" + description "Game Boy Advance Video - Cartoon Network Collection - Edition Premium (France)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Edition Premium (France).gba" size 33554432 crc 58f1cde8 sha1 f63052e2b53c42601f53148bee69f89d9591fbf9 ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Edition Speciale (France)" + description "Game Boy Advance Video - Cartoon Network Collection - Edition Speciale (France)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Edition Speciale (France).gba" size 33554432 crc 71154d42 sha1 73cbdd82640f166737173b0e8197d771d7906a91 ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Limited Edition (USA)" + description "Game Boy Advance Video - Cartoon Network Collection - Limited Edition (USA)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Limited Edition (USA).gba" size 33554432 crc 5d918b2d sha1 ac63a7691774bde6eaad4c2c5c4d305e7cc0535a ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Platinum Edition (USA, Europe)" + description "Game Boy Advance Video - Cartoon Network Collection - Platinum Edition (USA, Europe)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Platinum Edition (USA, Europe).gba" size 33554432 crc 6443554b sha1 0cf9b536f87cb21f738bd7552e95bd114b0f0b2e ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Premium Edition (USA, Europe)" + description "Game Boy Advance Video - Cartoon Network Collection - Premium Edition (USA, Europe)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Premium Edition (USA, Europe).gba" size 33554432 crc f2825729 sha1 d0fe380fcdf5b1bb99188ed95c8c3272cbb65ce8 flags verified ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Special Edition (USA, Europe)" + description "Game Boy Advance Video - Cartoon Network Collection - Special Edition (USA, Europe)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Special Edition (USA, Europe).gba" size 33554432 crc e9b7b8a4 sha1 5f17d2ec1a3ba1d605d486b6f6abcde6d82df767 ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Volume 1 (USA, Europe)" + description "Game Boy Advance Video - Cartoon Network Collection - Volume 1 (USA, Europe)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Volume 1 (USA, Europe).gba" size 33554432 crc 91f39447 sha1 3227d82e64024a5dc0e5b7f3fd768b0fec19e9b0 ) +) + +game ( + name "Game Boy Advance Video - Cartoon Network Collection - Volume 2 (USA, Europe)" + description "Game Boy Advance Video - Cartoon Network Collection - Volume 2 (USA, Europe)" + rom ( name "Game Boy Advance Video - Cartoon Network Collection - Volume 2 (USA, Europe).gba" size 33554432 crc 4bfaa8de sha1 692ffa1856780edd28b3aa55526ad06eeddc292e ) +) + +game ( + name "Game Boy Advance Video - Codename - Kids Next Door - Volume 1 (USA, Europe)" + description "Game Boy Advance Video - Codename - Kids Next Door - Volume 1 (USA, Europe)" + rom ( name "Game Boy Advance Video - Codename - Kids Next Door - Volume 1 (USA, Europe).gba" size 33554432 crc 4463f345 sha1 5e78808676213d6e5b55a78560fc47112f3008d4 ) +) + +game ( + name "Game Boy Advance Video - Disney Channel Collection - Volume 1 (USA)" + description "Game Boy Advance Video - Disney Channel Collection - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - Disney Channel Collection - Volume 1 (USA).gba" size 33554432 crc 7cc985cb sha1 53069a5a8b564495ad800f0685b4d1970859b0f8 ) +) + +game ( + name "Game Boy Advance Video - Disney Channel Collection - Volume 2 (USA) (Rev 5)" + description "Game Boy Advance Video - Disney Channel Collection - Volume 2 (USA) (Rev 5)" + rom ( name "Game Boy Advance Video - Disney Channel Collection - Volume 2 (USA) (Rev 5).gba" size 33554432 crc da3b64f3 sha1 6bb825bd00a8daae329ea40cdbfd3db48547585e ) +) + +game ( + name "Game Boy Advance Video - Dora the Explorer - Volume 1 (USA)" + description "Game Boy Advance Video - Dora the Explorer - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - Dora the Explorer - Volume 1 (USA).gba" size 33554432 crc dd6a7fcc sha1 67fe664bdadc7927ba888014ea923f11e3334b6c ) +) + +game ( + name "Game Boy Advance Video - Dragon Ball GT - Volume 1 (USA)" + description "Game Boy Advance Video - Dragon Ball GT - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - Dragon Ball GT - Volume 1 (USA).gba" size 33554432 crc da559199 sha1 dc85cccc0cede3fc11d257995cd7d3cc64ca00e0 ) +) + +game ( + name "Game Boy Advance Video - Nicktoons - Volume 3 (USA)" + description "Game Boy Advance Video - Nicktoons - Volume 3 (USA)" + rom ( name "Game Boy Advance Video - Nicktoons - Volume 3 (USA).gba" size 33554432 crc 6fbf5ceb sha1 2732103fda15d8fb017f21023d2fda22ae7a548a ) +) + +game ( + name "Game Boy Advance Video - Nicktoons Collection - Volume 1 (USA)" + description "Game Boy Advance Video - Nicktoons Collection - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - Nicktoons Collection - Volume 1 (USA).gba" size 33554432 crc 5d47676f sha1 b3bf6d95b548a82808a109cefaaf8b0c22ca66f2 ) +) + +game ( + name "Game Boy Advance Video - Nicktoons Collection - Volume 2 (USA)" + description "Game Boy Advance Video - Nicktoons Collection - Volume 2 (USA)" + rom ( name "Game Boy Advance Video - Nicktoons Collection - Volume 2 (USA).gba" size 33554432 crc 65deeeb6 sha1 03a767124f034b1eea51ab84ba6e80ec76f7f50c ) +) + +game ( + name "Game Boy Advance Video - Pokemon - Volume 1 (USA)" + description "Game Boy Advance Video - Pokemon - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - Pokemon - Volume 1 (USA).gba" size 33554432 crc a59ef954 sha1 ad0164df397860b0967590ee46ca1f41d6cecdcd ) +) + +game ( + name "Game Boy Advance Video - Pokemon - Volume 2 (USA)" + description "Game Boy Advance Video - Pokemon - Volume 2 (USA)" + rom ( name "Game Boy Advance Video - Pokemon - Volume 2 (USA).gba" size 33554432 crc 0da1a383 sha1 e7b2e00d6a60e8c149cd07b0ee8d98bf2b4efd15 ) +) + +game ( + name "Game Boy Advance Video - Pokemon - Volume 3 (USA)" + description "Game Boy Advance Video - Pokemon - Volume 3 (USA)" + rom ( name "Game Boy Advance Video - Pokemon - Volume 3 (USA).gba" size 33554432 crc a36aa9c5 sha1 0f0020ef4278e1777baaf74b6787355a5cb57d50 ) +) + +game ( + name "Game Boy Advance Video - Pokemon - Volume 4 (USA)" + description "Game Boy Advance Video - Pokemon - Volume 4 (USA)" + rom ( name "Game Boy Advance Video - Pokemon - Volume 4 (USA).gba" size 33554432 crc be468496 sha1 fd79b821ff601e0091f19ab9c86b08f1c39ea2f3 ) +) + +game ( + name "Game Boy Advance Video - Shark Tale (USA) (Rev 6)" + description "Game Boy Advance Video - Shark Tale (USA) (Rev 6)" + rom ( name "Game Boy Advance Video - Shark Tale (USA) (Rev 6).gba" size 67108864 crc d2cf417a sha1 9bc5b60793b8a6de3b80eb2c213cd125e1fa468e flags verified ) +) + +game ( + name "Game Boy Advance Video - Shark Tale (USA) (Rev 5)" + description "Game Boy Advance Video - Shark Tale (USA) (Rev 5)" + rom ( name "Game Boy Advance Video - Shark Tale (USA) (Rev 5).gba" size 67108864 crc 01468820 sha1 6128a476edb10e6839ac5bd2697e83b9b7a9b234 ) +) + +game ( + name "Game Boy Advance Video - Shrek (USA) (Rev 5)" + description "Game Boy Advance Video - Shrek (USA) (Rev 5)" + rom ( name "Game Boy Advance Video - Shrek (USA) (Rev 5).gba" size 67108864 crc 690176e5 sha1 2dedea86ec289a0e31089299613d9f74cca03609 flags verified ) +) + +game ( + name "Game Boy Advance Video - Shrek (USA) (Rev 6)" + description "Game Boy Advance Video - Shrek (USA) (Rev 6)" + rom ( name "Game Boy Advance Video - Shrek (USA) (Rev 6).gba" size 67108864 crc 4010e9fa sha1 95d612057682b7b886441d387b167ba2493e49ef ) +) + +game ( + name "Game Boy Advance Video - Shrek + Shark Tale (USA) (Rev 5)" + description "Game Boy Advance Video - Shrek + Shark Tale (USA) (Rev 5)" + rom ( name "Game Boy Advance Video - Shrek + Shark Tale (USA) (Rev 5).gba" size 67108864 crc aadb3e3d sha1 f23390b7a62c605fbce6730addc29d381488e076 flags verified ) +) + +game ( + name "Game Boy Advance Video - Shrek 2 (USA) (Rev 5)" + description "Game Boy Advance Video - Shrek 2 (USA) (Rev 5)" + rom ( name "Game Boy Advance Video - Shrek 2 (USA) (Rev 5).gba" size 67108864 crc 0d353654 sha1 5cd627e205020297b25d707131883be5850515fe flags verified ) +) + +game ( + name "Game Boy Advance Video - Shrek 2 (USA) (Rev 6)" + description "Game Boy Advance Video - Shrek 2 (USA) (Rev 6)" + rom ( name "Game Boy Advance Video - Shrek 2 (USA) (Rev 6).gba" size 67108864 crc 925b6c02 sha1 3bcb4acc5da539bc1b91c9537bf7ffa081ded1d4 ) +) + +game ( + name "Game Boy Advance Video - Sonic X - Volume 1 (USA)" + description "Game Boy Advance Video - Sonic X - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - Sonic X - Volume 1 (USA).gba" size 33554432 crc 363c3eb8 sha1 dc4c959a7740315d892633869f7fc6382aadbb0e ) +) + +game ( + name "Game Boy Advance Video - SpongeBob SquarePants - Volume 1 (USA) (Rev 1)" + description "Game Boy Advance Video - SpongeBob SquarePants - Volume 1 (USA) (Rev 1)" + rom ( name "Game Boy Advance Video - SpongeBob SquarePants - Volume 1 (USA) (Rev 1).gba" size 33554432 crc d47bf1d4 sha1 65f6b06c5397b9406960050025361550e0ff2e92 ) +) + +game ( + name "Game Boy Advance Video - SpongeBob SquarePants - Volume 2 (USA) (Rev 1)" + description "Game Boy Advance Video - SpongeBob SquarePants - Volume 2 (USA) (Rev 1)" + rom ( name "Game Boy Advance Video - SpongeBob SquarePants - Volume 2 (USA) (Rev 1).gba" size 33554432 crc 7074364a sha1 c4caa875f93657626bf73541ddd61eb5c8b4957e ) +) + +game ( + name "Game Boy Advance Video - SpongeBob SquarePants - Volume 3 (USA)" + description "Game Boy Advance Video - SpongeBob SquarePants - Volume 3 (USA)" + rom ( name "Game Boy Advance Video - SpongeBob SquarePants - Volume 3 (USA).gba" size 33554432 crc 9772ca45 sha1 fa11aeef21cdd18fcfea281990030acb65c7aa96 ) +) + +game ( + name "Game Boy Advance Video - Strawberry Shortcake - Volume 1 (USA)" + description "Game Boy Advance Video - Strawberry Shortcake - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - Strawberry Shortcake - Volume 1 (USA).gba" size 33554432 crc ff7582ef sha1 f84cc8a33e3a75e42f4e8023a5cc851c92ea409a ) +) + +game ( + name "Game Boy Advance Video - Super Robot Monkey Team - Hyper Force Go! - Volume 1 (USA)" + description "Game Boy Advance Video - Super Robot Monkey Team - Hyper Force Go! - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - Super Robot Monkey Team - Hyper Force Go! - Volume 1 (USA).gba" size 33554432 crc d743a070 sha1 e3bce6ddf9437bded89fc662e992c1ab0aa1dbba ) +) + +game ( + name "Game Boy Advance Video - Teenage Mutant Ninja Turtles - Le Demenagement (France)" + description "Game Boy Advance Video - Teenage Mutant Ninja Turtles - Le Demenagement (France)" + rom ( name "Game Boy Advance Video - Teenage Mutant Ninja Turtles - Le Demenagement (France).gba" size 33554432 crc 1ee78166 sha1 1c7444a45c9bd15b1643c195832497881955acee ) +) + +game ( + name "Game Boy Advance Video - Teenage Mutant Ninja Turtles - Things Change (USA, Europe)" + description "Game Boy Advance Video - Teenage Mutant Ninja Turtles - Things Change (USA, Europe)" + rom ( name "Game Boy Advance Video - Teenage Mutant Ninja Turtles - Things Change (USA, Europe).gba" size 33554432 crc 046589c8 sha1 8b9665ce2cefd663f7722f38b64f9271f7c00daa ) +) + +game ( + name "Game Boy Advance Video - The Adventures of Jimmy Neutron Boy Genius - Volume 1 (USA)" + description "Game Boy Advance Video - The Adventures of Jimmy Neutron Boy Genius - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - The Adventures of Jimmy Neutron Boy Genius - Volume 1 (USA).gba" size 33554432 crc 0e556edf sha1 1414c55bd3d252fdc05c0a94085f12dddf86ea31 ) +) + +game ( + name "Game Boy Advance Video - The Fairly OddParents! - Volume 1 (USA)" + description "Game Boy Advance Video - The Fairly OddParents! - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - The Fairly OddParents! - Volume 1 (USA).gba" size 33554432 crc 7958df20 sha1 42e343e5048f99f05f7cde5aad6bfcc3d2410399 ) +) + +game ( + name "Game Boy Advance Video - The Fairly OddParents! - Volume 2 (USA) (Rev 1)" + description "Game Boy Advance Video - The Fairly OddParents! - Volume 2 (USA) (Rev 1)" + rom ( name "Game Boy Advance Video - The Fairly OddParents! - Volume 2 (USA) (Rev 1).gba" size 33554432 crc bbcbc6fd sha1 5b8a83410d8f9de12113e2974d65b5790aaf62fb ) +) + +game ( + name "Game Boy Advance Video - The Proud Family - Volume 1 (USA)" + description "Game Boy Advance Video - The Proud Family - Volume 1 (USA)" + rom ( name "Game Boy Advance Video - The Proud Family - Volume 1 (USA).gba" size 33554432 crc c6a91365 sha1 31a2cbb9b1b5adf7de399eecf2a9cec4f5412767 ) +) + +game ( + name "Game Boy Advance Video - Yu-Gi-Oh! - Yugi vs. Joey (France)" + description "Game Boy Advance Video - Yu-Gi-Oh! - Yugi vs. Joey (France)" + rom ( name "Game Boy Advance Video - Yu-Gi-Oh! - Yugi vs. Joey (France).gba" size 33554432 crc c9b6ccf1 sha1 8fb43e5018cf2d4b1a2aa2d1693862ce248ab6b0 ) +) + +game ( + name "Game Boy Advance Video - Yu-Gi-Oh! - Yugi vs. Joey (USA, Europe)" + description "Game Boy Advance Video - Yu-Gi-Oh! - Yugi vs. Joey (USA, Europe)" + rom ( name "Game Boy Advance Video - Yu-Gi-Oh! - Yugi vs. Joey (USA, Europe).gba" size 33554432 crc 8d631f4e sha1 6daf15bb61a10d1bbf94009886d6a76f9c937f8b flags verified ) +) + +clrmamepro ( + name "Nintendo - Game Boy" + description "Nintendo - Game Boy" + version 20230422-225330 + author "aci68, akubi, Arctic Circle System, Aringon, baldjared, Bent, BigFred, BitLooter, buckwheat, C. V. Reynolds, chillerecke, darthcloud, DeadSkullzJr, Densetsu, DeriLoko3, ElBarto, foxe, fuzzball, Gefflon, Hiccup, hking0036, InternalLoss, jimmsu, kazumi213, leekindo, Lesserkuma, Madeline, NESBrew12, NGEfreak, nnssxx, norkmetnoil577, NovaAurora, omonim2007, Powerpuff, PPLToast, rarenight, relax, RetroUprising, rpg2813, sCZther, SonGoku, Tauwasser, togemet2, UnlockerPT, xNo, xuom2" + homepage No-Intro + url "https://www.no-intro.org" + forcenodump required +) + +emulator ( + name "datafile" +) + +game ( + name "[BIOS] Maxstation Boot ROM (China) (En) (Unl)" + description "[BIOS] Maxstation Boot ROM (China) (En) (Unl)" + rom ( name "[BIOS] Maxstation Boot ROM (China) (En) (Unl).gb" size 256 crc 783e69c2 sha1 1776bd61b8db71fc4c4d4b5feab4a21b3c1fd95b ) ) game ( @@ -20788,6 +21234,12 @@ game ( rom ( name "[BIOS] Nintendo Game Boy Boot ROM (World) (Rev 1).gb" size 256 crc 59c8598e sha1 4ed31ec6b0b175bb109c0eb5fd3d193da823339f flags verified ) ) +game ( + name "[BIOS] Nintendo Game Boy Boot ROM (Japan) (En)" + description "[BIOS] Nintendo Game Boy Boot ROM (Japan) (En)" + rom ( name "[BIOS] Nintendo Game Boy Boot ROM (Japan) (En).gb" size 256 crc c2f5cc97 sha1 8bd501e31921e9601788316dbd3ce9833a97bcbc flags verified ) +) + game ( name "[BIOS] Nintendo Game Boy Pocket Boot ROM (World)" description "[BIOS] Nintendo Game Boy Pocket Boot ROM (World)" @@ -20795,15 +21247,9 @@ game ( ) game ( - name "10-Pin Bowling (USA)" - description "10-Pin Bowling (USA)" - rom ( name "10-Pin Bowling (USA).gb" size 131072 crc 9a024415 sha1 952d154dd2c6189ef4b786ae37bd7887c8ca9037 ) -) - -game ( - name "1bit Game Collection (World) (Aftermarket) (Homebrew)" - description "1bit Game Collection (World) (Aftermarket) (Homebrew)" - rom ( name "1bit Game Collection (World) (Aftermarket) (Homebrew).gbc" size 131072 crc 7230319a sha1 8ab9b6d16e374172f9a88a3eded19f4888179143 flags verified ) + name "10-Pin Bowling (USA) (Proto)" + description "10-Pin Bowling (USA) (Proto)" + rom ( name "10-Pin Bowling (USA) (Proto).gb" size 131072 crc 9a024415 sha1 952d154dd2c6189ef4b786ae37bd7887c8ca9037 ) ) game ( @@ -20909,9 +21355,9 @@ game ( ) game ( - name "8-in-1 (Unknown) (Unl)" - description "8-in-1 (Unknown) (Unl)" - rom ( name "8-in-1 (Unknown) (Unl).gb" size 524288 crc fbc77225 sha1 f57efd4d5063cb87519fc3b574377cc9a2a841ee ) + name "8-in-1 (Taiwan) (En) (Unl)" + description "8-in-1 (Taiwan) (En) (Unl)" + rom ( name "8-in-1 (Taiwan) (En) (Unl).gb" size 524288 crc fbc77225 sha1 f57efd4d5063cb87519fc3b574377cc9a2a841ee ) ) game ( @@ -20926,6 +21372,12 @@ game ( rom ( name "Aa Harimanada (Japan).gb" size 131072 crc 5bffcc28 sha1 ff3565fcac22b44bf542732d1b0b20ba96a6c961 ) ) +game ( + name "Action Replay Pro (World)" + description "Action Replay Pro (World)" + rom ( name "Action Replay Pro (World).gb" size 16384 crc 2ea05daa sha1 e947b9264092168950ad1ce23bbe3d8ccfed765e ) +) + game ( name "Addams Family, The (Europe) (En,Fr,De)" description "Addams Family, The (Europe) (En,Fr,De)" @@ -20950,6 +21402,18 @@ game ( rom ( name "Addams Family, The - Pugsley's Scavenger Hunt (USA, Europe).gb" size 131072 crc 7e054a88 sha1 f9020e3d104cb5c5347e28f45ed9e24e6c0ebddd flags verified ) ) +game ( + name "Adulting! (World) (v2.0) (Aftermarket) (Unl)" + description "Adulting! (World) (v2.0) (Aftermarket) (Unl)" + rom ( name "Adulting! (World) (v2.0) (Aftermarket) (Unl).gb" size 524288 crc e56d1244 sha1 d107bd8bf32d0d94a988466885fe1a44aae32c9a ) +) + +game ( + name "Adulting! Soundtrack (World) (Aftermarket) (Unl)" + description "Adulting! Soundtrack (World) (Aftermarket) (Unl)" + rom ( name "Adulting! Soundtrack (World) (Aftermarket) (Unl).gb" size 131072 crc 1b5b9e64 sha1 c5dad32be800c36814a0f9b940e99b5eada34150 ) +) + game ( name "Adventure Island (USA, Europe)" description "Adventure Island (USA, Europe)" @@ -21112,6 +21576,12 @@ game ( rom ( name "Alien vs Predator - The Last of His Clan (USA).gb" size 131072 crc 4bbcf652 sha1 c0d39ee87b908cdbd68c59f73e5dc2a7a6ccbedc ) ) +game ( + name "All Humans Must Die! (World) (Aftermarket) (Unl)" + description "All Humans Must Die! (World) (Aftermarket) (Unl)" + rom ( name "All Humans Must Die! (World) (Aftermarket) (Unl).gb" size 524288 crc b4d50eed sha1 f4513fc525cbcfa4a2804b55ce20c809e01d1c87 ) +) + game ( name "All-Star Baseball 99 (USA)" description "All-Star Baseball 99 (USA)" @@ -21125,9 +21595,9 @@ game ( ) game ( - name "Alphamax (World) (Aftermarket) (Homebrew)" - description "Alphamax (World) (Aftermarket) (Homebrew)" - rom ( name "Alphamax (World) (Aftermarket) (Homebrew).gb" size 131072 crc 8b493b41 sha1 798dda34d04a06dcee32f44f8a4a045caf734927 ) + name "Alphamax (World) (Aftermarket) (Unl)" + description "Alphamax (World) (Aftermarket) (Unl)" + rom ( name "Alphamax (World) (Aftermarket) (Unl).gb" size 131072 crc 8b493b41 sha1 798dda34d04a06dcee32f44f8a4a045caf734927 ) ) game ( @@ -21221,9 +21691,9 @@ game ( ) game ( - name "Another Adventure (World) (Aftermarket) (Homebrew)" - description "Another Adventure (World) (Aftermarket) (Homebrew)" - rom ( name "Another Adventure (World) (Aftermarket) (Homebrew).gb" size 1048576 crc dfcd02ef sha1 041473b2381dd9e11b3cbda5858f9841324327af ) + name "Another Adventure (World) (En,Es) (Aftermarket) (Unl)" + description "Another Adventure (World) (En,Es) (Aftermarket) (Unl)" + rom ( name "Another Adventure (World) (En,Es) (Aftermarket) (Unl).gb" size 1048576 crc dfcd02ef sha1 041473b2381dd9e11b3cbda5858f9841324327af ) ) game ( @@ -21239,9 +21709,9 @@ game ( ) game ( - name "[MIA] Aprilia - DiTech Interface (Unknown) (Unl) [b]" - description "[MIA] Aprilia - DiTech Interface (Unknown) (Unl) [b]" - rom ( name "Aprilia - DiTech Interface (Unknown) (Unl) [b].gb" size 32768 crc 07d0f62c sha1 134625e507bc230efa236908034449fb1afcfdfc flags baddump ) + name "Aprilia - DiTech Interface (Unknown) (Unl) [b]" + description "Aprilia - DiTech Interface (Unknown) (Unl) [b]" + rom ( name "Aprilia - DiTech Interface (Unknown) (Unl) [b].gb" size 262144 crc 2e47d2d3 sha1 a921679b79191991eb75de3b6b21443f322a3699 flags baddump ) ) game ( @@ -21299,27 +21769,27 @@ game ( ) game ( - name "Art School Pocket (World) (Aftermarket) (Homebrew)" - description "Art School Pocket (World) (Aftermarket) (Homebrew)" - rom ( name "Art School Pocket (World) (Aftermarket) (Homebrew).gb" size 1048576 crc b4eab528 sha1 c482cfc6ec40b1f33c4ba48ecdc45fef4730b653 ) + name "Art School Pocket (World) (Aftermarket) (Unl)" + description "Art School Pocket (World) (Aftermarket) (Unl)" + rom ( name "Art School Pocket (World) (Aftermarket) (Unl).gb" size 1048576 crc b4eab528 sha1 c482cfc6ec40b1f33c4ba48ecdc45fef4730b653 ) ) game ( - name "Art School Pocket (Spain) (Aftermarket) (Homebrew)" - description "Art School Pocket (Spain) (Aftermarket) (Homebrew)" - rom ( name "Art School Pocket (Spain) (Aftermarket) (Homebrew).gb" size 1048576 crc 240067df sha1 c8a75895c87b11f9493f629c19c54d0c607505bd ) + name "Art School Pocket (Spain) (Aftermarket) (Unl)" + description "Art School Pocket (Spain) (Aftermarket) (Unl)" + rom ( name "Art School Pocket (Spain) (Aftermarket) (Unl).gb" size 1048576 crc 240067df sha1 c8a75895c87b11f9493f629c19c54d0c607505bd ) ) game ( - name "Art School Pocket (France) (Aftermarket) (Homebrew)" - description "Art School Pocket (France) (Aftermarket) (Homebrew)" - rom ( name "Art School Pocket (France) (Aftermarket) (Homebrew).gb" size 1048576 crc 49a5b74d sha1 73ecffaee185eb2caf1db385d04b863676c777fb ) + name "Art School Pocket (France) (Aftermarket) (Unl)" + description "Art School Pocket (France) (Aftermarket) (Unl)" + rom ( name "Art School Pocket (France) (Aftermarket) (Unl).gb" size 1048576 crc 49a5b74d sha1 73ecffaee185eb2caf1db385d04b863676c777fb ) ) game ( - name "Art School Pocket (Germany) (Aftermarket) (Homebrew)" - description "Art School Pocket (Germany) (Aftermarket) (Homebrew)" - rom ( name "Art School Pocket (Germany) (Aftermarket) (Homebrew).gb" size 1048576 crc 82b73e5b sha1 7772e2b9d5e722e54d19fc6283ccb1fa5d19b641 ) + name "Art School Pocket (Germany) (Aftermarket) (Unl)" + description "Art School Pocket (Germany) (Aftermarket) (Unl)" + rom ( name "Art School Pocket (Germany) (Aftermarket) (Unl).gb" size 1048576 crc 82b73e5b sha1 7772e2b9d5e722e54d19fc6283ccb1fa5d19b641 ) ) game ( @@ -21389,15 +21859,15 @@ game ( ) game ( - name "Astro-Jump (World) (Aftermarket) (Homebrew)" - description "Astro-Jump (World) (Aftermarket) (Homebrew)" - rom ( name "Astro-Jump (World) (Aftermarket) (Homebrew).gb" size 262144 crc c35a3b39 sha1 1bcb4be684626ce061aad105701548fa3a77e254 ) + name "Astro-Jump (World) (Aftermarket) (Unl)" + description "Astro-Jump (World) (Aftermarket) (Unl)" + rom ( name "Astro-Jump (World) (Aftermarket) (Unl).gb" size 262144 crc c35a3b39 sha1 1bcb4be684626ce061aad105701548fa3a77e254 ) ) game ( - name "Astro-Jump - The Sequel (World) (Aftermarket) (Homebrew)" - description "Astro-Jump - The Sequel (World) (Aftermarket) (Homebrew)" - rom ( name "Astro-Jump - The Sequel (World) (Aftermarket) (Homebrew).gb" size 131072 crc e6a96130 sha1 2cd0ef086c4b89497d9497a17d871a6568a9e2d3 ) + name "Astro-Jump - The Sequel (World) (Aftermarket) (Unl)" + description "Astro-Jump - The Sequel (World) (Aftermarket) (Unl)" + rom ( name "Astro-Jump - The Sequel (World) (Aftermarket) (Unl).gb" size 131072 crc e6a96130 sha1 2cd0ef086c4b89497d9497a17d871a6568a9e2d3 ) ) game ( @@ -21419,15 +21889,15 @@ game ( ) game ( - name "Auto Zone (World) (Aftermarket) (Homebrew)" - description "Auto Zone (World) (Aftermarket) (Homebrew)" - rom ( name "Auto Zone (World) (Aftermarket) (Homebrew).gb" size 524288 crc cee73c14 sha1 3070ec215014633dac5dbbb487aade2e2993c049 ) + name "Auto Zone (World) (Aftermarket) (Unl)" + description "Auto Zone (World) (Aftermarket) (Unl)" + rom ( name "Auto Zone (World) (Aftermarket) (Unl).gb" size 524288 crc cee73c14 sha1 3070ec215014633dac5dbbb487aade2e2993c049 ) ) game ( - name "Autumn With You, An (World) (Aftermarket) (Homebrew)" - description "Autumn With You, An (World) (Aftermarket) (Homebrew)" - rom ( name "Autumn With You, An (World) (Aftermarket) (Homebrew).gb" size 262144 crc eac459fa sha1 68a5520be76c52a4936ad1756ea274667f2ac0e3 ) + name "Autumn With You, An (World) (Aftermarket) (Unl)" + description "Autumn With You, An (World) (Aftermarket) (Unl)" + rom ( name "Autumn With You, An (World) (Aftermarket) (Unl).gb" size 262144 crc eac459fa sha1 68a5520be76c52a4936ad1756ea274667f2ac0e3 ) ) game ( @@ -21742,6 +22212,12 @@ game ( rom ( name "Battletoads-Double Dragon (USA).gb" size 262144 crc a727f9cd sha1 ce68df4dc2d604625164430266017b237b72303d ) ) +game ( + name "Batty Zabella (World) (Aftermarket) (Unl)" + description "Batty Zabella (World) (Aftermarket) (Unl)" + rom ( name "Batty Zabella (World) (Aftermarket) (Unl).gb" size 1048576 crc dfa3c64c sha1 2361d5f87bd525964e61bd39765a6fa843fa4c43 ) +) + game ( name "Beavis and Butt-head (USA, Europe)" description "Beavis and Butt-head (USA, Europe)" @@ -21790,6 +22266,12 @@ game ( rom ( name "Bill & Ted's Excellent Game Boy Adventure - A Bogus Journey! (USA, Europe).gb" size 131072 crc 5e8f656a sha1 e19a7e5a5bd8fde0f31773c22dbebc9b4cf3824e flags verified ) ) +game ( + name "Bill & Ted's Excellent Portable Adventure - A Bogus Journey! (World) (Aftermarket) (Unl)" + description "Bill & Ted's Excellent Portable Adventure - A Bogus Journey! (World) (Aftermarket) (Unl)" + rom ( name "Bill & Ted's Excellent Portable Adventure - A Bogus Journey! (World) (Aftermarket) (Unl).gb" size 131072 crc 7b5e19ca sha1 c6e606cb5e17f7530a529387ad2fb703a94b205a ) +) + game ( name "Bill Elliott's NASCAR Fast Tracks (USA)" description "Bill Elliott's NASCAR Fast Tracks (USA)" @@ -21839,15 +22321,15 @@ game ( ) game ( - name "Black Castle GB (World) (Aftermarket) (Homebrew)" - description "Black Castle GB (World) (Aftermarket) (Homebrew)" - rom ( name "Black Castle GB (World) (Aftermarket) (Homebrew).gb" size 65536 crc 10f577c7 sha1 45d979be572bb820835d2ecd4e990cd1eadbf5a6 ) + name "Black Castle GB (World) (Aftermarket) (Unl)" + description "Black Castle GB (World) (Aftermarket) (Unl)" + rom ( name "Black Castle GB (World) (Aftermarket) (Unl).gb" size 65536 crc 10f577c7 sha1 45d979be572bb820835d2ecd4e990cd1eadbf5a6 ) ) game ( - name "Black Tape (World) (Aftermarket) (Homebrew)" - description "Black Tape (World) (Aftermarket) (Homebrew)" - rom ( name "Black Tape (World) (Aftermarket) (Homebrew).gb" size 524288 crc 45c5f990 sha1 ec8c467e955d69b2084805f11a954e0d90855461 ) + name "Black Tape (World) (Aftermarket) (Unl)" + description "Black Tape (World) (Aftermarket) (Unl)" + rom ( name "Black Tape (World) (Aftermarket) (Unl).gb" size 524288 crc 45c5f990 sha1 ec8c467e955d69b2084805f11a954e0d90855461 ) ) game ( @@ -21887,9 +22369,9 @@ game ( ) game ( - name "Blitz Bomber (World) (Aftermarket) (Homebrew)" - description "Blitz Bomber (World) (Aftermarket) (Homebrew)" - rom ( name "Blitz Bomber (World) (Aftermarket) (Homebrew).gb" size 262144 crc 5e9956de sha1 b72cbc6bfa6ceef940f49c9c82024071c6f82b90 ) + name "Blitz Bomber (World) (Aftermarket) (Unl)" + description "Blitz Bomber (World) (Aftermarket) (Unl)" + rom ( name "Blitz Bomber (World) (Aftermarket) (Unl).gb" size 262144 crc 5e9956de sha1 b72cbc6bfa6ceef940f49c9c82024071c6f82b90 ) ) game ( @@ -21899,9 +22381,9 @@ game ( ) game ( - name "Blockade (World) (Aftermarket) (Homebrew)" - description "Blockade (World) (Aftermarket) (Homebrew)" - rom ( name "Blockade (World) (Aftermarket) (Homebrew).gb" size 262144 crc b8cfab16 sha1 9e753048a0eb036ac17c08f5a64d860abd1aaaf5 ) + name "Blockade (World) (Aftermarket) (Unl)" + description "Blockade (World) (Aftermarket) (Unl)" + rom ( name "Blockade (World) (Aftermarket) (Unl).gb" size 262144 crc b8cfab16 sha1 9e753048a0eb036ac17c08f5a64d860abd1aaaf5 ) ) game ( @@ -21910,6 +22392,12 @@ game ( rom ( name "Blodia (Japan).gb" size 65536 crc 51ff6e53 sha1 a3927543ca471f479ba7aae7295788434672464e ) ) +game ( + name "Blues Brothers (USA, Europe) (Beta)" + description "Blues Brothers (USA, Europe) (Beta)" + rom ( name "Blues Brothers (USA, Europe) (Beta).gb" size 131072 crc 1f90d12a sha1 f7139bb55c3cc7bae672ade9022fb548b72f6f2f ) +) + game ( name "Blues Brothers, The (USA, Europe)" description "Blues Brothers, The (USA, Europe)" @@ -21925,7 +22413,7 @@ game ( game ( name "Bo Jackson - Two Games in One (USA)" description "Bo Jackson - Two Games in One (USA)" - rom ( name "Bo Jackson - Two Games in One (USA).gb" size 131072 crc 7edb78ab sha1 cb360f44a398ff5cd3818b0e244ffdf7019d85c7 ) + rom ( name "Bo Jackson - Two Games in One (USA).gb" size 131072 crc 7edb78ab sha1 cb360f44a398ff5cd3818b0e244ffdf7019d85c7 flags verified ) ) game ( @@ -21947,9 +22435,9 @@ game ( ) game ( - name "BOING! (World) (Aftermarket) (Homebrew)" - description "BOING! (World) (Aftermarket) (Homebrew)" - rom ( name "BOING! (World) (Aftermarket) (Homebrew).gb" size 2097152 crc 91961796 sha1 09fde4065941784bab4cf8f624a0398049ed4add ) + name "BOING! (World) (Aftermarket) (Unl)" + description "BOING! (World) (Aftermarket) (Unl)" + rom ( name "BOING! (World) (Aftermarket) (Unl).gb" size 2097152 crc 91961796 sha1 09fde4065941784bab4cf8f624a0398049ed4add ) ) game ( @@ -22015,7 +22503,7 @@ game ( game ( name "Bomberman GB (USA, Europe) (SGB Enhanced)" description "Bomberman GB (USA, Europe) (SGB Enhanced)" - rom ( name "Bomberman GB (USA, Europe) (SGB Enhanced).gb" size 262144 crc f372d175 sha1 f7058f31ddaec63f3b9c45ee9caf3c8e2cae1ca8 ) + rom ( name "Bomberman GB (USA, Europe) (SGB Enhanced).gb" size 262144 crc f372d175 sha1 f7058f31ddaec63f3b9c45ee9caf3c8e2cae1ca8 flags verified ) ) game ( @@ -22061,9 +22549,9 @@ game ( ) game ( - name "Borbo's Quest (World) (Aftermarket) (Homebrew)" - description "Borbo's Quest (World) (Aftermarket) (Homebrew)" - rom ( name "Borbo's Quest (World) (Aftermarket) (Homebrew).gb" size 1048576 crc e92ce3d4 sha1 1ddf864188dfd741ccda0b0715e75e162d212605 ) + name "Borbo's Quest (World) (Aftermarket) (Unl)" + description "Borbo's Quest (World) (Aftermarket) (Unl)" + rom ( name "Borbo's Quest (World) (Aftermarket) (Unl).gb" size 1048576 crc e92ce3d4 sha1 1ddf864188dfd741ccda0b0715e75e162d212605 ) ) game ( @@ -22085,9 +22573,15 @@ game ( ) game ( - name "Bounce (World) (v1.1) (Homebrew)" - description "Bounce (World) (v1.1) (Homebrew)" - rom ( name "Bounce (World) (v1.1) (Homebrew).gb" size 32768 crc 827f2bd5 sha1 e9abf64de3faeb48151003ac8bb77dea61e838b5 ) + name "Bounce (World) (v1.1) (Aftermarket) (Unl)" + description "Bounce (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Bounce (World) (v1.1) (Aftermarket) (Unl).gb" size 32768 crc 827f2bd5 sha1 e9abf64de3faeb48151003ac8bb77dea61e838b5 ) +) + +game ( + name "Bouncing Ball, The (World) (Aftermarket)" + description "Bouncing Ball, The (World) (Aftermarket)" + rom ( name "Bouncing Ball, The (World) (Aftermarket).gb" size 65536 crc 42ddf53e sha1 5d331f2e66d7d3f4b4b0fcbaf6ab4b1a0147db3e ) ) game ( @@ -22235,21 +22729,21 @@ game ( ) game ( - name "Bung Greetings Demo (World) (Demo) (Homebrew)" - description "Bung Greetings Demo (World) (Demo) (Homebrew)" - rom ( name "Bung Greetings Demo (World) (Demo) (Homebrew).gb" size 65536 crc 1ae05af4 sha1 bb01f845d330377749aeb6df1f7bea0eff78bb89 ) + name "Bung Greetings Demo (World) (Demo) (Unl)" + description "Bung Greetings Demo (World) (Demo) (Unl)" + rom ( name "Bung Greetings Demo (World) (Demo) (Unl).gb" size 65536 crc 1ae05af4 sha1 bb01f845d330377749aeb6df1f7bea0eff78bb89 ) ) game ( - name "Bung New Year's Greetings Demo (World) (Demo) (Homebrew)" - description "Bung New Year's Greetings Demo (World) (Demo) (Homebrew)" - rom ( name "Bung New Year's Greetings Demo (World) (Demo) (Homebrew).gb" size 65536 crc 493d4a1e sha1 737402fe70924f893fc212393eeb7f46f225745a ) + name "Bung New Year's Greetings Demo (World) (Demo) (Unl)" + description "Bung New Year's Greetings Demo (World) (Demo) (Unl)" + rom ( name "Bung New Year's Greetings Demo (World) (Demo) (Unl).gb" size 65536 crc 493d4a1e sha1 737402fe70924f893fc212393eeb7f46f225745a ) ) game ( - name "Bung's Math Test (World) (Homebrew)" - description "Bung's Math Test (World) (Homebrew)" - rom ( name "Bung's Math Test (World) (Homebrew).gb" size 32768 crc ebeda9d2 sha1 68581c24793e8ea63f3273efa7bc098309d77d1a ) + name "Bung's Math Test (World) (Unl)" + description "Bung's Math Test (World) (Unl)" + rom ( name "Bung's Math Test (World) (Unl).gb" size 32768 crc ebeda9d2 sha1 68581c24793e8ea63f3273efa7bc098309d77d1a ) ) game ( @@ -22295,9 +22789,9 @@ game ( ) game ( - name "Busty Bunny the Bounty Babe (World) (v1.02) (Aftermarket) (Homebrew)" - description "Busty Bunny the Bounty Babe (World) (v1.02) (Aftermarket) (Homebrew)" - rom ( name "Busty Bunny the Bounty Babe (World) (v1.02) (Aftermarket) (Homebrew).gb" size 1048576 crc d0ed199c sha1 a1cbaacdf32cb8fb9a9c59fcb624c729e0ca17bf ) + name "Busty Bunny the Bounty Babe (World) (v1.02) (Aftermarket) (Unl)" + description "Busty Bunny the Bounty Babe (World) (v1.02) (Aftermarket) (Unl)" + rom ( name "Busty Bunny the Bounty Babe (World) (v1.02) (Aftermarket) (Unl).gb" size 1048576 crc d0ed199c sha1 a1cbaacdf32cb8fb9a9c59fcb624c729e0ca17bf ) ) game ( @@ -22468,6 +22962,12 @@ game ( rom ( name "Castlevania Legends (USA, Europe) (SGB Enhanced).gb" size 262144 crc ad9c17fb sha1 91a8e49bf6eac5fe62ec2cc5e6decbd08ce9b515 flags verified ) ) +game ( + name "Cat Boy's Stellar Journey (World) (Aftermarket) (Unl)" + description "Cat Boy's Stellar Journey (World) (Aftermarket) (Unl)" + rom ( name "Cat Boy's Stellar Journey (World) (Aftermarket) (Unl).gb" size 262144 crc 280937cc sha1 9ec53942f0136076b6468671562764e1ab9dee3b ) +) + game ( name "Catrap (USA)" description "Catrap (USA)" @@ -22480,6 +22980,12 @@ game ( rom ( name "Catrap (USA) (Beta).gb" size 32768 crc ca3bc888 sha1 928b9b3cb2ed5e6d6fc754679b3c994f7ed803c3 ) ) +game ( + name "Cave Fighter (World) (Aftermarket) (Unl)" + description "Cave Fighter (World) (Aftermarket) (Unl)" + rom ( name "Cave Fighter (World) (Aftermarket) (Unl).gb" size 262144 crc e8f91f6a sha1 290048717d0a8cabb3cc591161339550eb53c161 ) +) + game ( name "Cave Noire (Japan)" description "Cave Noire (Japan)" @@ -22529,9 +23035,9 @@ game ( ) game ( - name "Cherry Rescue! (World) (Aftermarket) (Homebrew)" - description "Cherry Rescue! (World) (Aftermarket) (Homebrew)" - rom ( name "Cherry Rescue! (World) (Aftermarket) (Homebrew).gb" size 524288 crc ba65812a sha1 740dba1827c730cc5d8bf67495bcede3a5352643 ) + name "Cherry Rescue! (World) (Aftermarket) (Unl)" + description "Cherry Rescue! (World) (Aftermarket) (Unl)" + rom ( name "Cherry Rescue! (World) (Aftermarket) (Unl).gb" size 524288 crc ba65812a sha1 740dba1827c730cc5d8bf67495bcede3a5352643 ) ) game ( @@ -22655,9 +23161,9 @@ game ( ) game ( - name "Christmas Carols (World) (Aftermarket) (Homebrew)" - description "Christmas Carols (World) (Aftermarket) (Homebrew)" - rom ( name "Christmas Carols (World) (Aftermarket) (Homebrew).gb" size 262144 crc a00bb310 sha1 e19b0a698d8194467d57c00664f00f9898ee5368 ) + name "Christmas Carols (World) (Aftermarket) (Unl)" + description "Christmas Carols (World) (Aftermarket) (Unl)" + rom ( name "Christmas Carols (World) (Aftermarket) (Unl).gb" size 262144 crc a00bb310 sha1 e19b0a698d8194467d57c00664f00f9898ee5368 ) ) game ( @@ -22667,27 +23173,27 @@ game ( ) game ( - name "Ciao Nonna (World) (v1.1) (Aftermarket) (Homebrew)" - description "Ciao Nonna (World) (v1.1) (Aftermarket) (Homebrew)" - rom ( name "Ciao Nonna (World) (v1.1) (Aftermarket) (Homebrew).gb" size 1048576 crc 4cd61bbf sha1 4bcae0e12b4d999099ce7ceb6d44c5ac45eeb292 ) + name "Ciao Nonna (World) (v1.1) (Aftermarket) (Unl)" + description "Ciao Nonna (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Ciao Nonna (World) (v1.1) (Aftermarket) (Unl).gb" size 1048576 crc 2165f2e4 sha1 f8e5020298183c3f47836da3890b6bb398e2ecab ) ) game ( - name "Ciao Nonna (World) (v2.0) (Aftermarket) (Homebrew)" - description "Ciao Nonna (World) (v2.0) (Aftermarket) (Homebrew)" - rom ( name "Ciao Nonna (World) (v2.0) (Aftermarket) (Homebrew).gb" size 1048576 crc 85f7d6e3 sha1 468b4902440ba87fbb7c7dbf89b094ec5e24d123 ) + name "Ciao Nonna (World) (v2.0) (Aftermarket) (Unl)" + description "Ciao Nonna (World) (v2.0) (Aftermarket) (Unl)" + rom ( name "Ciao Nonna (World) (v2.0) (Aftermarket) (Unl).gb" size 1048576 crc 6afee818 sha1 602709c8655febb42899881a8ef598fb4b3da0c9 ) ) game ( - name "Ciao Nonna (World) (v2.1) (Aftermarket) (Homebrew)" - description "Ciao Nonna (World) (v2.1) (Aftermarket) (Homebrew)" - rom ( name "Ciao Nonna (World) (v2.1) (Aftermarket) (Homebrew).gb" size 1048576 crc 225ce296 sha1 45aebfe4e7f5382a4b31c3db270b75d2c458d1b2 ) + name "Ciao Nonna (World) (v2.1) (Aftermarket) (Unl)" + description "Ciao Nonna (World) (v2.1) (Aftermarket) (Unl)" + rom ( name "Ciao Nonna (World) (v2.1) (Aftermarket) (Unl).gb" size 1048576 crc 225ce296 sha1 45aebfe4e7f5382a4b31c3db270b75d2c458d1b2 ) ) game ( - name "Ciao Nonna (World) (v1.0) (Aftermarket) (Homebrew)" - description "Ciao Nonna (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Ciao Nonna (World) (v1.0) (Aftermarket) (Homebrew).gb" size 1048576 crc dd7576d6 sha1 0556eb6160fdca6e7959bba1a619b8e34d974375 ) + name "Ciao Nonna (World) (v1.0) (Aftermarket) (Unl)" + description "Ciao Nonna (World) (v1.0) (Aftermarket) (Unl)" + rom ( name "Ciao Nonna (World) (v1.0) (Aftermarket) (Unl).gb" size 1048576 crc dd7576d6 sha1 0556eb6160fdca6e7959bba1a619b8e34d974375 ) ) game ( @@ -22697,9 +23203,9 @@ game ( ) game ( - name "Clockmaker's Tale, A (World) (Aftermarket) (Homebrew)" - description "Clockmaker's Tale, A (World) (Aftermarket) (Homebrew)" - rom ( name "Clockmaker's Tale, A (World) (Aftermarket) (Homebrew).gb" size 1048576 crc c4b00adb sha1 07e4254c7f74b9d4caa6a1231558b2430dff163a ) + name "Clockmaker's Tale, A (World) (Aftermarket) (Unl)" + description "Clockmaker's Tale, A (World) (Aftermarket) (Unl)" + rom ( name "Clockmaker's Tale, A (World) (Aftermarket) (Unl).gb" size 1048576 crc c4b00adb sha1 07e4254c7f74b9d4caa6a1231558b2430dff163a ) ) game ( @@ -22715,9 +23221,9 @@ game ( ) game ( - name "Commando (World) (Aftermarket) (Homebrew)" - description "Commando (World) (Aftermarket) (Homebrew)" - rom ( name "Commando (World) (Aftermarket) (Homebrew).gb" size 262144 crc 48173941 sha1 c861858e9f2cf7470e739c26ae9f17d3834ce464 ) + name "Commando (World) (Aftermarket) (Unl)" + description "Commando (World) (Aftermarket) (Unl)" + rom ( name "Commando (World) (Aftermarket) (Unl).gb" size 262144 crc 48173941 sha1 c861858e9f2cf7470e739c26ae9f17d3834ce464 ) ) game ( @@ -22745,9 +23251,9 @@ game ( ) game ( - name "Cookie's Bakery (World) (v1.0.3) (Aftermarket) (Homebrew)" - description "Cookie's Bakery (World) (v1.0.3) (Aftermarket) (Homebrew)" - rom ( name "Cookie's Bakery (World) (v1.0.3) (Aftermarket) (Homebrew).gb" size 524288 crc c53ace00 sha1 02fdc52bc934b80aa41520d92cf0340753d8f668 ) + name "Cookie's Bakery (World) (v1.0.3) (Aftermarket) (Unl)" + description "Cookie's Bakery (World) (v1.0.3) (Aftermarket) (Unl)" + rom ( name "Cookie's Bakery (World) (v1.0.3) (Aftermarket) (Unl).gb" size 524288 crc c53ace00 sha1 02fdc52bc934b80aa41520d92cf0340753d8f668 ) ) game ( @@ -22787,15 +23293,15 @@ game ( ) game ( - name "Coria and the Sunken City (Unknown) (Demo)" - description "Coria and the Sunken City (Unknown) (Demo)" - rom ( name "Coria and the Sunken City (Unknown) (Demo).gb" size 131072 crc fe1bdae6 sha1 00d86bbdbc228ff3ea0684984a261cadbed5fb0a ) + name "Coria and the Sunken City (World) (Demo) (Aftermarket) (Unl)" + description "Coria and the Sunken City (World) (Demo) (Aftermarket) (Unl)" + rom ( name "Coria and the Sunken City (World) (Demo) (Aftermarket) (Unl).gb" size 131072 crc fe1bdae6 sha1 00d86bbdbc228ff3ea0684984a261cadbed5fb0a ) ) game ( - name "Cosmic Courier - Trapped in Limbo (World) (Aftermarket) (Homebrew)" - description "Cosmic Courier - Trapped in Limbo (World) (Aftermarket) (Homebrew)" - rom ( name "Cosmic Courier - Trapped in Limbo (World) (Aftermarket) (Homebrew).gb" size 262144 crc fd304687 sha1 7df11ec2946099d49dd62928118b6d76703dc57c ) + name "Cosmic Courier - Trapped in Limbo (World) (Aftermarket) (Unl)" + description "Cosmic Courier - Trapped in Limbo (World) (Aftermarket) (Unl)" + rom ( name "Cosmic Courier - Trapped in Limbo (World) (Aftermarket) (Unl).gb" size 262144 crc fd304687 sha1 7df11ec2946099d49dd62928118b6d76703dc57c ) ) game ( @@ -22823,9 +23329,15 @@ game ( ) game ( - name "coucou (World) (Aftermarket) (Homebrew)" - description "coucou (World) (Aftermarket) (Homebrew)" - rom ( name "coucou (World) (Aftermarket) (Homebrew).gb" size 32768 crc e6aabd72 sha1 5283268e3640e2924d00aea3b12b2d3930bef43c ) + name "Coucou (World) (Aftermarket) (Unl)" + description "Coucou (World) (Aftermarket) (Unl)" + rom ( name "Coucou (World) (Aftermarket) (Unl).gb" size 32768 crc e6aabd72 sha1 5283268e3640e2924d00aea3b12b2d3930bef43c ) +) + +game ( + name "Counting Sheep (World) (Aftermarket) (Unl)" + description "Counting Sheep (World) (Aftermarket) (Unl)" + rom ( name "Counting Sheep (World) (Aftermarket) (Unl).GB" size 65536 crc 6e97c837 sha1 e7e251ad86fa00803837cf871de62d3f100c83ce ) ) game ( @@ -22877,9 +23389,9 @@ game ( ) game ( - name "Cuthbert in the Cooler (World) (Aftermarket) (Homebrew)" - description "Cuthbert in the Cooler (World) (Aftermarket) (Homebrew)" - rom ( name "Cuthbert in the Cooler (World) (Aftermarket) (Homebrew).gb" size 262144 crc 10838580 sha1 c740bc995adf2bb638bb125a36edc416558fd4c6 ) + name "Cuthbert in the Cooler (World) (Aftermarket) (Unl)" + description "Cuthbert in the Cooler (World) (Aftermarket) (Unl)" + rom ( name "Cuthbert in the Cooler (World) (Aftermarket) (Unl).gb" size 262144 crc 10838580 sha1 c740bc995adf2bb638bb125a36edc416558fd4c6 ) ) game ( @@ -22949,21 +23461,21 @@ game ( ) game ( - name "Dangan (World) (v1.1) (Aftermarket) (Homebrew)" - description "Dangan (World) (v1.1) (Aftermarket) (Homebrew)" - rom ( name "Dangan (World) (v1.1) (Aftermarket) (Homebrew).gb" size 262144 crc 5359d6db sha1 10029774046dabec2d8c0533caf94091f6e19071 ) + name "Dangan (World) (v1.1) (Aftermarket) (Unl)" + description "Dangan (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Dangan (World) (v1.1) (Aftermarket) (Unl).gb" size 262144 crc 5359d6db sha1 10029774046dabec2d8c0533caf94091f6e19071 ) ) game ( - name "Dangan (World) (v1.0) (Aftermarket) (Homebrew)" - description "Dangan (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Dangan (World) (v1.0) (Aftermarket) (Homebrew).gb" size 262144 crc daa59c9c sha1 8375d845fcfe4c236cd68f3d56a290f8c7b76c06 ) + name "Dangan (World) (v1.0) (Aftermarket) (Unl)" + description "Dangan (World) (v1.0) (Aftermarket) (Unl)" + rom ( name "Dangan (World) (v1.0) (Aftermarket) (Unl).gb" size 262144 crc daa59c9c sha1 8375d845fcfe4c236cd68f3d56a290f8c7b76c06 ) ) game ( - name "Dark Winter Wander, A (World) (Aftermarket) (Homebrew)" - description "Dark Winter Wander, A (World) (Aftermarket) (Homebrew)" - rom ( name "Dark Winter Wander, A (World) (Aftermarket) (Homebrew).gb" size 1048576 crc df19aa9f sha1 412f18ba08f2f2a2afbf3bfb5281e360d7aba31d ) + name "Dark Winter Wander, A (World) (Aftermarket) (Unl)" + description "Dark Winter Wander, A (World) (Aftermarket) (Unl)" + rom ( name "Dark Winter Wander, A (World) (Aftermarket) (Unl).gb" size 1048576 crc df19aa9f sha1 412f18ba08f2f2a2afbf3bfb5281e360d7aba31d ) ) game ( @@ -22975,7 +23487,7 @@ game ( game ( name "Darkwing Duck (Europe)" description "Darkwing Duck (Europe)" - rom ( name "Darkwing Duck (Europe).gb" size 131072 crc be975b4f sha1 4e51919597aa72dd32182f9afee34f148036655f ) + rom ( name "Darkwing Duck (Europe).gb" size 131072 crc be975b4f sha1 4e51919597aa72dd32182f9afee34f148036655f flags verified ) ) game ( @@ -22984,6 +23496,12 @@ game ( rom ( name "Darkwing Duck (USA).gb" size 131072 crc 238b9646 sha1 cc1f12f3ec3852657a14d11c13d1ef91fbdda5bb ) ) +game ( + name "Darkwing Duck (USA) (Beta)" + description "Darkwing Duck (USA) (Beta)" + rom ( name "Darkwing Duck (USA) (Beta).gb" size 131072 crc 311ade03 sha1 2883b8854529369cce4d473e71fa0193c7df02b6 ) +) + game ( name "Darkwing Duck (Germany)" description "Darkwing Duck (Germany)" @@ -22997,9 +23515,9 @@ game ( ) game ( - name "Dash (World) (Aftermarket) (Homebrew)" - description "Dash (World) (Aftermarket) (Homebrew)" - rom ( name "Dash (World) (Aftermarket) (Homebrew).gb" size 262144 crc 73868683 sha1 c1ffe7c25a34d65ed166293bc7d2b48b65ca922b ) + name "Dash (World) (Aftermarket) (Unl)" + description "Dash (World) (Aftermarket) (Unl)" + rom ( name "Dash (World) (Aftermarket) (Unl).gb" size 262144 crc 73868683 sha1 c1ffe7c25a34d65ed166293bc7d2b48b65ca922b ) ) game ( @@ -23015,9 +23533,9 @@ game ( ) game ( - name "Dawn Will Come (World) (Aftermarket) (Homebrew)" - description "Dawn Will Come (World) (Aftermarket) (Homebrew)" - rom ( name "Dawn Will Come (World) (Aftermarket) (Homebrew).gb" size 2097152 crc bb64f51c sha1 1611ddfdb9af72f20ddeca4133d8eebd0f14e424 ) + name "Dawn Will Come (World) (Aftermarket) (Unl)" + description "Dawn Will Come (World) (Aftermarket) (Unl)" + rom ( name "Dawn Will Come (World) (Aftermarket) (Unl).gb" size 2097152 crc bb64f51c sha1 1611ddfdb9af72f20ddeca4133d8eebd0f14e424 ) ) game ( @@ -23039,15 +23557,15 @@ game ( ) game ( - name "Deadeus (World) (1.3.8) (Aftermarket) (Unl)" - description "Deadeus (World) (1.3.8) (Aftermarket) (Unl)" - rom ( name "Deadeus (World) (1.3.8) (Aftermarket) (Unl).gb" size 1048576 crc 7da95971 sha1 23cff594ef4b0bb21883b422940526c7fe81f1fd ) + name "Deadeus (World) (v1.3.8) (Aftermarket) (Unl)" + description "Deadeus (World) (v1.3.8) (Aftermarket) (Unl)" + rom ( name "Deadeus (World) (v1.3.8) (Aftermarket) (Unl).gb" size 1048576 crc 7da95971 sha1 23cff594ef4b0bb21883b422940526c7fe81f1fd flags verified ) ) game ( - name "Deadeus (World) (1.2.5) (Aftermarket) (Unl)" - description "Deadeus (World) (1.2.5) (Aftermarket) (Unl)" - rom ( name "Deadeus (World) (1.2.5) (Aftermarket) (Unl).gb" size 1048576 crc 9e2bf649 sha1 3feeba5c438880f70cdfdc4ea7e29f77e645e9be ) + name "Deadeus (World) (v1.2.5) (Aftermarket) (Unl)" + description "Deadeus (World) (v1.2.5) (Aftermarket) (Unl)" + rom ( name "Deadeus (World) (v1.2.5) (Aftermarket) (Unl).gb" size 1048576 crc 9e2bf649 sha1 3feeba5c438880f70cdfdc4ea7e29f77e645e9be flags verified ) ) game ( @@ -23057,9 +23575,9 @@ game ( ) game ( - name "Deep Forest (World) (v1.1) (Aftermarket) (Homebrew)" - description "Deep Forest (World) (v1.1) (Aftermarket) (Homebrew)" - rom ( name "Deep Forest (World) (v1.1) (Aftermarket) (Homebrew).gb" size 1048576 crc 3ddf177d sha1 517302d20cb5140975e6cd1b3c495786b6390aaf ) + name "Deep Forest (World) (v1.1) (Aftermarket) (Unl)" + description "Deep Forest (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Deep Forest (World) (v1.1) (Aftermarket) (Unl).gb" size 1048576 crc 3ddf177d sha1 517302d20cb5140975e6cd1b3c495786b6390aaf ) ) game ( @@ -23105,9 +23623,9 @@ game ( ) game ( - name "DiaMaze (World) (Aftermarket) (Homebrew)" - description "DiaMaze (World) (Aftermarket) (Homebrew)" - rom ( name "DiaMaze (World) (Aftermarket) (Homebrew).gb" size 262144 crc 956fa901 sha1 f41b98ac4669920ffede1736ddbd9e1f62d4cb0d ) + name "DiaMaze (World) (Aftermarket) (Unl)" + description "DiaMaze (World) (Aftermarket) (Unl)" + rom ( name "DiaMaze (World) (Aftermarket) (Unl).gb" size 262144 crc 956fa901 sha1 f41b98ac4669920ffede1736ddbd9e1f62d4cb0d ) ) game ( @@ -23147,9 +23665,9 @@ game ( ) game ( - name "Dino's Offline Adventure (World) (Aftermarket) (Homebrew)" - description "Dino's Offline Adventure (World) (Aftermarket) (Homebrew)" - rom ( name "Dino's Offline Adventure (World) (Aftermarket) (Homebrew).gb" size 32768 crc d6bd0e6a sha1 6d11c145606f8e7ab25b2b07c299e36c8b442d23 ) + name "Dino's Offline Adventure (World) (Aftermarket) (Unl)" + description "Dino's Offline Adventure (World) (Aftermarket) (Unl)" + rom ( name "Dino's Offline Adventure (World) (Aftermarket) (Unl).gb" size 32768 crc d6bd0e6a sha1 6d11c145606f8e7ab25b2b07c299e36c8b442d23 ) ) game ( @@ -23165,81 +23683,69 @@ game ( ) game ( - name "Disco Elysium - Game Boy Edition (World) (Aftermarket) (Homebrew)" - description "Disco Elysium - Game Boy Edition (World) (Aftermarket) (Homebrew)" - rom ( name "Disco Elysium - Game Boy Edition (World) (Aftermarket) (Homebrew).gb" size 1048576 crc ef349515 sha1 06fe25086432c82f1e4b4c44473dd5b487a8af05 ) + name "Disco Elysium - Game Boy Edition (World) (Aftermarket) (Unl)" + description "Disco Elysium - Game Boy Edition (World) (Aftermarket) (Unl)" + rom ( name "Disco Elysium - Game Boy Edition (World) (Aftermarket) (Unl).gb" size 1048576 crc ef349515 sha1 06fe25086432c82f1e4b4c44473dd5b487a8af05 ) ) game ( - name "DMG Deals Damage (World) (Aftermarket) (Homebrew)" - description "DMG Deals Damage (World) (Aftermarket) (Homebrew)" - rom ( name "DMG Deals Damage (World) (Aftermarket) (Homebrew).gb" size 32768 crc 250e0cbd sha1 a15539199e4b6bd2a71d1ac0e7c61ae4f19a65e7 ) + name "DMG Deals Damage (World) (Aftermarket) (Unl)" + description "DMG Deals Damage (World) (Aftermarket) (Unl)" + rom ( name "DMG Deals Damage (World) (Aftermarket) (Unl).gb" size 32768 crc 250e0cbd sha1 a15539199e4b6bd2a71d1ac0e7c61ae4f19a65e7 ) ) game ( - name "DMG Express (World) (v3.0.1) (Aftermarket) (Homebrew)" - description "DMG Express (World) (v3.0.1) (Aftermarket) (Homebrew)" - rom ( name "DMG Express (World) (v3.0.1) (Aftermarket) (Homebrew).gb" size 262144 crc 56dbbbd4 sha1 07babaa6980fdd6c6470291511280008bfdec26c ) + name "Do I Pass (World) (Demo) (Aftermarket) (Unl)" + description "Do I Pass (World) (Demo) (Aftermarket) (Unl)" + rom ( name "Do I Pass (World) (Demo) (Aftermarket) (Unl).gb" size 1048576 crc f339fa82 sha1 bdbc082017bdf2e4caa84e7d00dad9707727e9d4 ) ) game ( - name "DMG Express (World) (v2.0.1) (Aftermarket) (Homebrew)" - description "DMG Express (World) (v2.0.1) (Aftermarket) (Homebrew)" - rom ( name "DMG Express (World) (v2.0.1) (Aftermarket) (Homebrew).gbc" size 262144 crc 4c2364a4 sha1 3077afd1920ea041dd2631834276a6234076b485 flags verified ) + name "Do I Pass (World) (v1.4) (Aftermarket) (Unl)" + description "Do I Pass (World) (v1.4) (Aftermarket) (Unl)" + rom ( name "Do I Pass (World) (v1.4) (Aftermarket) (Unl).gb" size 1048576 crc cfb44d68 sha1 6dde71bbec8b807af5132d1ef2e99c6bb6af3a1c ) ) game ( - name "Do I Pass (World) (Demo) (Aftermarket) (Homebrew)" - description "Do I Pass (World) (Demo) (Aftermarket) (Homebrew)" - rom ( name "Do I Pass (World) (Demo) (Aftermarket) (Homebrew).gb" size 1048576 crc f339fa82 sha1 bdbc082017bdf2e4caa84e7d00dad9707727e9d4 ) + name "Do I Pass (World) (v1.4) (Web) (Aftermarket) (Unl)" + description "Do I Pass (World) (v1.4) (Web) (Aftermarket) (Unl)" + rom ( name "Do I Pass (World) (v1.4) (Web) (Aftermarket) (Unl).gb" size 1048576 crc 62e0c3a2 sha1 e1b516e472e0c681b4ad1c7f4aeda55c91e7f340 ) ) game ( - name "Do I Pass (World) (v1.4) (Aftermarket) (Homebrew)" - description "Do I Pass (World) (v1.4) (Aftermarket) (Homebrew)" - rom ( name "Do I Pass (World) (v1.4) (Aftermarket) (Homebrew).gb" size 1048576 crc cfb44d68 sha1 6dde71bbec8b807af5132d1ef2e99c6bb6af3a1c ) + name "Do I Pass (World) (Fr) (v1.4.2) (Aftermarket) (Unl)" + description "Do I Pass (World) (Fr) (v1.4.2) (Aftermarket) (Unl)" + rom ( name "Do I Pass (World) (Fr) (v1.4.2) (Aftermarket) (Unl).gb" size 1048576 crc e393d4aa sha1 d45dfcab4a76b62ea1e10730d01f755e12137484 ) ) game ( - name "Do I Pass (World) (v1.4) (Web) (Aftermarket) (Homebrew)" - description "Do I Pass (World) (v1.4) (Web) (Aftermarket) (Homebrew)" - rom ( name "Do I Pass (World) (v1.4) (Web) (Aftermarket) (Homebrew).gb" size 1048576 crc 62e0c3a2 sha1 e1b516e472e0c681b4ad1c7f4aeda55c91e7f340 ) + name "Do I Pass (World) (v1.5) (Aftermarket) (Unl)" + description "Do I Pass (World) (v1.5) (Aftermarket) (Unl)" + rom ( name "Do I Pass (World) (v1.5) (Aftermarket) (Unl).gb" size 1048576 crc 3890e0a4 sha1 6998e1bbf2ccb9db3d661ddac2d7edf7571d0af7 ) ) game ( - name "Do I Pass (World) (Fr) (v1.4.2) (Aftermarket) (Homebrew)" - description "Do I Pass (World) (Fr) (v1.4.2) (Aftermarket) (Homebrew)" - rom ( name "Do I Pass (World) (Fr) (v1.4.2) (Aftermarket) (Homebrew).gb" size 1048576 crc e393d4aa sha1 d45dfcab4a76b62ea1e10730d01f755e12137484 ) + name "Doctor GB Card 16M Loader (World) (Unl)" + description "Doctor GB Card 16M Loader (World) (Unl)" + rom ( name "Doctor GB Card 16M Loader (World) (Unl).gb" size 32768 crc 1bddf36f sha1 c298eacc0c73e648e81fbf5192aec3b6a438e053 ) ) game ( - name "Do I Pass (World) (v1.5) (Aftermarket) (Homebrew)" - description "Do I Pass (World) (v1.5) (Aftermarket) (Homebrew)" - rom ( name "Do I Pass (World) (v1.5) (Aftermarket) (Homebrew).gb" size 1048576 crc 3890e0a4 sha1 6998e1bbf2ccb9db3d661ddac2d7edf7571d0af7 ) + name "Doctor GB Card 4M (World) (Unl)" + description "Doctor GB Card 4M (World) (Unl)" + rom ( name "Doctor GB Card 4M (World) (Unl).gb" size 524288 crc 5d2e8cff sha1 43962a7459c8ccc0e5988060abd0f8668f30dfde ) ) game ( - name "Doctor GB Card 16M Loader (World) (Homebrew)" - description "Doctor GB Card 16M Loader (World) (Homebrew)" - rom ( name "Doctor GB Card 16M Loader (World) (Homebrew).gb" size 32768 crc 1bddf36f sha1 c298eacc0c73e648e81fbf5192aec3b6a438e053 ) + name "Doctor GB Card Demo (World) (Demo) (Unl)" + description "Doctor GB Card Demo (World) (Demo) (Unl)" + rom ( name "Doctor GB Card Demo (World) (Demo) (Unl).gb" size 32768 crc b5dfdc26 sha1 1d403b254971faa5834bc48adfd42cc0bbed3fe4 ) ) game ( - name "Doctor GB Card 4M (World) (Homebrew)" - description "Doctor GB Card 4M (World) (Homebrew)" - rom ( name "Doctor GB Card 4M (World) (Homebrew).gb" size 524288 crc 5d2e8cff sha1 43962a7459c8ccc0e5988060abd0f8668f30dfde ) -) - -game ( - name "Doctor GB Card Demo (World) (Demo) (Homebrew) (Alt)" - description "Doctor GB Card Demo (World) (Demo) (Homebrew) (Alt)" - rom ( name "Doctor GB Card Demo (World) (Demo) (Homebrew) (Alt).gb" size 32768 crc 3592390b sha1 a6e3b81b8664f41eb18770cd5757ed4d8338be54 ) -) - -game ( - name "Doctor GB Card Demo (World) (Demo) (Homebrew)" - description "Doctor GB Card Demo (World) (Demo) (Homebrew)" - rom ( name "Doctor GB Card Demo (World) (Demo) (Homebrew).gb" size 32768 crc b5dfdc26 sha1 1d403b254971faa5834bc48adfd42cc0bbed3fe4 ) + name "Doctor GB Card Demo (World) (Demo) (Unl) (Alt)" + description "Doctor GB Card Demo (World) (Demo) (Unl) (Alt)" + rom ( name "Doctor GB Card Demo (World) (Demo) (Unl) (Alt).gb" size 32768 crc 3592390b sha1 a6e3b81b8664f41eb18770cd5757ed4d8338be54 ) ) game ( @@ -23249,15 +23755,27 @@ game ( ) game ( - name "Dog's Muck Island (World) (Aftermarket) (Homebrew)" - description "Dog's Muck Island (World) (Aftermarket) (Homebrew)" - rom ( name "Dog's Muck Island (World) (Aftermarket) (Homebrew).gb" size 262144 crc 79a7a06c sha1 eb4cea3b9db770bf3b586578af1ad7427d88ee8e ) + name "Dog's Muck Island (World) (Aftermarket) (Unl)" + description "Dog's Muck Island (World) (Aftermarket) (Unl)" + rom ( name "Dog's Muck Island (World) (Aftermarket) (Unl).gb" size 262144 crc 79a7a06c sha1 eb4cea3b9db770bf3b586578af1ad7427d88ee8e ) ) game ( - name "Don't Forget About Me (World) (Prototype) (Aftermarket) (Homebrew)" - description "Don't Forget About Me (World) (Prototype) (Aftermarket) (Homebrew)" - rom ( name "Don't Forget About Me (World) (Prototype) (Aftermarket) (Homebrew).gb" size 524288 crc 52cf7153 sha1 74e6246405dd52d4c5c2eabc417946550c637164 ) + name "Don't Forget About Me (World) (v1.0) (Aftermarket) (Unl)" + description "Don't Forget About Me (World) (v1.0) (Aftermarket) (Unl)" + rom ( name "Don't Forget About Me (World) (v1.0) (Aftermarket) (Unl).gb" size 524288 crc 52cf7153 sha1 74e6246405dd52d4c5c2eabc417946550c637164 ) +) + +game ( + name "Don't Forget About Me (World) (v1.2) (Aftermarket) (Unl)" + description "Don't Forget About Me (World) (v1.2) (Aftermarket) (Unl)" + rom ( name "Don't Forget About Me (World) (v1.2) (Aftermarket) (Unl).gb" size 1048576 crc 3d87efd8 sha1 83201c43e9fc89f4393678ade8c6107b4cfdb628 ) +) + +game ( + name "Don't Forget About Me (World) (v1.1) (Aftermarket) (Unl)" + description "Don't Forget About Me (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Don't Forget About Me (World) (v1.1) (Aftermarket) (Unl).gb" size 1048576 crc 8ca9dbe0 sha1 cf6eaac37c4ae1b3cfc4ed8d2ce73a143635b525 ) ) game ( @@ -23665,7 +24183,7 @@ game ( game ( name "DX Bakenou Z (Japan) (Rev 1)" description "DX Bakenou Z (Japan) (Rev 1)" - rom ( name "DX Bakenou Z (Japan) (Rev 1).gb" size 131072 crc fadfd0f6 sha1 6e2b9b21ea7d8d482be1f17722a579f720f2029d ) + rom ( name "DX Bakenou Z (Japan) (Rev 1).gb" size 131072 crc fadfd0f6 sha1 6e2b9b21ea7d8d482be1f17722a579f720f2029d flags verified ) ) game ( @@ -23677,7 +24195,7 @@ game ( game ( name "Earthworm Jim (Europe)" description "Earthworm Jim (Europe)" - rom ( name "Earthworm Jim (Europe).gb" size 262144 crc b1a7a008 sha1 4249abe39285b02ccb0e739b5a2cf96ace1281ff ) + rom ( name "Earthworm Jim (Europe).gb" size 262144 crc b1a7a008 sha1 4249abe39285b02ccb0e739b5a2cf96ace1281ff flags verified ) ) game ( @@ -23716,6 +24234,12 @@ game ( rom ( name "Elite Soccer (USA) (SGB Enhanced).gb" size 131072 crc f54158a6 sha1 4576881ea25e29ebba91c49c89f8e70e105fbc60 ) ) +game ( + name "Empire of Dreams, The (World) (Aftermarket) (Unl)" + description "Empire of Dreams, The (World) (Aftermarket) (Unl)" + rom ( name "Empire of Dreams, The (World) (Aftermarket) (Unl).gb" size 524288 crc d87e5e68 sha1 92b2da6bd10337950ddcb8bb372cabccdce4f6bd ) +) + game ( name "Exodus - Journey to the Promised Land (USA) (Unl)" description "Exodus - Journey to the Promised Land (USA) (Unl)" @@ -23759,9 +24283,9 @@ game ( ) game ( - name "F-Zero - Project (World) (Aftermarket) (Homebrew)" - description "F-Zero - Project (World) (Aftermarket) (Homebrew)" - rom ( name "F-Zero - Project (World) (Aftermarket) (Homebrew).gb" size 1048576 crc 4c707059 sha1 5b823ee17691d286a45f0667cddd59ccaaab5d8b ) + name "F-Zero - Project (World) (Aftermarket) (Unl)" + description "F-Zero - Project (World) (Aftermarket) (Unl)" + rom ( name "F-Zero - Project (World) (Aftermarket) (Unl).gb" size 1048576 crc 4c707059 sha1 5b823ee17691d286a45f0667cddd59ccaaab5d8b ) ) game ( @@ -23785,7 +24309,7 @@ game ( game ( name "Faceball 2000 (USA)" description "Faceball 2000 (USA)" - rom ( name "Faceball 2000 (USA).gb" size 131072 crc 7d890cd0 sha1 b0bd15bace04e0a3eb89773f231ac3a532181a0a ) + rom ( name "Faceball 2000 (USA).gb" size 131072 crc 7d890cd0 sha1 b0bd15bace04e0a3eb89773f231ac3a532181a0a flags verified ) ) game ( @@ -23819,9 +24343,9 @@ game ( ) game ( - name "Farm, The (World) (Aftermarket) (Homebrew)" - description "Farm, The (World) (Aftermarket) (Homebrew)" - rom ( name "Farm, The (World) (Aftermarket) (Homebrew).gb" size 524288 crc fbc1b5e8 sha1 d0fe06920f7e771d76507f60881904370ffbc145 ) + name "Farm, The (World) (Aftermarket) (Unl)" + description "Farm, The (World) (Aftermarket) (Unl)" + rom ( name "Farm, The (World) (Aftermarket) (Unl).gb" size 524288 crc fbc1b5e8 sha1 d0fe06920f7e771d76507f60881904370ffbc145 ) ) game ( @@ -23963,9 +24487,9 @@ game ( ) game ( - name "Finders Keepers (World) (Aftermarket) (Homebrew)" - description "Finders Keepers (World) (Aftermarket) (Homebrew)" - rom ( name "Finders Keepers (World) (Aftermarket) (Homebrew).gb" size 524288 crc 6a98ac61 sha1 e40dd804388df8c08d3890d79eeafd8542cc8805 ) + name "Finders Keepers (World) (Aftermarket) (Unl)" + description "Finders Keepers (World) (Aftermarket) (Unl)" + rom ( name "Finders Keepers (World) (Aftermarket) (Unl).gb" size 524288 crc 6a98ac61 sha1 e40dd804388df8c08d3890d79eeafd8542cc8805 ) ) game ( @@ -23987,9 +24511,9 @@ game ( ) game ( - name "Fix My Heart (World) (Aftermarket) (Homebrew)" - description "Fix My Heart (World) (Aftermarket) (Homebrew)" - rom ( name "Fix My Heart (World) (Aftermarket) (Homebrew).gb" size 524288 crc 507f9ea2 sha1 28ad64e379dc60d6e1c2617e073f6361c0feb475 ) + name "Fix My Heart (World) (Aftermarket) (Unl)" + description "Fix My Heart (World) (Aftermarket) (Unl)" + rom ( name "Fix My Heart (World) (Aftermarket) (Unl).gb" size 524288 crc 507f9ea2 sha1 28ad64e379dc60d6e1c2617e073f6361c0feb475 ) ) game ( @@ -24011,9 +24535,9 @@ game ( ) game ( - name "Flashin' (World) (v3.0) (Aftermarket) (Homebrew)" - description "Flashin' (World) (v3.0) (Aftermarket) (Homebrew)" - rom ( name "Flashin' (World) (v3.0) (Aftermarket) (Homebrew).gb" size 1048576 crc fcc05ec4 sha1 64d31f34e189b3af08bf0c659a93f71dbf83ef71 ) + name "Flashin' (World) (v3.0) (Aftermarket) (Unl)" + description "Flashin' (World) (v3.0) (Aftermarket) (Unl)" + rom ( name "Flashin' (World) (v3.0) (Aftermarket) (Unl).gb" size 1048576 crc fcc05ec4 sha1 64d31f34e189b3af08bf0c659a93f71dbf83ef71 ) ) game ( @@ -24053,9 +24577,9 @@ game ( ) game ( - name "Flooder (World) (Aftermarket) (Homebrew)" - description "Flooder (World) (Aftermarket) (Homebrew)" - rom ( name "Flooder (World) (Aftermarket) (Homebrew).gb" size 32768 crc 253dcbe0 sha1 8fdcd8b02604ac5259fb6aa80e5ba67a03c861fb ) + name "Flooder (World) (Aftermarket) (Unl)" + description "Flooder (World) (Aftermarket) (Unl)" + rom ( name "Flooder (World) (Aftermarket) (Unl).gb" size 32768 crc 253dcbe0 sha1 8fdcd8b02604ac5259fb6aa80e5ba67a03c861fb ) ) game ( @@ -24077,9 +24601,15 @@ game ( ) game ( - name "Forest of Fallen Knights (World) (Aftermarket) (Homebrew)" - description "Forest of Fallen Knights (World) (Aftermarket) (Homebrew)" - rom ( name "Forest of Fallen Knights (World) (Aftermarket) (Homebrew).gb" size 524288 crc 4c21e563 sha1 f6f84f1a44d41fa7d26a9195ddf791fae53dfcdf ) + name "Forest of Fallen Knights (World) (Aftermarket) (Unl)" + description "Forest of Fallen Knights (World) (Aftermarket) (Unl)" + rom ( name "Forest of Fallen Knights (World) (Aftermarket) (Unl).gb" size 524288 crc fa2d92a1 sha1 24f02d116aafb7b55d6cfd6d263b7595986af843 ) +) + +game ( + name "Forest of Fallen Knights (World) (Beta) (Aftermarket) (Unl)" + description "Forest of Fallen Knights (World) (Beta) (Aftermarket) (Unl)" + rom ( name "Forest of Fallen Knights (World) (Beta) (Aftermarket) (Unl).gb" size 524288 crc 4c21e563 sha1 f6f84f1a44d41fa7d26a9195ddf791fae53dfcdf ) ) game ( @@ -24101,9 +24631,9 @@ game ( ) game ( - name "Friday the 13th - The GB Game (World) (Aftermarket) (Homebrew)" - description "Friday the 13th - The GB Game (World) (Aftermarket) (Homebrew)" - rom ( name "Friday the 13th - The GB Game (World) (Aftermarket) (Homebrew).gb" size 1048576 crc 7543586a sha1 397821751ac6464582f61566f3a7c731a46b24ba ) + name "Friday the 13th - The GB Game (World) (Aftermarket) (Unl)" + description "Friday the 13th - The GB Game (World) (Aftermarket) (Unl)" + rom ( name "Friday the 13th - The GB Game (World) (Aftermarket) (Unl).gb" size 1048576 crc 7543586a sha1 397821751ac6464582f61566f3a7c731a46b24ba ) ) game ( @@ -24161,15 +24691,15 @@ game ( ) game ( - name "G-Man (World) (Aftermarket) (Homebrew)" - description "G-Man (World) (Aftermarket) (Homebrew)" - rom ( name "G-Man (World) (Aftermarket) (Homebrew).gb" size 524288 crc 7296da69 sha1 fdc9933d46a063575c175453b1da8042fd28b135 ) + name "G-Man (World) (Aftermarket) (Unl)" + description "G-Man (World) (Aftermarket) (Unl)" + rom ( name "G-Man (World) (Aftermarket) (Unl).gb" size 524288 crc 7296da69 sha1 fdc9933d46a063575c175453b1da8042fd28b135 ) ) game ( - name "G-ZERO (World) (v2.6) (Aftermarket) (Homebrew)" - description "G-ZERO (World) (v2.6) (Aftermarket) (Homebrew)" - rom ( name "G-ZERO (World) (v2.6) (Aftermarket) (Homebrew).gb" size 65536 crc 7dd0c878 sha1 929d25a612308614ac3ac2ee5a19a9cd4a9968d6 ) + name "G-ZERO (World) (v2.6) (Aftermarket) (Unl)" + description "G-ZERO (World) (v2.6) (Aftermarket) (Unl)" + rom ( name "G-ZERO (World) (v2.6) (Aftermarket) (Unl).gb" size 65536 crc 7dd0c878 sha1 929d25a612308614ac3ac2ee5a19a9cd4a9968d6 ) ) game ( @@ -24209,9 +24739,9 @@ game ( ) game ( - name "Game Boy Camera Gallery 2022, The (World) (Aftermarket) (Homebrew)" - description "Game Boy Camera Gallery 2022, The (World) (Aftermarket) (Homebrew)" - rom ( name "Game Boy Camera Gallery 2022, The (World) (Aftermarket) (Homebrew).gb" size 524288 crc f1948966 sha1 14849ab5831c71949ec3a1fe7657050057d2cf29 ) + name "Game Boy Camera Gallery 2022, The (World) (Aftermarket) (Unl)" + description "Game Boy Camera Gallery 2022, The (World) (Aftermarket) (Unl)" + rom ( name "Game Boy Camera Gallery 2022, The (World) (Aftermarket) (Unl).gb" size 524288 crc f1948966 sha1 14849ab5831c71949ec3a1fe7657050057d2cf29 ) ) game ( @@ -24233,9 +24763,15 @@ game ( ) game ( - name "Game Boy Digital Sampling Oscilloscope (Europe) (v3.6) (Unl)" - description "Game Boy Digital Sampling Oscilloscope (Europe) (v3.6) (Unl)" - rom ( name "Game Boy Digital Sampling Oscilloscope (Europe) (v3.6) (Unl).gb" size 32768 crc 572ea59c sha1 68128bd8b01970054590a88b785d553456b68ec1 ) + name "Game Boy Digital Sampling Oscilloscope (Europe) (Unl)" + description "Game Boy Digital Sampling Oscilloscope (Europe) (Unl)" + rom ( name "Game Boy Digital Sampling Oscilloscope (Europe) (Unl).gb" size 32768 crc 572ea59c sha1 68128bd8b01970054590a88b785d553456b68ec1 ) +) + +game ( + name "Game Boy Digital Sampling Oscilloscope (Europe) (Demo) (Unl)" + description "Game Boy Digital Sampling Oscilloscope (Europe) (Demo) (Unl)" + rom ( name "Game Boy Digital Sampling Oscilloscope (Europe) (Demo) (Unl).gb" size 32768 crc a800444b sha1 db3e76bced7bd9844aedf23345c4307403006600 ) ) game ( @@ -24323,9 +24859,9 @@ game ( ) game ( - name "GameShark (USA) (Unl)" - description "GameShark (USA) (Unl)" - rom ( name "GameShark (USA) (Unl).gb" size 131072 crc d6909596 sha1 09a6dae5e5faae1c4b5cf20e1b913b1c5e72297e ) + name "GameShark (USA) (Unl) [b]" + description "GameShark (USA) (Unl) [b]" + rom ( name "GameShark (USA) (Unl) [b].gb" size 131072 crc d6909596 sha1 09a6dae5e5faae1c4b5cf20e1b913b1c5e72297e flags baddump ) ) game ( @@ -24400,6 +24936,12 @@ game ( rom ( name "GB Basketball (Japan).gb" size 131072 crc d9b24d21 sha1 65f73db2942d400a4c555fbcfd6313f07303d4e4 ) ) +game ( + name "GB Corp. (World) (Aftermarket) (Unl)" + description "GB Corp. (World) (Aftermarket) (Unl)" + rom ( name "GB Corp. (World) (Aftermarket) (Unl).gb" size 32768 crc fd2e40f4 sha1 e442316132506ff223a9e5f33d874b71ec09d71a ) +) + game ( name "GB Genjin (Japan)" description "GB Genjin (Japan)" @@ -24425,63 +24967,63 @@ game ( ) game ( - name "GB-Wordyl (World) (Pt-BR) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (Pt-BR) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (Pt-BR) (Aftermarket) (Homebrew).gb" size 32768 crc f20c0097 sha1 691af25434a09931a302ced52c757d28b9b06619 ) + name "GB-Wordyl (World) (Pt-BR) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (Pt-BR) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (Pt-BR) (Aftermarket) (Unl).gb" size 32768 crc f20c0097 sha1 691af25434a09931a302ced52c757d28b9b06619 ) ) game ( - name "GB-Wordyl (World) (Ca) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (Ca) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (Ca) (Aftermarket) (Homebrew).gb" size 32768 crc 164999eb sha1 197d3d194d4cb6cae9a358f7fb53d6de649e7c5f ) + name "GB-Wordyl (World) (Ca) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (Ca) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (Ca) (Aftermarket) (Unl).gb" size 32768 crc 164999eb sha1 197d3d194d4cb6cae9a358f7fb53d6de649e7c5f ) ) game ( - name "GB-Wordyl (World) (De) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (De) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (De) (Aftermarket) (Homebrew).gb" size 32768 crc 60b075d1 sha1 0a66254b2327b48657de64d6102ac05f1e8700e7 ) + name "GB-Wordyl (World) (De) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (De) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (De) (Aftermarket) (Unl).gb" size 32768 crc 60b075d1 sha1 0a66254b2327b48657de64d6102ac05f1e8700e7 ) ) game ( - name "GB-Wordyl (World) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (Aftermarket) (Homebrew).gb" size 32768 crc 8780e125 sha1 7163bfbaa2cae2f9d41c472128f69a4fa5879180 ) + name "GB-Wordyl (World) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (Aftermarket) (Unl).gb" size 32768 crc 8780e125 sha1 7163bfbaa2cae2f9d41c472128f69a4fa5879180 ) ) game ( - name "GB-Wordyl (World) (Es) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (Es) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (Es) (Aftermarket) (Homebrew).gb" size 32768 crc 1a169897 sha1 0636b968e8e24fe7c1910f61db6e94fa84824494 ) + name "GB-Wordyl (World) (Es) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (Es) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (Es) (Aftermarket) (Unl).gb" size 32768 crc 1a169897 sha1 0636b968e8e24fe7c1910f61db6e94fa84824494 ) ) game ( - name "GB-Wordyl (World) (Fr) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (Fr) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (Fr) (Aftermarket) (Homebrew).gb" size 32768 crc 593965ff sha1 75d566045a680aaed4d40b83b6418cbfab5b4422 ) + name "GB-Wordyl (World) (Fr) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (Fr) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (Fr) (Aftermarket) (Unl).gb" size 32768 crc 593965ff sha1 75d566045a680aaed4d40b83b6418cbfab5b4422 ) ) game ( - name "GB-Wordyl (World) (It) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (It) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (It) (Aftermarket) (Homebrew).gb" size 32768 crc d81ab567 sha1 1dfd3c4508ffb6598662bcf1b16ec109a9282dd5 ) + name "GB-Wordyl (World) (It) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (It) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (It) (Aftermarket) (Unl).gb" size 32768 crc d81ab567 sha1 1dfd3c4508ffb6598662bcf1b16ec109a9282dd5 ) ) game ( - name "GB-Wordyl (World) (Cornish Edition) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (Cornish Edition) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (Cornish Edition) (Aftermarket) (Homebrew).gb" size 32768 crc f795689c sha1 9a332127182e8155d1a3dc53e1382a6c9dde6aeb ) + name "GB-Wordyl (World) (Kw) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (Kw) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (Kw) (Aftermarket) (Unl).gb" size 32768 crc f795689c sha1 9a332127182e8155d1a3dc53e1382a6c9dde6aeb ) ) game ( - name "GB-Wordyl (World) (Es-XL) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (Es-XL) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (Es-XL) (Aftermarket) (Homebrew).gb" size 32768 crc 660ed37b sha1 0792fbef010cac21c12cbcb8b3c85b3af30faec4 ) + name "GB-Wordyl (World) (Es-XL) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (Es-XL) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (Es-XL) (Aftermarket) (Unl).gb" size 32768 crc 660ed37b sha1 0792fbef010cac21c12cbcb8b3c85b3af30faec4 ) ) game ( - name "GB-Wordyl (World) (Nl) (Aftermarket) (Homebrew)" - description "GB-Wordyl (World) (Nl) (Aftermarket) (Homebrew)" - rom ( name "GB-Wordyl (World) (Nl) (Aftermarket) (Homebrew).gb" size 32768 crc 6dc4faaa sha1 7617083f9b13b2f95bdf1b0b327ffb4f8ccbe3c9 ) + name "GB-Wordyl (World) (Nl) (Aftermarket) (Unl)" + description "GB-Wordyl (World) (Nl) (Aftermarket) (Unl)" + rom ( name "GB-Wordyl (World) (Nl) (Aftermarket) (Unl).gb" size 32768 crc 6dc4faaa sha1 7617083f9b13b2f95bdf1b0b327ffb4f8ccbe3c9 ) ) game ( @@ -24514,6 +25056,12 @@ game ( rom ( name "Gem Gem (Japan).gb" size 65536 crc a64a8710 sha1 90a29d7a56f64b596cda1c64c8998b63d12c321e ) ) +game ( + name "Genesis (World) (Aftermarket) (Unl)" + description "Genesis (World) (Aftermarket) (Unl)" + rom ( name "Genesis (World) (Aftermarket) (Unl).gb" size 65536 crc 74b3ec78 sha1 ca43f82d73ba0b3e43ec17f6bc6761c09ca23626 ) +) + game ( name "Genjin Collection (Japan) (SGB Enhanced)" description "Genjin Collection (Japan) (SGB Enhanced)" @@ -24551,9 +25099,9 @@ game ( ) game ( - name "Ghost Town (World) (Aftermarket) (Homebrew)" - description "Ghost Town (World) (Aftermarket) (Homebrew)" - rom ( name "Ghost Town (World) (Aftermarket) (Homebrew).gb" size 262144 crc 2d27cdf2 sha1 af526273cdaa6423b92d0484fb27af56fe355a5d ) + name "Ghost Town (World) (Aftermarket) (Unl)" + description "Ghost Town (World) (Aftermarket) (Unl)" + rom ( name "Ghost Town (World) (Aftermarket) (Unl).gb" size 262144 crc 2d27cdf2 sha1 af526273cdaa6423b92d0484fb27af56fe355a5d ) ) game ( @@ -24671,9 +25219,9 @@ game ( ) game ( - name "Gorf the Ghost Saves Halloween (World) (Aftermarket) (Homebrew)" - description "Gorf the Ghost Saves Halloween (World) (Aftermarket) (Homebrew)" - rom ( name "Gorf the Ghost Saves Halloween (World) (Aftermarket) (Homebrew).gb" size 262144 crc ab10cec6 sha1 9dcaa6824fab806683737bdf1c76609c68c451a5 ) + name "Gorf the Ghost Saves Halloween (World) (Aftermarket) (Unl)" + description "Gorf the Ghost Saves Halloween (World) (Aftermarket) (Unl)" + rom ( name "Gorf the Ghost Saves Halloween (World) (Aftermarket) (Unl).gb" size 262144 crc ab10cec6 sha1 9dcaa6824fab806683737bdf1c76609c68c451a5 ) ) game ( @@ -24791,15 +25339,15 @@ game ( ) game ( - name "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eijukugo 350 (Japan) (IE Institute)" - description "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eijukugo 350 (Japan) (IE Institute)" - rom ( name "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eijukugo 350 (Japan) (IE Institute).gb" size 262144 crc 5855b281 sha1 25f69cc4da819f7d5c449f6ad0cf4963011dc5a7 ) + name "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eijukugo 350 (Japan) (Possible Proto) (IE Institute)" + description "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eijukugo 350 (Japan) (Possible Proto) (IE Institute)" + rom ( name "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eijukugo 350 (Japan) (Possible Proto) (IE Institute).gb" size 262144 crc 5855b281 sha1 25f69cc4da819f7d5c449f6ad0cf4963011dc5a7 ) ) game ( - name "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eitango 1700 (Japan) (IE Institute)" - description "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eitango 1700 (Japan) (IE Institute)" - rom ( name "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eitango 1700 (Japan) (IE Institute).gb" size 262144 crc bc14fce6 sha1 48775723113ac0e3d1cbcf7381ba876d6dc30a7c ) + name "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eitango 1700 (Japan) (Possible Proto) (IE Institute)" + description "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eitango 1700 (Japan) (Possible Proto) (IE Institute)" + rom ( name "Goukaku Boy Series - Koukou Nyuushi Derujun - Chuugaku Eitango 1700 (Japan) (Possible Proto) (IE Institute).gb" size 262144 crc bc14fce6 sha1 48775723113ac0e3d1cbcf7381ba876d6dc30a7c ) ) game ( @@ -24959,21 +25507,21 @@ game ( ) game ( - name "Gun Law (World) (Aftermarket) (Homebrew)" - description "Gun Law (World) (Aftermarket) (Homebrew)" - rom ( name "Gun Law (World) (Aftermarket) (Homebrew).gb" size 262144 crc b0d53211 sha1 ef6e3d287e99bd61861a333165c92b306238a45f ) + name "Gun Law (World) (Aftermarket) (Unl)" + description "Gun Law (World) (Aftermarket) (Unl)" + rom ( name "Gun Law (World) (Aftermarket) (Unl).gb" size 262144 crc b0d53211 sha1 ef6e3d287e99bd61861a333165c92b306238a45f ) ) game ( - name "Gunman Clive (World) (Aftermarket) (Homebrew)" - description "Gunman Clive (World) (Aftermarket) (Homebrew)" - rom ( name "Gunman Clive (World) (Aftermarket) (Homebrew).gb" size 65536 crc 11f5fded sha1 ec03763db2c0d754e2eb7e98384ed92fc8aeeb1d ) + name "Gunman Clive (World) (Aftermarket) (Unl)" + description "Gunman Clive (World) (Aftermarket) (Unl)" + rom ( name "Gunman Clive (World) (Aftermarket) (Unl).gb" size 65536 crc 11f5fded sha1 ec03763db2c0d754e2eb7e98384ed92fc8aeeb1d ) ) game ( - name "Gunship (USA) (Aftermarket) (Homebrew)" - description "Gunship (USA) (Aftermarket) (Homebrew)" - rom ( name "Gunship (USA) (Aftermarket) (Homebrew).gb" size 131072 crc bd31eef8 sha1 a801977c3746799cfb4d8bdfd679e45cccd3b719 ) + name "Gunship (USA) (Aftermarket) (Unl)" + description "Gunship (USA) (Aftermarket) (Unl)" + rom ( name "Gunship (USA) (Aftermarket) (Unl).gb" size 131072 crc bd31eef8 sha1 a801977c3746799cfb4d8bdfd679e45cccd3b719 ) ) game ( @@ -25000,6 +25548,12 @@ game ( rom ( name "Hammerin' Harry - Ghost Building Company (USA) (Proto).gb" size 262144 crc 6c4d0377 sha1 c5f73b09f001fc4d7eaa40ffee00234ca6a41a41 ) ) +game ( + name "Harbour Attack (World) (Aftermarket) (Unl)" + description "Harbour Attack (World) (Aftermarket) (Unl)" + rom ( name "Harbour Attack (World) (Aftermarket) (Unl).gb" size 262144 crc 4018ebf7 sha1 5bbbe727ebc6a489f1a498d067e4f05d0d69b60f ) +) + game ( name "Harvest Moon GB (USA) (SGB Enhanced)" description "Harvest Moon GB (USA) (SGB Enhanced)" @@ -25013,9 +25567,9 @@ game ( ) game ( - name "Hauntsfield (World) (Aftermarket) (Homebrew)" - description "Hauntsfield (World) (Aftermarket) (Homebrew)" - rom ( name "Hauntsfield (World) (Aftermarket) (Homebrew).gb" size 1048576 crc 287afc75 sha1 42ccc76883c041bc5c6c25a2e61ccc939c14811f ) + name "Hauntsfield (World) (Aftermarket) (Unl)" + description "Hauntsfield (World) (Aftermarket) (Unl)" + rom ( name "Hauntsfield (World) (Aftermarket) (Unl).gb" size 1048576 crc 287afc75 sha1 42ccc76883c041bc5c6c25a2e61ccc939c14811f ) ) game ( @@ -25031,9 +25585,9 @@ game ( ) game ( - name "Heart Knight (World) (v1.0) (Aftermarket) (Homebrew)" - description "Heart Knight (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Heart Knight (World) (v1.0) (Aftermarket) (Homebrew).gb" size 32768 crc c7ecac73 sha1 dedcab41f58c58834e3534e877c494669a0d8952 ) + name "Heart Knight (World) (Aftermarket) (Unl)" + description "Heart Knight (World) (Aftermarket) (Unl)" + rom ( name "Heart Knight (World) (Aftermarket) (Unl).gb" size 32768 crc c7ecac73 sha1 dedcab41f58c58834e3534e877c494669a0d8952 ) ) game ( @@ -25205,9 +25759,9 @@ game ( ) game ( - name "Hook (Europe) (Beta)" - description "Hook (Europe) (Beta)" - rom ( name "Hook (Europe) (Beta).gb" size 131072 crc c091abc8 sha1 0299d84e5f722e9298c1ce190b8e4a785849b868 ) + name "Hook (USA) (Sample)" + description "Hook (USA) (Sample)" + rom ( name "Hook (USA) (Sample).gb" size 131072 crc c091abc8 sha1 0299d84e5f722e9298c1ce190b8e4a785849b868 ) ) game ( @@ -25252,6 +25806,12 @@ game ( rom ( name "Hugo (Europe) (SGB Enhanced).gb" size 131072 crc 74aa5e0f sha1 d314dffcf5fb441d5d6d6419e01dc825e90cf0fc flags verified ) ) +game ( + name "Hugo (World) (Aftermarket) (Unl)" + description "Hugo (World) (Aftermarket) (Unl)" + rom ( name "Hugo (World) (Aftermarket) (Unl).gb" size 262144 crc 8cc87f60 sha1 53fac2774b29a32501788e1eac65db23bf5c7b8f ) +) + game ( name "Hugo 2 (Germany)" description "Hugo 2 (Germany)" @@ -25313,9 +25873,9 @@ game ( ) game ( - name "If (World) (Aftermarket) (Homebrew)" - description "If (World) (Aftermarket) (Homebrew)" - rom ( name "If (World) (Aftermarket) (Homebrew).gb" size 1048576 crc be7e4454 sha1 c11d8dc9ce96133f679678b07822a82f985e16f9 ) + name "If (World) (Aftermarket) (Unl)" + description "If (World) (Aftermarket) (Unl)" + rom ( name "If (World) (Aftermarket) (Unl).gb" size 1048576 crc be7e4454 sha1 c11d8dc9ce96133f679678b07822a82f985e16f9 ) ) game ( @@ -25330,10 +25890,16 @@ game ( rom ( name "Ikari no Yousai 2 (Japan).gb" size 131072 crc 6d4fd9aa sha1 ae437d4fb39d7438fc9eb98c91820aa2b5161b4f ) ) +game ( + name "In the Dark (World) (Aftermarket) (Unl)" + description "In the Dark (World) (Aftermarket) (Unl)" + rom ( name "In the Dark (World) (Aftermarket) (Unl).gb" size 1048576 crc 89ac8948 sha1 dfed92c906d9fd4e4ee405453ee391e0e9b174bf ) +) + game ( name "In Your Face (USA)" description "In Your Face (USA)" - rom ( name "In Your Face (USA).gb" size 131072 crc 80ac487e sha1 0be0f2a952497b321655dbee5c89678f40bf0b5a ) + rom ( name "In Your Face (USA).gb" size 131072 crc 80ac487e sha1 0be0f2a952497b321655dbee5c89678f40bf0b5a flags verified ) ) game ( @@ -25342,6 +25908,12 @@ game ( rom ( name "Incredible Crash Dummies, The (USA, Europe).gb" size 131072 crc d81c08fa sha1 f62d369b576cfd2882f8e03a5a32238eb1599477 ) ) +game ( + name "IndestructoTank! (World) (Aftermarket) (Unl)" + description "IndestructoTank! (World) (Aftermarket) (Unl)" + rom ( name "IndestructoTank! (World) (Aftermarket) (Unl).gb" size 32768 crc aa176926 sha1 e70992f8598c489bd34c30bd3a4c3789dddb8057 ) +) + game ( name "Indiana Jones - Saigo no Seisen (Japan)" description "Indiana Jones - Saigo no Seisen (Japan)" @@ -25363,7 +25935,7 @@ game ( game ( name "InfoGenius Productivity Pak - Berlitz French Translator (USA, Europe)" description "InfoGenius Productivity Pak - Berlitz French Translator (USA, Europe)" - rom ( name "InfoGenius Productivity Pak - Berlitz French Translator (USA, Europe).gb" size 131072 crc 80485fda sha1 ea77a00e9982ffa710ce9e179d4614419f9e1a35 ) + rom ( name "InfoGenius Productivity Pak - Berlitz French Translator (USA, Europe).gb" size 131072 crc 80485fda sha1 ea77a00e9982ffa710ce9e179d4614419f9e1a35 flags verified ) ) game ( @@ -25414,6 +25986,12 @@ game ( rom ( name "Initial D Gaiden (Japan) (SGB Enhanced).gb" size 262144 crc 6cc56612 sha1 1b3c4c1c4dfca46a009eb2e5cd45b343d7ee6681 ) ) +game ( + name "Interblocked (World) (Aftermarket) (Unl)" + description "Interblocked (World) (Aftermarket) (Unl)" + rom ( name "Interblocked (World) (Aftermarket) (Unl).gb" size 262144 crc 5c208855 sha1 8e46486533a3de9ee87cc07bf8efaf818752a61a ) +) + game ( name "International Superstar Soccer (USA, Europe) (SGB Enhanced)" description "International Superstar Soccer (USA, Europe) (SGB Enhanced)" @@ -25481,9 +26059,9 @@ game ( ) game ( - name "Jabberwocky (World) (Aftermarket) (Homebrew)" - description "Jabberwocky (World) (Aftermarket) (Homebrew)" - rom ( name "Jabberwocky (World) (Aftermarket) (Homebrew).gb" size 1048576 crc cfc51717 sha1 b4e447f2197688c740a45dce27879a62c742fb96 ) + name "Jabberwocky (World) (Aftermarket) (Unl)" + description "Jabberwocky (World) (Aftermarket) (Unl)" + rom ( name "Jabberwocky (World) (Aftermarket) (Unl).gb" size 1048576 crc cfc51717 sha1 b4e447f2197688c740a45dce27879a62c742fb96 ) ) game ( @@ -25511,9 +26089,9 @@ game ( ) game ( - name "Jana of the Jungle (World) (Demo) (Aftermarket) (Homebrew)" - description "Jana of the Jungle (World) (Demo) (Aftermarket) (Homebrew)" - rom ( name "Jana of the Jungle (World) (Demo) (Aftermarket) (Homebrew).gb" size 262144 crc c68e751a sha1 dfc65dcf700a26b273d705b7cfececbc44b80587 ) + name "Jane in the Jungle (World) (Aftermarket) (Unl)" + description "Jane in the Jungle (World) (Aftermarket) (Unl)" + rom ( name "Jane in the Jungle (World) (Aftermarket) (Unl).gb" size 262144 crc c68e751a sha1 dfc65dcf700a26b273d705b7cfececbc44b80587 ) ) game ( @@ -25543,7 +26121,7 @@ game ( game ( name "Jeep Jamboree - Off-Road Adventure (USA)" description "Jeep Jamboree - Off-Road Adventure (USA)" - rom ( name "Jeep Jamboree - Off-Road Adventure (USA).gb" size 131072 crc a1e76a33 sha1 988a40823f7db661bc91ffff8fc1bcd80e64f18d ) + rom ( name "Jeep Jamboree - Off-Road Adventure (USA).gb" size 131072 crc a1e76a33 sha1 988a40823f7db661bc91ffff8fc1bcd80e64f18d flags verified ) ) game ( @@ -25600,6 +26178,18 @@ game ( rom ( name "Jet Pak Man (Europe) (Proto).gb" size 32768 crc e40dfc1b sha1 0080f76961164810d6c0543d951a465cfa1cab54 ) ) +game ( + name "Jet Set Willy (World) (Aftermarket) (Unl)" + description "Jet Set Willy (World) (Aftermarket) (Unl)" + rom ( name "Jet Set Willy (World) (Aftermarket) (Unl).gb" size 262144 crc 1686d7ed sha1 0e594224506fd087e36c66bb66905d4c91b02461 ) +) + +game ( + name "Jetsons, The - Robot Panic (USA, Europe)" + description "Jetsons, The - Robot Panic (USA, Europe)" + rom ( name "Jetsons, The - Robot Panic (USA, Europe).gb" size 131072 crc 6386c870 sha1 1790c7462907f803e9641a330df6f06aa5e4e985 flags verified ) +) + game ( name "Jetsons, The - Robot Panic (USA) (Rev 1) (Possible Proto)" description "Jetsons, The - Robot Panic (USA) (Rev 1) (Possible Proto)" @@ -25612,12 +26202,6 @@ game ( rom ( name "Jetsons, The - Robot Panic (Japan) (Proto).gb" size 131072 crc d016d141 sha1 6035ee9364413b9eac7eb2290a806ffb57dc25cc ) ) -game ( - name "Jetsons, The - Robot Panic (USA, Europe)" - description "Jetsons, The - Robot Panic (USA, Europe)" - rom ( name "Jetsons, The - Robot Panic (USA, Europe).gb" size 131072 crc 6386c870 sha1 1790c7462907f803e9641a330df6f06aa5e4e985 flags verified ) -) - game ( name "Jikuu Senki Mu (Japan)" description "Jikuu Senki Mu (Japan)" @@ -25672,6 +26256,12 @@ game ( rom ( name "Joe & Mac - Caveman Ninja (Europe) (En,Fr,De,Es,It,Nl,Sv) (Beta).gb" size 262144 crc 381c62ee sha1 979e36765291153b82025b1f036c08fe9c23cfa5 ) ) +game ( + name "Joe Blade 2 (World) (Aftermarket) (Unl)" + description "Joe Blade 2 (World) (Aftermarket) (Unl)" + rom ( name "Joe Blade 2 (World) (Aftermarket) (Unl).gb" size 524288 crc 09f75c70 sha1 f001dffcd16be670c36a98dd9136f6f9fbf5b85d ) +) + game ( name "John Madden Football (USA) (Proto 2) (SGB Enhanced)" description "John Madden Football (USA) (Proto 2) (SGB Enhanced)" @@ -25801,7 +26391,7 @@ game ( game ( name "Kaisen Game - Navyblue (Japan)" description "Kaisen Game - Navyblue (Japan)" - rom ( name "Kaisen Game - Navyblue (Japan).gb" size 65536 crc afd8c47c sha1 7fbf13dd0b5a4e7798724270a81006be9950f81a ) + rom ( name "Kaisen Game - Navyblue (Japan).gb" size 65536 crc afd8c47c sha1 7fbf13dd0b5a4e7798724270a81006be9950f81a flags verified ) ) game ( @@ -25889,9 +26479,9 @@ game ( ) game ( - name "Kenzie's Birthday Dash (World) (Aftermarket) (Homebrew)" - description "Kenzie's Birthday Dash (World) (Aftermarket) (Homebrew)" - rom ( name "Kenzie's Birthday Dash (World) (Aftermarket) (Homebrew).gb" size 1048576 crc 986bfd75 sha1 6d15b5bc33d7c7771ba62d56ae7f25fa081bc564 ) + name "Kenzie's Birthday Dash (World) (Aftermarket) (Unl)" + description "Kenzie's Birthday Dash (World) (Aftermarket) (Unl)" + rom ( name "Kenzie's Birthday Dash (World) (Aftermarket) (Unl).gb" size 1048576 crc 986bfd75 sha1 6d15b5bc33d7c7771ba62d56ae7f25fa081bc564 ) ) game ( @@ -26005,19 +26595,19 @@ game ( game ( name "Kirby no Block Ball (Japan) (SGB Enhanced)" description "Kirby no Block Ball (Japan) (SGB Enhanced)" - rom ( name "Kirby no Block Ball (Japan) (SGB Enhanced).gb" size 524288 crc df3bbcd7 sha1 3fecc964d2c13ad98abac1df49f05c488833ef5a ) + rom ( name "Kirby no Block Ball (Japan) (SGB Enhanced).gb" size 524288 crc df3bbcd7 sha1 3fecc964d2c13ad98abac1df49f05c488833ef5a flags verified ) ) game ( name "Kirby no Kirakira Kids (Japan) (SGB Enhanced)" description "Kirby no Kirakira Kids (Japan) (SGB Enhanced)" - rom ( name "Kirby no Kirakira Kids (Japan) (SGB Enhanced).gb" size 262144 crc 47f42f42 sha1 2c46c42be76eca134e188814345afa390e298811 ) + rom ( name "Kirby no Kirakira Kids (Japan) (SGB Enhanced).gb" size 262144 crc 47f42f42 sha1 2c46c42be76eca134e188814345afa390e298811 flags verified ) ) game ( name "Kirby no Pinball (Japan)" description "Kirby no Pinball (Japan)" - rom ( name "Kirby no Pinball (Japan).gb" size 262144 crc 8b44fb7d sha1 678ca586f5a2e2fc894fb8e9f7b9efa54fabba44 ) + rom ( name "Kirby no Pinball (Japan).gb" size 262144 crc 8b44fb7d sha1 678ca586f5a2e2fc894fb8e9f7b9efa54fabba44 flags verified ) ) game ( @@ -26206,6 +26796,12 @@ game ( rom ( name "Kung-Fu Master (USA, Europe).gb" size 65536 crc 3340e600 sha1 b0bb485e2b57793da9f153db074c13a060a1e0d4 flags verified ) ) +game ( + name "Kung-Fu Master (USA, Europe) (Beta)" + description "Kung-Fu Master (USA, Europe) (Beta)" + rom ( name "Kung-Fu Master (USA, Europe) (Beta).gb" size 65536 crc e0c33c1a sha1 1ce6301d56294b71068514c666ad2157551134e7 ) +) + game ( name "Kuusou Kagaku Sekai Gulliver Boy - Kuusou Kagaku Puzzle Purittopon!! (Japan) (SGB Enhanced)" description "Kuusou Kagaku Sekai Gulliver Boy - Kuusou Kagaku Puzzle Purittopon!! (Japan) (SGB Enhanced)" @@ -26243,9 +26839,9 @@ game ( ) game ( - name "Laser Squad Alter (World) (Aftermarket) (Homebrew)" - description "Laser Squad Alter (World) (Aftermarket) (Homebrew)" - rom ( name "Laser Squad Alter (World) (Aftermarket) (Homebrew).gb" size 1048576 crc a39b3a5a sha1 1620e585e77a2457cffde82453bec0c7a04e07cf ) + name "Laser Squad Alter (World) (Aftermarket) (Unl)" + description "Laser Squad Alter (World) (Aftermarket) (Unl)" + rom ( name "Laser Squad Alter (World) (Aftermarket) (Unl).gb" size 1048576 crc a39b3a5a sha1 1620e585e77a2457cffde82453bec0c7a04e07cf ) ) game ( @@ -26255,9 +26851,9 @@ game ( ) game ( - name "Lawn Mower Land (World) (Aftermarket) (Homebrew)" - description "Lawn Mower Land (World) (Aftermarket) (Homebrew)" - rom ( name "Lawn Mower Land (World) (Aftermarket) (Homebrew).gb" size 32768 crc 11c62e39 sha1 1d6aa817ebf2a7e5d22285b906540272b67169da ) + name "Lawn Mower Land (World) (Aftermarket) (Unl)" + description "Lawn Mower Land (World) (Aftermarket) (Unl)" + rom ( name "Lawn Mower Land (World) (Aftermarket) (Unl).gb" size 32768 crc 11c62e39 sha1 1d6aa817ebf2a7e5d22285b906540272b67169da ) ) game ( @@ -26284,6 +26880,12 @@ game ( rom ( name "Lazlos' Leap (USA).gb" size 65536 crc 31fb404b sha1 c111470fcdadf191cf843e50c8f3d3dbb995a5c5 ) ) +game ( + name "Leak, The (World) (Aftermarket) (Unl)" + description "Leak, The (World) (Aftermarket) (Unl)" + rom ( name "Leak, The (World) (Aftermarket) (Unl).GB" size 65536 crc ba3cfeae sha1 98de2d8be75e6d9cb4e87afe14b1380c033de797 ) +) + game ( name "Learn and Play - Blackjack & Solitaire (USA) (1994-08-09) (Proto)" description "Learn and Play - Blackjack & Solitaire (USA) (1994-08-09) (Proto)" @@ -26543,9 +27145,9 @@ game ( ) game ( - name "Lucy and the Gem Dungeon (World) (Aftermarket) (Homebrew)" - description "Lucy and the Gem Dungeon (World) (Aftermarket) (Homebrew)" - rom ( name "Lucy and the Gem Dungeon (World) (Aftermarket) (Homebrew).gb" size 524288 crc 1a5106a2 sha1 f56c1b324b8c1e17da86e40c5e9a887ec19b0c80 ) + name "Lucy and the Gem Dungeon (World) (Aftermarket) (Unl)" + description "Lucy and the Gem Dungeon (World) (Aftermarket) (Unl)" + rom ( name "Lucy and the Gem Dungeon (World) (Aftermarket) (Unl).gb" size 524288 crc 1a5106a2 sha1 f56c1b324b8c1e17da86e40c5e9a887ec19b0c80 ) ) game ( @@ -26572,6 +27174,12 @@ game ( rom ( name "Mach Go Go Go (Japan) (SGB Enhanced).gb" size 262144 crc 016d230b sha1 baea8b49ce2402c5c094ddae0320a382c0d95d86 ) ) +game ( + name "Machine, The (World) (Aftermarket) (Unl)" + description "Machine, The (World) (Aftermarket) (Unl)" + rom ( name "Machine, The (World) (Aftermarket) (Unl).gb" size 2097152 crc b06036a9 sha1 50c5d1eb7c8946ac2d7e2c566b1ea4a9b0d58e90 ) +) + game ( name "Madden 95 (USA, Europe) (SGB Enhanced)" description "Madden 95 (USA, Europe) (SGB Enhanced)" @@ -26621,9 +27229,15 @@ game ( ) game ( - name "Magipanels (World) (Rev 1) (Demo) (Aftermarket) (Homebrew)" - description "Magipanels (World) (Rev 1) (Demo) (Aftermarket) (Homebrew)" - rom ( name "Magipanels (World) (Rev 1) (Demo) (Aftermarket) (Homebrew).gb" size 131072 crc 76fcbc30 sha1 7a759372bd013dcdb59c9131a2908085fc04d648 ) + name "Magipanels (World) (2022-02-09) (Demo) (Aftermarket) (Unl)" + description "Magipanels (World) (2022-02-09) (Demo) (Aftermarket) (Unl)" + rom ( name "Magipanels (World) (2022-02-09) (Demo) (Aftermarket) (Unl).gb" size 131072 crc 76fcbc30 sha1 7a759372bd013dcdb59c9131a2908085fc04d648 ) +) + +game ( + name "Magipanels (World) (Aftermarket) (Unl)" + description "Magipanels (World) (Aftermarket) (Unl)" + rom ( name "Magipanels (World) (Aftermarket) (Unl).gb" size 131072 crc 0b66561b sha1 c9513f6769098f2185f632f87386788f0f0b6ad8 ) ) game ( @@ -26663,9 +27277,9 @@ game ( ) game ( - name "Make Way (World) (Aftermarket) (Homebrew)" - description "Make Way (World) (Aftermarket) (Homebrew)" - rom ( name "Make Way (World) (Aftermarket) (Homebrew).gb" size 1048576 crc edeee5a8 sha1 19fd02e9318ed81ec968a2fca31ea0c3d8d94a7b ) + name "Make Way (World) (Aftermarket) (Unl)" + description "Make Way (World) (Aftermarket) (Unl)" + rom ( name "Make Way (World) (Aftermarket) (Unl).gb" size 1048576 crc edeee5a8 sha1 19fd02e9318ed81ec968a2fca31ea0c3d8d94a7b ) ) game ( @@ -26735,15 +27349,15 @@ game ( ) game ( - name "Marron Helps a Friend (World) (Aftermarket) (Homebrew)" - description "Marron Helps a Friend (World) (Aftermarket) (Homebrew)" - rom ( name "Marron Helps a Friend (World) (Aftermarket) (Homebrew).gb" size 1048576 crc 08a3a987 sha1 adcd56fa3eba93edd20be25ae5cb349070cd0323 ) + name "Marron Helps a Friend (World) (Aftermarket) (Unl)" + description "Marron Helps a Friend (World) (Aftermarket) (Unl)" + rom ( name "Marron Helps a Friend (World) (Aftermarket) (Unl).gb" size 1048576 crc 08a3a987 sha1 adcd56fa3eba93edd20be25ae5cb349070cd0323 flags verified ) ) game ( name "Maru's Mission (USA)" description "Maru's Mission (USA)" - rom ( name "Maru's Mission (USA).gb" size 131072 crc 6e4f1eb3 sha1 91446b1cc6dbf3b98b81f13e35ffe7bfaaa027aa ) + rom ( name "Maru's Mission (USA).gb" size 131072 crc 6e4f1eb3 sha1 91446b1cc6dbf3b98b81f13e35ffe7bfaaa027aa flags verified ) ) game ( @@ -26902,6 +27516,12 @@ game ( rom ( name "Mega Man V (USA) (SGB Enhanced).gb" size 524288 crc 72e6d21d sha1 9a7da0e4d3f49e4a0b94e85cd64e28a687d81260 ) ) +game ( + name "Mega Memory (World) (Unl)" + description "Mega Memory (World) (Unl)" + rom ( name "Mega Memory (World) (Unl).gb" size 32768 crc dba81e98 sha1 7690ccaa54b71188d07cb546d3c4d42c1d6363a8 ) +) + game ( name "Megalit (Japan)" description "Megalit (Japan)" @@ -26939,9 +27559,9 @@ game ( ) game ( - name "Melanie and the Magic Forest (World) (Aftermarket) (Homebrew)" - description "Melanie and the Magic Forest (World) (Aftermarket) (Homebrew)" - rom ( name "Melanie and the Magic Forest (World) (Aftermarket) (Homebrew).gb" size 262144 crc f614bdff sha1 7e46c193f6566f620acf20ead3a1fca1d677c221 ) + name "Melanie and the Magic Forest (World) (Aftermarket) (Unl)" + description "Melanie and the Magic Forest (World) (Aftermarket) (Unl)" + rom ( name "Melanie and the Magic Forest (World) (Aftermarket) (Unl).gb" size 262144 crc f614bdff sha1 7e46c193f6566f620acf20ead3a1fca1d677c221 ) ) game ( @@ -26962,6 +27582,12 @@ game ( rom ( name "Metal Masters (USA) (Beta).gb" size 131072 crc 3378064b sha1 572c820f7ec7a3c96df9af441da244befa5223b9 ) ) +game ( + name "Meteorite (World) (Aftermarket) (Unl)" + description "Meteorite (World) (Aftermarket) (Unl)" + rom ( name "Meteorite (World) (Aftermarket) (Unl).gb" size 262144 crc 50cf593d sha1 0e88a47faf3273f87c1ead7f789faeb06d5333f3 ) +) + game ( name "Metroid II - Return of Samus (World)" description "Metroid II - Return of Samus (World)" @@ -27079,13 +27705,13 @@ game ( game ( name "Midori no Makibaou (Japan) (SGB Enhanced)" description "Midori no Makibaou (Japan) (SGB Enhanced)" - rom ( name "Midori no Makibaou (Japan) (SGB Enhanced).gb" size 262144 crc 1dd93d95 sha1 4de380b202e375761f9fe7fb13349f5b02e2dac2 ) + rom ( name "Midori no Makibaou (Japan) (SGB Enhanced).gb" size 262144 crc 1dd93d95 sha1 4de380b202e375761f9fe7fb13349f5b02e2dac2 flags verified ) ) game ( - name "Midterm Moments (World) (Aftermarket) (Homebrew)" - description "Midterm Moments (World) (Aftermarket) (Homebrew)" - rom ( name "Midterm Moments (World) (Aftermarket) (Homebrew).gb" size 262144 crc f9a5ed4e sha1 8b3e18287d5adf3b5a711e28a896f843927f22a2 ) + name "Midterm Moments (World) (Aftermarket) (Unl)" + description "Midterm Moments (World) (Aftermarket) (Unl)" + rom ( name "Midterm Moments (World) (Aftermarket) (Unl).gb" size 262144 crc f9a5ed4e sha1 8b3e18287d5adf3b5a711e28a896f843927f22a2 ) ) game ( @@ -27203,9 +27829,9 @@ game ( ) game ( - name "Mission to Mars (World) (Aftermarket) (Homebrew)" - description "Mission to Mars (World) (Aftermarket) (Homebrew)" - rom ( name "Mission to Mars (World) (Aftermarket) (Homebrew).gb" size 262144 crc cb3a9ab8 sha1 31625a90b61d28ac782909ed8b00b3a48df7742e ) + name "Mission to Mars (World) (Aftermarket) (Unl)" + description "Mission to Mars (World) (Aftermarket) (Unl)" + rom ( name "Mission to Mars (World) (Aftermarket) (Unl).gb" size 262144 crc cb3a9ab8 sha1 31625a90b61d28ac782909ed8b00b3a48df7742e ) ) game ( @@ -27281,9 +27907,9 @@ game ( ) game ( - name "Mona and the Witch's Hat (World) (Aftermarket) (Homebrew)" - description "Mona and the Witch's Hat (World) (Aftermarket) (Homebrew)" - rom ( name "Mona and the Witch's Hat (World) (Aftermarket) (Homebrew).gb" size 65536 crc 32ed7f4a sha1 625d3664d99dee075021b03ea6ff074ae2e49204 ) + name "Mona and the Witch's Hat (World) (Aftermarket) (Unl)" + description "Mona and the Witch's Hat (World) (Aftermarket) (Unl)" + rom ( name "Mona and the Witch's Hat (World) (Aftermarket) (Unl).gb" size 65536 crc 32ed7f4a sha1 625d3664d99dee075021b03ea6ff074ae2e49204 ) ) game ( @@ -27377,9 +28003,9 @@ game ( ) game ( - name "Monty on the Run (World) (Aftermarket) (Homebrew)" - description "Monty on the Run (World) (Aftermarket) (Homebrew)" - rom ( name "Monty on the Run (World) (Aftermarket) (Homebrew).gb" size 524288 crc e2ebb406 sha1 41ac5993fbff58be0712b554aae5e4367b68677b ) + name "Monty on the Run (World) (Aftermarket) (Unl)" + description "Monty on the Run (World) (Aftermarket) (Unl)" + rom ( name "Monty on the Run (World) (Aftermarket) (Unl).gb" size 524288 crc e2ebb406 sha1 41ac5993fbff58be0712b554aae5e4367b68677b ) ) game ( @@ -27461,9 +28087,9 @@ game ( ) game ( - name "Mountain Climber (World) (Aftermarket) (Homebrew)" - description "Mountain Climber (World) (Aftermarket) (Homebrew)" - rom ( name "Mountain Climber (World) (Aftermarket) (Homebrew).gb" size 262144 crc 362a84ba sha1 b4b3afa2379a07acab6965ae8fcf6d2ba82d6d7e ) + name "Mountain Climber (World) (En,Es) (Aftermarket) (Unl)" + description "Mountain Climber (World) (En,Es) (Aftermarket) (Unl)" + rom ( name "Mountain Climber (World) (En,Es) (Aftermarket) (Unl).gb" size 262144 crc 362a84ba sha1 b4b3afa2379a07acab6965ae8fcf6d2ba82d6d7e ) ) game ( @@ -27515,9 +28141,9 @@ game ( ) game ( - name "Mud Warriors (World) (Aftermarket) (Homebrew)" - description "Mud Warriors (World) (Aftermarket) (Homebrew)" - rom ( name "Mud Warriors (World) (Aftermarket) (Homebrew).gb" size 524288 crc 846afc79 sha1 dae84fbc60839da29878714cd845c3704b915323 ) + name "Mud Warriors (World) (Aftermarket) (Unl)" + description "Mud Warriors (World) (Aftermarket) (Unl)" + rom ( name "Mud Warriors (World) (Aftermarket) (Unl).gb" size 524288 crc 846afc79 sha1 dae84fbc60839da29878714cd845c3704b915323 ) ) game ( @@ -27571,7 +28197,7 @@ game ( game ( name "Mystic Quest (France)" description "Mystic Quest (France)" - rom ( name "Mystic Quest (France).gb" size 262144 crc b6e134af sha1 b52d82248849f1ead9bf22954b3cbf7bf8e02907 ) + rom ( name "Mystic Quest (France).gb" size 262144 crc b6e134af sha1 b52d82248849f1ead9bf22954b3cbf7bf8e02907 flags verified ) ) game ( @@ -27766,6 +28392,12 @@ game ( rom ( name "Nekketsu! Beach Volley Da yo Kunio-kun (Japan) (SGB Enhanced).gb" size 262144 crc abfb84df sha1 d3ad43c40c21d9825b4879dbf28ca58835da658a ) ) +game ( + name "Neko Can Dream (World) (Ja) (Demo) (Aftermarket) (Unl)" + description "Neko Can Dream (World) (Ja) (Demo) (Aftermarket) (Unl)" + rom ( name "Neko Can Dream (World) (Ja) (Demo) (Aftermarket) (Unl).gb" size 2097152 crc a0ad7a18 sha1 5fb1a09e04cf1704b059468de8855dfa7e23043b ) +) + game ( name "Nekojara Monogatari (Japan)" description "Nekojara Monogatari (Japan)" @@ -28097,9 +28729,9 @@ game ( ) game ( - name "Oblique Strategies (World) (Aftermarket) (Homebrew)" - description "Oblique Strategies (World) (Aftermarket) (Homebrew)" - rom ( name "Oblique Strategies (World) (Aftermarket) (Homebrew).gb" size 524288 crc 30eca8d3 sha1 6a09aabf30e2ebab2e31ae845cd2e8796c84797f ) + name "Oblique Strategies (World) (Aftermarket) (Unl)" + description "Oblique Strategies (World) (Aftermarket) (Unl)" + rom ( name "Oblique Strategies (World) (Aftermarket) (Unl).gb" size 524288 crc 30eca8d3 sha1 6a09aabf30e2ebab2e31ae845cd2e8796c84797f ) ) game ( @@ -28109,9 +28741,9 @@ game ( ) game ( - name "Office Combat (World) (Aftermarket) (Homebrew)" - description "Office Combat (World) (Aftermarket) (Homebrew)" - rom ( name "Office Combat (World) (Aftermarket) (Homebrew).gb" size 262144 crc 9742276d sha1 cd8f18d6e5fabaa130a42e5618e29c5ff26f6a93 ) + name "Office Combat (World) (Aftermarket) (Unl)" + description "Office Combat (World) (Aftermarket) (Unl)" + rom ( name "Office Combat (World) (Aftermarket) (Unl).gb" size 262144 crc 9742276d sha1 cd8f18d6e5fabaa130a42e5618e29c5ff26f6a93 ) ) game ( @@ -28121,9 +28753,9 @@ game ( ) game ( - name "Olympic Skier (World) (Aftermarket) (Homebrew)" - description "Olympic Skier (World) (Aftermarket) (Homebrew)" - rom ( name "Olympic Skier (World) (Aftermarket) (Homebrew).gb" size 524288 crc 93174431 sha1 7bcdf10533f8fe053e22534c4962ed4b1a5cf2e2 ) + name "Olympic Skier (World) (Aftermarket) (Unl)" + description "Olympic Skier (World) (Aftermarket) (Unl)" + rom ( name "Olympic Skier (World) (Aftermarket) (Unl).gb" size 524288 crc 93174431 sha1 7bcdf10533f8fe053e22534c4962ed4b1a5cf2e2 ) ) game ( @@ -28234,6 +28866,18 @@ game ( rom ( name "Out of Gas (USA).gb" size 131072 crc 1b67e8b1 sha1 770ac35c0780cf432235593bd5674e72edd6cf7d ) ) +game ( + name "Out on a Limb (World) (Aftermarket) (Unl)" + description "Out on a Limb (World) (Aftermarket) (Unl)" + rom ( name "Out on a Limb (World) (Aftermarket) (Unl).gb" size 262144 crc be197f87 sha1 2ddc2378539316cf7a10e2cf8b562f17e1cf7a6a ) +) + +game ( + name "Outburst (Japan) (Demo)" + description "Outburst (Japan) (Demo)" + rom ( name "Outburst (Japan) (Demo).gb" size 262144 crc 9837ffac sha1 4a45cc555b486f5dafa4e844a7b6afceb709a1b9 ) +) + game ( name "Outburst (Japan)" description "Outburst (Japan)" @@ -28277,9 +28921,9 @@ game ( ) game ( - name "Pac-In-Time (Europe) (Rev 1) (Possible Proto) (SGB Enhanced)" - description "Pac-In-Time (Europe) (Rev 1) (Possible Proto) (SGB Enhanced)" - rom ( name "Pac-In-Time (Europe) (Rev 1) (Possible Proto) (SGB Enhanced).gb" size 262144 crc 7ff79df9 sha1 f30108b8e422c4b9776c90c884710194508545ea ) + name "Pac-In-Time (Europe) (Rev 1) (SGB Enhanced)" + description "Pac-In-Time (Europe) (Rev 1) (SGB Enhanced)" + rom ( name "Pac-In-Time (Europe) (Rev 1) (SGB Enhanced).gb" size 262144 crc 7ff79df9 sha1 f30108b8e422c4b9776c90c884710194508545ea ) ) game ( @@ -28468,6 +29112,12 @@ game ( rom ( name "Pang (Europe) (Beta).gb" size 131072 crc 54d2754a sha1 b57d436d8a83f2fb1e42bad3150893ef8ffab0d4 ) ) +game ( + name "Panty Hunty (World) (v1.4) (Aftermarket) (Unl)" + description "Panty Hunty (World) (v1.4) (Aftermarket) (Unl)" + rom ( name "Panty Hunty (World) (v1.4) (Aftermarket) (Unl).gb" size 524288 crc ce707b9a sha1 b5757b268a09d9085d469eda0676991f7361ad8c ) +) + game ( name "Paperboy (USA, Europe)" description "Paperboy (USA, Europe)" @@ -28487,9 +29137,9 @@ game ( ) game ( - name "Parasol Islands (World) (Aftermarket) (Homebrew)" - description "Parasol Islands (World) (Aftermarket) (Homebrew)" - rom ( name "Parasol Islands (World) (Aftermarket) (Homebrew).gb" size 1048576 crc b1d79c80 sha1 381f76af67f77e575beae2f45aefd21f079709e6 ) + name "Parasol Islands (World) (Aftermarket) (Unl)" + description "Parasol Islands (World) (Aftermarket) (Unl)" + rom ( name "Parasol Islands (World) (Aftermarket) (Unl).gb" size 1048576 crc b1d79c80 sha1 381f76af67f77e575beae2f45aefd21f079709e6 ) ) game ( @@ -28547,15 +29197,15 @@ game ( ) game ( - name "Perfect Blend (World) (Aftermarket) (Homebrew)" - description "Perfect Blend (World) (Aftermarket) (Homebrew)" - rom ( name "Perfect Blend (World) (Aftermarket) (Homebrew).gb" size 262144 crc 9b4bacaa sha1 09b0691f6823c54f1515dd3b66f135ac45a7dbc3 ) + name "Perfect Blend (World) (v0.9) (Aftermarket) (Unl)" + description "Perfect Blend (World) (v0.9) (Aftermarket) (Unl)" + rom ( name "Perfect Blend (World) (v0.9) (Aftermarket) (Unl).gb" size 262144 crc 9b4bacaa sha1 09b0691f6823c54f1515dd3b66f135ac45a7dbc3 ) ) game ( - name "Perfect Blend (World) (Rev 1) (Aftermarket) (Homebrew)" - description "Perfect Blend (World) (Rev 1) (Aftermarket) (Homebrew)" - rom ( name "Perfect Blend (World) (Rev 1) (Aftermarket) (Homebrew).gb" size 262144 crc 1e8b2a29 sha1 a25dfc43408080ac116479fa55c9e7320e5cc82a ) + name "Perfect Blend (World) (v0.9) (Bugfix) (Aftermarket) (Unl)" + description "Perfect Blend (World) (v0.9) (Bugfix) (Aftermarket) (Unl)" + rom ( name "Perfect Blend (World) (v0.9) (Bugfix) (Aftermarket) (Unl).gb" size 262144 crc 1e8b2a29 sha1 a25dfc43408080ac116479fa55c9e7320e5cc82a ) ) game ( @@ -28583,9 +29233,9 @@ game ( ) game ( - name "Phantom Fright (World) (Aftermarket) (Homebrew)" - description "Phantom Fright (World) (Aftermarket) (Homebrew)" - rom ( name "Phantom Fright (World) (Aftermarket) (Homebrew).gb" size 524288 crc 154cb547 sha1 3b217c595e6a5b115dc7130123a321049cf01d22 ) + name "Phantom Fright (World) (Aftermarket) (Unl)" + description "Phantom Fright (World) (Aftermarket) (Unl)" + rom ( name "Phantom Fright (World) (Aftermarket) (Unl).gb" size 524288 crc 154cb547 sha1 3b217c595e6a5b115dc7130123a321049cf01d22 ) ) game ( @@ -28595,15 +29245,15 @@ game ( ) game ( - name "Phobos Dere .GB (World) (Rev 1) (Demo) (Aftermarket) (Homebrew)" - description "Phobos Dere .GB (World) (Rev 1) (Demo) (Aftermarket) (Homebrew)" - rom ( name "Phobos Dere .GB (World) (Rev 1) (Demo) (Aftermarket) (Homebrew).gbc" size 262144 crc 58362b86 sha1 b4cc3c3735bc134e34b97e08ed24c57aef5c5ccf flags verified ) + name "Phobos Dere .GB (World) (Aftermarket) (Unl)" + description "Phobos Dere .GB (World) (Aftermarket) (Unl)" + rom ( name "Phobos Dere .GB (World) (Aftermarket) (Unl).gb" size 1048576 crc ae97f4f8 sha1 e541505078ee5e7e5c897b4621cc693ce71e35ba flags verified ) ) game ( - name "Phobos Dere .GB (World) (Aftermarket) (Homebrew)" - description "Phobos Dere .GB (World) (Aftermarket) (Homebrew)" - rom ( name "Phobos Dere .GB (World) (Aftermarket) (Homebrew).gbc" size 1048576 crc ae97f4f8 sha1 e541505078ee5e7e5c897b4621cc693ce71e35ba ) + name "Phobos Dere .GB (World) (2022-03-06) (Demo) (Aftermarket) (Unl)" + description "Phobos Dere .GB (World) (2022-03-06) (Demo) (Aftermarket) (Unl)" + rom ( name "Phobos Dere .GB (World) (2022-03-06) (Demo) (Aftermarket) (Unl).gb" size 262144 crc 58362b86 sha1 b4cc3c3735bc134e34b97e08ed24c57aef5c5ccf flags verified ) ) game ( @@ -28660,6 +29310,12 @@ game ( rom ( name "Pinball Mania (Europe).gb" size 262144 crc edc8d122 sha1 0541161b4b93fffc3c75708aca86ea0873747ced ) ) +game ( + name "Pineapple Kid (World) (Aftermarket) (Unl)" + description "Pineapple Kid (World) (Aftermarket) (Unl)" + rom ( name "Pineapple Kid (World) (Aftermarket) (Unl).gb" size 131072 crc 9541488b sha1 fd574b5d407f3654db3227900a8ed7eff4fe48ae ) +) + game ( name "Pingu - Sekai de 1ban Genki na Penguin (Japan)" description "Pingu - Sekai de 1ban Genki na Penguin (Japan)" @@ -28703,9 +29359,15 @@ game ( ) game ( - name "Pixel Who - The Lost Legions (World) (v1.0) (Aftermarket) (Homebrew)" - description "Pixel Who - The Lost Legions (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Pixel Who - The Lost Legions (World) (v1.0) (Aftermarket) (Homebrew).gb" size 1048576 crc f41cf2a7 sha1 c07378c774602060f1f93f18fbfd3f0d552fe2d2 ) + name "Pixel Who - The Lost Legions (World) (v1.0) (Aftermarket) (Unl)" + description "Pixel Who - The Lost Legions (World) (v1.0) (Aftermarket) (Unl)" + rom ( name "Pixel Who - The Lost Legions (World) (v1.0) (Aftermarket) (Unl).gb" size 1048576 crc f41cf2a7 sha1 c07378c774602060f1f93f18fbfd3f0d552fe2d2 ) +) + +game ( + name "Plants Eat My Zombies (World) (v1.1) (Aftermarket) (Unl)" + description "Plants Eat My Zombies (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Plants Eat My Zombies (World) (v1.1) (Aftermarket) (Unl).gb" size 524288 crc 60c84f2e sha1 c39ec5e6a9a0be0970b56e788c3f60e9821dbbd3 ) ) game ( @@ -28715,9 +29377,9 @@ game ( ) game ( - name "Pluto's Corner (World) (Aftermarket) (Homebrew)" - description "Pluto's Corner (World) (Aftermarket) (Homebrew)" - rom ( name "Pluto's Corner (World) (Aftermarket) (Homebrew).gb" size 65536 crc 21fc06b3 sha1 94e4a442205079d85d5ca37042eaec555f7f49db ) + name "Pluto's Corner (World) (Aftermarket) (Unl)" + description "Pluto's Corner (World) (Aftermarket) (Unl)" + rom ( name "Pluto's Corner (World) (Aftermarket) (Unl).gb" size 65536 crc 21fc06b3 sha1 94e4a442205079d85d5ca37042eaec555f7f49db ) ) game ( @@ -28753,7 +29415,7 @@ game ( game ( name "Pocket Camera (Japan) (Rev 1) (SGB Enhanced)" description "Pocket Camera (Japan) (Rev 1) (SGB Enhanced)" - rom ( name "Pocket Camera (Japan) (Rev 1) (SGB Enhanced).gb" size 1048576 crc 73e8ef96 sha1 912205ea22e1dd735ed4f41d247039eebf39dddc ) + rom ( name "Pocket Camera (Japan) (Rev 1) (SGB Enhanced).gb" size 1048576 crc 73e8ef96 sha1 912205ea22e1dd735ed4f41d247039eebf39dddc flags verified ) ) game ( @@ -28919,9 +29581,9 @@ game ( ) game ( - name "Pogo Pete (World) (Aftermarket) (Homebrew)" - description "Pogo Pete (World) (Aftermarket) (Homebrew)" - rom ( name "Pogo Pete (World) (Aftermarket) (Homebrew).gb" size 262144 crc 185ce1d5 sha1 74021e84c1d095b2401302e26352e810dbc432d8 ) + name "Pogo Pete (World) (Aftermarket) (Unl)" + description "Pogo Pete (World) (Aftermarket) (Unl)" + rom ( name "Pogo Pete (World) (Aftermarket) (Unl).gb" size 262144 crc 185ce1d5 sha1 74021e84c1d095b2401302e26352e810dbc432d8 ) ) game ( @@ -29050,6 +29712,12 @@ game ( rom ( name "Pop'n TwinBee (Europe).gb" size 131072 crc d07db274 sha1 0206230b55c15a8c18fbb85f852967e44b33a0a4 ) ) +game ( + name "Popcorn Caravan (World) (Aftermarket) (Unl)" + description "Popcorn Caravan (World) (Aftermarket) (Unl)" + rom ( name "Popcorn Caravan (World) (Aftermarket) (Unl).gb" size 65536 crc ead528f9 sha1 909d3421747d211a2dc70918d2c952db953e49b2 ) +) + game ( name "Popeye (Japan)" description "Popeye (Japan)" @@ -29105,9 +29773,15 @@ game ( ) game ( - name "Porklike (World) (Aftermarket) (Homebrew)" - description "Porklike (World) (Aftermarket) (Homebrew)" - rom ( name "Porklike (World) (Aftermarket) (Homebrew).gb" size 65536 crc 6c57e55e sha1 143f31656a5007f65b19d6e32967698bb8f72a66 ) + name "Porklike (World) (v1.0.3) (Aftermarket) (Unl)" + description "Porklike (World) (v1.0.3) (Aftermarket) (Unl)" + rom ( name "Porklike (World) (v1.0.3) (Aftermarket) (Unl).gb" size 65536 crc 6c57e55e sha1 143f31656a5007f65b19d6e32967698bb8f72a66 ) +) + +game ( + name "Porklike (World) (v1.0.9) (Aftermarket) (Unl)" + description "Porklike (World) (v1.0.9) (Aftermarket) (Unl)" + rom ( name "Porklike (World) (v1.0.9) (Aftermarket) (Unl).gb" size 65536 crc 7aaa853a sha1 0868bffd52b2bdc5601ab6cf1e25f40924ca643d ) ) game ( @@ -29212,6 +29886,12 @@ game ( rom ( name "Prince of Persia (Europe) (En,Fr,De,Es,It) (Beta).gb" size 131072 crc e0aaad99 sha1 42b6dfbf283946998caa6c383fa70bf1b646217e ) ) +game ( + name "Prince of Persia (USA) (Beta)" + description "Prince of Persia (USA) (Beta)" + rom ( name "Prince of Persia (USA) (Beta).gb" size 131072 crc c81ca14b sha1 355709bd9d4b19be5ee57bd62a0b58b73b25e0bf ) +) + game ( name "Prince YehRude (Taiwan) (Unl)" description "Prince YehRude (Taiwan) (Unl)" @@ -29315,9 +29995,15 @@ game ( ) game ( - name "Pushingo (World) (Aftermarket) (Homebrew)" - description "Pushingo (World) (Aftermarket) (Homebrew)" - rom ( name "Pushingo (World) (Aftermarket) (Homebrew).gb" size 262144 crc a8541ed0 sha1 1ac1e22b191a95fdfa00c34ccba7387c991c827b ) + name "Purple Turtles (World) (Aftermarket) (Unl)" + description "Purple Turtles (World) (Aftermarket) (Unl)" + rom ( name "Purple Turtles (World) (Aftermarket) (Unl).gb" size 262144 crc f7041a97 sha1 d2d7eff063104c8f205c192a902a53092c0b5f4a ) +) + +game ( + name "Pushingo (World) (Aftermarket) (Unl)" + description "Pushingo (World) (Aftermarket) (Unl)" + rom ( name "Pushingo (World) (Aftermarket) (Unl).gb" size 262144 crc a8541ed0 sha1 1ac1e22b191a95fdfa00c34ccba7387c991c827b ) ) game ( @@ -29405,9 +30091,9 @@ game ( ) game ( - name "Quartet (World) (Aftermarket) (Homebrew)" - description "Quartet (World) (Aftermarket) (Homebrew)" - rom ( name "Quartet (World) (Aftermarket) (Homebrew).gb" size 32768 crc 46743216 sha1 bf866b438a602af386f8fad02727b4084edf6047 ) + name "Quartet (World) (Aftermarket) (Unl)" + description "Quartet (World) (Aftermarket) (Unl)" + rom ( name "Quartet (World) (Aftermarket) (Unl).gb" size 32768 crc 46743216 sha1 bf866b438a602af386f8fad02727b4084edf6047 ) ) game ( @@ -29423,9 +30109,15 @@ game ( ) game ( - name "Quest Arrest (World) (v1.1) (Aftermarket) (Homebrew)" - description "Quest Arrest (World) (v1.1) (Aftermarket) (Homebrew)" - rom ( name "Quest Arrest (World) (v1.1) (Aftermarket) (Homebrew).gb" size 1048576 crc 9ac546d5 sha1 25b3a21135bfc7587c096b10f4a20d8b3095d721 ) + name "Quest Arrest (World) (v1.1) (Aftermarket) (Unl)" + description "Quest Arrest (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Quest Arrest (World) (v1.1) (Aftermarket) (Unl).gb" size 1048576 crc 9ac546d5 sha1 25b3a21135bfc7587c096b10f4a20d8b3095d721 ) +) + +game ( + name "Quick Draw (World) (Aftermarket) (Unl)" + description "Quick Draw (World) (Aftermarket) (Unl)" + rom ( name "Quick Draw (World) (Aftermarket) (Unl).gb" size 262144 crc 7704671f sha1 75f96ce54ed8739d58153780e9b573a65f1c5880 ) ) game ( @@ -29455,7 +30147,7 @@ game ( game ( name "R-Type II (Europe)" description "R-Type II (Europe)" - rom ( name "R-Type II (Europe).gb" size 131072 crc 2fe2a72d sha1 f19436dfed41b9c2e94e070a76c5bdbcc7f993c3 ) + rom ( name "R-Type II (Europe).gb" size 131072 crc 2fe2a72d sha1 f19436dfed41b9c2e94e070a76c5bdbcc7f993c3 flags verified ) ) game ( @@ -29503,7 +30195,7 @@ game ( game ( name "Racing Damashii (Japan)" description "Racing Damashii (Japan)" - rom ( name "Racing Damashii (Japan).gb" size 65536 crc 796fbb66 sha1 4466b36294176b3e1fc1558cd00abdfa4247a75d ) + rom ( name "Racing Damashii (Japan).gb" size 65536 crc 796fbb66 sha1 4466b36294176b3e1fc1558cd00abdfa4247a75d flags verified ) ) game ( @@ -29512,6 +30204,12 @@ game ( rom ( name "Radar Mission (USA, Europe).gb" size 131072 crc 581da9c9 sha1 5ab5998a84eebc769f75c482cb0a5586ed97e888 ) ) +game ( + name "Raffles (World) (Aftermarket) (Unl)" + description "Raffles (World) (Aftermarket) (Unl)" + rom ( name "Raffles (World) (Aftermarket) (Unl).gb" size 262144 crc 0b400a91 sha1 aadfe3fd0720696547b73b272354b592b96f98f7 ) +) + game ( name "Raging Fighter (USA, Europe)" description "Raging Fighter (USA, Europe)" @@ -29579,9 +30277,9 @@ game ( ) game ( - name "Remute - Living Electronics (World) (Aftermarket) (Homebrew)" - description "Remute - Living Electronics (World) (Aftermarket) (Homebrew)" - rom ( name "Remute - Living Electronics (World) (Aftermarket) (Homebrew).gb" size 2097152 crc 1ced0a62 sha1 bc4bf12344d3b9d028a7013c84b94aea515be196 ) + name "Remute - Living Electronics (World) (Aftermarket) (Unl)" + description "Remute - Living Electronics (World) (Aftermarket) (Unl)" + rom ( name "Remute - Living Electronics (World) (Aftermarket) (Unl).gb" size 2097152 crc 1ced0a62 sha1 bc4bf12344d3b9d028a7013c84b94aea515be196 ) ) game ( @@ -29615,21 +30313,21 @@ game ( ) game ( - name "Rewind Time (World) (Aftermarket) (Homebrew)" - description "Rewind Time (World) (Aftermarket) (Homebrew)" - rom ( name "Rewind Time (World) (Aftermarket) (Homebrew).gb" size 262144 crc d8ad184a sha1 903ef8074e58a52bc98dcb9e3f2c88b52fc1335b ) + name "Rewind Time (World) (Aftermarket) (Unl)" + description "Rewind Time (World) (Aftermarket) (Unl)" + rom ( name "Rewind Time (World) (Aftermarket) (Unl).gb" size 262144 crc d8ad184a sha1 903ef8074e58a52bc98dcb9e3f2c88b52fc1335b ) ) game ( - name "Rhythm Land (World) (v1.0) (Aftermarket) (Homebrew)" - description "Rhythm Land (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Rhythm Land (World) (v1.0) (Aftermarket) (Homebrew).gb" size 131072 crc 4312c7ff sha1 c79f26ce187edea18cf92be8340ec0a0c8beec87 ) + name "Rhythm Land (World) (v1.0.0) (Aftermarket) (Unl)" + description "Rhythm Land (World) (v1.0.0) (Aftermarket) (Unl)" + rom ( name "Rhythm Land (World) (v1.0.0) (Aftermarket) (Unl).gb" size 131072 crc 4312c7ff sha1 c79f26ce187edea18cf92be8340ec0a0c8beec87 ) ) game ( - name "Rhythm Land (World) (v1.01) (Aftermarket) (Homebrew)" - description "Rhythm Land (World) (v1.01) (Aftermarket) (Homebrew)" - rom ( name "Rhythm Land (World) (v1.01) (Aftermarket) (Homebrew).gb" size 131072 crc fbf98400 sha1 1b831806d32e1ec35bc94a84384c6989433a274d ) + name "Rhythm Land (World) (v1.0.1) (Aftermarket) (Unl)" + description "Rhythm Land (World) (v1.0.1) (Aftermarket) (Unl)" + rom ( name "Rhythm Land (World) (v1.0.1) (Aftermarket) (Unl).gb" size 131072 crc fbf98400 sha1 1b831806d32e1ec35bc94a84384c6989433a274d ) ) game ( @@ -29651,9 +30349,9 @@ game ( ) game ( - name "Rig Attack (World) (Aftermarket) (Homebrew)" - description "Rig Attack (World) (Aftermarket) (Homebrew)" - rom ( name "Rig Attack (World) (Aftermarket) (Homebrew).gb" size 262144 crc dea8749d sha1 6f0fbac6b6c2e8f2d8df0a0a0e2f0c947e37b39a ) + name "Rig Attack (World) (Aftermarket) (Unl)" + description "Rig Attack (World) (Aftermarket) (Unl)" + rom ( name "Rig Attack (World) (Aftermarket) (Unl).gb" size 262144 crc dea8749d sha1 6f0fbac6b6c2e8f2d8df0a0a0e2f0c947e37b39a ) ) game ( @@ -29687,9 +30385,9 @@ game ( ) game ( - name "Robby's Day Out (World) (Aftermarket) (Homebrew)" - description "Robby's Day Out (World) (Aftermarket) (Homebrew)" - rom ( name "Robby's Day Out (World) (Aftermarket) (Homebrew).gb" size 262144 crc c3c89cd9 sha1 9d38b69ab9a4ff7bf387c88f2afed410d450f2e0 ) + name "Robby's Day Out (World) (Aftermarket) (Unl)" + description "Robby's Day Out (World) (Aftermarket) (Unl)" + rom ( name "Robby's Day Out (World) (Aftermarket) (Unl).gb" size 262144 crc c3c89cd9 sha1 9d38b69ab9a4ff7bf387c88f2afed410d450f2e0 ) ) game ( @@ -29887,7 +30585,7 @@ game ( game ( name "Sa-Ga 3 - Jikuu no Hasha (Japan)" description "Sa-Ga 3 - Jikuu no Hasha (Japan)" - rom ( name "Sa-Ga 3 - Jikuu no Hasha (Japan).gb" size 262144 crc 575d6d9d sha1 c8dcbefc0352b0590fd85a683d983b5510a63519 ) + rom ( name "Sa-Ga 3 - Jikuu no Hasha (Japan).gb" size 262144 crc 575d6d9d sha1 c8dcbefc0352b0590fd85a683d983b5510a63519 flags verified ) ) game ( @@ -29921,9 +30619,9 @@ game ( ) game ( - name "Sam Mallard - The Case of the Missing Swan (World) (Aftermarket) (Homebrew)" - description "Sam Mallard - The Case of the Missing Swan (World) (Aftermarket) (Homebrew)" - rom ( name "Sam Mallard - The Case of the Missing Swan (World) (Aftermarket) (Homebrew).gb" size 1048576 crc 994a2edd sha1 0132f0311d8478d4f45b3b8e2728698570091d69 ) + name "Sam Mallard - The Case of the Missing Swan (World) (Aftermarket) (Unl)" + description "Sam Mallard - The Case of the Missing Swan (World) (Aftermarket) (Unl)" + rom ( name "Sam Mallard - The Case of the Missing Swan (World) (Aftermarket) (Unl).gb" size 1048576 crc 994a2edd sha1 0132f0311d8478d4f45b3b8e2728698570091d69 ) ) game ( @@ -29974,6 +30672,12 @@ game ( rom ( name "Sanrio Uranai Party (Japan) (Rev 1).gb" size 262144 crc d0687d9f sha1 72dfcc83dbd78ad2b5b43014fc9adf05431e230c flags verified ) ) +game ( + name "Sapphire & Shiny Kidnap the Crows (World) (v2) (Aftermarket) (Unl)" + description "Sapphire & Shiny Kidnap the Crows (World) (v2) (Aftermarket) (Unl)" + rom ( name "Sapphire & Shiny Kidnap the Crows (World) (v2) (Aftermarket) (Unl).gb" size 1048576 crc edb63c15 sha1 4617b71836c3ddabcfef46b33aa8a83803eaea90 ) +) + game ( name "Sarakon (Europe) (Proto)" description "Sarakon (Europe) (Proto)" @@ -30148,12 +30852,31 @@ game ( rom ( name "Shanghai Pocket (Japan) (SGB Enhanced).gb" size 262144 crc 6b8eff2c sha1 82fe97442aa625fd36cf865d82f78b07108ede7f ) ) +game ( + name "Shapeshifter 2, The (World) (Aftermarket)" + description "Shapeshifter 2, The (World) (Aftermarket)" + rom ( name "Shapeshifter 2, The (World) (Aftermarket) (Cartridge 1).gb" size 2097152 crc 276125f1 sha1 b181b58984cb44c950fc56f88a4fb8112da8fcff ) + rom ( name "Shapeshifter 2, The (World) (Aftermarket) (Cartridge 2).gb" size 2097152 crc 5b45bce2 sha1 79d16ef5383ddc7ae299f825cd03268d00887500 ) +) + +game ( + name "Shapeshifter, The (World) (Aftermarket)" + description "Shapeshifter, The (World) (Aftermarket)" + rom ( name "Shapeshifter, The (World) (Aftermarket).gb" size 1048576 crc ef735794 sha1 179e69199f19b403b4c1a9675a3ee431ee570797 ) +) + game ( name "Shaq Fu (USA) (SGB Enhanced)" description "Shaq Fu (USA) (SGB Enhanced)" rom ( name "Shaq Fu (USA) (SGB Enhanced).gb" size 524288 crc 7ed43fe6 sha1 5e9e68d9235cf8149232b85fd6080ba8796cb85e ) ) +game ( + name "Shark Attack (World) (Aftermarket) (Unl)" + description "Shark Attack (World) (Aftermarket) (Unl)" + rom ( name "Shark Attack (World) (Aftermarket) (Unl).gb" size 262144 crc 3032025a sha1 6cc65bb719af53128bb04c9ee7c6abbf4ff1c3cf ) +) + game ( name "Shikinjou (Japan)" description "Shikinjou (Japan)" @@ -30221,21 +30944,21 @@ game ( ) game ( - name "Shock Lobster (World) (Aftermarket) (Homebrew)" - description "Shock Lobster (World) (Aftermarket) (Homebrew)" - rom ( name "Shock Lobster (World) (Aftermarket) (Homebrew).gb" size 32768 crc 7a0622e6 sha1 10c1816724cd6e332d2a5aeed4e546e7bd764ebf ) + name "Shock Lobster (World) (Aftermarket) (Unl)" + description "Shock Lobster (World) (Aftermarket) (Unl)" + rom ( name "Shock Lobster (World) (Aftermarket) (Unl).gb" size 32768 crc 7a0622e6 sha1 10c1816724cd6e332d2a5aeed4e546e7bd764ebf ) ) game ( - name "Shock Lobster (World) (v1.3) (Aftermarket) (Homebrew)" - description "Shock Lobster (World) (v1.3) (Aftermarket) (Homebrew)" - rom ( name "Shock Lobster (World) (v1.3) (Aftermarket) (Homebrew).gb" size 32768 crc 8f244e86 sha1 7b8c8a9ec4758c6880598b9d508bc5805293dd87 ) + name "Shock Lobster (World) (v1.3) (Aftermarket) (Unl)" + description "Shock Lobster (World) (v1.3) (Aftermarket) (Unl)" + rom ( name "Shock Lobster (World) (v1.3) (Aftermarket) (Unl).gb" size 32768 crc 8f244e86 sha1 7b8c8a9ec4758c6880598b9d508bc5805293dd87 ) ) game ( - name "Shootris (World) (Aftermarket) (Homebrew)" - description "Shootris (World) (Aftermarket) (Homebrew)" - rom ( name "Shootris (World) (Aftermarket) (Homebrew).gb" size 262144 crc 50180f64 sha1 5d978e6fc4bd5d9717d022360122458e95fffc29 ) + name "Shootris (World) (Aftermarket) (Unl)" + description "Shootris (World) (Aftermarket) (Unl)" + rom ( name "Shootris (World) (Aftermarket) (Unl).gb" size 262144 crc 50180f64 sha1 5d978e6fc4bd5d9717d022360122458e95fffc29 ) ) game ( @@ -30323,9 +31046,15 @@ game ( ) game ( - name "Sloth Story (World) (v1.0) (Aftermarket) (Homebrew)" - description "Sloth Story (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Sloth Story (World) (v1.0) (Aftermarket) (Homebrew).gb" size 262144 crc c873f232 sha1 369d2afec4ffb59d4f3a59ee684c5bfec0b34ff8 ) + name "Sloth Story (World) (Aftermarket) (Unl)" + description "Sloth Story (World) (Aftermarket) (Unl)" + rom ( name "Sloth Story (World) (Aftermarket) (Unl).gb" size 262144 crc c873f232 sha1 369d2afec4ffb59d4f3a59ee684c5bfec0b34ff8 ) +) + +game ( + name "Sludge & Sorcery (World) (Aftermarket) (Unl)" + description "Sludge & Sorcery (World) (Aftermarket) (Unl)" + rom ( name "Sludge & Sorcery (World) (Aftermarket) (Unl).gb" size 1048576 crc c906c5af sha1 0e0d472b282e3ea590e0f126baf633ef0d9298fa ) ) game ( @@ -30335,15 +31064,15 @@ game ( ) game ( - name "SMARTCOM (Europe) (Unl)" - description "SMARTCOM (Europe) (Unl)" - rom ( name "SMARTCOM (Europe) (Unl).gb" size 262144 crc 1ef0abf1 sha1 a04bbf813bcd9b7e3af97d388ad77d25c1403831 ) + name "SmartCom (Europe) (Unl)" + description "SmartCom (Europe) (Unl)" + rom ( name "SmartCom (Europe) (Unl).gb" size 262144 crc 1ef0abf1 sha1 a04bbf813bcd9b7e3af97d388ad77d25c1403831 ) ) game ( name "Smurfs Nightmare, The (Europe) (En,Fr,De,Es)" description "Smurfs Nightmare, The (Europe) (En,Fr,De,Es)" - rom ( name "Smurfs Nightmare, The (Europe) (En,Fr,De,Es).gb" size 262144 crc 8ef938c4 sha1 efbc4bc9714393d5f493ad653fc1fa2180b40892 ) + rom ( name "Smurfs Nightmare, The (Europe) (En,Fr,De,Es).gb" size 262144 crc 8ef938c4 sha1 efbc4bc9714393d5f493ad653fc1fa2180b40892 flags verified ) ) game ( @@ -30365,21 +31094,21 @@ game ( ) game ( - name "Snail DX, The (World) (v1.0) (Aftermarket) (Homebrew)" - description "Snail DX, The (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Snail DX, The (World) (v1.0) (Aftermarket) (Homebrew).gb" size 262144 crc 1aecd096 sha1 8ce256d963a55badf71fd45047c173a74d76a92c ) + name "Snail DX, The (World) (Aftermarket) (Unl)" + description "Snail DX, The (World) (Aftermarket) (Unl)" + rom ( name "Snail DX, The (World) (Aftermarket) (Unl).gb" size 262144 crc 1aecd096 sha1 8ce256d963a55badf71fd45047c173a74d76a92c ) ) game ( - name "Snail, The (World) (v1.3) (Aftermarket) (Homebrew)" - description "Snail, The (World) (v1.3) (Aftermarket) (Homebrew)" - rom ( name "Snail, The (World) (v1.3) (Aftermarket) (Homebrew).gb" size 262144 crc 422a7b44 sha1 776f7578d747f935ebccda93d6487ffa4dc4bce3 ) + name "Snail, The (World) (v1.3) (Aftermarket) (Unl)" + description "Snail, The (World) (v1.3) (Aftermarket) (Unl)" + rom ( name "Snail, The (World) (v1.3) (Aftermarket) (Unl).gb" size 262144 crc 422a7b44 sha1 776f7578d747f935ebccda93d6487ffa4dc4bce3 ) ) game ( - name "Snakebird (World) (Aftermarket) (Homebrew)" - description "Snakebird (World) (Aftermarket) (Homebrew)" - rom ( name "Snakebird (World) (Aftermarket) (Homebrew).gb" size 131072 crc 4627f98e sha1 99cc82279d65b86b4366bf61db9bf67a78e051dc ) + name "Snakebird (World) (Aftermarket) (Unl)" + description "Snakebird (World) (Aftermarket) (Unl)" + rom ( name "Snakebird (World) (Aftermarket) (Unl).gb" size 131072 crc 4627f98e sha1 99cc82279d65b86b4366bf61db9bf67a78e051dc ) ) game ( @@ -30407,9 +31136,9 @@ game ( ) game ( - name "Snooze (World) (Aftermarket) (Homebrew)" - description "Snooze (World) (Aftermarket) (Homebrew)" - rom ( name "Snooze (World) (Aftermarket) (Homebrew).gb" size 262144 crc b246095f sha1 b7fdcc006c9c06dd391ee26d44b278d9d3793f6d ) + name "Snooze (World) (Aftermarket) (Unl)" + description "Snooze (World) (Aftermarket) (Unl)" + rom ( name "Snooze (World) (Aftermarket) (Unl).gb" size 262144 crc b246095f sha1 b7fdcc006c9c06dd391ee26d44b278d9d3793f6d ) ) game ( @@ -30497,9 +31226,9 @@ game ( ) game ( - name "Sonic 6 (Unknown) (En) (Pirate)" - description "Sonic 6 (Unknown) (En) (Pirate)" - rom ( name "Sonic 6 (Unknown) (En) (Pirate).gb" size 524288 crc aa4e9379 sha1 5f70ee1a1265536c6ed3657bfd5e54516add287d ) + name "Sonic 6 (USA) (Pirate)" + description "Sonic 6 (USA) (Pirate)" + rom ( name "Sonic 6 (USA) (Pirate).gb" size 524288 crc aa4e9379 sha1 5f70ee1a1265536c6ed3657bfd5e54516add287d ) ) game ( @@ -30581,9 +31310,9 @@ game ( ) game ( - name "Speedball 2 - Brutal Deluxe (Japan) (En)" - description "Speedball 2 - Brutal Deluxe (Japan) (En)" - rom ( name "Speedball 2 - Brutal Deluxe (Japan) (En).gb" size 131072 crc d0e0116f sha1 2b9bc1e7eadf90611112463053a9915c8159c3bc ) + name "Speedball 2 - Brutal Deluxe (Japan) (En) (Possible Proto)" + description "Speedball 2 - Brutal Deluxe (Japan) (En) (Possible Proto)" + rom ( name "Speedball 2 - Brutal Deluxe (Japan) (En) (Possible Proto).gb" size 131072 crc d0e0116f sha1 2b9bc1e7eadf90611112463053a9915c8159c3bc ) ) game ( @@ -30622,6 +31351,12 @@ game ( rom ( name "Spider-Man 3 - Invasion of the Spider-Slayers (USA, Europe) (Beta 2) (1993-04-11).gb" size 131072 crc bf2d1f10 sha1 34740dc60663ef33c42573a26e7b207f2cfe4a16 ) ) +game ( + name "Spiky Harold (World) (Aftermarket) (Unl)" + description "Spiky Harold (World) (Aftermarket) (Unl)" + rom ( name "Spiky Harold (World) (Aftermarket) (Unl).gb" size 524288 crc 2ac1b945 sha1 b8f37a64524bde4c2b6e9a1938ed6e5c0a5aad7c ) +) + game ( name "Spirit of F-1, The (Europe)" description "Spirit of F-1, The (Europe)" @@ -30821,9 +31556,9 @@ game ( ) game ( - name "StarFox - Grounded (World) (Aftermarket) (Homebrew)" - description "StarFox - Grounded (World) (Aftermarket) (Homebrew)" - rom ( name "StarFox - Grounded (World) (Aftermarket) (Homebrew).gb" size 524288 crc 82658661 sha1 dd45b0e3f346ce33668d49092b29a44597686061 ) + name "StarFox - Grounded (World) (Aftermarket) (Unl)" + description "StarFox - Grounded (World) (Aftermarket) (Unl)" + rom ( name "StarFox - Grounded (World) (Aftermarket) (Unl).gb" size 524288 crc 82658661 sha1 dd45b0e3f346ce33668d49092b29a44597686061 ) ) game ( @@ -30880,6 +31615,12 @@ game ( rom ( name "Street Racer (USA, Europe).gb" size 131072 crc fcfb4ce4 sha1 4e1d17772eb939cb0a3bce73e4378384d96836ce flags verified ) ) +game ( + name "Suicide Run (World) (Aftermarket) (Unl)" + description "Suicide Run (World) (Aftermarket) (Unl)" + rom ( name "Suicide Run (World) (Aftermarket) (Unl).gb" size 262144 crc d29ab1aa sha1 df373f58e51155e32fb81825a9082150f6d263c8 ) +) + game ( name "Sumo Fighter (USA)" description "Sumo Fighter (USA)" @@ -30899,9 +31640,9 @@ game ( ) game ( - name "Super 69 in 1 (World) (Unl)" - description "Super 69 in 1 (World) (Unl)" - rom ( name "Super 69 in 1 (World) (Unl).gb" size 32768 crc 9cf64fb6 sha1 91e7012c1eeeea280117c245b031400d060bd978 ) + name "Super 69 in 1 (Europe) (Unl)" + description "Super 69 in 1 (Europe) (Unl)" + rom ( name "Super 69 in 1 (Europe) (Unl).gb" size 32768 crc 9cf64fb6 sha1 91e7012c1eeeea280117c245b031400d060bd978 ) ) game ( @@ -31007,9 +31748,9 @@ game ( ) game ( - name "Super Covid Go Get Biscuits Adventure (World) (Aftermarket) (Homebrew)" - description "Super Covid Go Get Biscuits Adventure (World) (Aftermarket) (Homebrew)" - rom ( name "Super Covid Go Get Biscuits Adventure (World) (Aftermarket) (Homebrew).gb" size 262144 crc 51946e8d sha1 cac4917d56da02cf88ba68e0c5189a18c2c234ac ) + name "Super Covid Go Get Biscuits Adventure (World) (Aftermarket) (Unl)" + description "Super Covid Go Get Biscuits Adventure (World) (Aftermarket) (Unl)" + rom ( name "Super Covid Go Get Biscuits Adventure (World) (Aftermarket) (Unl).gb" size 262144 crc 51946e8d sha1 cac4917d56da02cf88ba68e0c5189a18c2c234ac ) ) game ( @@ -31043,9 +31784,9 @@ game ( ) game ( - name "Super Imposter Bros. (World) (Aftermarket) (Homebrew)" - description "Super Imposter Bros. (World) (Aftermarket) (Homebrew)" - rom ( name "Super Imposter Bros. (World) (Aftermarket) (Homebrew).gb" size 1048576 crc a38d702f sha1 301f93da4e5b0cd8dc3f1bf3e66e38176b8a909b ) + name "Super Imposter Bros. (World) (Aftermarket) (Unl)" + description "Super Imposter Bros. (World) (Aftermarket) (Unl)" + rom ( name "Super Imposter Bros. (World) (Aftermarket) (Unl).gb" size 1048576 crc a38d702f sha1 301f93da4e5b0cd8dc3f1bf3e66e38176b8a909b ) ) game ( @@ -31211,9 +31952,9 @@ game ( ) game ( - name "Sushi Gun (World) (Aftermarket) (Homebrew)" - description "Sushi Gun (World) (Aftermarket) (Homebrew)" - rom ( name "Sushi Gun (World) (Aftermarket) (Homebrew).gb" size 262144 crc a9d405d6 sha1 c075856939d4fe3cd250083b527b3e09be70052d ) + name "Sushi Gun (World) (Aftermarket) (Unl)" + description "Sushi Gun (World) (Aftermarket) (Unl)" + rom ( name "Sushi Gun (World) (Aftermarket) (Unl).gb" size 262144 crc a9d405d6 sha1 c075856939d4fe3cd250083b527b3e09be70052d ) ) game ( @@ -31241,9 +31982,9 @@ game ( ) game ( - name "SWAPLATFORMER (World) (Aftermarket) (Homebrew)" - description "SWAPLATFORMER (World) (Aftermarket) (Homebrew)" - rom ( name "SWAPLATFORMER (World) (Aftermarket) (Homebrew).gb" size 1048576 crc babe7935 sha1 3df0c37862b7c391a0d091a55db0d305341529c2 ) + name "SWAPLATFORMER (World) (Aftermarket) (Unl)" + description "SWAPLATFORMER (World) (Aftermarket) (Unl)" + rom ( name "SWAPLATFORMER (World) (Aftermarket) (Unl).gb" size 1048576 crc babe7935 sha1 3df0c37862b7c391a0d091a55db0d305341529c2 ) ) game ( @@ -31282,6 +32023,12 @@ game ( rom ( name "Sword of Hope, The (Europe) (Proto).gb" size 131072 crc c360f279 sha1 383ef07e04280f82da374852d04e7f98e9a00ae1 ) ) +game ( + name "Swordbird Song - The Iron Owl Tower (World) (v3.1) (Aftermarket) (Unl)" + description "Swordbird Song - The Iron Owl Tower (World) (v3.1) (Aftermarket) (Unl)" + rom ( name "Swordbird Song - The Iron Owl Tower (World) (v3.1) (Aftermarket) (Unl).gb" size 1048576 crc 64921bb1 sha1 b36ec89e0299c19767cc5c6467fa3664abbc3017 ) +) + game ( name "T2 - The Arcade Game (Japan)" description "T2 - The Arcade Game (Japan)" @@ -31348,6 +32095,12 @@ game ( rom ( name "Takeda Nobuhiro no Ace Striker (Japan).gb" size 262144 crc 7ff546df sha1 e354310c9946f48eb325c070e9c2c5ccb2e9f429 ) ) +game ( + name "Tales of Monsterland (World) (v2.83) (Aftermarket) (Unl)" + description "Tales of Monsterland (World) (v2.83) (Aftermarket) (Unl)" + rom ( name "Tales of Monsterland (World) (v2.83) (Aftermarket) (Unl).gb" size 2097152 crc a685f89a sha1 15a709d4300464b03ede4d774ce69cfb699c0fcf ) +) + game ( name "TaleSpin (Europe)" description "TaleSpin (Europe)" @@ -31415,9 +32168,9 @@ game ( ) game ( - name "Tech and Blood (World) (Aftermarket) (Homebrew)" - description "Tech and Blood (World) (Aftermarket) (Homebrew)" - rom ( name "Tech and Blood (World) (Aftermarket) (Homebrew).gb" size 262144 crc 3da2a358 sha1 40a00fce49b16cb36793df30e5156e5bf3e47778 ) + name "Tech and Blood (World) (Aftermarket) (Unl)" + description "Tech and Blood (World) (Aftermarket) (Unl)" + rom ( name "Tech and Blood (World) (Aftermarket) (Unl).gb" size 262144 crc 3da2a358 sha1 40a00fce49b16cb36793df30e5156e5bf3e47778 ) ) game ( @@ -31462,6 +32215,18 @@ game ( rom ( name "Teenage Mutant Ninja Turtles (Japan).gb" size 131072 crc 74078236 sha1 1f17169d70365831537376beb2477374649f830b ) ) +game ( + name "Teenage Mutant Ninja Turtles (Japan) (Cowabunga Collection, The)" + description "Teenage Mutant Ninja Turtles (Japan) (Cowabunga Collection, The)" + rom ( name "Teenage Mutant Ninja Turtles (Japan) (Cowabunga Collection, The).gb" size 131072 crc b3f6eec2 sha1 8e5398a96523b83da1ed0600f57a774cde7729a7 ) +) + +game ( + name "Teenage Mutant Ninja Turtles - Fall of the Foot Clan (USA) (Cowabunga Collection, The)" + description "Teenage Mutant Ninja Turtles - Fall of the Foot Clan (USA) (Cowabunga Collection, The)" + rom ( name "Teenage Mutant Ninja Turtles - Fall of the Foot Clan (USA) (Cowabunga Collection, The).gb" size 131072 crc 3b11a875 sha1 d20ba3c691a484bfcc80331e11bb76f652b4290c ) +) + game ( name "Teenage Mutant Ninja Turtles - Fall of the Foot Clan (USA)" description "Teenage Mutant Ninja Turtles - Fall of the Foot Clan (USA)" @@ -31480,6 +32245,18 @@ game ( rom ( name "Teenage Mutant Ninja Turtles 2 (Japan) (En) (Beta) (1991-05-20).gb" size 262144 crc 64a667a3 sha1 864a686f74a22ff8d27da511b08bbfbef1bd563d ) ) +game ( + name "Teenage Mutant Ninja Turtles 2 (Japan) (Cowabunga Collection, The)" + description "Teenage Mutant Ninja Turtles 2 (Japan) (Cowabunga Collection, The)" + rom ( name "Teenage Mutant Ninja Turtles 2 (Japan) (Cowabunga Collection, The).gb" size 262144 crc 3e83abd3 sha1 a05025f1840f27d4a12574fd6ee60e0b42c68ed8 ) +) + +game ( + name "Teenage Mutant Ninja Turtles 3 - Turtles Kikiippatsu (Japan) (Cowabunga Collection, The)" + description "Teenage Mutant Ninja Turtles 3 - Turtles Kikiippatsu (Japan) (Cowabunga Collection, The)" + rom ( name "Teenage Mutant Ninja Turtles 3 - Turtles Kikiippatsu (Japan) (Cowabunga Collection, The).gb" size 131072 crc cc91ab3b sha1 fc94ee81585583a39542b31a80c05f176da8b9e2 ) +) + game ( name "Teenage Mutant Ninja Turtles 3 - Turtles Kikiippatsu (Japan)" description "Teenage Mutant Ninja Turtles 3 - Turtles Kikiippatsu (Japan)" @@ -31498,6 +32275,18 @@ game ( rom ( name "Teenage Mutant Ninja Turtles II - Back from the Sewers (USA) (Beta).gb" size 262144 crc ee25b752 sha1 7fd1df99ed3bc088c737f0ef8b7f0caa95d73b65 ) ) +game ( + name "Teenage Mutant Ninja Turtles II - Back from the Sewers (USA) (Cowabunga Collection, The)" + description "Teenage Mutant Ninja Turtles II - Back from the Sewers (USA) (Cowabunga Collection, The)" + rom ( name "Teenage Mutant Ninja Turtles II - Back from the Sewers (USA) (Cowabunga Collection, The).gb" size 262144 crc 78b901d5 sha1 41c8ed509ec914e50f7e28601e0f6e5736de90fb ) +) + +game ( + name "Teenage Mutant Ninja Turtles III - Radical Rescue (USA) (Cowabunga Collection, The)" + description "Teenage Mutant Ninja Turtles III - Radical Rescue (USA) (Cowabunga Collection, The)" + rom ( name "Teenage Mutant Ninja Turtles III - Radical Rescue (USA) (Cowabunga Collection, The).gb" size 131072 crc 3a4d4b09 sha1 ade406d5ec0f3253c4d3be3b7507e027098f04a3 ) +) + game ( name "Teenage Mutant Ninja Turtles III - Radical Rescue (USA)" description "Teenage Mutant Ninja Turtles III - Radical Rescue (USA)" @@ -31637,15 +32426,15 @@ game ( ) game ( - name "There's Nothing To Do In This Town (World) (v1.0) (Aftermarket) (Homebrew)" - description "There's Nothing To Do In This Town (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "There's Nothing To Do In This Town (World) (v1.0) (Aftermarket) (Homebrew).gb" size 1048576 crc 173b549a sha1 e000fbecda9ea6bbbd0c0370cf8c3faaa54bf64e ) + name "There's Nothing To Do In This Town (World) (Aftermarket) (Unl)" + description "There's Nothing To Do In This Town (World) (Aftermarket) (Unl)" + rom ( name "There's Nothing To Do In This Town (World) (Aftermarket) (Unl).gb" size 1048576 crc 173b549a sha1 e000fbecda9ea6bbbd0c0370cf8c3faaa54bf64e ) ) game ( - name "Thin Ice Rescue, The (World) (Aftermarket) (Homebrew)" - description "Thin Ice Rescue, The (World) (Aftermarket) (Homebrew)" - rom ( name "Thin Ice Rescue, The (World) (Aftermarket) (Homebrew).gb" size 32768 crc 3e0483d6 sha1 b789ced0fe26785e7bce765a9d6ee783f9d967d8 ) + name "Thin Ice Rescue, The (World) (Aftermarket) (Unl)" + description "Thin Ice Rescue, The (World) (Aftermarket) (Unl)" + rom ( name "Thin Ice Rescue, The (World) (Aftermarket) (Unl).gb" size 32768 crc 3e0483d6 sha1 b789ced0fe26785e7bce765a9d6ee783f9d967d8 ) ) game ( @@ -31673,15 +32462,15 @@ game ( ) game ( - name "Tiny Grasshopper Goes Away (World) (v1.4) (Aftermarket) (Homebrew)" - description "Tiny Grasshopper Goes Away (World) (v1.4) (Aftermarket) (Homebrew)" - rom ( name "Tiny Grasshopper Goes Away (World) (v1.4) (Aftermarket) (Homebrew).gb" size 262144 crc af13e718 sha1 307e51664db2101755b6c617f3d81ee58676c485 ) + name "Tiny Grasshopper Goes Away (World) (En,Hu) (v1.4) (Aftermarket) (Unl)" + description "Tiny Grasshopper Goes Away (World) (En,Hu) (v1.4) (Aftermarket) (Unl)" + rom ( name "Tiny Grasshopper Goes Away (World) (En,Hu) (v1.4) (Aftermarket) (Unl).gb" size 262144 crc af13e718 sha1 307e51664db2101755b6c617f3d81ee58676c485 ) ) game ( - name "Tiny Grasshopper Goes Away (World) (v1.2) (Aftermarket) (Homebrew)" - description "Tiny Grasshopper Goes Away (World) (v1.2) (Aftermarket) (Homebrew)" - rom ( name "Tiny Grasshopper Goes Away (World) (v1.2) (Aftermarket) (Homebrew).gb" size 262144 crc 0518f5aa sha1 85e5cd0cf7a6cc4a5946c93fa8665235a686e554 ) + name "Tiny Grasshopper Goes Away (World) (En,Hu) (v1.2) (Aftermarket) (Unl)" + description "Tiny Grasshopper Goes Away (World) (En,Hu) (v1.2) (Aftermarket) (Unl)" + rom ( name "Tiny Grasshopper Goes Away (World) (En,Hu) (v1.2) (Aftermarket) (Unl).gb" size 262144 crc 0518f5aa sha1 85e5cd0cf7a6cc4a5946c93fa8665235a686e554 ) ) game ( @@ -31751,9 +32540,9 @@ game ( ) game ( - name "Tobu Tobu Girl (World) (Aftermarket) (Homebrew)" - description "Tobu Tobu Girl (World) (Aftermarket) (Homebrew)" - rom ( name "Tobu Tobu Girl (World) (Aftermarket) (Homebrew).gb" size 262144 crc ed12be6c sha1 8a8f3c1f21f903ea5a7df8fc8b0a6aa5a602e150 ) + name "Tobu Tobu Girl (World) (Aftermarket) (Unl)" + description "Tobu Tobu Girl (World) (Aftermarket) (Unl)" + rom ( name "Tobu Tobu Girl (World) (Aftermarket) (Unl).gb" size 262144 crc ed12be6c sha1 8a8f3c1f21f903ea5a7df8fc8b0a6aa5a602e150 ) ) game ( @@ -31921,13 +32710,13 @@ game ( game ( name "Trax (USA, Europe)" description "Trax (USA, Europe)" - rom ( name "Trax (USA, Europe).gb" size 131072 crc 4a38be7d sha1 3f3a31ed7e47319c5815dd6e31ca27a52377423c ) + rom ( name "Trax (USA, Europe).gb" size 131072 crc 4a38be7d sha1 3f3a31ed7e47319c5815dd6e31ca27a52377423c flags verified ) ) game ( - name "Treasure Island (World) (Aftermarket) (Homebrew)" - description "Treasure Island (World) (Aftermarket) (Homebrew)" - rom ( name "Treasure Island (World) (Aftermarket) (Homebrew).gb" size 262144 crc cbdec393 sha1 adeaba2723d286d143e0987f51ce9c8ad2f5e839 ) + name "Treasure Island (World) (Aftermarket) (Unl)" + description "Treasure Island (World) (Aftermarket) (Unl)" + rom ( name "Treasure Island (World) (Aftermarket) (Unl).gb" size 262144 crc cbdec393 sha1 adeaba2723d286d143e0987f51ce9c8ad2f5e839 ) ) game ( @@ -31955,9 +32744,9 @@ game ( ) game ( - name "Trouble City - Pocket Mission (World) (Aftermarket) (Homebrew)" - description "Trouble City - Pocket Mission (World) (Aftermarket) (Homebrew)" - rom ( name "Trouble City - Pocket Mission (World) (Aftermarket) (Homebrew).gb" size 524288 crc 4b9143ad sha1 0e51e398d44aaad74a7c622f7818b56a3b34db56 ) + name "Trouble City - Pocket Mission (World) (Aftermarket) (Unl)" + description "Trouble City - Pocket Mission (World) (Aftermarket) (Unl)" + rom ( name "Trouble City - Pocket Mission (World) (Aftermarket) (Unl).gb" size 524288 crc 4b9143ad sha1 0e51e398d44aaad74a7c622f7818b56a3b34db56 ) ) game ( @@ -32177,9 +32966,15 @@ game ( ) game ( - name "Ungrateful Son, The (World) (Aftermarket) (Homebrew)" - description "Ungrateful Son, The (World) (Aftermarket) (Homebrew)" - rom ( name "Ungrateful Son, The (World) (Aftermarket) (Homebrew).gb" size 262144 crc 977b54f4 sha1 016dde406896d827dcad682d40b52d0e9eeefa88 ) + name "Unearthed (World) (v1.3) (Aftermarket) (Unl)" + description "Unearthed (World) (v1.3) (Aftermarket) (Unl)" + rom ( name "Unearthed (World) (v1.3) (Aftermarket) (Unl).gb" size 2097152 crc 29bb9238 sha1 39fbfb220e9be0120090f770249049b3f3583f64 ) +) + +game ( + name "Ungrateful Son, The (World) (v1.1) (Aftermarket) (Unl)" + description "Ungrateful Son, The (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Ungrateful Son, The (World) (v1.1) (Aftermarket) (Unl).gb" size 262144 crc 977b54f4 sha1 016dde406896d827dcad682d40b52d0e9eeefa88 ) ) game ( @@ -32224,6 +33019,12 @@ game ( rom ( name "V-Rally - Championship Edition (Europe) (En,Fr,De).gb" size 262144 crc d149652b sha1 67d2a6e41b6a1d8372808535ac8c8222d7c53480 flags verified ) ) +game ( + name "Vampire Night Shift (World) (Aftermarket) (Unl)" + description "Vampire Night Shift (World) (Aftermarket) (Unl)" + rom ( name "Vampire Night Shift (World) (Aftermarket) (Unl).gb" size 262144 crc 88acbc1a sha1 ef617803c2cdb14cfe3b3b2111aa239a3744e29e ) +) + game ( name "Vattle Giuce (Japan)" description "Vattle Giuce (Japan)" @@ -32279,9 +33080,9 @@ game ( ) game ( - name "Waifu Clicker (World) (Aftermarket) (Homebrew)" - description "Waifu Clicker (World) (Aftermarket) (Homebrew)" - rom ( name "Waifu Clicker (World) (Aftermarket) (Homebrew).gb" size 65536 crc 155f85dc sha1 bb571ed69c3a33e45ba33fbb03a066ba98ae0b20 ) + name "Waifu Clicker (World) (Aftermarket) (Unl)" + description "Waifu Clicker (World) (Aftermarket) (Unl)" + rom ( name "Waifu Clicker (World) (Aftermarket) (Unl).gb" size 65536 crc 155f85dc sha1 bb571ed69c3a33e45ba33fbb03a066ba98ae0b20 ) ) game ( @@ -32303,21 +33104,21 @@ game ( ) game ( - name "Warp Coin Catastrophe, The (World) (v1.1) (Aftermarket) (Homebrew)" - description "Warp Coin Catastrophe, The (World) (v1.1) (Aftermarket) (Homebrew)" - rom ( name "Warp Coin Catastrophe, The (World) (v1.1) (Aftermarket) (Homebrew).gb" size 1048576 crc a381c914 sha1 adf37c5d2f706743b2a4946378df49ed19212039 ) + name "Warp Coin Catastrophe, The (World) (v1.1) (Aftermarket) (Unl)" + description "Warp Coin Catastrophe, The (World) (v1.1) (Aftermarket) (Unl)" + rom ( name "Warp Coin Catastrophe, The (World) (v1.1) (Aftermarket) (Unl).gb" size 1048576 crc a381c914 sha1 adf37c5d2f706743b2a4946378df49ed19212039 ) ) game ( - name "Warp Coin Catastrophe, The (World) (v1.0) (Aftermarket) (Homebrew)" - description "Warp Coin Catastrophe, The (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Warp Coin Catastrophe, The (World) (v1.0) (Aftermarket) (Homebrew).gb" size 1048576 crc 1bfe1bf9 sha1 fae12dbbb75ae024e96a7ee21ad4077fdb5ed9a1 ) + name "Warp Coin Catastrophe, The (World) (v1.0) (Aftermarket) (Unl)" + description "Warp Coin Catastrophe, The (World) (v1.0) (Aftermarket) (Unl)" + rom ( name "Warp Coin Catastrophe, The (World) (v1.0) (Aftermarket) (Unl).gb" size 1048576 crc 1bfe1bf9 sha1 fae12dbbb75ae024e96a7ee21ad4077fdb5ed9a1 ) ) game ( - name "Warp Coin Catastrophe, The (World) (v1.1.2) (Aftermarket) (Homebrew)" - description "Warp Coin Catastrophe, The (World) (v1.1.2) (Aftermarket) (Homebrew)" - rom ( name "Warp Coin Catastrophe, The (World) (v1.1.2) (Aftermarket) (Homebrew).gb" size 1048576 crc 5519c167 sha1 04a53af6a77b884af91f8047d70fc0331bf23913 ) + name "Warp Coin Catastrophe, The (World) (v1.1.2) (Aftermarket) (Unl)" + description "Warp Coin Catastrophe, The (World) (v1.1.2) (Aftermarket) (Unl)" + rom ( name "Warp Coin Catastrophe, The (World) (v1.1.2) (Aftermarket) (Unl).gb" size 1048576 crc 5519c167 sha1 04a53af6a77b884af91f8047d70fc0331bf23913 ) ) game ( @@ -32362,6 +33163,12 @@ game ( rom ( name "Welcome Nakayoshi Park (Japan).gb" size 262144 crc f6e2baae sha1 8779a1557ba0de3c5c8b770413f30bd31717b2ef ) ) +game ( + name "What's Updog (World) (Aftermarket) (Unl)" + description "What's Updog (World) (Aftermarket) (Unl)" + rom ( name "What's Updog (World) (Aftermarket) (Unl).gb" size 262144 crc 2c3d5cbf sha1 a235c0915ac2b826eeb5884d0eccd321236fdb0c ) +) + game ( name "Wheel of Fortune (USA)" description "Wheel of Fortune (USA)" @@ -32398,6 +33205,12 @@ game ( rom ( name "Wily & Right no Rockboard - That's Paradise (Japan) (Proto).gb" size 262144 crc af84dc97 sha1 cc917f113c5cdd1afc3340feb8bf2094d0902cec ) ) +game ( + name "Windows93 Adventure (World) (Aftermarket) (Unl)" + description "Windows93 Adventure (World) (Aftermarket) (Unl)" + rom ( name "Windows93 Adventure (World) (Aftermarket) (Unl).gb" size 1048576 crc e1ad0b6f sha1 3ad7327f6f94976c3d12054cf6deb2666ba9fa66 ) +) + game ( name "Winner's Horse (Japan)" description "Winner's Horse (Japan)" @@ -32423,9 +33236,9 @@ game ( ) game ( - name "Wizard of Wor (World) (Aftermarket) (Homebrew)" - description "Wizard of Wor (World) (Aftermarket) (Homebrew)" - rom ( name "Wizard of Wor (World) (Aftermarket) (Homebrew).gb" size 524288 crc bfb1563e sha1 108eefa154412e3422dce4e63bf34f5826ec1baa ) + name "Wizard of Wor (World) (Aftermarket) (Unl)" + description "Wizard of Wor (World) (Aftermarket) (Unl)" + rom ( name "Wizard of Wor (World) (Aftermarket) (Unl).gb" size 524288 crc bfb1563e sha1 108eefa154412e3422dce4e63bf34f5826ec1baa ) ) game ( @@ -32453,9 +33266,9 @@ game ( ) game ( - name "Woolball's Backyard (World) (Aftermarket) (Homebrew)" - description "Woolball's Backyard (World) (Aftermarket) (Homebrew)" - rom ( name "Woolball's Backyard (World) (Aftermarket) (Homebrew).gb" size 65536 crc 69e26eb6 sha1 28795c4b829440549b3ac2afe3282d85a439f7d7 ) + name "Woolball's Backyard (World) (Aftermarket) (Unl)" + description "Woolball's Backyard (World) (Aftermarket) (Unl)" + rom ( name "Woolball's Backyard (World) (Aftermarket) (Unl).gb" size 65536 crc 69e26eb6 sha1 28795c4b829440549b3ac2afe3282d85a439f7d7 ) ) game ( @@ -32687,27 +33500,27 @@ game ( ) game ( - name "Year After, The (USA) (Aftermarket) (Homebrew)" - description "Year After, The (USA) (Aftermarket) (Homebrew)" - rom ( name "Year After, The (USA) (Aftermarket) (Homebrew).gb" size 1048576 crc 4e463a1a sha1 5d84f53f38aa24f54a6f897ca69ffc5d978805af ) + name "Year After, The (World) (Beta 2) (Aftermarket) (Unl)" + description "Year After, The (World) (Beta 2) (Aftermarket) (Unl)" + rom ( name "Year After, The (World) (Beta 2) (Aftermarket) (Unl).gb" size 1048576 crc 4e463a1a sha1 5d84f53f38aa24f54a6f897ca69ffc5d978805af ) ) game ( - name "Year After, The (World) (Beta) (Aftermarket) (Homebrew)" - description "Year After, The (World) (Beta) (Aftermarket) (Homebrew)" - rom ( name "Year After, The (World) (Beta) (Aftermarket) (Homebrew).gb" size 1048576 crc f15351c8 sha1 a78ff2eef780287bbdc37df122cfc1c504526662 ) + name "Year After, The (World) (Beta) (Aftermarket) (Unl)" + description "Year After, The (World) (Beta) (Aftermarket) (Unl)" + rom ( name "Year After, The (World) (Beta) (Aftermarket) (Unl).gb" size 1048576 crc f15351c8 sha1 a78ff2eef780287bbdc37df122cfc1c504526662 ) ) game ( - name "Year After, The (World) (Pt) (Beta) (Aftermarket) (Homebrew)" - description "Year After, The (World) (Pt) (Beta) (Aftermarket) (Homebrew)" - rom ( name "Year After, The (World) (Pt) (Beta) (Aftermarket) (Homebrew).gb" size 1048576 crc b70c1c7f sha1 ce25cdf6a6264586423e76e34dc42779d39a1cb1 ) + name "Year After, The (World) (Pt) (Beta) (Aftermarket) (Unl)" + description "Year After, The (World) (Pt) (Beta) (Aftermarket) (Unl)" + rom ( name "Year After, The (World) (Pt) (Beta) (Aftermarket) (Unl).gb" size 1048576 crc b70c1c7f sha1 ce25cdf6a6264586423e76e34dc42779d39a1cb1 ) ) game ( - name "Year After, The (World) (Fr) (Beta) (Aftermarket) (Homebrew)" - description "Year After, The (World) (Fr) (Beta) (Aftermarket) (Homebrew)" - rom ( name "Year After, The (World) (Fr) (Beta) (Aftermarket) (Homebrew).gb" size 1048576 crc fbc50ee8 sha1 52c27e64fb37412c97a53d5dde52c3803fd1e4f7 ) + name "Year After, The (World) (Fr) (Beta) (Aftermarket) (Unl)" + description "Year After, The (World) (Fr) (Beta) (Aftermarket) (Unl)" + rom ( name "Year After, The (World) (Fr) (Beta) (Aftermarket) (Unl).gb" size 1048576 crc fbc50ee8 sha1 52c27e64fb37412c97a53d5dde52c3803fd1e4f7 ) ) game ( @@ -32795,9 +33608,15 @@ game ( ) game ( - name "Yuuto Ichika Makes Friends (World) (Aftermarket) (Homebrew)" - description "Yuuto Ichika Makes Friends (World) (Aftermarket) (Homebrew)" - rom ( name "Yuuto Ichika Makes Friends (World) (Aftermarket) (Homebrew).gb" size 524288 crc 8a667058 sha1 e765d6915f543a5a65e8be440b3f29eb674a5448 ) + name "Yuuto Ichika Makes Friends (World) (En,Ja) (Aftermarket) (Unl)" + description "Yuuto Ichika Makes Friends (World) (En,Ja) (Aftermarket) (Unl)" + rom ( name "Yuuto Ichika Makes Friends (World) (En,Ja) (Aftermarket) (Unl).gb" size 524288 crc 8a667058 sha1 e765d6915f543a5a65e8be440b3f29eb674a5448 ) +) + +game ( + name "Zagan Warrior (World) (Aftermarket) (Unl)" + description "Zagan Warrior (World) (Aftermarket) (Unl)" + rom ( name "Zagan Warrior (World) (Aftermarket) (Unl).gb" size 262144 crc 97fe7a94 sha1 88f76297a4ccf0e3a5ba16ca15237c88346c134a ) ) game ( @@ -32905,8 +33724,8 @@ game ( clrmamepro ( name "Nintendo - Game Boy Color" description "Nintendo - Game Boy Color" - version 20220829-075630 - author "akubi, Arctic Circle System, Aringon, Bent, BigFred, BitLooter, C. V. Reynolds, chillerecke, coraz, darthcloud, DeadSkullzJr, Densetsu, DeriLoko3, Flashfire42, foxe, fuzzball, Hiccup, hking0036, InternalLoss, kazumi213, Lesserkuma, Madeline, Money_114, NGEfreak, nnssxx, norkmetnoil577, NovaAurora, omonim2007, PPLToast, relax, Rifu, sCZther, SonGoku, Tauwasser, togemet2, UnlockerPT, Whovian9369, xuom2, zg" + version 20230422-225414 + author "akubi, Arctic Circle System, Aringon, baldjared, Bent, BigFred, BitLooter, C. V. Reynolds, chillerecke, coraz, darthcloud, DeadSkullzJr, Densetsu, DeriLoko3, Flashfire42, foxe, fuzzball, Gefflon, Hiccup, hking0036, InternalLoss, Just001Kim, kazumi213, Lesserkuma, Madeline, Money_114, NESBrew12, NGEfreak, nnssxx, norkmetnoil577, NovaAurora, omonim2007, PPLToast, rarenight, relax, Rifu, sCZther, SonGoku, Tauwasser, togemet2, UnlockerPT, Whovian9369, xuom2, zg" homepage No-Intro url "https://www.no-intro.org" forcenodump required @@ -33006,6 +33825,78 @@ game ( rom ( name "1942 (USA, Europe).gbc" size 1048576 crc 87431672 sha1 d960e951b18d07e79d046313df49c18313664224 ) ) +game ( + name "2002 Adventure Digimon 7 (Taiwan) (En) (Unl)" + description "2002 Adventure Digimon 7 (Taiwan) (En) (Unl)" + rom ( name "2002 Adventure Digimon 7 (Taiwan) (En) (Unl).gbc" size 2097152 crc 1bfc7099 sha1 532206800253fce0e81a0825ba49d60a7d6c5ca4 ) +) + +game ( + name "2002 Digimon Adventure 6 (Taiwan) (En) (Unl)" + description "2002 Digimon Adventure 6 (Taiwan) (En) (Unl)" + rom ( name "2002 Digimon Adventure 6 (Taiwan) (En) (Unl).gbc" size 1048576 crc 3f0d046b sha1 eea8928b277beb56ca8878b6dbfc094b0c3c5463 ) +) + +game ( + name "2003 Crash II Advance (USA) (Unl)" + description "2003 Crash II Advance (USA) (Unl)" + rom ( name "2003 Crash II Advance (USA) (Unl).gbc" size 2097152 crc 973d38a8 sha1 acdc42716f61bf11ecf08d50a980047f8fb7dc57 ) +) + +game ( + name "2003 Digimom Sapphii (Taiwan) (En) (Unl)" + description "2003 Digimom Sapphii (Taiwan) (En) (Unl)" + rom ( name "2003 Digimom Sapphii (Taiwan) (En) (Unl).gbc" size 2097152 crc 7cff9f0b sha1 1e42c461e0c6705637a698dbda8b34bc37acc51d ) +) + +game ( + name "2003 Gu Huo Lang II (Taiwan) (Unl)" + description "2003 Gu Huo Lang II (Taiwan) (Unl)" + rom ( name "2003 Gu Huo Lang II (Taiwan) (Unl).gbc" size 2097152 crc 74d71b0c sha1 fcbb769de0896ae69fe1063fa5d87cf079c8606a ) +) + +game ( + name "2003 Hali Bote 2 - Xiaoshi de Mishi (Taiwan) (Unl)" + description "2003 Hali Bote 2 - Xiaoshi de Mishi (Taiwan) (Unl)" + rom ( name "2003 Hali Bote 2 - Xiaoshi de Mishi (Taiwan) (Unl).gbc" size 2097152 crc 1b3e1243 sha1 0829cbcab3ef5d03c94d5efe769f4f4a93ac47fa ) +) + +game ( + name "2003 Hali Xiaozi IV (Taiwan) (Unl)" + description "2003 Hali Xiaozi IV (Taiwan) (Unl)" + rom ( name "2003 Hali Xiaozi IV (Taiwan) (Unl).gbc" size 2097152 crc db3c8b95 sha1 0d230b20388f4396d6345b8eb59f54fc169ad8ee ) +) + +game ( + name "2003 Harry Potter 3 (Taiwan) (Unl)" + description "2003 Harry Potter 3 (Taiwan) (Unl)" + rom ( name "2003 Harry Potter 3 (Taiwan) (Unl).gbc" size 1048576 crc 4ea2c869 sha1 739c0e5dc0efd4ddb912236a6f630c3d6987d064 ) +) + +game ( + name "2003 King Lion Advance III (USA) (Unl)" + description "2003 King Lion Advance III (USA) (Unl)" + rom ( name "2003 King Lion Advance III (USA) (Unl).gbc" size 2097152 crc 0c22466d sha1 7c43ccfca93cae23e04e58fa0d7d7400a9694e0e ) +) + +game ( + name "2003 Koudai Guaishou - Lanbaoshi (Taiwan) (Unl)" + description "2003 Koudai Guaishou - Lanbaoshi (Taiwan) (Unl)" + rom ( name "2003 Koudai Guaishou - Lanbaoshi (Taiwan) (Unl).gbc" size 2097152 crc 4c76d4d8 sha1 d688201e6031b88f122ec753a37daedfb233b48d ) +) + +game ( + name "2003 Pocket Monster - Carbuncle (USA) (Unl)" + description "2003 Pocket Monster - Carbuncle (USA) (Unl)" + rom ( name "2003 Pocket Monster - Carbuncle (USA) (Unl).gbc" size 2097152 crc 3a0e9b6f sha1 025973627743f2f1ae1ff5b8a3f549cbcc227ef3 ) +) + +game ( + name "2003 Shuma Baolong - Gedou Ban (Taiwan) (Unl)" + description "2003 Shuma Baolong - Gedou Ban (Taiwan) (Unl)" + rom ( name "2003 Shuma Baolong - Gedou Ban (Taiwan) (Unl).gbc" size 2097152 crc 1219eec6 sha1 4a722c10e3893e04c67a66d0aea401d6d220ec7e ) +) + game ( name "23 in 1 (Taiwan) (Unl)" description "23 in 1 (Taiwan) (Unl)" @@ -33048,6 +33939,12 @@ game ( rom ( name "3D Pool Allstars (USA) (En,Fr,Es) (Proto).gbc" size 1048576 crc 245de3e2 sha1 f7289c3eed275286d0fb3dc7097098276b746786 ) ) +game ( + name "3D Quasars (World) (Aftermarket) (Unl)" + description "3D Quasars (World) (Aftermarket) (Unl)" + rom ( name "3D Quasars (World) (Aftermarket) (Unl).gbc" size 262144 crc 1fd94e67 sha1 3f32226538d8c7f0c866c4b73e9f828265f2bb5b ) +) + game ( name "4 in 1 + 8 in 1 (World) (4B-001, 4B-009, 8B-001, Sachen) (Unl)" description "4 in 1 + 8 in 1 (World) (4B-001, 4B-009, 8B-001, Sachen) (Unl)" @@ -33085,9 +33982,9 @@ game ( ) game ( - name "Aardvark (World) (Aftermarket) (Homebrew)" - description "Aardvark (World) (Aftermarket) (Homebrew)" - rom ( name "Aardvark (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 270d45b9 sha1 34a8d00af6be9083409e6fccd0825c6f185d135d flags verified ) + name "Aardvark (World) (Aftermarket) (Unl)" + description "Aardvark (World) (Aftermarket) (Unl)" + rom ( name "Aardvark (World) (Aftermarket) (Unl).gbc" size 262144 crc 270d45b9 sha1 34a8d00af6be9083409e6fccd0825c6f185d135d flags verified ) ) game ( @@ -33114,12 +34011,6 @@ game ( rom ( name "Action Replay Xtreme - Special Edition for Pokemon Crystal (Europe) (Unl).gbc" size 131072 crc c288e400 sha1 0280b05885fe5aca8c1884a16bd01513b99f4dc2 flags verified ) ) -game ( - name "Adventure Digimon 7 2002 (Taiwan) (En) (Unl)" - description "Adventure Digimon 7 2002 (Taiwan) (En) (Unl)" - rom ( name "Adventure Digimon 7 2002 (Taiwan) (En) (Unl).gbc" size 2097152 crc 1bfc7099 sha1 532206800253fce0e81a0825ba49d60a7d6c5ca4 ) -) - game ( name "Adventures of the Smurfs, The (Europe) (En,Fr,De,Es,It,Nl)" description "Adventures of the Smurfs, The (Europe) (En,Fr,De,Es,It,Nl)" @@ -33127,15 +34018,15 @@ game ( ) game ( - name "Agent B (World) (Aftermarket) (Homebrew)" - description "Agent B (World) (Aftermarket) (Homebrew)" - rom ( name "Agent B (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 31b6e3fa sha1 11b3ce59c4eb86cee05873eb473dbf262cf986bd ) + name "Agent B (World) (2021-12-29) (GB Compatible) (Aftermarket) (Unl)" + description "Agent B (World) (2021-12-29) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Agent B (World) (2021-12-29) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 31b6e3fa sha1 11b3ce59c4eb86cee05873eb473dbf262cf986bd ) ) game ( - name "Agent B (World) (Demo) (Aftermarket) (Homebrew)" - description "Agent B (World) (Demo) (Aftermarket) (Homebrew)" - rom ( name "Agent B (World) (Demo) (Aftermarket) (Homebrew).gbc" size 262144 crc ae767ad7 sha1 1a332aac66b24381f16819d1145fd4a917f2e0f3 ) + name "Agent B (World) (2021-12-29) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Agent B (World) (2021-12-29) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Agent B (World) (2021-12-29) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc ae767ad7 sha1 1a332aac66b24381f16819d1145fd4a917f2e0f3 ) ) game ( @@ -33216,6 +34107,24 @@ game ( rom ( name "All-Star Baseball 2001 (USA).gbc" size 1048576 crc bc562466 sha1 f702df64d368b763187a5b1263a60fb3e842962b ) ) +game ( + name "Alley Cat (World) (Aftermarket) (Unl)" + description "Alley Cat (World) (Aftermarket) (Unl)" + rom ( name "Alley Cat (World) (Aftermarket) (Unl).gbc" size 262144 crc edb3ac37 sha1 a9aa1ecad6b67a6e5096fb1c10e39899c00a96a0 ) +) + +game ( + name "Alone in the Dark - The New Nightmare (Europe) (En,Fr,De,Es,It,Nl) (Beta)" + description "Alone in the Dark - The New Nightmare (Europe) (En,Fr,De,Es,It,Nl) (Beta)" + rom ( name "Alone in the Dark - The New Nightmare (Europe) (En,Fr,De,Es,It,Nl) (Beta).gbc" size 4194304 crc 7c101475 sha1 120e2dd012cfe47a7e37cc91fcbbce92494bdf2a ) +) + +game ( + name "Alone in the Dark - The New Nightmare (USA) (Beta) (2001-02-21)" + description "Alone in the Dark - The New Nightmare (USA) (Beta) (2001-02-21)" + rom ( name "Alone in the Dark - The New Nightmare (USA) (Beta) (2001-02-21).gbc" size 4194304 crc 3821d140 sha1 fc2a7dbe5ff60e2704ac78f83d6b6df2bf9dcc97 ) +) + game ( name "Alone in the Dark - The New Nightmare (Europe) (En,Fr,De,Es,It,Nl)" description "Alone in the Dark - The New Nightmare (Europe) (En,Fr,De,Es,It,Nl)" @@ -33228,12 +34137,6 @@ game ( rom ( name "Alone in the Dark - The New Nightmare (USA) (En,Fr,Es).gbc" size 4194304 crc c145c036 sha1 a348aeac500d0d8fdaf90f5277631a026504ff44 ) ) -game ( - name "Alone in the Dark - The New Nightmare (Europe) (En,Fr,De,Es,It,Nl) (Beta)" - description "Alone in the Dark - The New Nightmare (Europe) (En,Fr,De,Es,It,Nl) (Beta)" - rom ( name "Alone in the Dark - The New Nightmare (Europe) (En,Fr,De,Es,It,Nl) (Beta).gbc" size 4194304 crc 7c101475 sha1 120e2dd012cfe47a7e37cc91fcbbce92494bdf2a ) -) - game ( name "AMF Bowling (USA) (Proto)" description "AMF Bowling (USA) (Proto)" @@ -33469,9 +34372,9 @@ game ( ) game ( - name "Auto Zone (World) (Aftermarket) (Homebrew)" - description "Auto Zone (World) (Aftermarket) (Homebrew)" - rom ( name "Auto Zone (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 2a86d386 sha1 83a37fb5c5d82e0ff06bb22b63761af996e4ad61 ) + name "Auto Zone (World) (Aftermarket) (Unl)" + description "Auto Zone (World) (Aftermarket) (Unl)" + rom ( name "Auto Zone (World) (Aftermarket) (Unl).gbc" size 524288 crc 2a86d386 sha1 83a37fb5c5d82e0ff06bb22b63761af996e4ad61 ) ) game ( @@ -33513,7 +34416,7 @@ game ( game ( name "Babe and Friends (Europe) (En,Fr,De,Es,It) (GB Compatible)" description "Babe and Friends (Europe) (En,Fr,De,Es,It) (GB Compatible)" - rom ( name "Babe and Friends (Europe) (En,Fr,De,Es,It) (GB Compatible).gbc" size 1048576 crc b3681854 sha1 52a560185b7e77e5286771f7851f69323512658e ) + rom ( name "Babe and Friends (Europe) (En,Fr,De,Es,It) (GB Compatible).gbc" size 1048576 crc b3681854 sha1 52a560185b7e77e5286771f7851f69323512658e flags verified ) ) game ( @@ -33535,9 +34438,9 @@ game ( ) game ( - name "Back to Nature (World) (Aftermarket) (Homebrew)" - description "Back to Nature (World) (Aftermarket) (Homebrew)" - rom ( name "Back to Nature (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 861f9a63 sha1 3f4a6cd105d4c8af0312bef482c08784bbbfb0eb ) + name "Back to Nature (World) (Aftermarket) (Unl)" + description "Back to Nature (World) (Aftermarket) (Unl)" + rom ( name "Back to Nature (World) (Aftermarket) (Unl).gbc" size 262144 crc 861f9a63 sha1 3f4a6cd105d4c8af0312bef482c08784bbbfb0eb ) ) game ( @@ -33607,9 +34510,9 @@ game ( ) game ( - name "Bandits at Zero (World) (Aftermarket) (Homebrew)" - description "Bandits at Zero (World) (Aftermarket) (Homebrew)" - rom ( name "Bandits at Zero (World) (Aftermarket) (Homebrew).gbc" size 524288 crc c70f413e sha1 c885e109fbb53445db9618c6768c2259e6135921 ) + name "Bandits at Zero (World) (Aftermarket) (Unl)" + description "Bandits at Zero (World) (Aftermarket) (Unl)" + rom ( name "Bandits at Zero (World) (Aftermarket) (Unl).gbc" size 524288 crc c70f413e sha1 c885e109fbb53445db9618c6768c2259e6135921 ) ) game ( @@ -33738,6 +34641,12 @@ game ( rom ( name "Battle Fishers (Japan).gbc" size 2097152 crc c99cf3c5 sha1 25d063b151c2d45a14d6952196490615b7e341c5 ) ) +game ( + name "Battle Star (World) (Aftermarket) (Unl)" + description "Battle Star (World) (Aftermarket) (Unl)" + rom ( name "Battle Star (World) (Aftermarket) (Unl).gbc" size 524288 crc b2fd062f sha1 ac4206129704e31386c9c0a1c961a148398f0ba3 ) +) + game ( name "Battleship (USA, Europe) (GB Compatible)" description "Battleship (USA, Europe) (GB Compatible)" @@ -33777,7 +34686,7 @@ game ( game ( name "Beatmania GB (Japan) (SGB Enhanced) (GB Compatible)" description "Beatmania GB (Japan) (SGB Enhanced) (GB Compatible)" - rom ( name "Beatmania GB (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 0d9cb195 sha1 640c4633fe8ec60b767c15a094c87ab7e113d555 ) + rom ( name "Beatmania GB (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 0d9cb195 sha1 640c4633fe8ec60b767c15a094c87ab7e113d555 flags verified ) ) game ( @@ -33811,9 +34720,9 @@ game ( ) game ( - name "Berks (World) (Aftermarket) (Homebrew)" - description "Berks (World) (Aftermarket) (Homebrew)" - rom ( name "Berks (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 10ded9ab sha1 8b4c282cf973cdd9926a69ae1d8fcbaf79f8552b ) + name "Berks (World) (Aftermarket) (Unl)" + description "Berks (World) (Aftermarket) (Unl)" + rom ( name "Berks (World) (Aftermarket) (Unl).gbc" size 262144 crc 10ded9ab sha1 8b4c282cf973cdd9926a69ae1d8fcbaf79f8552b ) ) game ( @@ -33853,21 +34762,21 @@ game ( ) game ( - name "Bing Yuan Li Xian Ji (Taiwan) (Unl)" - description "Bing Yuan Li Xian Ji (Taiwan) (Unl)" - rom ( name "Bing Yuan Li Xian Ji (Taiwan) (Unl).gbc" size 2097152 crc 7ca57891 sha1 1bebc84f0fbfa731f33343c56032c8fe5803e4ae ) + name "Bingyuan Lixian Ji (Taiwan) (Unl)" + description "Bingyuan Lixian Ji (Taiwan) (Unl)" + rom ( name "Bingyuan Lixian Ji (Taiwan) (Unl).gbc" size 2097152 crc 7ca57891 sha1 1bebc84f0fbfa731f33343c56032c8fe5803e4ae ) ) game ( - name "Bing Yuan Li Xian Ji II (Taiwan) (Unl) (Alt)" - description "Bing Yuan Li Xian Ji II (Taiwan) (Unl) (Alt)" - rom ( name "Bing Yuan Li Xian Ji II (Taiwan) (Unl) (Alt).gbc" size 2097152 crc b149f421 sha1 39060bd6fa872738ae126b64f123a1242d06680a ) + name "Bingyuan Lixian Ji II (Taiwan) (Unl) (Alt)" + description "Bingyuan Lixian Ji II (Taiwan) (Unl) (Alt)" + rom ( name "Bingyuan Lixian Ji II (Taiwan) (Unl) (Alt).gbc" size 2097152 crc b149f421 sha1 39060bd6fa872738ae126b64f123a1242d06680a ) ) game ( - name "Bing Yuan Li Xian Ji II (Taiwan) (Unl)" - description "Bing Yuan Li Xian Ji II (Taiwan) (Unl)" - rom ( name "Bing Yuan Li Xian Ji II (Taiwan) (Unl).gbc" size 2097152 crc c4eba914 sha1 9e1c0c019630d7e4884ad3d5a447bd1acf668277 ) + name "Bingyuan Lixian Ji II (Taiwan) (Unl)" + description "Bingyuan Lixian Ji II (Taiwan) (Unl)" + rom ( name "Bingyuan Lixian Ji II (Taiwan) (Unl).gbc" size 2097152 crc c4eba914 sha1 9e1c0c019630d7e4884ad3d5a447bd1acf668277 ) ) game ( @@ -33907,9 +34816,9 @@ game ( ) game ( - name "Blinky's Revenge (World) (Aftermarket) (Homebrew)" - description "Blinky's Revenge (World) (Aftermarket) (Homebrew)" - rom ( name "Blinky's Revenge (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 2d2f9c2b sha1 1c39cc02396c74e7b0b282ead510e94c77ff7f1e ) + name "Blinky's Revenge (World) (Aftermarket) (Unl)" + description "Blinky's Revenge (World) (Aftermarket) (Unl)" + rom ( name "Blinky's Revenge (World) (Aftermarket) (Unl).gbc" size 262144 crc 2d2f9c2b sha1 1c39cc02396c74e7b0b282ead510e94c77ff7f1e ) ) game ( @@ -33984,12 +34893,6 @@ game ( rom ( name "Bokujou Monogatari 3 GB - Boy Meets Girl (Japan) (Rev 1).gbc" size 2097152 crc 75af0e84 sha1 acb2e549fb7d107d20507af88c36f40234f74958 flags verified ) ) -game ( - name "Bomberman 3 (Taiwan) (Unl)" - description "Bomberman 3 (Taiwan) (Unl)" - rom ( name "Bomberman 3 (Taiwan) (Unl).gbc" size 2097152 crc 8059e009 sha1 84011e3ae407613cfd7b6b1fe85f3812097afc66 ) -) - game ( name "Bomberman Max - Ain Version (Japan)" description "Bomberman Max - Ain Version (Japan)" @@ -34005,7 +34908,7 @@ game ( game ( name "Bomberman Max - Hikari no Yuusha (Japan)" description "Bomberman Max - Hikari no Yuusha (Japan)" - rom ( name "Bomberman Max - Hikari no Yuusha (Japan).gbc" size 2097152 crc 7a44ce88 sha1 9c38166e83e45707cbc2e0aff38fd4590dce7c3f ) + rom ( name "Bomberman Max - Hikari no Yuusha (Japan).gbc" size 2097152 crc 7a44ce88 sha1 9c38166e83e45707cbc2e0aff38fd4590dce7c3f flags verified ) ) game ( @@ -34017,7 +34920,7 @@ game ( game ( name "Bomberman Max - Yami no Senshi (Japan)" description "Bomberman Max - Yami no Senshi (Japan)" - rom ( name "Bomberman Max - Yami no Senshi (Japan).gbc" size 2097152 crc 48b60e8e sha1 12addfb8f890a44b87efb900e9d6ad5028b58936 ) + rom ( name "Bomberman Max - Yami no Senshi (Japan).gbc" size 2097152 crc 48b60e8e sha1 12addfb8f890a44b87efb900e9d6ad5028b58936 flags verified ) ) game ( @@ -34057,15 +34960,15 @@ game ( ) game ( - name "Booty (World) (Aftermarket) (Homebrew)" - description "Booty (World) (Aftermarket) (Homebrew)" - rom ( name "Booty (World) (Aftermarket) (Homebrew).gbc" size 262144 crc ef6c39c8 sha1 3c487ddc03d935b583e186d1cc395966ef490412 ) + name "Booty (World) (Aftermarket) (Unl)" + description "Booty (World) (Aftermarket) (Unl)" + rom ( name "Booty (World) (Aftermarket) (Unl).gbc" size 262144 crc ef6c39c8 sha1 3c487ddc03d935b583e186d1cc395966ef490412 ) ) game ( - name "Bouken! Dondoko-tou (Japan)" - description "Bouken! Dondoko-tou (Japan)" - rom ( name "Bouken! Dondoko-tou (Japan).gbc" size 2097152 crc 5fe759c7 sha1 c054abfea01a3ceb7b20b27e7ea937abe5157834 ) + name "Bouken! Dondoko Shima (Japan)" + description "Bouken! Dondoko Shima (Japan)" + rom ( name "Bouken! Dondoko Shima (Japan).gbc" size 2097152 crc 5fe759c7 sha1 c054abfea01a3ceb7b20b27e7ea937abe5157834 ) ) game ( @@ -34081,9 +34984,9 @@ game ( ) game ( - name "Bubble Trouble (World) (Aftermarket) (Homebrew)" - description "Bubble Trouble (World) (Aftermarket) (Homebrew)" - rom ( name "Bubble Trouble (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 3374918b sha1 892388f81f7815ea3bca17632a6a9a33a6edafac ) + name "Bubble Trouble (World) (Aftermarket) (Unl)" + description "Bubble Trouble (World) (Aftermarket) (Unl)" + rom ( name "Bubble Trouble (World) (Aftermarket) (Unl).gbc" size 262144 crc 3374918b sha1 892388f81f7815ea3bca17632a6a9a33a6edafac ) ) game ( @@ -34147,9 +35050,9 @@ game ( ) game ( - name "Bulb! (World) (Aftermarket) (Homebrew)" - description "Bulb! (World) (Aftermarket) (Homebrew)" - rom ( name "Bulb! (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 650114a2 sha1 0c32d4b48a34614fb11fc69c17187d35ee22fabe ) + name "Bulb! (World) (v2.5) (Aftermarket) (Unl)" + description "Bulb! (World) (v2.5) (Aftermarket) (Unl)" + rom ( name "Bulb! (World) (v2.5) (Aftermarket) (Unl).gbc" size 524288 crc 650114a2 sha1 0c32d4b48a34614fb11fc69c17187d35ee22fabe flags verified ) ) game ( @@ -34386,6 +35289,12 @@ game ( rom ( name "Catz (USA).gbc" size 1048576 crc 769a2c5a sha1 5c3c9a4d85a92779c7e8304f2c122296f62f9a9c ) ) +game ( + name "Cave Fighter (World) (Aftermarket) (Unl)" + description "Cave Fighter (World) (Aftermarket) (Unl)" + rom ( name "Cave Fighter (World) (Aftermarket) (Unl).gbc" size 262144 crc 6f4641f0 sha1 05b441e3542452b1724017d20e2db602d0997773 ) +) + game ( name "Centipede (Europe) (En,Fr,De,Es,It,Nl) (GB Compatible)" description "Centipede (Europe) (En,Fr,De,Es,It,Nl) (GB Compatible)" @@ -34411,39 +35320,45 @@ game ( ) game ( - name "Chao Ji Ji Qi Ren Da Zhan X - Super Robot War X (Taiwan) (Unl)" - description "Chao Ji Ji Qi Ren Da Zhan X - Super Robot War X (Taiwan) (Unl)" - rom ( name "Chao Ji Ji Qi Ren Da Zhan X - Super Robot War X (Taiwan) (Unl).gbc" size 2097152 crc 279be0cc sha1 c2a64ba4c1fd60829429330bc13b70d9bd18f023 ) + name "Chao Jinhua - Shuma Baobei D-3 (Taiwan) (Unl)" + description "Chao Jinhua - Shuma Baobei D-3 (Taiwan) (Unl)" + rom ( name "Chao Jinhua - Shuma Baobei D-3 (Taiwan) (Unl).gbc" size 1048576 crc 1517d27e sha1 0d14f2e2ab630b6f99a8125e5c0120c2d7967270 ) ) game ( - name "Chao Ji Ji Qi Ren Da Zhan X - Super Robot War X (Taiwan) (Unl) (Alt)" - description "Chao Ji Ji Qi Ren Da Zhan X - Super Robot War X (Taiwan) (Unl) (Alt)" - rom ( name "Chao Ji Ji Qi Ren Da Zhan X - Super Robot War X (Taiwan) (Unl) (Alt).gbc" size 2097152 crc 1feaeb47 sha1 26d5841fc898dab17458dc39e871dab4947a7f2b ) -) - -game ( - name "Chao Jin Hua - Shu Ma Bao Bei D-3 (Taiwan) (Unl)" - description "Chao Jin Hua - Shu Ma Bao Bei D-3 (Taiwan) (Unl)" - rom ( name "Chao Jin Hua - Shu Ma Bao Bei D-3 (Taiwan) (Unl).gbc" size 1048576 crc 1517d27e sha1 0d14f2e2ab630b6f99a8125e5c0120c2d7967270 ) -) - -game ( - name "Chao Jinhua Shuma Baolong - Zuanshi Ban (Taiwan) (Unl)" - description "Chao Jinhua Shuma Baolong - Zuanshi Ban (Taiwan) (Unl)" - rom ( name "Chao Jinhua Shuma Baolong - Zuanshi Ban (Taiwan) (Unl).gbc" size 1048576 crc 50babe99 sha1 c153550886e812f5e37746fe1be6f294f13f97b3 ) + name "Chao Jinhua - Shuma Baolong - Zuanshi Ban (Taiwan) (Unl)" + description "Chao Jinhua - Shuma Baolong - Zuanshi Ban (Taiwan) (Unl)" + rom ( name "Chao Jinhua - Shuma Baolong - Zuanshi Ban (Taiwan) (Unl).gbc" size 1048576 crc 50babe99 sha1 c153550886e812f5e37746fe1be6f294f13f97b3 ) ) game ( name "Chaoji Gedou 2001 Alpha (Taiwan) (Unl)" description "Chaoji Gedou 2001 Alpha (Taiwan) (Unl)" - rom ( name "Chaoji Gedou 2001 Alpha (Taiwan) (Unl).gbc" size 2097152 crc afd7a0cc sha1 f44f629687ec91aade40ff52014587003f728ec9 ) + rom ( name "Chaoji Gedou 2001 Alpha (Taiwan) (Unl).gbc" size 2097152 crc afd7a0cc sha1 f44f629687ec91aade40ff52014587003f728ec9 flags verified ) ) game ( - name "Chaoji Yinsu Xiaozi II - Super Sonik II (Taiwan) (Unl)" - description "Chaoji Yinsu Xiaozi II - Super Sonik II (Taiwan) (Unl)" - rom ( name "Chaoji Yinsu Xiaozi II - Super Sonik II (Taiwan) (Unl).gbc" size 2097152 crc a9dd9da7 sha1 006df0ddb10c5970002134da4d75ccc466e95edd ) + name "Chaoji Jiqiren Dazhan X - Super Robot War X (Taiwan) (Unl)" + description "Chaoji Jiqiren Dazhan X - Super Robot War X (Taiwan) (Unl)" + rom ( name "Chaoji Jiqiren Dazhan X - Super Robot War X (Taiwan) (Unl).gbc" size 2097152 crc 279be0cc sha1 c2a64ba4c1fd60829429330bc13b70d9bd18f023 ) +) + +game ( + name "Chaoji Jiqiren Dazhan X - Super Robot War X (Taiwan) (Unl) (Alt)" + description "Chaoji Jiqiren Dazhan X - Super Robot War X (Taiwan) (Unl) (Alt)" + rom ( name "Chaoji Jiqiren Dazhan X - Super Robot War X (Taiwan) (Unl) (Alt).gbc" size 2097152 crc 1feaeb47 sha1 26d5841fc898dab17458dc39e871dab4947a7f2b ) +) + +game ( + name "Chaoji Yinsu de Xiaozi II - Super Sonik II (Taiwan) (Unl)" + description "Chaoji Yinsu de Xiaozi II - Super Sonik II (Taiwan) (Unl)" + rom ( name "Chaoji Yinsu de Xiaozi II - Super Sonik II (Taiwan) (Unl).gbc" size 2097152 crc a9dd9da7 sha1 006df0ddb10c5970002134da4d75ccc466e95edd ) +) + +game ( + name "Chaoren Tegong Dui (Taiwan) (Unl)" + description "Chaoren Tegong Dui (Taiwan) (Unl)" + rom ( name "Chaoren Tegong Dui (Taiwan) (Unl).gbc" size 2097152 crc e1d61242 sha1 31f54bdfa59d1e76777f044c1ef4686552f715f6 flags verified ) ) game ( @@ -34501,9 +35416,9 @@ game ( ) game ( - name "Chong Wu Xiao Jing Ling - Jie Jin Ta Zhi Wang (Taiwan) (Unl)" - description "Chong Wu Xiao Jing Ling - Jie Jin Ta Zhi Wang (Taiwan) (Unl)" - rom ( name "Chong Wu Xiao Jing Ling - Jie Jin Ta Zhi Wang (Taiwan) (Unl).gbc" size 1048576 crc 620e785d sha1 74da832c2eeb27a4260fe6f42514539473f48b11 ) + name "Chongwu Xiao Jingling - Jiejin Ta Zhi Wang (Taiwan) (Unl)" + description "Chongwu Xiao Jingling - Jiejin Ta Zhi Wang (Taiwan) (Unl)" + rom ( name "Chongwu Xiao Jingling - Jiejin Ta Zhi Wang (Taiwan) (Unl).gbc" size 1048576 crc 620e785d sha1 74da832c2eeb27a4260fe6f42514539473f48b11 ) ) game ( @@ -34513,9 +35428,9 @@ game ( ) game ( - name "Chuan Shuo (Taiwan) (Unl)" - description "Chuan Shuo (Taiwan) (Unl)" - rom ( name "Chuan Shuo (Taiwan) (Unl).gbc" size 2097152 crc 0b20d3af sha1 f390b702d4b160149c1030bcccb5ed6f76c0b94a ) + name "Chuanshuo (Taiwan) (Unl)" + description "Chuanshuo (Taiwan) (Unl)" + rom ( name "Chuanshuo (Taiwan) (Unl).gbc" size 2097152 crc 0b20d3af sha1 f390b702d4b160149c1030bcccb5ed6f76c0b94a ) ) game ( @@ -34531,9 +35446,9 @@ game ( ) game ( - name "Climb It (World) (Aftermarket) (Homebrew)" - description "Climb It (World) (Aftermarket) (Homebrew)" - rom ( name "Climb It (World) (Aftermarket) (Homebrew).gbc" size 262144 crc e7210290 sha1 19f3b825eada3eda3135350bd0ddca6a20a5281f ) + name "Climb It (World) (Aftermarket) (Unl)" + description "Climb It (World) (Aftermarket) (Unl)" + rom ( name "Climb It (World) (Aftermarket) (Unl).gbc" size 262144 crc e7210290 sha1 19f3b825eada3eda3135350bd0ddca6a20a5281f ) ) game ( @@ -34561,9 +35476,9 @@ game ( ) game ( - name "Commando (World) (Aftermarket) (Homebrew)" - description "Commando (World) (Aftermarket) (Homebrew)" - rom ( name "Commando (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 5381b103 sha1 414bb6bdc1ed5647707d7d49f194c387d4098f00 ) + name "Commando (World) (Aftermarket) (Unl)" + description "Commando (World) (Aftermarket) (Unl)" + rom ( name "Commando (World) (Aftermarket) (Unl).gbc" size 262144 crc 5381b103 sha1 414bb6bdc1ed5647707d7d49f194c387d4098f00 ) ) game ( @@ -34584,12 +35499,6 @@ game ( rom ( name "Cool Hand (Europe) (En,Fr,De) (GB Compatible).gbc" size 524288 crc e6c91fb8 sha1 d116a77c501d5e553d1490993f8c0a9a92c3ea0c ) ) -game ( - name "Crash II Advance 2003 (USA) (Unl)" - description "Crash II Advance 2003 (USA) (Unl)" - rom ( name "Crash II Advance 2003 (USA) (Unl).gbc" size 2097152 crc 973d38a8 sha1 acdc42716f61bf11ecf08d50a980047f8fb7dc57 ) -) - game ( name "Crazy Bikers (Europe)" description "Crazy Bikers (Europe)" @@ -34609,9 +35518,9 @@ game ( ) game ( - name "Crazy Golf (World) (Aftermarket) (Homebrew)" - description "Crazy Golf (World) (Aftermarket) (Homebrew)" - rom ( name "Crazy Golf (World) (Aftermarket) (Homebrew).gbc" size 262144 crc f7fe3d01 sha1 b06f47a713b66efa9133be43653c7c4cb5ebc93c ) + name "Crazy Golf (World) (Aftermarket) (Unl)" + description "Crazy Golf (World) (Aftermarket) (Unl)" + rom ( name "Crazy Golf (World) (Aftermarket) (Unl).gbc" size 262144 crc f7fe3d01 sha1 b06f47a713b66efa9133be43653c7c4cb5ebc93c ) ) game ( @@ -34669,9 +35578,9 @@ game ( ) game ( - name "Cuthbert in the Cooler (World) (Aftermarket) (Homebrew)" - description "Cuthbert in the Cooler (World) (Aftermarket) (Homebrew)" - rom ( name "Cuthbert in the Cooler (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 30204c4e sha1 d6fb35f3bdd44429f88c10551692e1adf14356ab ) + name "Cuthbert in the Cooler (World) (Aftermarket) (Unl)" + description "Cuthbert in the Cooler (World) (Aftermarket) (Unl)" + rom ( name "Cuthbert in the Cooler (World) (Aftermarket) (Unl).gbc" size 262144 crc 30204c4e sha1 d6fb35f3bdd44429f88c10551692e1adf14356ab ) ) game ( @@ -34830,12 +35739,24 @@ game ( rom ( name "Dear Daniel no Sweet Adventure - Kitty-chan o Sagashite (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 0fd34427 sha1 8598594285f0342b3d93c447b475fadd4722c6cb ) ) +game ( + name "Death Race 16 (World) (Aftermarket) (Unl)" + description "Death Race 16 (World) (Aftermarket) (Unl)" + rom ( name "Death Race 16 (World) (Aftermarket) (Unl).gbc" size 262144 crc 7b9488ec sha1 56b8a2d7117b12ff5e709e6a258f9a0b3b0ebe24 ) +) + game ( name "Deer Hunter (USA)" description "Deer Hunter (USA)" rom ( name "Deer Hunter (USA).gbc" size 1048576 crc 40a715fb sha1 87a012e82f2361760ae337b30e9d41ef2245419a ) ) +game ( + name "Deisanebe (World) (GB Compatible) (Aftermarket) (Unl)" + description "Deisanebe (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Deisanebe (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 7cff943e sha1 065b5325d44cb0e40f7b7803d707b8c1ea9ea5b2 ) +) + game ( name "Deja Vu I & II (Japan)" description "Deja Vu I & II (Japan)" @@ -34902,12 +35823,6 @@ game ( rom ( name "Dexter's Laboratory - Robot Rampage (USA, Europe).gbc" size 1048576 crc d24d6601 sha1 db107af55df07fb8c771c712b05d6aebb175e3c5 ) ) -game ( - name "Digimom Sapphii 2003 (Taiwan) (En) (Unl)" - description "Digimom Sapphii 2003 (Taiwan) (En) (Unl)" - rom ( name "Digimom Sapphii 2003 (Taiwan) (En) (Unl).gbc" size 2097152 crc 7cff9f0b sha1 1e42c461e0c6705637a698dbda8b34bc37acc51d ) -) - game ( name "Digimon - Yellow Jade (USA) (Unl)" description "Digimon - Yellow Jade (USA) (Unl)" @@ -34938,12 +35853,6 @@ game ( rom ( name "Digimon Adventure 2001 (USA) (Unl).gbc" size 1048576 crc 01f1fcee sha1 cb4327829bc90c7bb448e7591e6ee3729a12a8cd ) ) -game ( - name "Digimon Adventure 6 2002 (Taiwan) (En) (Unl)" - description "Digimon Adventure 6 2002 (Taiwan) (En) (Unl)" - rom ( name "Digimon Adventure 6 2002 (Taiwan) (En) (Unl).gbc" size 1048576 crc 3f0d046b sha1 eea8928b277beb56ca8878b6dbfc094b0c3c5463 ) -) - game ( name "Digimon Crystal II (USA) (Unl)" description "Digimon Crystal II (USA) (Unl)" @@ -35022,6 +35931,12 @@ game ( rom ( name "Diva Starz - Mall Mania (USA).gbc" size 1048576 crc ebe0ecd6 sha1 1d0edb87404409f1e4c124503be4cc59165c01da ) ) +game ( + name "Doc Cosmos - The Saga Begins (World) (Aftermarket) (Unl)" + description "Doc Cosmos - The Saga Begins (World) (Aftermarket) (Unl)" + rom ( name "Doc Cosmos - The Saga Begins (World) (Aftermarket) (Unl).gbc" size 262144 crc 754dff51 sha1 a066d9973a2b90a08067dfcd22b04844557e89e2 ) +) + game ( name "Dogz (Europe)" description "Dogz (Europe)" @@ -35073,7 +35988,7 @@ game ( game ( name "Donkey Kong 2001 (Japan)" description "Donkey Kong 2001 (Japan)" - rom ( name "Donkey Kong 2001 (Japan).gbc" size 4194304 crc cb065eba sha1 5be5500d9ff9c4416df77816ebd21cca7a0b19de ) + rom ( name "Donkey Kong 2001 (Japan).gbc" size 4194304 crc cb065eba sha1 5be5500d9ff9c4416df77816ebd21cca7a0b19de flags verified ) ) game ( @@ -35161,9 +36076,9 @@ game ( ) game ( - name "Dork's Dilemma (World) (Aftermarket) (Homebrew)" - description "Dork's Dilemma (World) (Aftermarket) (Homebrew)" - rom ( name "Dork's Dilemma (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 77b8b43b sha1 bd525f97cc316fed3f6271ed0929414df4c0389c ) + name "Dork's Dilemma (World) (Aftermarket) (Unl)" + description "Dork's Dilemma (World) (Aftermarket) (Unl)" + rom ( name "Dork's Dilemma (World) (Aftermarket) (Unl).gbc" size 524288 crc 77b8b43b sha1 bd525f97cc316fed3f6271ed0929414df4c0389c ) ) game ( @@ -35214,6 +36129,12 @@ game ( rom ( name "Dragon Ball - Final Bout (Taiwan) (Unl).gbc" size 1048576 crc d9849157 sha1 51ebe1a27e8295340f996d2841c5cc6e2bc9f548 ) ) +game ( + name "Dragon Ball Z - 2002 Fighting (Taiwan) (Zh) (Unl)" + description "Dragon Ball Z - 2002 Fighting (Taiwan) (Zh) (Unl)" + rom ( name "Dragon Ball Z - 2002 Fighting (Taiwan) (Zh) (Unl).gbc" size 2097152 crc 8fa35740 sha1 008a559635c9d351d9f68b3a1ac0c687521821cf ) +) + game ( name "Dragon Ball Z - Densetsu no Chou Senshi-tachi (Japan) (Beta) (All Unlocked)" description "Dragon Ball Z - Densetsu no Chou Senshi-tachi (Japan) (Beta) (All Unlocked)" @@ -35323,15 +36244,9 @@ game ( ) game ( - name "Dragon Ball Z 3 2002 Fighting (Taiwan) (En) (Unl)" - description "Dragon Ball Z 3 2002 Fighting (Taiwan) (En) (Unl)" - rom ( name "Dragon Ball Z 3 2002 Fighting (Taiwan) (En) (Unl).gbc" size 2097152 crc dbe0f44e sha1 baea9e5ee1411988039d563ece97d8d7f765396a ) -) - -game ( - name "Dragon Ball Z Fighting 2002 (Taiwan) (Zh) (Unl)" - description "Dragon Ball Z Fighting 2002 (Taiwan) (Zh) (Unl)" - rom ( name "Dragon Ball Z Fighting 2002 (Taiwan) (Zh) (Unl).gbc" size 2097152 crc 8fa35740 sha1 008a559635c9d351d9f68b3a1ac0c687521821cf ) + name "Dragon Ball Z 3 - 2002 Fighting (Taiwan) (En) (Unl)" + description "Dragon Ball Z 3 - 2002 Fighting (Taiwan) (En) (Unl)" + rom ( name "Dragon Ball Z 3 - 2002 Fighting (Taiwan) (En) (Unl).gbc" size 2097152 crc dbe0f44e sha1 baea9e5ee1411988039d563ece97d8d7f765396a ) ) game ( @@ -35389,9 +36304,9 @@ game ( ) game ( - name "Dragon Quest Monsters - Terry no Wonderland (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP)" - description "Dragon Quest Monsters - Terry no Wonderland (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP)" - rom ( name "Dragon Quest Monsters - Terry no Wonderland (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP).gbc" size 2097152 crc 66b83e3a sha1 378794e041f2eb286f2d5243a3cada2faf265bb5 ) + name "Dragon Quest Monsters - Terry no Wonderland (Japan) (Rev 1) (Possible Proto) (SGB Enhanced) (GB Compatible) (Alt)" + description "Dragon Quest Monsters - Terry no Wonderland (Japan) (Rev 1) (Possible Proto) (SGB Enhanced) (GB Compatible) (Alt)" + rom ( name "Dragon Quest Monsters - Terry no Wonderland (Japan) (Rev 1) (Possible Proto) (SGB Enhanced) (GB Compatible) (Alt).gbc" size 2097152 crc 66b83e3a sha1 378794e041f2eb286f2d5243a3cada2faf265bb5 ) ) game ( @@ -35557,9 +36472,9 @@ game ( ) game ( - name "E Mo Dao (Taiwan) (Unl)" - description "E Mo Dao (Taiwan) (Unl)" - rom ( name "E Mo Dao (Taiwan) (Unl).gbc" size 524288 crc 2b2acb79 sha1 929784599694efc6db16163f5bf91e49c2e459fc ) + name "E.T. - The Extra-Terrestrial - Digital Companion (USA)" + description "E.T. - The Extra-Terrestrial - Digital Companion (USA)" + rom ( name "E.T. - The Extra-Terrestrial - Digital Companion (USA).gbc" size 1048576 crc 84872999 sha1 7dd940c8044ccc0e18a511fa32c73551ce30463e ) ) game ( @@ -35568,12 +36483,6 @@ game ( rom ( name "E.T. - The Extra-Terrestrial - Digital Companion (Europe) (Proto).gbc" size 1048576 crc 513f38b7 sha1 14ad36a7939be12d1212c12a59de31d27e4df8f3 ) ) -game ( - name "E.T. - The Extra-Terrestrial - Digital Companion (USA)" - description "E.T. - The Extra-Terrestrial - Digital Companion (USA)" - rom ( name "E.T. - The Extra-Terrestrial - Digital Companion (USA).gbc" size 1048576 crc 84872999 sha1 7dd940c8044ccc0e18a511fa32c73551ce30463e ) -) - game ( name "E.T. - The Extra-Terrestrial - Escape from Planet Earth (Europe) (En,Fr,De,Es,It,Nl)" description "E.T. - The Extra-Terrestrial - Escape from Planet Earth (Europe) (En,Fr,De,Es,It,Nl)" @@ -35601,7 +36510,7 @@ game ( game ( name "Earthworm Jim - Menace 2 the Galaxy (USA, Europe) (GB Compatible)" description "Earthworm Jim - Menace 2 the Galaxy (USA, Europe) (GB Compatible)" - rom ( name "Earthworm Jim - Menace 2 the Galaxy (USA, Europe) (GB Compatible).gbc" size 1048576 crc 2e65daaf sha1 78f9bd7f8c40274cf7282892a45e814103944060 ) + rom ( name "Earthworm Jim - Menace 2 the Galaxy (USA, Europe) (GB Compatible).gbc" size 1048576 crc 2e65daaf sha1 78f9bd7f8c40274cf7282892a45e814103944060 flags verified ) ) game ( @@ -35652,6 +36561,12 @@ game ( rom ( name "Emo Cheng DX (Taiwan) (Beta) (Unl).gbc" size 1048576 crc ebf7bf6e sha1 29b1b595ca8f5ad76ebf4f997d0e4fb8069829f1 ) ) +game ( + name "Emo Dao (Taiwan) (Unl)" + description "Emo Dao (Taiwan) (Unl)" + rom ( name "Emo Dao (Taiwan) (Unl).gbc" size 524288 crc 2b2acb79 sha1 929784599694efc6db16163f5bf91e49c2e459fc ) +) + game ( name "Emperor's New Groove, The (Europe) (En,Fr,De,Es,It)" description "Emperor's New Groove, The (Europe) (En,Fr,De,Es,It)" @@ -35719,15 +36634,15 @@ game ( ) game ( - name "Expiration Date (World) (Aftermarket) (Homebrew)" - description "Expiration Date (World) (Aftermarket) (Homebrew)" - rom ( name "Expiration Date (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 050caa5e sha1 e4b61c220b046e2287272f9b1bef85ad2d835fe3 ) + name "Expiration Date (World) (GB Compatible) (Aftermarket) (Unl)" + description "Expiration Date (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Expiration Date (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 050caa5e sha1 e4b61c220b046e2287272f9b1bef85ad2d835fe3 ) ) game ( - name "Exploits of Fingers Malone, The (World) (Aftermarket) (Homebrew)" - description "Exploits of Fingers Malone, The (World) (Aftermarket) (Homebrew)" - rom ( name "Exploits of Fingers Malone, The (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 20fe84af sha1 18056b5b032955099c606651f26164de131e54a6 ) + name "Exploits of Fingers Malone, The (World) (Aftermarket) (Unl)" + description "Exploits of Fingers Malone, The (World) (Aftermarket) (Unl)" + rom ( name "Exploits of Fingers Malone, The (World) (Aftermarket) (Unl).gbc" size 524288 crc 20fe84af sha1 18056b5b032955099c606651f26164de131e54a6 ) ) game ( @@ -35845,15 +36760,9 @@ game ( ) game ( - name "Feng Kuang Da Fu Weng (Taiwan) (Unl)" - description "Feng Kuang Da Fu Weng (Taiwan) (Unl)" - rom ( name "Feng Kuang Da Fu Weng (Taiwan) (Unl).gbc" size 2097152 crc fbf62c35 sha1 7128a6ddbb44ba5ba226516996eb9a6077d365a9 ) -) - -game ( - name "Feng Kuang Da Fu Weng (Taiwan) (Unl) (Alt)" - description "Feng Kuang Da Fu Weng (Taiwan) (Unl) (Alt)" - rom ( name "Feng Kuang Da Fu Weng (Taiwan) (Unl) (Alt).gbc" size 2097152 crc 45b79edc sha1 cc86e1247a352a7c7134db927cc41a1e9950fb96 ) + name "Feng Kuang A Gei III - Chaoji Zhadan Ren (Taiwan) (Unl)" + description "Feng Kuang A Gei III - Chaoji Zhadan Ren (Taiwan) (Unl)" + rom ( name "Feng Kuang A Gei III - Chaoji Zhadan Ren (Taiwan) (Unl).gbc" size 2097152 crc 8059e009 sha1 84011e3ae407613cfd7b6b1fe85f3812097afc66 flags verified ) ) game ( @@ -35862,6 +36771,18 @@ game ( rom ( name "Feng Zhi Gou II (Taiwan) (Unl).gbc" size 2097152 crc 842cb4fe sha1 4322980e5b21332a71fb7fcadbf57a79aad6346b ) ) +game ( + name "Fengkuang Dafuweng (Taiwan) (Unl)" + description "Fengkuang Dafuweng (Taiwan) (Unl)" + rom ( name "Fengkuang Dafuweng (Taiwan) (Unl).gbc" size 2097152 crc fbf62c35 sha1 7128a6ddbb44ba5ba226516996eb9a6077d365a9 ) +) + +game ( + name "Fengkuang Dafuweng (Taiwan) (Unl) (Alt)" + description "Fengkuang Dafuweng (Taiwan) (Unl) (Alt)" + rom ( name "Fengkuang Dafuweng (Taiwan) (Unl) (Alt).gbc" size 2097152 crc 45b79edc sha1 cc86e1247a352a7c7134db927cc41a1e9950fb96 ) +) + game ( name "Ferret Monogatari (Japan)" description "Ferret Monogatari (Japan)" @@ -35881,33 +36802,39 @@ game ( ) game ( - name "Fillo - Crystal Version (World) (Aftermarket) (Homebrew)" - description "Fillo - Crystal Version (World) (Aftermarket) (Homebrew)" - rom ( name "Fillo - Crystal Version (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 88bb855c sha1 2ffc4199d94f8b29b3f84b7bdb2694154a0bd285 ) + name "Fillo - Crystal Version (World) (Aftermarket) (Unl)" + description "Fillo - Crystal Version (World) (Aftermarket) (Unl)" + rom ( name "Fillo - Crystal Version (World) (Aftermarket) (Unl).gbc" size 524288 crc 88bb855c sha1 2ffc4199d94f8b29b3f84b7bdb2694154a0bd285 ) ) game ( - name "Find Out (World) (Aftermarket) (Homebrew)" - description "Find Out (World) (Aftermarket) (Homebrew)" - rom ( name "Find Out (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 5f8b660e sha1 3605a96f6713157a88e4d88989d2e2f11b9db42f ) + name "Find Out (World) (GB Compatible) (Aftermarket) (Unl)" + description "Find Out (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Find Out (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 5f8b660e sha1 3605a96f6713157a88e4d88989d2e2f11b9db42f ) ) game ( - name "Finders Keepers (World) (Aftermarket) (Homebrew)" - description "Finders Keepers (World) (Aftermarket) (Homebrew)" - rom ( name "Finders Keepers (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 4cfa0cfd sha1 db002f4a2500a9dc98a9ac7c263293d6e4a72cf1 ) + name "Finders Keepers (World) (Aftermarket) (Unl)" + description "Finders Keepers (World) (Aftermarket) (Unl)" + rom ( name "Finders Keepers (World) (Aftermarket) (Unl).gbc" size 524288 crc 4cfa0cfd sha1 db002f4a2500a9dc98a9ac7c263293d6e4a72cf1 ) ) game ( - name "Fire Ant (World) (Aftermarket) (Homebrew)" - description "Fire Ant (World) (Aftermarket) (Homebrew)" - rom ( name "Fire Ant (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 154cc020 sha1 b58ed66838db34fc7f8475ccb670267119d623a5 ) + name "Fire Ant (World) (Aftermarket) (Unl)" + description "Fire Ant (World) (Aftermarket) (Unl)" + rom ( name "Fire Ant (World) (Aftermarket) (Unl).gbc" size 262144 crc 154cc020 sha1 b58ed66838db34fc7f8475ccb670267119d623a5 ) ) game ( - name "Firemen (World) (Aftermarket) (Homebrew)" - description "Firemen (World) (Aftermarket) (Homebrew)" - rom ( name "Firemen (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 7299401f sha1 84c68614334578f866cfa929df736377f04f5d04 ) + name "Fireman Fred (World) (Aftermarket) (Unl)" + description "Fireman Fred (World) (Aftermarket) (Unl)" + rom ( name "Fireman Fred (World) (Aftermarket) (Unl).gbc" size 524288 crc 563d9ca7 sha1 e62a9c6b6a39f0cb662fdb07b72669355b706f57 ) +) + +game ( + name "Firemen (World) (Aftermarket) (Unl)" + description "Firemen (World) (Aftermarket) (Unl)" + rom ( name "Firemen (World) (Aftermarket) (Unl).gbc" size 262144 crc 7299401f sha1 84c68614334578f866cfa929df736377f04f5d04 ) ) game ( @@ -35923,9 +36850,9 @@ game ( ) game ( - name "Fix It Felix Jr. (World) (Aftermarket) (Homebrew)" - description "Fix It Felix Jr. (World) (Aftermarket) (Homebrew)" - rom ( name "Fix It Felix Jr. (World) (Aftermarket) (Homebrew).gbc" size 131072 crc c8014615 sha1 7221f5e53f6027183301d06e258c6cb417007b8c ) + name "Fix It Felix Jr. (World) (GB Compatible) (Aftermarket) (Unl)" + description "Fix It Felix Jr. (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Fix It Felix Jr. (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 131072 crc c8014615 sha1 7221f5e53f6027183301d06e258c6cb417007b8c ) ) game ( @@ -36091,9 +37018,9 @@ game ( ) game ( - name "G-Man (World) (Aftermarket) (Homebrew)" - description "G-Man (World) (Aftermarket) (Homebrew)" - rom ( name "G-Man (World) (Aftermarket) (Homebrew).gbc" size 524288 crc ff3beab1 sha1 cb9486ca9a6ad3903662963aa966145257ef92a6 ) + name "G-Man (World) (Aftermarket) (Unl)" + description "G-Man (World) (Aftermarket) (Unl)" + rom ( name "G-Man (World) (Aftermarket) (Unl).gbc" size 524288 crc ff3beab1 sha1 cb9486ca9a6ad3903662963aa966145257ef92a6 ) ) game ( @@ -36109,9 +37036,9 @@ game ( ) game ( - name "Gakkyuu Ou Yamazaki (Japan) (GB Compatible)" - description "Gakkyuu Ou Yamazaki (Japan) (GB Compatible)" - rom ( name "Gakkyuu Ou Yamazaki (Japan) (GB Compatible).gbc" size 1048576 crc b8147b5c sha1 132b872391565d418ccc9d0e1c643510d778c066 ) + name "Gakkyuu Ou Yamazaki (Japan) (Possible Proto) (GB Compatible)" + description "Gakkyuu Ou Yamazaki (Japan) (Possible Proto) (GB Compatible)" + rom ( name "Gakkyuu Ou Yamazaki (Japan) (Possible Proto) (GB Compatible).gbc" size 1048576 crc b8147b5c sha1 132b872391565d418ccc9d0e1c643510d778c066 ) ) game ( @@ -36151,9 +37078,9 @@ game ( ) game ( - name "Game Boy Gallery 2 (Japan) (SGB Enhanced, GB Compatible) (NP)" - description "Game Boy Gallery 2 (Japan) (SGB Enhanced, GB Compatible) (NP)" - rom ( name "Game Boy Gallery 2 (Japan) (SGB Enhanced, GB Compatible) (NP).gbc" size 1048576 crc e99beba5 sha1 90a9b368bca0f47fe72028ff4d50fb3d3060c32f ) + name "Game Boy Gallery 2 (Japan) (Possible Proto) (SGB Enhanced, GB Compatible) (NP)" + description "Game Boy Gallery 2 (Japan) (Possible Proto) (SGB Enhanced, GB Compatible) (NP)" + rom ( name "Game Boy Gallery 2 (Japan) (Possible Proto) (SGB Enhanced, GB Compatible) (NP).gbc" size 1048576 crc e99beba5 sha1 90a9b368bca0f47fe72028ff4d50fb3d3060c32f ) ) game ( @@ -36199,15 +37126,15 @@ game ( ) game ( - name "Gamer Boy Mission (World) (Aftermarket) (Homebrew)" - description "Gamer Boy Mission (World) (Aftermarket) (Homebrew)" - rom ( name "Gamer Boy Mission (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 96721e5d sha1 90fad94433a5b0dbcf73f4a2d4ebb75d7ab065c4 ) + name "Gamer Boy Mission (World) (GB Compatible) (Aftermarket) (Unl)" + description "Gamer Boy Mission (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Gamer Boy Mission (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 524288 crc 96721e5d sha1 90fad94433a5b0dbcf73f4a2d4ebb75d7ab065c4 ) ) game ( - name "Gamer Boy Mission (World) (Demo) (Aftermarket) (Homebrew)" - description "Gamer Boy Mission (World) (Demo) (Aftermarket) (Homebrew)" - rom ( name "Gamer Boy Mission (World) (Demo) (Aftermarket) (Homebrew).gbc" size 262144 crc a02d2ae2 sha1 7eb6865c5c265193763ec0bf751d26964e870120 ) + name "Gamer Boy Mission (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Gamer Boy Mission (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Gamer Boy Mission (World) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc a02d2ae2 sha1 7eb6865c5c265193763ec0bf751d26964e870120 ) ) game ( @@ -36282,10 +37209,16 @@ game ( rom ( name "GB Memory Multi Menu (Japan) (SGB Enhanced, GB Compatible) (NP).gbc" size 131072 crc ec823cc1 sha1 0781eaecb7fd25c068e396b5eb02c6231baf6ea3 ) ) +game ( + name "Gedou Jian Shen - Soul Falchion (Taiwan) (Unl) (Alt)" + description "Gedou Jian Shen - Soul Falchion (Taiwan) (Unl) (Alt)" + rom ( name "Gedou Jian Shen - Soul Falchion (Taiwan) (Unl) (Alt).gbc" size 1048576 crc c0ac1b50 sha1 4dcef6fcfe009e17da0bb13e50e7410a3f586e23 ) +) + game ( name "Gedou Jian Shen - Soul Falchion (Taiwan) (Unl)" description "Gedou Jian Shen - Soul Falchion (Taiwan) (Unl)" - rom ( name "Gedou Jian Shen - Soul Falchion (Taiwan) (Unl).gbc" size 1048576 crc c0ac1b50 sha1 4dcef6fcfe009e17da0bb13e50e7410a3f586e23 ) + rom ( name "Gedou Jian Shen - Soul Falchion (Taiwan) (Unl).gbc" size 1048576 crc 1b1c6f68 sha1 fd06c62b42878e8094e610b180c1848c785f67e9 ) ) game ( @@ -36343,9 +37276,9 @@ game ( ) game ( - name "Ghost Town (World) (Aftermarket) (Homebrew)" - description "Ghost Town (World) (Aftermarket) (Homebrew)" - rom ( name "Ghost Town (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 52efcc35 sha1 1a4a7846d6c87f0bd8b267aa6a8aa98e797fedf8 ) + name "Ghost Town (World) (Aftermarket) (Unl)" + description "Ghost Town (World) (Aftermarket) (Unl)" + rom ( name "Ghost Town (World) (Aftermarket) (Unl).gbc" size 262144 crc 52efcc35 sha1 1a4a7846d6c87f0bd8b267aa6a8aa98e797fedf8 ) ) game ( @@ -36589,27 +37522,21 @@ game ( ) game ( - name "Gu Huo Lang II 2003 (Taiwan) (Unl)" - description "Gu Huo Lang II 2003 (Taiwan) (Unl)" - rom ( name "Gu Huo Lang II 2003 (Taiwan) (Unl).gbc" size 2097152 crc 74d71b0c sha1 fcbb769de0896ae69fe1063fa5d87cf079c8606a ) + name "Guaishou Go! Go! II (Taiwan) (Unl)" + description "Guaishou Go! Go! II (Taiwan) (Unl)" + rom ( name "Guaishou Go! Go! II (Taiwan) (Unl).gbc" size 1048576 crc b0237467 sha1 8ed6f39a9973d634266806455ef845b6bdb5ab95 ) ) game ( - name "Guai Shou Go! Go! II (Taiwan)" - description "Guai Shou Go! Go! II (Taiwan)" - rom ( name "Guai Shou Go! Go! II (Taiwan).gbc" size 1048576 crc b0237467 sha1 8ed6f39a9973d634266806455ef845b6bdb5ab95 ) + name "Guiwu Zhe 2 (Taiwan) (Unl)" + description "Guiwu Zhe 2 (Taiwan) (Unl)" + rom ( name "Guiwu Zhe 2 (Taiwan) (Unl).gbc" size 2097152 crc 955bc6ad sha1 5fe41ac064f4b4d4476ef4177a2f4d69fae1a933 ) ) game ( - name "Gui Wu Zhe 2 (Taiwan) (Unl)" - description "Gui Wu Zhe 2 (Taiwan) (Unl)" - rom ( name "Gui Wu Zhe 2 (Taiwan) (Unl).gbc" size 2097152 crc 955bc6ad sha1 5fe41ac064f4b4d4476ef4177a2f4d69fae1a933 ) -) - -game ( - name "Gun Law (World) (Aftermarket) (Homebrew)" - description "Gun Law (World) (Aftermarket) (Homebrew)" - rom ( name "Gun Law (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 3a0b6823 sha1 c49f7e0a5055f836528a7bc0cb6f45f221c76475 ) + name "Gun Law (World) (Aftermarket) (Unl)" + description "Gun Law (World) (Aftermarket) (Unl)" + rom ( name "Gun Law (World) (Aftermarket) (Unl).gbc" size 262144 crc 3a0b6823 sha1 c49f7e0a5055f836528a7bc0cb6f45f221c76475 ) ) game ( @@ -36637,27 +37564,9 @@ game ( ) game ( - name "Ha Li Bo Te 2 - Xiao Shi De Mi Shi 2003 (Taiwan) (Unl)" - description "Ha Li Bo Te 2 - Xiao Shi De Mi Shi 2003 (Taiwan) (Unl)" - rom ( name "Ha Li Bo Te 2 - Xiao Shi De Mi Shi 2003 (Taiwan) (Unl).gbc" size 2097152 crc 1b3e1243 sha1 0829cbcab3ef5d03c94d5efe769f4f4a93ac47fa ) -) - -game ( - name "Ha Li Xiao Zi Di Er Bu - Mi Shi De Mi (Taiwan) (Unl)" - description "Ha Li Xiao Zi Di Er Bu - Mi Shi De Mi (Taiwan) (Unl)" - rom ( name "Ha Li Xiao Zi Di Er Bu - Mi Shi De Mi (Taiwan) (Unl).gbc" size 2097152 crc 859457c8 sha1 3844b61eb455507d634ee7048dc8c9e64216fffe ) -) - -game ( - name "Ha Li Xiao Zi IV 2003 (Taiwan) (Unl)" - description "Ha Li Xiao Zi IV 2003 (Taiwan) (Unl)" - rom ( name "Ha Li Xiao Zi IV 2003 (Taiwan) (Unl).gbc" size 2097152 crc db3c8b95 sha1 0d230b20388f4396d6345b8eb59f54fc169ad8ee ) -) - -game ( - name "Hai Zhan Qi Bing (Taiwan) (Unl)" - description "Hai Zhan Qi Bing (Taiwan) (Unl)" - rom ( name "Hai Zhan Qi Bing (Taiwan) (Unl).gbc" size 2097152 crc a962ad73 sha1 36f6454e3e70bb29f27782f2ec4200d5aef5ca98 ) + name "Haizhan Qibing (Taiwan) (Unl)" + description "Haizhan Qibing (Taiwan) (Unl)" + rom ( name "Haizhan Qibing (Taiwan) (Unl).gbc" size 2097152 crc a962ad73 sha1 36f6454e3e70bb29f27782f2ec4200d5aef5ca98 ) ) game ( @@ -36666,6 +37575,12 @@ game ( rom ( name "Hajimari no Mori (Japan) (Proto).gbc" size 2097152 crc fecc8ee8 sha1 75a3e1794fd6bd6d35f0a357a82612c016dfba24 ) ) +game ( + name "Hali Xiaozi Dier Bu - Mishi de Mi (Taiwan) (Unl)" + description "Hali Xiaozi Dier Bu - Mishi de Mi (Taiwan) (Unl)" + rom ( name "Hali Xiaozi Dier Bu - Mishi de Mi (Taiwan) (Unl).gbc" size 2097152 crc 859457c8 sha1 3844b61eb455507d634ee7048dc8c9e64216fffe ) +) + game ( name "Halloween Racer (Europe) (En,Fr,De,Es,It,Pt)" description "Halloween Racer (Europe) (En,Fr,De,Es,It,Pt)" @@ -36774,6 +37689,12 @@ game ( rom ( name "Hang Time Basketball (Europe) (Unl).gbc" size 262144 crc 3207b7d9 sha1 340201c045c020be6de3504fdecbce1ff07a2139 ) ) +game ( + name "Harbour Attack (World) (Aftermarket) (Unl)" + description "Harbour Attack (World) (Aftermarket) (Unl)" + rom ( name "Harbour Attack (World) (Aftermarket) (Unl).gbc" size 262144 crc 7455782e sha1 1d0b3779007ba8f34e11b189552955dbf5c75a28 ) +) + game ( name "Harley-Davidson Motor Cycles - Race Across America (USA)" description "Harley-Davidson Motor Cycles - Race Across America (USA)" @@ -36804,12 +37725,6 @@ game ( rom ( name "Harry Potter 3 - Shen Qi Zhi Guang Lun (Taiwan) (Unl).gbc" size 524288 crc aa83a110 sha1 7e572f24a302902cd78b238be5732d838bada7b2 ) ) -game ( - name "Harry Potter 3 2003 (Taiwan) (Unl)" - description "Harry Potter 3 2003 (Taiwan) (Unl)" - rom ( name "Harry Potter 3 2003 (Taiwan) (Unl).gbc" size 1048576 crc 4ea2c869 sha1 739c0e5dc0efd4ddb912236a6f630c3d6987d064 ) -) - game ( name "Harry Potter and the Chamber of Secrets (USA, Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,Da)" description "Harry Potter and the Chamber of Secrets (USA, Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,Da)" @@ -36817,9 +37732,9 @@ game ( ) game ( - name "Harry Potter and the Sorcerer's Stone (Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,No,Da,Fi)" - description "Harry Potter and the Sorcerer's Stone (Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,No,Da,Fi)" - rom ( name "Harry Potter and the Sorcerer's Stone (Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,No,Da,Fi).gbc" size 4194304 crc 4fd8b7c5 sha1 4e6f676ec15e0e6238cb81853b5a74bbb20657a1 ) + name "Harry Potter and the Sorcerer's Stone (USA, Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,No,Da,Fi)" + description "Harry Potter and the Sorcerer's Stone (USA, Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,No,Da,Fi)" + rom ( name "Harry Potter and the Sorcerer's Stone (USA, Europe) (En,Fr,De,Es,It,Nl,Pt,Sv,No,Da,Fi).gbc" size 4194304 crc 4fd8b7c5 sha1 4e6f676ec15e0e6238cb81853b5a74bbb20657a1 flags verified ) ) game ( @@ -36877,9 +37792,9 @@ game ( ) game ( - name "He Jin Zhuang Bei II (Taiwan) (Unl)" - description "He Jin Zhuang Bei II (Taiwan) (Unl)" - rom ( name "He Jin Zhuang Bei II (Taiwan) (Unl).gbc" size 2097152 crc ef4bbb34 sha1 6a15fc7dbb3b9d7c130b9a25b568d39c655d68ca ) + name "Hejin Zhuangbei II (Taiwan) (Unl)" + description "Hejin Zhuangbei II (Taiwan) (Unl)" + rom ( name "Hejin Zhuangbei II (Taiwan) (Unl).gbc" size 2097152 crc ef4bbb34 sha1 6a15fc7dbb3b9d7c130b9a25b568d39c655d68ca ) ) game ( @@ -36973,9 +37888,15 @@ game ( ) game ( - name "High Noon (World) (Aftermarket) (Homebrew)" - description "High Noon (World) (Aftermarket) (Homebrew)" - rom ( name "High Noon (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 1d00e48d sha1 21d5e871ed009a674659344d3d768fb2389c0d04 ) + name "High Noon (World) (Aftermarket) (Unl)" + description "High Noon (World) (Aftermarket) (Unl)" + rom ( name "High Noon (World) (Aftermarket) (Unl).gbc" size 262144 crc 1d00e48d sha1 21d5e871ed009a674659344d3d768fb2389c0d04 ) +) + +game ( + name "Hime's Quest (World) (Aftermarket) (Unl)" + description "Hime's Quest (World) (Aftermarket) (Unl)" + rom ( name "Hime's Quest (World) (Aftermarket) (Unl).gbc" size 2097152 crc de5c21c2 sha1 7b6672c46c23aaa282bd4fded0713c6dfae2eb66 ) ) game ( @@ -37057,9 +37978,9 @@ game ( ) game ( - name "Hugo (World) (Aftermarket) (Homebrew)" - description "Hugo (World) (Aftermarket) (Homebrew)" - rom ( name "Hugo (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 6581c78a sha1 390d2665f34ceaa5e34cc9f8f7c26d9e7583a92b ) + name "Hugo (World) (Aftermarket) (Unl)" + description "Hugo (World) (Aftermarket) (Unl)" + rom ( name "Hugo (World) (Aftermarket) (Unl).gbc" size 524288 crc 6581c78a sha1 390d2665f34ceaa5e34cc9f8f7c26d9e7583a92b ) ) game ( @@ -37279,9 +38200,9 @@ game ( ) game ( - name "Jeremy McGrath Supercross 2000 (Japan) (En) (NP)" - description "Jeremy McGrath Supercross 2000 (Japan) (En) (NP)" - rom ( name "Jeremy McGrath Supercross 2000 (Japan) (En) (NP).gbc" size 1048576 crc 666d2a75 sha1 dd204c1d47290f52ef264ce32d59cb5f58a2701d ) + name "Jeremy McGrath Supercross 2000 (Japan) (En) (Possible Proto) (NP)" + description "Jeremy McGrath Supercross 2000 (Japan) (En) (Possible Proto) (NP)" + rom ( name "Jeremy McGrath Supercross 2000 (Japan) (En) (Possible Proto) (NP).gbc" size 1048576 crc 666d2a75 sha1 dd204c1d47290f52ef264ce32d59cb5f58a2701d ) ) game ( @@ -37345,9 +38266,9 @@ game ( ) game ( - name "Jing Ling Wang III (Taiwan) (Unl)" - description "Jing Ling Wang III (Taiwan) (Unl)" - rom ( name "Jing Ling Wang III (Taiwan) (Unl).gbc" size 2097152 crc f19e780c sha1 8ef6ceb1b7894f43caa5f422d62d6c437f81e56d ) + name "Jingling Wang III (Taiwan) (Unl)" + description "Jingling Wang III (Taiwan) (Unl)" + rom ( name "Jingling Wang III (Taiwan) (Unl).gbc" size 2097152 crc f19e780c sha1 8ef6ceb1b7894f43caa5f422d62d6c437f81e56d ) ) game ( @@ -37369,9 +38290,9 @@ game ( ) game ( - name "Jissen ni Yakudatsu Tsumego (Japan)" - description "Jissen ni Yakudatsu Tsumego (Japan)" - rom ( name "Jissen ni Yakudatsu Tsumego (Japan).gbc" size 262144 crc 69c6dbef sha1 f7ed6cdce7637a11d7fa7f5cb59b8f8ce131f9d1 ) + name "Jissen ni Yakudatsu Tsumego (Japan) (Possible Proto)" + description "Jissen ni Yakudatsu Tsumego (Japan) (Possible Proto)" + rom ( name "Jissen ni Yakudatsu Tsumego (Japan) (Possible Proto).gbc" size 262144 crc 69c6dbef sha1 f7ed6cdce7637a11d7fa7f5cb59b8f8ce131f9d1 ) ) game ( @@ -37392,6 +38313,12 @@ game ( rom ( name "Joryuu Janshi ni Chousen GB - Watashi-tachi ni Chousen Shitene! (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 9fa5cdb5 sha1 a332817b9561dcab7d1f3dc0cf300e1656b253c3 ) ) +game ( + name "Juedui Wuli (Taiwan) (Unl)" + description "Juedui Wuli (Taiwan) (Unl)" + rom ( name "Juedui Wuli (Taiwan) (Unl).gbc" size 2097152 crc f4d63a7e sha1 f0b9f02335ee89aa6189e687e6e759d8c49bc2b3 ) +) + game ( name "JumpStart Dino Adventure - Field Trip (USA)" description "JumpStart Dino Adventure - Field Trip (USA)" @@ -37527,7 +38454,13 @@ game ( game ( name "Kaseki Sousei Reborn II - Monster Digger (Japan) (SGB Enhanced) (GB Compatible)" description "Kaseki Sousei Reborn II - Monster Digger (Japan) (SGB Enhanced) (GB Compatible)" - rom ( name "Kaseki Sousei Reborn II - Monster Digger (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc bf80c897 sha1 857fb62dc310268e0b92e9383c6b6fd61aa33fa0 ) + rom ( name "Kaseki Sousei Reborn II - Monster Digger (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc bf80c897 sha1 857fb62dc310268e0b92e9383c6b6fd61aa33fa0 flags verified ) +) + +game ( + name "Katakis 3D (USA, Europe) (Proto)" + description "Katakis 3D (USA, Europe) (Proto)" + rom ( name "Katakis 3D (USA, Europe) (Proto).gbc" size 2097152 crc 7c4b3795 sha1 8cd4be6772c592bbc2908fa826c090ec122e384c ) ) game ( @@ -37543,9 +38476,9 @@ game ( ) game ( - name "Kawa no Nushi Tsuri 4 (Japan) (Rev 1) (Rumble Version) (SGB Enhanced, GB Compatible) (NP)" - description "Kawa no Nushi Tsuri 4 (Japan) (Rev 1) (Rumble Version) (SGB Enhanced, GB Compatible) (NP)" - rom ( name "Kawa no Nushi Tsuri 4 (Japan) (Rev 1) (Rumble Version) (SGB Enhanced, GB Compatible) (NP).gbc" size 1048576 crc 456d4dfc sha1 69e56fd73eb365dba87713c2a3ea2e74bd63f63c ) + name "Kawa no Nushi Tsuri 4 (Japan) (Rev 1) (Possible Proto) (Rumble Version) (SGB Enhanced, GB Compatible) (NP)" + description "Kawa no Nushi Tsuri 4 (Japan) (Rev 1) (Possible Proto) (Rumble Version) (SGB Enhanced, GB Compatible) (NP)" + rom ( name "Kawa no Nushi Tsuri 4 (Japan) (Rev 1) (Possible Proto) (Rumble Version) (SGB Enhanced, GB Compatible) (NP).gbc" size 1048576 crc 456d4dfc sha1 69e56fd73eb365dba87713c2a3ea2e74bd63f63c ) ) game ( @@ -37621,9 +38554,9 @@ game ( ) game ( - name "Kikstart (World) (Aftermarket) (Homebrew)" - description "Kikstart (World) (Aftermarket) (Homebrew)" - rom ( name "Kikstart (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 9a9f483b sha1 bc472506f73f0c1a3f0a610c0136b324b7635c7f ) + name "Kikstart (World) (Aftermarket) (Unl)" + description "Kikstart (World) (Aftermarket) (Unl)" + rom ( name "Kikstart (World) (Aftermarket) (Unl).gbc" size 524288 crc 9a9f483b sha1 bc472506f73f0c1a3f0a610c0136b324b7635c7f ) ) game ( @@ -37632,12 +38565,6 @@ game ( rom ( name "Kindaichi Shounen no Jikenbo - 10 Nenme no Shoutaijou (Japan) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc de52ffdc sha1 468ea148eb15eac3b771a2520e0b5248ea5085a1 ) ) -game ( - name "King Lion Advance III 2003 (USA) (Unl)" - description "King Lion Advance III 2003 (USA) (Unl)" - rom ( name "King Lion Advance III 2003 (USA) (Unl).gbc" size 2097152 crc 0c22466d sha1 7c43ccfca93cae23e04e58fa0d7d7400a9694e0e ) -) - game ( name "Kinniku Banzuke GB - Chousensha wa Kimida! (Japan) (SGB Enhanced) (GB Compatible)" description "Kinniku Banzuke GB - Chousensha wa Kimida! (Japan) (SGB Enhanced) (GB Compatible)" @@ -37723,9 +38650,9 @@ game ( ) game ( - name "Knit-Wit (World) (v1.1) (Aftermarket) (Homebrew)" - description "Knit-Wit (World) (v1.1) (Aftermarket) (Homebrew)" - rom ( name "Knit-Wit (World) (v1.1) (Aftermarket) (Homebrew).gbc" size 262144 crc f19d5e86 sha1 0f0e01a143182a9784c80a882ac206d02d8679d7 ) + name "Knit-Wit (World) (v1.1) (GB Compatible) (Aftermarket) (Unl)" + description "Knit-Wit (World) (v1.1) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Knit-Wit (World) (v1.1) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc f19d5e86 sha1 0f0e01a143182a9784c80a882ac206d02d8679d7 ) ) game ( @@ -37813,27 +38740,21 @@ game ( ) game ( - name "Kou Dai Guai Shou - Dong Zuo Pian (Taiwan) (En) (Unl)" - description "Kou Dai Guai Shou - Dong Zuo Pian (Taiwan) (En) (Unl)" - rom ( name "Kou Dai Guai Shou - Dong Zuo Pian (Taiwan) (En) (Unl).gbc" size 1048576 crc 14355371 sha1 f398086d1f8cb435a632e5ea45fb19e980b0568d ) + name "Koudai Guaishou - Dongzuo Pian (Taiwan) (En) (Unl)" + description "Koudai Guaishou - Dongzuo Pian (Taiwan) (En) (Unl)" + rom ( name "Koudai Guaishou - Dongzuo Pian (Taiwan) (En) (Unl).gbc" size 1048576 crc 14355371 sha1 f398086d1f8cb435a632e5ea45fb19e980b0568d ) ) game ( - name "Kou Dai Guai Shou - Dong Zuo Pian (Taiwan) (En) (Unl) (Alt)" - description "Kou Dai Guai Shou - Dong Zuo Pian (Taiwan) (En) (Unl) (Alt)" - rom ( name "Kou Dai Guai Shou - Dong Zuo Pian (Taiwan) (En) (Unl) (Alt).gbc" size 1048576 crc ec2fbdfd sha1 c7a1020797b57d292e16c5297199d977e025256b ) + name "Koudai Guaishou - Dongzuo Pian (Taiwan) (En) (Unl) (Alt)" + description "Koudai Guaishou - Dongzuo Pian (Taiwan) (En) (Unl) (Alt)" + rom ( name "Koudai Guaishou - Dongzuo Pian (Taiwan) (En) (Unl) (Alt).gbc" size 1048576 crc ec2fbdfd sha1 c7a1020797b57d292e16c5297199d977e025256b ) ) game ( - name "Kou Dai Guai Shou - Lan Bao Shi 2003 (Taiwan) (Unl)" - description "Kou Dai Guai Shou - Lan Bao Shi 2003 (Taiwan) (Unl)" - rom ( name "Kou Dai Guai Shou - Lan Bao Shi 2003 (Taiwan) (Unl).gbc" size 2097152 crc 4c76d4d8 sha1 d688201e6031b88f122ec753a37daedfb233b48d ) -) - -game ( - name "Kou Dai Yao Guai - Bai Jin Ban (Taiwan) (Unl)" - description "Kou Dai Yao Guai - Bai Jin Ban (Taiwan) (Unl)" - rom ( name "Kou Dai Yao Guai - Bai Jin Ban (Taiwan) (Unl).gbc" size 2097152 crc 998ad382 sha1 f9013985d7c40eccfa863e2ba808e64deaa4b384 ) + name "Koudai Yaoguai - Baijin Ban (Taiwan) (Unl)" + description "Koudai Yaoguai - Baijin Ban (Taiwan) (Unl)" + rom ( name "Koudai Yaoguai - Baijin Ban (Taiwan) (Unl).gbc" size 2097152 crc 998ad382 sha1 f9013985d7c40eccfa863e2ba808e64deaa4b384 ) ) game ( @@ -37861,9 +38782,9 @@ game ( ) game ( - name "Lao Fuzi Chuanqi (Taiwan) (Unl)" - description "Lao Fuzi Chuanqi (Taiwan) (Unl)" - rom ( name "Lao Fuzi Chuanqi (Taiwan) (Unl).gbc" size 2097152 crc aeca45be sha1 b7193fcb8b9b8958a2522be0d1a3874cffc1e1db ) + name "Laofuzi Chuanqi (Taiwan) (Unl)" + description "Laofuzi Chuanqi (Taiwan) (Unl)" + rom ( name "Laofuzi Chuanqi (Taiwan) (Unl).gbc" size 2097152 crc aeca45be sha1 b7193fcb8b9b8958a2522be0d1a3874cffc1e1db ) ) game ( @@ -37873,9 +38794,9 @@ game ( ) game ( - name "Laser Squad (World) (Aftermarket) (Homebrew)" - description "Laser Squad (World) (Aftermarket) (Homebrew)" - rom ( name "Laser Squad (World) (Aftermarket) (Homebrew).gbc" size 1048576 crc 32f24248 sha1 26f4efe636214e72ec724f8887ca79fbc8abad80 ) + name "Laser Squad (World) (Aftermarket) (Unl)" + description "Laser Squad (World) (Aftermarket) (Unl)" + rom ( name "Laser Squad (World) (Aftermarket) (Unl).gbc" size 1048576 crc 32f24248 sha1 26f4efe636214e72ec724f8887ca79fbc8abad80 ) ) game ( @@ -38131,9 +39052,9 @@ game ( ) game ( - name "Liberator (World) (Aftermarket) (Homebrew)" - description "Liberator (World) (Aftermarket) (Homebrew)" - rom ( name "Liberator (World) (Aftermarket) (Homebrew).gbc" size 262144 crc ebe70d3d sha1 e3afce5a2357732ed15d7f3c0900f7b48113efdc ) + name "Liberator (World) (Aftermarket) (Unl)" + description "Liberator (World) (Aftermarket) (Unl)" + rom ( name "Liberator (World) (Aftermarket) (Unl).gbc" size 262144 crc ebe70d3d sha1 e3afce5a2357732ed15d7f3c0900f7b48113efdc ) ) game ( @@ -38163,7 +39084,7 @@ game ( game ( name "Little Mermaid II, The - Pinball Frenzy (USA) (En,Fr,De,Es,It) (Rumble Version)" description "Little Mermaid II, The - Pinball Frenzy (USA) (En,Fr,De,Es,It) (Rumble Version)" - rom ( name "Little Mermaid II, The - Pinball Frenzy (USA) (En,Fr,De,Es,It) (Rumble Version).gbc" size 1048576 crc 364f9ccd sha1 0940a1a86127cf8228a6a015035d8189218f57db ) + rom ( name "Little Mermaid II, The - Pinball Frenzy (USA) (En,Fr,De,Es,It) (Rumble Version).gbc" size 1048576 crc 364f9ccd sha1 0940a1a86127cf8228a6a015035d8189218f57db flags verified ) ) game ( @@ -38184,6 +39105,12 @@ game ( rom ( name "LNF Stars 2001 (France).gbc" size 1048576 crc f8bf3ee7 sha1 07a0e1c0ddde6371dbaf25fd016bdc77c0eca090 ) ) +game ( + name "Loco-coco (World) (Aftermarket) (Unl)" + description "Loco-coco (World) (Aftermarket) (Unl)" + rom ( name "Loco-coco (World) (Aftermarket) (Unl).gbc" size 262144 crc f6b9fe4e sha1 8a8972ff3e208280a746a2015a20f67248180a1c ) +) + game ( name "Lode Runner - Domudomu Dan no Yabou (Japan) (GB Compatible)" description "Lode Runner - Domudomu Dan no Yabou (Japan) (GB Compatible)" @@ -38323,9 +39250,9 @@ game ( ) game ( - name "Loppi Puzzle Magazine - Kangaeru Puzzle Dai-2-gou (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP)" - description "Loppi Puzzle Magazine - Kangaeru Puzzle Dai-2-gou (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP)" - rom ( name "Loppi Puzzle Magazine - Kangaeru Puzzle Dai-2-gou (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP).gbc" size 262144 crc 27ab2187 sha1 49a63339bc253d1bd55f6db75f7324980572925a ) + name "Loppi Puzzle Magazine - Kangaeru Puzzle Dai-2-gou (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP) [b]" + description "Loppi Puzzle Magazine - Kangaeru Puzzle Dai-2-gou (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP) [b]" + rom ( name "Loppi Puzzle Magazine - Kangaeru Puzzle Dai-2-gou (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP) [b].gbc" size 262144 crc a30ad68d sha1 bbb3c1b9d0dcfabc177f443ea11efa6b6e3112e1 flags baddump ) ) game ( @@ -38431,15 +39358,15 @@ game ( ) game ( - name "Lunar Docking (World) (Aftermarket) (Homebrew)" - description "Lunar Docking (World) (Aftermarket) (Homebrew)" - rom ( name "Lunar Docking (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 9edd066c sha1 4d43a38e8b5df41309f5b6ec13e4829db739d980 ) + name "Lunar Docking (World) (Aftermarket) (Unl)" + description "Lunar Docking (World) (Aftermarket) (Unl)" + rom ( name "Lunar Docking (World) (Aftermarket) (Unl).gbc" size 262144 crc 9edd066c sha1 4d43a38e8b5df41309f5b6ec13e4829db739d980 ) ) game ( - name "Luo Ke Ying Xiong EXE5 (Taiwan) (Unl)" - description "Luo Ke Ying Xiong EXE5 (Taiwan) (Unl)" - rom ( name "Luo Ke Ying Xiong EXE5 (Taiwan) (Unl).gbc" size 2097152 crc 51f92403 sha1 c498a5fb261e921828831bff5c2c705453ea314e ) + name "Luoke Yingxiong EXE5 (Taiwan) (Unl)" + description "Luoke Yingxiong EXE5 (Taiwan) (Unl)" + rom ( name "Luoke Yingxiong EXE5 (Taiwan) (Unl).gbc" size 2097152 crc 51f92403 sha1 c498a5fb261e921828831bff5c2c705453ea314e ) ) game ( @@ -38467,15 +39394,21 @@ game ( ) game ( - name "Machine, The (USA) (v1.1) (Demo) (Aftermarket) (Homebrew)" - description "Machine, The (USA) (v1.1) (Demo) (Aftermarket) (Homebrew)" - rom ( name "Machine, The (USA) (v1.1) (Demo) (Aftermarket) (Homebrew).gbc" size 2097152 crc f55a9d95 sha1 2b22fbd76e11e6e45053b1e6989de303e3033c9e ) + name "Machine, The (USA) (v1.1) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Machine, The (USA) (v1.1) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Machine, The (USA) (v1.1) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc f55a9d95 sha1 2b22fbd76e11e6e45053b1e6989de303e3033c9e ) ) game ( - name "Machine, The (USA) (Demo) (Aftermarket) (Homebrew)" - description "Machine, The (USA) (Demo) (Aftermarket) (Homebrew)" - rom ( name "Machine, The (USA) (Demo) (Aftermarket) (Homebrew).gbc" size 2097152 crc 5662865e sha1 a5ac99a4087bd5ea45417e5b4a7c9af10433911a ) + name "Machine, The (USA) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Machine, The (USA) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Machine, The (USA) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc 5662865e sha1 a5ac99a4087bd5ea45417e5b4a7c9af10433911a ) +) + +game ( + name "Machine, The (World) (GB Compatible) (Aftermarket) (Unl)" + description "Machine, The (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Machine, The (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc da01eb3e sha1 cd9a27077ba7ed49d2f2878577cb6ff005d6f785 ) ) game ( @@ -38490,6 +39423,12 @@ game ( rom ( name "Madden NFL 2000 (USA, Europe) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 482944aa sha1 df03f3e5e3f6fd3905dc332b307b5566f0f35252 flags verified ) ) +game ( + name "Madden NFL 2000 (USA, Europe) (Beta) (SGB Enhanced) (GB Compatible)" + description "Madden NFL 2000 (USA, Europe) (Beta) (SGB Enhanced) (GB Compatible)" + rom ( name "Madden NFL 2000 (USA, Europe) (Beta) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc b3fead85 sha1 09bf3dea1dc01b018ba86e3affbcbab7b9d2eb63 ) +) + game ( name "Madden NFL 2001 (USA)" description "Madden NFL 2001 (USA)" @@ -38532,6 +39471,12 @@ game ( rom ( name "Magical Drop (USA).gbc" size 1048576 crc e4188a79 sha1 2106b22776a87825d3edb408503e719f4927c3e6 ) ) +game ( + name "Magical Drop (Europe) (En,Fr,De) (Beta)" + description "Magical Drop (Europe) (En,Fr,De) (Beta)" + rom ( name "Magical Drop (Europe) (En,Fr,De) (Beta).gbc" size 1048576 crc 799e2020 sha1 99fbf24ca924acf5a4ea64817fb29d6e408cb722 ) +) + game ( name "Magical Tetris Challenge (Europe) (En,Fr,De,Es,It,Nl,Sv)" description "Magical Tetris Challenge (Europe) (En,Fr,De,Es,It,Nl,Sv)" @@ -38551,9 +39496,9 @@ game ( ) game ( - name "Magician's Curse, The (World) (Aftermarket) (Homebrew)" - description "Magician's Curse, The (World) (Aftermarket) (Homebrew)" - rom ( name "Magician's Curse, The (World) (Aftermarket) (Homebrew).gbc" size 524288 crc f2b1967e sha1 3b5f97f9a0b4d63a3795695b0bda6e969de8e051 ) + name "Magician's Curse, The (World) (Aftermarket) (Unl)" + description "Magician's Curse, The (World) (Aftermarket) (Unl)" + rom ( name "Magician's Curse, The (World) (Aftermarket) (Unl).gbc" size 524288 crc f2b1967e sha1 3b5f97f9a0b4d63a3795695b0bda6e969de8e051 ) ) game ( @@ -38736,6 +39681,12 @@ game ( rom ( name "Maya the Bee & Her Friends (Europe) (En,Fr,De) (GB Compatible).gbc" size 1048576 crc 983b1d26 sha1 522d7f33fd39bd00f48208743fb2246ef0bd3ff1 ) ) +game ( + name "Mayhem (World) (Aftermarket) (Unl)" + description "Mayhem (World) (Aftermarket) (Unl)" + rom ( name "Mayhem (World) (Aftermarket) (Unl).gbc" size 262144 crc 4d739ad7 sha1 9f141728a74432820f47cc24d230dc56d6ac099b ) +) + game ( name "McDonald's Monogatari - Honobono Tenchou Ikusei Game (Japan)" description "McDonald's Monogatari - Honobono Tenchou Ikusei Game (Japan)" @@ -38817,7 +39768,7 @@ game ( game ( name "Mega Man Xtreme (USA, Europe) (GB Compatible)" description "Mega Man Xtreme (USA, Europe) (GB Compatible)" - rom ( name "Mega Man Xtreme (USA, Europe) (GB Compatible).gbc" size 1048576 crc 3a4d94d5 sha1 c877449ba0889fdcacf23c49b0611d0ca57283c5 ) + rom ( name "Mega Man Xtreme (USA, Europe) (GB Compatible).gbc" size 1048576 crc 3a4d94d5 sha1 c877449ba0889fdcacf23c49b0611d0ca57283c5 flags verified ) ) game ( @@ -38869,9 +39820,9 @@ game ( ) game ( - name "Memory Mania Challenge (World) (v1.3) (Aftermarket) (Homebrew)" - description "Memory Mania Challenge (World) (v1.3) (Aftermarket) (Homebrew)" - rom ( name "Memory Mania Challenge (World) (v1.3) (Aftermarket) (Homebrew).gbc" size 1048576 crc 50618f4b sha1 c8d74af4746313800df3a23c1ad01f931e0e2eca flags verified ) + name "Memory Mania Challenge (World) (v1.3) (GB Compatible) (Aftermarket) (Unl)" + description "Memory Mania Challenge (World) (v1.3) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Memory Mania Challenge (World) (v1.3) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc 50618f4b sha1 c8d74af4746313800df3a23c1ad01f931e0e2eca flags verified ) ) game ( @@ -38940,6 +39891,12 @@ game ( rom ( name "Metamode (Japan).gbc" size 2097152 crc a76eed5b sha1 1c1d74c810b90cf4d2ee32859eaea569a5b45c3f ) ) +game ( + name "Meteorite (World) (Aftermarket) (Unl)" + description "Meteorite (World) (Aftermarket) (Unl)" + rom ( name "Meteorite (World) (Aftermarket) (Unl).gbc" size 131072 crc fafbcebf sha1 25a81d916f1ed80a2bca2fe7a555433f06d025e3 ) +) + game ( name "Mia Hamm Soccer Shootout (USA)" description "Mia Hamm Soccer Shootout (USA)" @@ -38964,6 +39921,12 @@ game ( rom ( name "Micro Machines 1 and 2 - Twin Turbo (USA, Europe).gbc" size 2097152 crc 5dd337eb sha1 18b5c14ee3b3f3c16b9d641c3b3824b15c0a0c78 ) ) +game ( + name "Micro Machines 2 - Turbo Tournament (Europe) (Proto)" + description "Micro Machines 2 - Turbo Tournament (Europe) (Proto)" + rom ( name "Micro Machines 2 - Turbo Tournament (Europe) (Proto).gbc" size 2097152 crc e932c818 sha1 cbdefc7f63bf9a6c0fc15d7ab72b4b532b0d2422 ) +) + game ( name "Micro Machines V3 (USA, Europe)" description "Micro Machines V3 (USA, Europe)" @@ -39061,9 +40024,9 @@ game ( ) game ( - name "Mirror Between Us, The (World) (Aftermarket) (Homebrew)" - description "Mirror Between Us, The (World) (Aftermarket) (Homebrew)" - rom ( name "Mirror Between Us, The (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 854f4297 sha1 a1a44ce8052f06349df3cbdf9a6d976609213d68 ) + name "Mirror Between Us, The (World) (GB Compatible) (Aftermarket) (Unl)" + description "Mirror Between Us, The (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Mirror Between Us, The (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 854f4297 sha1 a1a44ce8052f06349df3cbdf9a6d976609213d68 ) ) game ( @@ -39091,9 +40054,9 @@ game ( ) game ( - name "Mission - Impossible (Europe) (En,Fr,De,Es,It) (Rev 1)" - description "Mission - Impossible (Europe) (En,Fr,De,Es,It) (Rev 1)" - rom ( name "Mission - Impossible (Europe) (En,Fr,De,Es,It) (Rev 1).gbc" size 1048576 crc 9c51f4c7 sha1 f822bda01d881f34d1e17664465faf6a3ff64356 ) + name "Mission - Impossible (Europe) (En,Fr,De,Es,It) (Rev 1) (Possible Proto)" + description "Mission - Impossible (Europe) (En,Fr,De,Es,It) (Rev 1) (Possible Proto)" + rom ( name "Mission - Impossible (Europe) (En,Fr,De,Es,It) (Rev 1) (Possible Proto).gbc" size 1048576 crc 9c51f4c7 sha1 f822bda01d881f34d1e17664465faf6a3ff64356 ) ) game ( @@ -39103,9 +40066,9 @@ game ( ) game ( - name "Mission to Mars (World) (Aftermarket) (Homebrew)" - description "Mission to Mars (World) (Aftermarket) (Homebrew)" - rom ( name "Mission to Mars (World) (Aftermarket) (Homebrew).gbc" size 262144 crc f651fcf4 sha1 479c70dd9c63cdcad99ca44720a9e0e2582df119 ) + name "Mission to Mars (World) (Aftermarket) (Unl)" + description "Mission to Mars (World) (Aftermarket) (Unl)" + rom ( name "Mission to Mars (World) (Aftermarket) (Unl).gbc" size 262144 crc f651fcf4 sha1 479c70dd9c63cdcad99ca44720a9e0e2582df119 ) ) game ( @@ -39115,21 +40078,9 @@ game ( ) game ( - name "Mo Jie 3 - Bu Wanme De Shijie (Taiwan) (Unl)" - description "Mo Jie 3 - Bu Wanme De Shijie (Taiwan) (Unl)" - rom ( name "Mo Jie 3 - Bu Wanme De Shijie (Taiwan) (Unl).gbc" size 524288 crc d91e3f98 sha1 556913f2687a49034e1229ef1e375f6036ec5719 ) -) - -game ( - name "Mo Jie Chuan Shuo (Taiwan) (Unl)" - description "Mo Jie Chuan Shuo (Taiwan) (Unl)" - rom ( name "Mo Jie Chuan Shuo (Taiwan) (Unl).gbc" size 524288 crc 7aa7eea5 sha1 109e4ba61b04afe15cc5a5ee994f56405938cee3 ) -) - -game ( - name "Mo Shou Shi Ji - Zhan Shen (Taiwan) (Unl)" - description "Mo Shou Shi Ji - Zhan Shen (Taiwan) (Unl)" - rom ( name "Mo Shou Shi Ji - Zhan Shen (Taiwan) (Unl).gbc" size 2097152 crc 80c4fd52 sha1 d2569f8176164cef1cc2a8bd94949ea9cd7dbbea ) + name "Mo Jie 3-bu Wanme de Shijie (Taiwan) (Unl)" + description "Mo Jie 3-bu Wanme de Shijie (Taiwan) (Unl)" + rom ( name "Mo Jie 3-bu Wanme de Shijie (Taiwan) (Unl).gbc" size 524288 crc d91e3f98 sha1 556913f2687a49034e1229ef1e375f6036ec5719 ) ) game ( @@ -39144,6 +40095,12 @@ game ( rom ( name "Mobile Trainer (Japan).gbc" size 2097152 crc 7226ead0 sha1 ecc0579edeaf9eccd722d605cc288cd023c8576a flags verified ) ) +game ( + name "Mojie Chuanshuo (Taiwan) (Unl)" + description "Mojie Chuanshuo (Taiwan) (Unl)" + rom ( name "Mojie Chuanshuo (Taiwan) (Unl).gbc" size 524288 crc 7aa7eea5 sha1 109e4ba61b04afe15cc5a5ee994f56405938cee3 ) +) + game ( name "Momotarou Densetsu 1-2 (Japan)" description "Momotarou Densetsu 1-2 (Japan)" @@ -39151,9 +40108,15 @@ game ( ) game ( - name "Mona and the Witch's Hat Deluxe (World) (Aftermarket) (Homebrew)" - description "Mona and the Witch's Hat Deluxe (World) (Aftermarket) (Homebrew)" - rom ( name "Mona and the Witch's Hat Deluxe (World) (Aftermarket) (Homebrew).gbc" size 131072 crc 39480c79 sha1 4348a7903e538e5367f2a60718f43cbeeccf96df ) + name "Mona and the Witch's Hat Deluxe (World) (GB Compatible) (Aftermarket) (Unl)" + description "Mona and the Witch's Hat Deluxe (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Mona and the Witch's Hat Deluxe (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 131072 crc 39480c79 sha1 4348a7903e538e5367f2a60718f43cbeeccf96df ) +) + +game ( + name "Monkey Magic (World) (Aftermarket) (Unl)" + description "Monkey Magic (World) (Aftermarket) (Unl)" + rom ( name "Monkey Magic (World) (Aftermarket) (Unl).gbc" size 262144 crc c9c68715 sha1 bf5dc5593334fe1114461065a2b7284d28b31462 ) ) game ( @@ -39183,7 +40146,7 @@ game ( game ( name "Monster Farm Battle Card GB (Japan) (SGB Enhanced) (GB Compatible)" description "Monster Farm Battle Card GB (Japan) (SGB Enhanced) (GB Compatible)" - rom ( name "Monster Farm Battle Card GB (Japan) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc 69b88f1e sha1 7e036e175c9e865572ab613ae0f8e6d3642ffb0e ) + rom ( name "Monster Farm Battle Card GB (Japan) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc 69b88f1e sha1 7e036e175c9e865572ab613ae0f8e6d3642ffb0e flags verified ) ) game ( @@ -39217,9 +40180,9 @@ game ( ) game ( - name "Monster Traveler (Japan)" - description "Monster Traveler (Japan)" - rom ( name "Monster Traveler (Japan).gbc" size 4194304 crc 5d596cf6 sha1 5195f55ad6aaa302c0a0e11c0ec6ecad4efe0e27 ) + name "Monster Traveler (Japan) (Possible Proto)" + description "Monster Traveler (Japan) (Possible Proto)" + rom ( name "Monster Traveler (Japan) (Possible Proto).gbc" size 4194304 crc 5d596cf6 sha1 5195f55ad6aaa302c0a0e11c0ec6ecad4efe0e27 ) ) game ( @@ -39259,9 +40222,9 @@ game ( ) game ( - name "Monty on the Run (World) (Aftermarket) (Homebrew)" - description "Monty on the Run (World) (Aftermarket) (Homebrew)" - rom ( name "Monty on the Run (World) (Aftermarket) (Homebrew).gbc" size 524288 crc fd1066ac sha1 8390175e6be4731d9afa91715d7f8dd11693bb69 ) + name "Monty on the Run (World) (Aftermarket) (Unl)" + description "Monty on the Run (World) (Aftermarket) (Unl)" + rom ( name "Monty on the Run (World) (Aftermarket) (Unl).gbc" size 524288 crc fd1066ac sha1 8390175e6be4731d9afa91715d7f8dd11693bb69 ) ) game ( @@ -39306,6 +40269,12 @@ game ( rom ( name "Mortal Kombat 4 (USA, Europe) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 4eb71448 sha1 ea54fb35cd8c4d4cea234423a81f8f953b1d33e4 ) ) +game ( + name "Moshou Shiji - Zhanshen (Taiwan) (Unl)" + description "Moshou Shiji - Zhanshen (Taiwan) (Unl)" + rom ( name "Moshou Shiji - Zhanshen (Taiwan) (Unl).gbc" size 2097152 crc 80c4fd52 sha1 d2569f8176164cef1cc2a8bd94949ea9cd7dbbea ) +) + game ( name "Motocross Maniacs 2 (USA)" description "Motocross Maniacs 2 (USA)" @@ -39313,9 +40282,9 @@ game ( ) game ( - name "Mr. Angry (World) (Aftermarket) (Homebrew)" - description "Mr. Angry (World) (Aftermarket) (Homebrew)" - rom ( name "Mr. Angry (World) (Aftermarket) (Homebrew).gbc" size 524288 crc b85e63a5 sha1 127ad5315e2bce37614a1e9f8605798e68718926 ) + name "Mr. Angry (World) (Aftermarket) (Unl)" + description "Mr. Angry (World) (Aftermarket) (Unl)" + rom ( name "Mr. Angry (World) (Aftermarket) (Unl).gbc" size 524288 crc b85e63a5 sha1 127ad5315e2bce37614a1e9f8605798e68718926 ) ) game ( @@ -39385,9 +40354,9 @@ game ( ) game ( - name "Mu Chang Wu Yu GB 6 (Taiwan) (Unl)" - description "Mu Chang Wu Yu GB 6 (Taiwan) (Unl)" - rom ( name "Mu Chang Wu Yu GB 6 (Taiwan) (Unl).gbc" size 2097152 crc 416e6efa sha1 86b2de0fc2806f3d13145e2c0296389ec0897a6a ) + name "Muchang Wuyu GB 6 (Taiwan) (Unl)" + description "Muchang Wuyu GB 6 (Taiwan) (Unl)" + rom ( name "Muchang Wuyu GB 6 (Taiwan) (Unl).gbc" size 2097152 crc 416e6efa sha1 86b2de0fc2806f3d13145e2c0296389ec0897a6a ) ) game ( @@ -39495,7 +40464,7 @@ game ( game ( name "Nakayoshi Pet Series 4 - Kawaii Koneko (Japan)" description "Nakayoshi Pet Series 4 - Kawaii Koneko (Japan)" - rom ( name "Nakayoshi Pet Series 4 - Kawaii Koneko (Japan).gbc" size 1048576 crc 1a382367 sha1 e5d5f1092e991a7538b9d24a12de35da21e23edd ) + rom ( name "Nakayoshi Pet Series 4 - Kawaii Koneko (Japan).gbc" size 1048576 crc 1a382367 sha1 e5d5f1092e991a7538b9d24a12de35da21e23edd flags verified ) ) game ( @@ -39559,9 +40528,9 @@ game ( ) game ( - name "NBA in the Zone (USA) (SGB Enhanced) (GB Compatible)" - description "NBA in the Zone (USA) (SGB Enhanced) (GB Compatible)" - rom ( name "NBA in the Zone (USA) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc f6bae9a0 sha1 7f0d5a37956ae58077656a42b26629784c203038 ) + name "NBA in the Zone (USA) (Possible Proto) (SGB Enhanced) (GB Compatible)" + description "NBA in the Zone (USA) (Possible Proto) (SGB Enhanced) (GB Compatible)" + rom ( name "NBA in the Zone (USA) (Possible Proto) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc f6bae9a0 sha1 7f0d5a37956ae58077656a42b26629784c203038 ) ) game ( @@ -39607,15 +40576,15 @@ game ( ) game ( - name "Neclaus' Quest (World) (v1.0) (Aftermarket) (Homebrew)" - description "Neclaus' Quest (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Neclaus' Quest (World) (v1.0) (Aftermarket) (Homebrew).gbc" size 1048576 crc eedefa0c sha1 e63c16fe987fe655408be66198366a0147ac607c ) + name "Neclaus' Quest (World) (v1.0) (GB Compatible) (Aftermarket) (Unl)" + description "Neclaus' Quest (World) (v1.0) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Neclaus' Quest (World) (v1.0) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc eedefa0c sha1 e63c16fe987fe655408be66198366a0147ac607c ) ) game ( - name "Neclaus' Quest (World) (v1.1) (Aftermarket) (Homebrew)" - description "Neclaus' Quest (World) (v1.1) (Aftermarket) (Homebrew)" - rom ( name "Neclaus' Quest (World) (v1.1) (Aftermarket) (Homebrew).gbc" size 1048576 crc 53cf930c sha1 bd3272ca7ead64da3f68f774661a560add433b94 ) + name "Neclaus' Quest (World) (v1.1) (GB Compatible) (Aftermarket) (Unl)" + description "Neclaus' Quest (World) (v1.1) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Neclaus' Quest (World) (v1.1) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc 53cf930c sha1 bd3272ca7ead64da3f68f774661a560add433b94 ) ) game ( @@ -39708,6 +40677,12 @@ game ( rom ( name "Nicktoons Racing (USA).gbc" size 1048576 crc a3f079d4 sha1 8b023f596d9b37ae442578c02c6ba859bc81c5c6 ) ) +game ( + name "Ninja JaJaMaru - The Great World Adventure DX (USA, Europe) (Ninja JaJaMaru Retro Collection) (Switch)" + description "Ninja JaJaMaru - The Great World Adventure DX (USA, Europe) (Ninja JaJaMaru Retro Collection) (Switch)" + rom ( name "Ninja JaJaMaru - The Great World Adventure DX (USA, Europe) (Ninja JaJaMaru Retro Collection) (Switch).gbc" size 262144 crc b5af4ca5 sha1 73efaca14c998dd6790e08f2ce2b6f73e7909ce4 flags verified ) +) + game ( name "Nintama Rantarou - Ninjutsu Gakuen ni Nyuugaku Shiyou no Dan (Japan)" description "Nintama Rantarou - Ninjutsu Gakuen ni Nyuugaku Shiyou no Dan (Japan)" @@ -39751,9 +40726,51 @@ game ( ) game ( - name "Nuwang Gedou (Taiwan) (Unl)" - description "Nuwang Gedou (Taiwan) (Unl)" - rom ( name "Nuwang Gedou (Taiwan) (Unl).gbc" size 2097152 crc e1668b49 sha1 37516139aa317a16440379c1dc00bdfc4c1e607a ) + name "Nv Wang Gedou 2000 (Taiwan) (Unl)" + description "Nv Wang Gedou 2000 (Taiwan) (Unl)" + rom ( name "Nv Wang Gedou 2000 (Taiwan) (Unl).gbc" size 2097152 crc e1668b49 sha1 37516139aa317a16440379c1dc00bdfc4c1e607a flags verified ) +) + +game ( + name "Nyghtmare - The Ninth King (World) (v0.2.6) (Beta) (Aftermarket) (Unl)" + description "Nyghtmare - The Ninth King (World) (v0.2.6) (Beta) (Aftermarket) (Unl)" + rom ( name "Nyghtmare - The Ninth King (World) (v0.2.6) (Beta) (Aftermarket) (Unl).gbc" size 1048576 crc e983e8d1 sha1 fc2a58d49327c88c895ae6681bde57e1a2687974 flags verified ) +) + +game ( + name "Nyghtmare - The Ninth King (World) (v0.1.2) (Beta, Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Nyghtmare - The Ninth King (World) (v0.1.2) (Beta, Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Nyghtmare - The Ninth King (World) (v0.1.2) (Beta, Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 524288 crc ef42955e sha1 485b4d672d9edda40f9191f5f5cc223046000e67 flags verified ) +) + +game ( + name "Nyghtmare - The Ninth King (World) (v0.2.1) (Beta) (Aftermarket) (Unl)" + description "Nyghtmare - The Ninth King (World) (v0.2.1) (Beta) (Aftermarket) (Unl)" + rom ( name "Nyghtmare - The Ninth King (World) (v0.2.1) (Beta) (Aftermarket) (Unl).gbc" size 1048576 crc 581efe4d sha1 125bb0564ee17f8e55046a1c25e53ee9a4254303 ) +) + +game ( + name "Nyghtmare - The Ninth King (World) (v0.2.3) (Beta) (Aftermarket) (Unl)" + description "Nyghtmare - The Ninth King (World) (v0.2.3) (Beta) (Aftermarket) (Unl)" + rom ( name "Nyghtmare - The Ninth King (World) (v0.2.3) (Beta) (Aftermarket) (Unl).gbc" size 1048576 crc e26ddbbb sha1 853115369b8ca583d038d00c1dde59b6a74b73a2 ) +) + +game ( + name "Nyghtmare - The Ninth King (World) (v0.2.5) (Beta) (Aftermarket) (Unl)" + description "Nyghtmare - The Ninth King (World) (v0.2.5) (Beta) (Aftermarket) (Unl)" + rom ( name "Nyghtmare - The Ninth King (World) (v0.2.5) (Beta) (Aftermarket) (Unl).gbc" size 1048576 crc 1e8ac5b9 sha1 99c11bef7feb17a9e7212cf9b6593478f671bc32 ) +) + +game ( + name "Nyghtmare - The Ninth King (World) (Free Version) (GB Compatible) (Aftermarket) (Unl)" + description "Nyghtmare - The Ninth King (World) (Free Version) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Nyghtmare - The Ninth King (World) (Free Version) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc f5df28c2 sha1 7b511444e1eb86fffcf93599a78e0e2c44aecc8a ) +) + +game ( + name "Nyghtmare - The Ninth King (World) (Rev 1) (Free Version) (Aftermarket) (Unl)" + description "Nyghtmare - The Ninth King (World) (Rev 1) (Free Version) (Aftermarket) (Unl)" + rom ( name "Nyghtmare - The Ninth King (World) (Rev 1) (Free Version) (Aftermarket) (Unl).gbc" size 2097152 crc 63bddc68 sha1 fab777e607bd8aba70eb65829143039dbe6ac08e ) ) game ( @@ -39811,15 +40828,15 @@ game ( ) game ( - name "Olympic Skier (World) (Aftermarket) (Homebrew)" - description "Olympic Skier (World) (Aftermarket) (Homebrew)" - rom ( name "Olympic Skier (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 5e81cef7 sha1 a7a552a9eb984098a67e063c2eca907eec000fc1 ) + name "Olympic Skier (World) (Aftermarket) (Unl)" + description "Olympic Skier (World) (Aftermarket) (Unl)" + rom ( name "Olympic Skier (World) (Aftermarket) (Unl).gbc" size 524288 crc 5e81cef7 sha1 a7a552a9eb984098a67e063c2eca907eec000fc1 ) ) game ( - name "Opossum Country (World) (Aftermarket) (Homebrew)" - description "Opossum Country (World) (Aftermarket) (Homebrew)" - rom ( name "Opossum Country (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 7348bb32 sha1 e63e06d602473206b21138f5322acf86b5b6b9b8 ) + name "Opossum Country (World) (GB Compatible) (Aftermarket) (Unl)" + description "Opossum Country (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Opossum Country (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 524288 crc 7348bb32 sha1 e63e06d602473206b21138f5322acf86b5b6b9b8 ) ) game ( @@ -39858,6 +40875,12 @@ game ( rom ( name "Ou Dorobou Jing - Devil Version (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 2a39a874 sha1 71797aa125b88614182ce3d0f8252d6ed8cfa08e ) ) +game ( + name "Out on a Limb (World) (Aftermarket) (Unl)" + description "Out on a Limb (World) (Aftermarket) (Unl)" + rom ( name "Out on a Limb (World) (Aftermarket) (Unl).gbc" size 262144 crc a6671528 sha1 f21cb71adcea1029151930eb98c4e9b446e4cfee ) +) + game ( name "Owarai Yoiko no Geemumichi - Oyaji Sagashite 3 Choume (Japan) (SGB Enhanced) (GB Compatible)" description "Owarai Yoiko no Geemumichi - Oyaji Sagashite 3 Choume (Japan) (SGB Enhanced) (GB Compatible)" @@ -39867,7 +40890,7 @@ game ( game ( name "Pac-Man - Special Color Edition (USA) (SGB Enhanced) (GB Compatible)" description "Pac-Man - Special Color Edition (USA) (SGB Enhanced) (GB Compatible)" - rom ( name "Pac-Man - Special Color Edition (USA) (SGB Enhanced) (GB Compatible).gbc" size 262144 crc 3485ef86 sha1 a0a6f55c15dca60350b3a74b1973cc13ff328730 ) + rom ( name "Pac-Man - Special Color Edition (USA) (SGB Enhanced) (GB Compatible).gbc" size 262144 crc 3485ef86 sha1 a0a6f55c15dca60350b3a74b1973cc13ff328730 flags verified ) ) game ( @@ -39901,9 +40924,9 @@ game ( ) game ( - name "Panik!16 (World) (Aftermarket) (Homebrew)" - description "Panik!16 (World) (Aftermarket) (Homebrew)" - rom ( name "Panik!16 (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 7b31122d sha1 98d74fd64e4f7e835f1101b05816b8938a34a416 ) + name "Panik!16 (World) (Aftermarket) (Unl)" + description "Panik!16 (World) (Aftermarket) (Unl)" + rom ( name "Panik!16 (World) (Aftermarket) (Unl).gbc" size 262144 crc 7b31122d sha1 98d74fd64e4f7e835f1101b05816b8938a34a416 ) ) game ( @@ -39939,7 +40962,7 @@ game ( game ( name "Peugeot - Orbital Diagnostic System (Unknown) (Unl) [b]" description "Peugeot - Orbital Diagnostic System (Unknown) (Unl) [b]" - rom ( name "Peugeot - Orbital Diagnostic System (Unknown) (Unl) [b].gbc" size 32768 crc 731c6ff4 sha1 2d4f9dc954d9485fbd7d402e07edf42beebbeb8c flags baddump ) + rom ( name "Peugeot - Orbital Diagnostic System (Unknown) (Unl) [b].gbc" size 1048576 crc f971fce9 sha1 6bc140722fad759830fd6e4b6ac0b41443795fb2 flags baddump ) ) game ( @@ -39961,27 +40984,27 @@ game ( ) game ( - name "Pian Wai Zhang Huang Jin Tai Yang - Feng Yin De Yuan Gu Lian Jin Shu (Taiwan) (Unl)" - description "Pian Wai Zhang Huang Jin Tai Yang - Feng Yin De Yuan Gu Lian Jin Shu (Taiwan) (Unl)" - rom ( name "Pian Wai Zhang Huang Jin Tai Yang - Feng Yin De Yuan Gu Lian Jin Shu (Taiwan) (Unl).gbc" size 2097152 crc 0db9fdfa sha1 6d2acdca141001bfefdcbf0076702fec6619e368 ) + name "Pian Wai Zhang - Huangjin Taiyang - Fengyin de Yuangu Lianjin Shu (Taiwan) (Unl)" + description "Pian Wai Zhang - Huangjin Taiyang - Fengyin de Yuangu Lianjin Shu (Taiwan) (Unl)" + rom ( name "Pian Wai Zhang - Huangjin Taiyang - Fengyin de Yuangu Lianjin Shu (Taiwan) (Unl).gbc" size 2097152 crc 0db9fdfa sha1 6d2acdca141001bfefdcbf0076702fec6619e368 ) ) game ( - name "Pie Crust (World) (v1.0) (Homebrew)" - description "Pie Crust (World) (v1.0) (Homebrew)" - rom ( name "Pie Crust (World) (v1.0) (Homebrew).gbc" size 32768 crc f39c8119 sha1 b01cad9652bf1c65151dd8d2bd21251a9cf44d43 ) + name "Pie Crust (World) (v1.0) (Unl)" + description "Pie Crust (World) (v1.0) (Unl)" + rom ( name "Pie Crust (World) (v1.0) (Unl).gbc" size 32768 crc f39c8119 sha1 b01cad9652bf1c65151dd8d2bd21251a9cf44d43 ) ) game ( - name "Pine Creek (World) (En-US,Es-MX,Pt-BR) (Aftermarket) (Homebrew)" - description "Pine Creek (World) (En-US,Es-MX,Pt-BR) (Aftermarket) (Homebrew)" - rom ( name "Pine Creek (World) (En-US,Es-MX,Pt-BR) (Aftermarket) (Homebrew).gbc" size 2097152 crc 189aa999 sha1 2cf46a36502eaf22ac9572fe1136d369fcbe9e46 ) + name "Pine Creek (World) (En-US,Es-MX,Pt-BR) (GB Compatible) (Aftermarket) (Unl)" + description "Pine Creek (World) (En-US,Es-MX,Pt-BR) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Pine Creek (World) (En-US,Es-MX,Pt-BR) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc 189aa999 sha1 2cf46a36502eaf22ac9572fe1136d369fcbe9e46 ) ) game ( - name "Pinecone Pizza Party (World) (Prototype) (Aftermarket) (Homebrew)" - description "Pinecone Pizza Party (World) (Prototype) (Aftermarket) (Homebrew)" - rom ( name "Pinecone Pizza Party (World) (Prototype) (Aftermarket) (Homebrew).gbc" size 262144 crc 1601d6fa sha1 87b4edf0398f0154d7556e3cd96f97b19e699a40 ) + name "Pinecone Pizza Party (World) (Prototype) (GB Compatible) (Aftermarket) (Unl)" + description "Pinecone Pizza Party (World) (Prototype) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Pinecone Pizza Party (World) (Prototype) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 1601d6fa sha1 87b4edf0398f0154d7556e3cd96f97b19e699a40 ) ) game ( @@ -40123,9 +41146,9 @@ game ( ) game ( - name "Pocket Monster Carbuncle 2003 (USA) (Unl)" - description "Pocket Monster Carbuncle 2003 (USA) (Unl)" - rom ( name "Pocket Monster Carbuncle 2003 (USA) (Unl).gbc" size 2097152 crc 3a0e9b6f sha1 025973627743f2f1ae1ff5b8a3f549cbcc227ef3 ) + name "Pocket Monsters - Crystal Version (Japan)" + description "Pocket Monsters - Crystal Version (Japan)" + rom ( name "Pocket Monsters - Crystal Version (Japan).gbc" size 2097152 crc 270c4ecc sha1 95127b901bbce2407daf43cce9f45d4c27ef635d flags verified ) ) game ( @@ -40134,12 +41157,6 @@ game ( rom ( name "Pocket Monsters - Crystal Version (Taiwan) (Unl).gbc" size 2097152 crc 4b2b8b57 sha1 9a2b381a134bb7ed1b566bb6f94596b57e690a4e ) ) -game ( - name "Pocket Monsters - Crystal Version (Japan)" - description "Pocket Monsters - Crystal Version (Japan)" - rom ( name "Pocket Monsters - Crystal Version (Japan).gbc" size 2097152 crc 270c4ecc sha1 95127b901bbce2407daf43cce9f45d4c27ef635d flags verified ) -) - game ( name "Pocket Monsters Diamond (Taiwan) (En) (Unl)" description "Pocket Monsters Diamond (Taiwan) (En) (Unl)" @@ -40261,9 +41278,9 @@ game ( ) game ( - name "Pogo Pete (World) (Aftermarket) (Homebrew)" - description "Pogo Pete (World) (Aftermarket) (Homebrew)" - rom ( name "Pogo Pete (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 3001ed1b sha1 f9dab150843c2b8f300d36b1dd53dd1a22b481a3 ) + name "Pogo Pete (World) (Aftermarket) (Unl)" + description "Pogo Pete (World) (Aftermarket) (Unl)" + rom ( name "Pogo Pete (World) (Aftermarket) (Unl).gbc" size 262144 crc 3001ed1b sha1 f9dab150843c2b8f300d36b1dd53dd1a22b481a3 ) ) game ( @@ -40335,7 +41352,7 @@ game ( game ( name "Pokemon - Silberne Edition (Germany) (Beta) (SGB Enhanced) (GB Compatible)" description "Pokemon - Silberne Edition (Germany) (Beta) (SGB Enhanced) (GB Compatible)" - rom ( name "Pokemon - Silberne Edition (Germany) (Beta) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc 576f5ced sha1 76fa60d66b2f22a035adc54c61aad9a415c894cd ) + rom ( name "Pokemon - Silberne Edition (Germany) (Beta) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc 576f5ced sha1 76fa60d66b2f22a035adc54c61aad9a415c894cd flags verified ) ) game ( @@ -40512,10 +41529,16 @@ game ( rom ( name "Pokemon Vision Jade (Taiwan) (De) (Unl).gbc" size 2097152 crc 2705ca20 sha1 72d5c6b8a570e67e7bce59070417cb89e985efb0 ) ) +game ( + name "Polaris SnoCross (USA) (Beta) (Rumble Version)" + description "Polaris SnoCross (USA) (Beta) (Rumble Version)" + rom ( name "Polaris SnoCross (USA) (Beta) (Rumble Version).gbc" size 1048576 crc 673623c4 sha1 4176ef1f9fd37156a4cac37a2d146ed2deeb5a7b ) +) + game ( name "Polaris SnoCross (USA) (Rumble Version)" description "Polaris SnoCross (USA) (Rumble Version)" - rom ( name "Polaris SnoCross (USA) (Rumble Version).gbc" size 1048576 crc dd8b189e sha1 e893808fe227a1608c0604382d6a0340ec704c3b ) + rom ( name "Polaris SnoCross (USA) (Rumble Version).gbc" size 1048576 crc dd8b189e sha1 e893808fe227a1608c0604382d6a0340ec704c3b flags verified ) ) game ( @@ -40573,21 +41596,21 @@ game ( ) game ( - name "Powa! (Europe)" - description "Powa! (Europe)" - rom ( name "Powa! (Europe).gbc" size 262144 crc f0191467 sha1 3876f1aa896759f427ad2dc88a48788817e60c06 ) + name "Powa! (World) (En) (GB Compatible) (Aftermarket) (Unl)" + description "Powa! (World) (En) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Powa! (World) (En) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc f0191467 sha1 3876f1aa896759f427ad2dc88a48788817e60c06 flags verified ) ) game ( - name "Powa! (Japan)" - description "Powa! (Japan)" - rom ( name "Powa! (Japan).gbc" size 262144 crc e8f68acc sha1 156d315b7a2a035d5cd429fb8c7e051e67e83c93 ) + name "Powa! (World) (Ja) (GB Compatible) (Aftermarket) (Unl)" + description "Powa! (World) (Ja) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Powa! (World) (Ja) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc e8f68acc sha1 156d315b7a2a035d5cd429fb8c7e051e67e83c93 flags verified ) ) game ( - name "Powa! (Unknown) (Demo)" - description "Powa! (Unknown) (Demo)" - rom ( name "Powa! (Unknown) (Demo).gbc" size 262144 crc 592b6097 sha1 874d5e4ffc7d36259bef6ec6a86a1de8289b8d54 ) + name "Powa! (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + description "Powa! (World) (Demo) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Powa! (World) (Demo) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 592b6097 sha1 874d5e4ffc7d36259bef6ec6a86a1de8289b8d54 ) ) game ( @@ -40605,7 +41628,7 @@ game ( game ( name "Power Pro Kun Pocket 2 (Japan) (SGB Enhanced) (GB Compatible)" description "Power Pro Kun Pocket 2 (Japan) (SGB Enhanced) (GB Compatible)" - rom ( name "Power Pro Kun Pocket 2 (Japan) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc c2a4a3eb sha1 5c0ba404cf30296dceeb427a54e3644415c9e8db ) + rom ( name "Power Pro Kun Pocket 2 (Japan) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc c2a4a3eb sha1 5c0ba404cf30296dceeb427a54e3644415c9e8db flags verified ) ) game ( @@ -40680,6 +41703,12 @@ game ( rom ( name "Powerpuff Girls, The - Bulle Contre Lui (France).gbc" size 2097152 crc 7085270c sha1 5c1c53b9c002680c2da8b24aa31d4a247586263d ) ) +game ( + name "Powerpuff Girls, The - Il Terribile Mojo Jojo (Italy) (Proto)" + description "Powerpuff Girls, The - Il Terribile Mojo Jojo (Italy) (Proto)" + rom ( name "Powerpuff Girls, The - Il Terribile Mojo Jojo (Italy) (Proto).gbc" size 2097152 crc 5c265103 sha1 5ca837632930689fad9086b772928bf5995193f9 ) +) + game ( name "Powerpuff Girls, The - L'Affreux Mojo Jojo (France)" description "Powerpuff Girls, The - L'Affreux Mojo Jojo (France)" @@ -40801,15 +41830,21 @@ game ( ) game ( - name "Proof of Destruction (World) (Aftermarket) (Homebrew)" - description "Proof of Destruction (World) (Aftermarket) (Homebrew)" - rom ( name "Proof of Destruction (World) (Aftermarket) (Homebrew).gbc" size 262144 crc fa528f88 sha1 5062f16346c31cae1b13e469cb610ce84d4eecb2 ) + name "Proof of Destruction (World) (Aftermarket) (Unl)" + description "Proof of Destruction (World) (Aftermarket) (Unl)" + rom ( name "Proof of Destruction (World) (Aftermarket) (Unl).gbc" size 262144 crc fa528f88 sha1 5062f16346c31cae1b13e469cb610ce84d4eecb2 ) ) game ( - name "Puchi Carat (USA) (Proto) (SGB Enhanced) (GB Compatible)" - description "Puchi Carat (USA) (Proto) (SGB Enhanced) (GB Compatible)" - rom ( name "Puchi Carat (USA) (Proto) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc a97ea742 sha1 de755124425f9de8bef5eea8026751c54f7f1e00 ) + name "Puchi Carat (USA) (Proto 2) (SGB Enhanced) (GB Compatible)" + description "Puchi Carat (USA) (Proto 2) (SGB Enhanced) (GB Compatible)" + rom ( name "Puchi Carat (USA) (Proto 2) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc a97ea742 sha1 de755124425f9de8bef5eea8026751c54f7f1e00 ) +) + +game ( + name "Puchi Carat (USA) (Proto 1) (SGB Enhanced) (GB Compatible)" + description "Puchi Carat (USA) (Proto 1) (SGB Enhanced) (GB Compatible)" + rom ( name "Puchi Carat (USA) (Proto 1) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc d79b6f46 sha1 b91b121a6951bcc74f0e4af33d41a1fba62f9822 ) ) game ( @@ -40836,6 +41871,12 @@ game ( rom ( name "Pumuckls Abenteuer im Geisterschloss (Germany).gbc" size 1048576 crc 87fcec24 sha1 0fe14ef81b11c854f080e804c5d3cb14601b2794 ) ) +game ( + name "Purple Turtles (World) (Aftermarket) (Unl)" + description "Purple Turtles (World) (Aftermarket) (Unl)" + rom ( name "Purple Turtles (World) (Aftermarket) (Unl).gbc" size 262144 crc 471277df sha1 a73cb69d1fb42f45f3649c4d1b4775e98fe35b78 ) +) + game ( name "Puyo Puyo Gaiden - Puyo Wars (Japan) (SGB Enhanced) (GB Compatible)" description "Puyo Puyo Gaiden - Puyo Wars (Japan) (SGB Enhanced) (GB Compatible)" @@ -40891,15 +41932,15 @@ game ( ) game ( - name "Qi Long Zhu Z 3 (Taiwan) (Unl)" - description "Qi Long Zhu Z 3 (Taiwan) (Unl)" - rom ( name "Qi Long Zhu Z 3 (Taiwan) (Unl).gbc" size 2097152 crc ccfdd63a sha1 4f3b63cd11522b6fb291661f4d918601d95138e0 ) + name "Qi Tian Dasheng - Sun Wukong (Taiwan) (Unl)" + description "Qi Tian Dasheng - Sun Wukong (Taiwan) (Unl)" + rom ( name "Qi Tian Dasheng - Sun Wukong (Taiwan) (Unl).gbc" size 524288 crc a336c40c sha1 3d7e987f1315242422c0db45c4aea120c1b41429 ) ) game ( - name "Qi Tian Da Sheng - Sun Wu Kong (Taiwan) (Unl)" - description "Qi Tian Da Sheng - Sun Wu Kong (Taiwan) (Unl)" - rom ( name "Qi Tian Da Sheng - Sun Wu Kong (Taiwan) (Unl).gbc" size 524288 crc a336c40c sha1 3d7e987f1315242422c0db45c4aea120c1b41429 ) + name "Qilongzhu Z3 (Taiwan) (Unl)" + description "Qilongzhu Z3 (Taiwan) (Unl)" + rom ( name "Qilongzhu Z3 (Taiwan) (Unl).gbc" size 2097152 crc ccfdd63a sha1 4f3b63cd11522b6fb291661f4d918601d95138e0 ) ) game ( @@ -40921,9 +41962,9 @@ game ( ) game ( - name "Quan Ba Tian Xia (Taiwan) (Unl)" - description "Quan Ba Tian Xia (Taiwan) (Unl)" - rom ( name "Quan Ba Tian Xia (Taiwan) (Unl).gbc" size 4194304 crc 2c922ed6 sha1 c19eb98a9745d2c2ce9e4dba3f17ae0b3dee0f49 ) + name "Quan Ba Tianxia (Taiwan) (Unl)" + description "Quan Ba Tianxia (Taiwan) (Unl)" + rom ( name "Quan Ba Tianxia (Taiwan) (Unl).gbc" size 4194304 crc 2c922ed6 sha1 c19eb98a9745d2c2ce9e4dba3f17ae0b3dee0f49 ) ) game ( @@ -41040,6 +42081,12 @@ game ( rom ( name "Rats! (USA) (En,Es) (GB Compatible).gbc" size 524288 crc 17635ad1 sha1 5e423dfab8221b69a641d2e535ebfe1e3759a2e4 ) ) +game ( + name "Ravenia (World) (GB Compatible) (Aftermarket) (Unl)" + description "Ravenia (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Ravenia (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 09196a32 sha1 8218ad22b29fb32c66430a17a97775dd30b2f34b flags verified ) +) + game ( name "Rayman (Europe) (En,Fr,De,Es,It,Nl)" description "Rayman (Europe) (En,Fr,De,Es,It,Nl)" @@ -41118,6 +42165,18 @@ game ( rom ( name "Real Pro Yakyuu! - Pacific League Hen (Japan) (SGB Enhanced) (GB Compatible).gbc" size 524288 crc 16b81c36 sha1 a7a2cd7bbb4cae171b5f2c798293afefe2882afc ) ) +game ( + name "Repugnant Bounty (World) (Demo) (Aftermarket) (Unl)" + description "Repugnant Bounty (World) (Demo) (Aftermarket) (Unl)" + rom ( name "Repugnant Bounty (World) (Demo) (Aftermarket) (Unl).gbc" size 2097152 crc f8c7f180 sha1 07674d42f5d4efaec14750ae56d8f28c4b54c020 ) +) + +game ( + name "Repugnant Bounty (World) (Rev B) (Beta) (Aftermarket) (Unl)" + description "Repugnant Bounty (World) (Rev B) (Beta) (Aftermarket) (Unl)" + rom ( name "Repugnant Bounty (World) (Rev B) (Beta) (Aftermarket) (Unl).gbc" size 4194304 crc 9cce02d9 sha1 b83f9aca2a382729a490856c4100dc4a17594921 ) +) + game ( name "Rescue Heroes - Fire Frenzy (USA)" description "Rescue Heroes - Fire Frenzy (USA)" @@ -41185,9 +42244,9 @@ game ( ) game ( - name "Rig Attack (World) (Aftermarket) (Homebrew)" - description "Rig Attack (World) (Aftermarket) (Homebrew)" - rom ( name "Rig Attack (World) (Aftermarket) (Homebrew).gbc" size 262144 crc d013d775 sha1 90bfd921c986abff4555b761cedc90ec36c43fff ) + name "Rig Attack (World) (Aftermarket) (Unl)" + description "Rig Attack (World) (Aftermarket) (Unl)" + rom ( name "Rig Attack (World) (Aftermarket) (Unl).gbc" size 262144 crc d013d775 sha1 90bfd921c986abff4555b761cedc90ec36c43fff ) ) game ( @@ -41233,9 +42292,9 @@ game ( ) game ( - name "Robin to the Rescue (World) (Aftermarket) (Homebrew)" - description "Robin to the Rescue (World) (Aftermarket) (Homebrew)" - rom ( name "Robin to the Rescue (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 9cd52c18 sha1 ef3bea9ed0de41e311b5f15fccbb0f451848d56c ) + name "Robin to the Rescue (World) (Aftermarket) (Unl)" + description "Robin to the Rescue (World) (Aftermarket) (Unl)" + rom ( name "Robin to the Rescue (World) (Aftermarket) (Unl).gbc" size 262144 crc 9cd52c18 sha1 ef3bea9ed0de41e311b5f15fccbb0f451848d56c ) ) game ( @@ -41253,7 +42312,7 @@ game ( game ( name "Robopon - Sun Version (USA) (SGB Enhanced) (GB Compatible)" description "Robopon - Sun Version (USA) (SGB Enhanced) (GB Compatible)" - rom ( name "Robopon - Sun Version (USA) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 32caef11 sha1 399c928a38a3901b7a1093bd61f5a4d8c05b9771 ) + rom ( name "Robopon - Sun Version (USA) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 32caef11 sha1 399c928a38a3901b7a1093bd61f5a4d8c05b9771 flags verified ) ) game ( @@ -41295,7 +42354,7 @@ game ( game ( name "Rocket Power - Gettin' Air (USA, Europe)" description "Rocket Power - Gettin' Air (USA, Europe)" - rom ( name "Rocket Power - Gettin' Air (USA, Europe).gbc" size 1048576 crc 7025eb63 sha1 d9c5c22f1f5a2a922cb2b39d5e8f3df31c1155d7 ) + rom ( name "Rocket Power - Gettin' Air (USA, Europe).gbc" size 1048576 crc 7025eb63 sha1 d9c5c22f1f5a2a922cb2b39d5e8f3df31c1155d7 flags verified ) ) game ( @@ -41305,9 +42364,9 @@ game ( ) game ( - name "Rockman (World) (Aftermarket) (Homebrew)" - description "Rockman (World) (Aftermarket) (Homebrew)" - rom ( name "Rockman (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 490b5676 sha1 6f78200acb6698e268e26c1912d64a002cbd3d89 ) + name "Rockman (World) (Aftermarket) (Unl)" + description "Rockman (World) (Aftermarket) (Unl)" + rom ( name "Rockman (World) (Aftermarket) (Unl).gbc" size 262144 crc 490b5676 sha1 6f78200acb6698e268e26c1912d64a002cbd3d89 ) ) game ( @@ -41425,27 +42484,33 @@ game ( ) game ( - name "RPG Tsukuru GB (Japan) (Rev 1) (NP)" - description "RPG Tsukuru GB (Japan) (Rev 1) (NP)" - rom ( name "RPG Tsukuru GB (Japan) (Rev 1) (NP).gbc" size 2097152 crc 57f82031 sha1 f4c36b3cbb13d3cbaaa81e97b0636c4515ce501e ) + name "RPG Tsukuru GB (Japan) (Rev 1) (Possible Proto) (NP)" + description "RPG Tsukuru GB (Japan) (Rev 1) (Possible Proto) (NP)" + rom ( name "RPG Tsukuru GB (Japan) (Rev 1) (Possible Proto) (NP).gbc" size 2097152 crc 57f82031 sha1 f4c36b3cbb13d3cbaaa81e97b0636c4515ce501e ) ) game ( - name "Ruby & Rusty Save the Crows (World) (v1.0) (Aftermarket) (Homebrew)" - description "Ruby & Rusty Save the Crows (World) (v1.0) (Aftermarket) (Homebrew)" - rom ( name "Ruby & Rusty Save the Crows (World) (v1.0) (Aftermarket) (Homebrew).gbc" size 2097152 crc a46baa15 sha1 87dd5a6ccb5db59dae09f1953062420977ad2d92 ) + name "Ruby & Rusty Save the Crows (World) (v1.0) (GB Compatible) (Aftermarket) (Unl)" + description "Ruby & Rusty Save the Crows (World) (v1.0) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Ruby & Rusty Save the Crows (World) (v1.0) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc a46baa15 sha1 87dd5a6ccb5db59dae09f1953062420977ad2d92 ) ) game ( - name "Ruby & Rusty Save the Crows (World) (v2.0) (Aftermarket) (Homebrew)" - description "Ruby & Rusty Save the Crows (World) (v2.0) (Aftermarket) (Homebrew)" - rom ( name "Ruby & Rusty Save the Crows (World) (v2.0) (Aftermarket) (Homebrew).gbc" size 2097152 crc 5602ca84 sha1 49d692796a6d0f3dcfc988648b446c0934f0e794 ) + name "Ruby & Rusty Save the Crows (World) (v2.0) (GB Compatible) (Aftermarket) (Unl)" + description "Ruby & Rusty Save the Crows (World) (v2.0) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Ruby & Rusty Save the Crows (World) (v2.0) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc 5602ca84 sha1 49d692796a6d0f3dcfc988648b446c0934f0e794 ) ) game ( - name "Ruby & Rusty Save the Crows (World) (Beta 3) (Aftermarket) (Homebrew)" - description "Ruby & Rusty Save the Crows (World) (Beta 3) (Aftermarket) (Homebrew)" - rom ( name "Ruby & Rusty Save the Crows (World) (Beta 3) (Aftermarket) (Homebrew).gbc" size 2097152 crc b2547aa6 sha1 981a464d6e1c9e5ac59549845b41dae4817ed3b8 ) + name "Ruby & Rusty Save the Crows (World) (Beta 3) (GB Compatible) (Aftermarket) (Unl)" + description "Ruby & Rusty Save the Crows (World) (Beta 3) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Ruby & Rusty Save the Crows (World) (Beta 3) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc b2547aa6 sha1 981a464d6e1c9e5ac59549845b41dae4817ed3b8 ) +) + +game ( + name "Ruby & Rusty Save the Crows (World) (v2.6) (GB Compatible) (Aftermarket) (Unl)" + description "Ruby & Rusty Save the Crows (World) (v2.6) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Ruby & Rusty Save the Crows (World) (v2.6) (GB Compatible) (Aftermarket) (Unl).gbc" size 1048576 crc c18f82cb sha1 bd8cd5822dcc61f7eb0cf2584c9a5b5a94bde14a ) ) game ( @@ -41517,7 +42582,7 @@ game ( game ( name "Sabrina - The Animated Series - Spooked! (USA, Europe)" description "Sabrina - The Animated Series - Spooked! (USA, Europe)" - rom ( name "Sabrina - The Animated Series - Spooked! (USA, Europe).gbc" size 1048576 crc 2cf48188 sha1 7a219159ef46c5ef88eb6b478667c2ec80194edc ) + rom ( name "Sabrina - The Animated Series - Spooked! (USA, Europe).gbc" size 1048576 crc 2cf48188 sha1 7a219159ef46c5ef88eb6b478667c2ec80194edc flags verified ) ) game ( @@ -41550,6 +42615,12 @@ game ( rom ( name "Sakura Taisen GB 2 - Thunderbolt Sakusen (Japan).gbc" size 4194304 crc 47636a2c sha1 13092603ea1d54264bc48f02c2796947badb462c ) ) +game ( + name "Sam the Optimistic Hedgehog (World) (GB Compatible) (Aftermarket) (Unl)" + description "Sam the Optimistic Hedgehog (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Sam the Optimistic Hedgehog (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 4e4af60b sha1 ab8d49a99d02eb5c97d75f62dfb3720eec2b5abd ) +) + game ( name "Samurai Kid (Japan)" description "Samurai Kid (Japan)" @@ -41569,9 +42640,9 @@ game ( ) game ( - name "San Guo Wu Shang 5 (Taiwan) (Unl)" - description "San Guo Wu Shang 5 (Taiwan) (Unl)" - rom ( name "San Guo Wu Shang 5 (Taiwan) (Unl).gbc" size 2097152 crc 8a64c933 sha1 e97508fa49564d56550ad467f4c543f3f218db76 ) + name "San Guo Wushang 5 (Taiwan) (Unl)" + description "San Guo Wushang 5 (Taiwan) (Unl)" + rom ( name "San Guo Wushang 5 (Taiwan) (Unl).gbc" size 2097152 crc 8a64c933 sha1 e97508fa49564d56550ad467f4c543f3f218db76 ) ) game ( @@ -41622,6 +42693,12 @@ game ( rom ( name "Santa Claus Junior (Europe).gbc" size 1048576 crc a744df64 sha1 ab74474cd63a2c74bf9617907270d26b5d183b89 ) ) +game ( + name "Sapphire Hotel - The Little Tales of (World) (v1.2) (Demo) (MBC5) (Aftermarket) (Unl)" + description "Sapphire Hotel - The Little Tales of (World) (v1.2) (Demo) (MBC5) (Aftermarket) (Unl)" + rom ( name "Sapphire Hotel - The Little Tales of (World) (v1.2) (Demo) (MBC5) (Aftermarket) (Unl).gbc" size 262144 crc db928e22 sha1 e175d35e6112c347ecd1e0a379e98f430823ba94 ) +) + game ( name "Saru Puncher (Japan) (SGB Enhanced) (GB Compatible)" description "Saru Puncher (Japan) (SGB Enhanced) (GB Compatible)" @@ -41652,6 +42729,12 @@ game ( rom ( name "Sea-Doo HydroCross (USA) (Proto).gbc" size 1048576 crc fb1783f3 sha1 3612e549ba2a71585fc9a57033c97562386cdea4 ) ) +game ( + name "Second Edition Harry Boy, The - The Secret of the Chamber of Secrets (USA) (Unl)" + description "Second Edition Harry Boy, The - The Secret of the Chamber of Secrets (USA) (Unl)" + rom ( name "Second Edition Harry Boy, The - The Secret of the Chamber of Secrets (USA) (Unl).gbc" size 2097152 crc cba2c784 sha1 99d852998fa84c8c0160cc9dfb2e2165a94756ca ) +) + game ( name "Sei Hai Densetsu (Japan) (SGB Enhanced) (GB Compatible)" description "Sei Hai Densetsu (Japan) (SGB Enhanced) (GB Compatible)" @@ -41821,9 +42904,21 @@ game ( ) game ( - name "Shao Lin Shi San Gun - Ying Xiong Jiu Zhu (Taiwan) (Zh) (Unl)" - description "Shao Lin Shi San Gun - Ying Xiong Jiu Zhu (Taiwan) (Zh) (Unl)" - rom ( name "Shao Lin Shi San Gun - Ying Xiong Jiu Zhu (Taiwan) (Zh) (Unl).gbc" size 2097152 crc 25a857af sha1 2fbbf4e3a40eda75b0876d207502727f8dacca11 ) + name "Shantae (World) (Switch)" + description "Shantae (World) (Switch)" + rom ( name "Shantae (World) (Switch).gbc" size 4194304 crc 5009b832 sha1 e1cc66f1950d585d5b644bf9720c87a356354e6a ) +) + +game ( + name "Shantae (World) (GBA Enhanced) (Switch)" + description "Shantae (World) (GBA Enhanced) (Switch)" + rom ( name "Shantae (World) (GBA Enhanced) (Switch).gbc" size 4194304 crc 1ef9fa6b sha1 d14e8b0029b0aff52134a4fa6794eea72f25f4a6 ) +) + +game ( + name "Shaolin Shisan Gun - Ying Xiong Jiu Zhu (Taiwan) (Zh) (Unl)" + description "Shaolin Shisan Gun - Ying Xiong Jiu Zhu (Taiwan) (Zh) (Unl)" + rom ( name "Shaolin Shisan Gun - Ying Xiong Jiu Zhu (Taiwan) (Zh) (Unl).gbc" size 2097152 crc 25a857af sha1 2fbbf4e3a40eda75b0876d207502727f8dacca11 ) ) game ( @@ -41832,6 +42927,12 @@ game ( rom ( name "Shaoling Legend - Hero, the Saver (Taiwan) (En) (Unl).gbc" size 2097152 crc a321ff7d sha1 b1194edd0962c50cf909262004a102f01f556723 ) ) +game ( + name "Shark Attack (World) (Aftermarket) (Unl)" + description "Shark Attack (World) (Aftermarket) (Unl)" + rom ( name "Shark Attack (World) (Aftermarket) (Unl).gbc" size 262144 crc cf80a2ba sha1 291f453a185f836fcafbd7e41945a409638980c0 ) +) + game ( name "Shaun Palmer's Pro Snowboarder (USA, Australia)" description "Shaun Palmer's Pro Snowboarder (USA, Australia)" @@ -41845,9 +42946,9 @@ game ( ) game ( - name "Sheng Shou Wu Yu - Shen Long Chuan Shuo (Taiwan) (Unl)" - description "Sheng Shou Wu Yu - Shen Long Chuan Shuo (Taiwan) (Unl)" - rom ( name "Sheng Shou Wu Yu - Shen Long Chuan Shuo (Taiwan) (Unl).gbc" size 1048576 crc 99a6e58a sha1 13bd97249b68eb824858a9d7a6061edf94140ac3 ) + name "Sheng Shou Wuyu - Shenlong Chuanshuo (Taiwan) (Unl)" + description "Sheng Shou Wuyu - Shenlong Chuanshuo (Taiwan) (Unl)" + rom ( name "Sheng Shou Wuyu - Shenlong Chuanshuo (Taiwan) (Unl).gbc" size 1048576 crc 99a6e58a sha1 13bd97249b68eb824858a9d7a6061edf94140ac3 ) ) game ( @@ -41856,18 +42957,18 @@ game ( rom ( name "Shengui Diguo Zhi Emo Cheng (Taiwan) (Unl).gbc" size 2097152 crc 1823e24d sha1 3fc2533f71cea3aec6310f5f7c33fa7eac427b99 ) ) -game ( - name "Shi Kong Xing Shou (Taiwan) (Unl)" - description "Shi Kong Xing Shou (Taiwan) (Unl)" - rom ( name "Shi Kong Xing Shou (Taiwan) (Unl).gbc" size 2097152 crc ec970863 sha1 bc43036da01bb1aef4daa9e7316907193285011f ) -) - game ( name "Shi Mian Maifu - Fengyun Pian (Taiwan) (Unl)" description "Shi Mian Maifu - Fengyun Pian (Taiwan) (Unl)" rom ( name "Shi Mian Maifu - Fengyun Pian (Taiwan) (Unl).gbc" size 1048576 crc a5f686c3 sha1 68798ac090df9f5fb1ea9c409adf3dc639bc9842 ) ) +game ( + name "Shikong Xing Shou (Taiwan) (Unl)" + description "Shikong Xing Shou (Taiwan) (Unl)" + rom ( name "Shikong Xing Shou (Taiwan) (Unl).gbc" size 2097152 crc ec970863 sha1 bc43036da01bb1aef4daa9e7316907193285011f ) +) + game ( name "Shin Megami Tensei Devil Children - Aka no Sho (Japan) (SGB Enhanced) (GB Compatible)" description "Shin Megami Tensei Devil Children - Aka no Sho (Japan) (SGB Enhanced) (GB Compatible)" @@ -41940,30 +43041,6 @@ game ( rom ( name "Shrek - Fairy Tale Freakdown (USA, Europe) (En,Fr,De,Es,It).gbc" size 2097152 crc 387e6459 sha1 e6a728cafd14a4df952467f2a7434ff14e53e268 flags verified ) ) -game ( - name "Shu Ma Bao Long - Ge Dou Ban 2003 (Taiwan) (Unl)" - description "Shu Ma Bao Long - Ge Dou Ban 2003 (Taiwan) (Unl)" - rom ( name "Shu Ma Bao Long - Ge Dou Ban 2003 (Taiwan) (Unl).gbc" size 2097152 crc 1219eec6 sha1 4a722c10e3893e04c67a66d0aea401d6d220ec7e ) -) - -game ( - name "Shu Ma Bao Long - Shui Jing Ban II (Taiwan) (Unl)" - description "Shu Ma Bao Long - Shui Jing Ban II (Taiwan) (Unl)" - rom ( name "Shu Ma Bao Long - Shui Jing Ban II (Taiwan) (Unl).gbc" size 2097152 crc b67999ae sha1 435c26f47c4e244e8adb3b7f18f2b1d129e163e9 ) -) - -game ( - name "Shu Ma Bao Long 9 - Bao Long Pian 2002 (Taiwan) (Zh) (Unl)" - description "Shu Ma Bao Long 9 - Bao Long Pian 2002 (Taiwan) (Zh) (Unl)" - rom ( name "Shu Ma Bao Long 9 - Bao Long Pian 2002 (Taiwan) (Zh) (Unl).gbc" size 1048576 crc c10fa909 sha1 bdc4a2ca7170f41bc10b89a2e6e96fb990a44f0b ) -) - -game ( - name "Shu Ma Bao Long Zhuan Ji 10 in 1 (Taiwan) (Unl)" - description "Shu Ma Bao Long Zhuan Ji 10 in 1 (Taiwan) (Unl)" - rom ( name "Shu Ma Bao Long Zhuan Ji 10 in 1 (Taiwan) (Unl).gbc" size 4194304 crc 37603b3a sha1 37366ff0b3a722da867327c62cae6ad0e51d4a12 ) -) - game ( name "Shuihu Shenshou (Taiwan) (Unl)" description "Shuihu Shenshou (Taiwan) (Unl)" @@ -41989,9 +43066,9 @@ game ( ) game ( - name "Shuma Baobei - Chao Mengmeng Fanji Zhan (Taiwan) (Zh)" - description "Shuma Baobei - Chao Mengmeng Fanji Zhan (Taiwan) (Zh)" - rom ( name "Shuma Baobei - Chao Mengmeng Fanji Zhan (Taiwan) (Zh).gbc" size 524288 crc fc844e0e sha1 6abec431a861315fb1b284ff964c2abf933444ae ) + name "Shuma Baobei - Chao Mengmeng Fanji Zhan (Taiwan) (Zh) (Unl)" + description "Shuma Baobei - Chao Mengmeng Fanji Zhan (Taiwan) (Zh) (Unl)" + rom ( name "Shuma Baobei - Chao Mengmeng Fanji Zhan (Taiwan) (Zh) (Unl).gbc" size 524288 crc fc844e0e sha1 6abec431a861315fb1b284ff964c2abf933444ae ) ) game ( @@ -42042,12 +43119,30 @@ game ( rom ( name "Shuma Baolong - Shuijing Ban (Taiwan) (Li Cheng) (Unl).gbc" size 1048576 crc ba03bd71 sha1 39d8ebe977cf139be398d87be735ea4f7e6885f7 ) ) +game ( + name "Shuma Baolong - Shuijing Ban II (Taiwan) (Unl)" + description "Shuma Baolong - Shuijing Ban II (Taiwan) (Unl)" + rom ( name "Shuma Baolong - Shuijing Ban II (Taiwan) (Unl).gbc" size 2097152 crc b67999ae sha1 435c26f47c4e244e8adb3b7f18f2b1d129e163e9 ) +) + game ( name "Shuma Baolong 02 4 (Taiwan) (Zh) (Unl)" description "Shuma Baolong 02 4 (Taiwan) (Zh) (Unl)" rom ( name "Shuma Baolong 02 4 (Taiwan) (Zh) (Unl).gbc" size 1048576 crc 2ee18ab2 sha1 839f0880749735ba2113e437f8efede171b7474d ) ) +game ( + name "Shuma Baolong 9 - Baolong Pian (Taiwan) (Zh) (Unl)" + description "Shuma Baolong 9 - Baolong Pian (Taiwan) (Zh) (Unl)" + rom ( name "Shuma Baolong 9 - Baolong Pian (Taiwan) (Zh) (Unl).gbc" size 1048576 crc c10fa909 sha1 bdc4a2ca7170f41bc10b89a2e6e96fb990a44f0b ) +) + +game ( + name "Shuma Baolong Zhuanji 10 in 1 (Taiwan) (Unl)" + description "Shuma Baolong Zhuanji 10 in 1 (Taiwan) (Unl)" + rom ( name "Shuma Baolong Zhuanji 10 in 1 (Taiwan) (Unl).gbc" size 4194304 crc 37603b3a sha1 37366ff0b3a722da867327c62cae6ad0e51d4a12 ) +) + game ( name "Shutokou Racing, The (Japan) (SGB Enhanced) (GB Compatible)" description "Shutokou Racing, The (Japan) (SGB Enhanced) (GB Compatible)" @@ -42061,15 +43156,15 @@ game ( ) game ( - name "Skelby (World) (Aftermarket) (Homebrew)" - description "Skelby (World) (Aftermarket) (Homebrew)" - rom ( name "Skelby (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 6ec6c18c sha1 887555aa7e2e61b68a21020237b7d508bb71aded ) + name "Skelby (World) (Aftermarket) (Unl)" + description "Skelby (World) (Aftermarket) (Unl)" + rom ( name "Skelby (World) (Aftermarket) (Unl).gbc" size 524288 crc 6ec6c18c sha1 887555aa7e2e61b68a21020237b7d508bb71aded ) ) game ( - name "Skycon (World) (Aftermarket) (Homebrew)" - description "Skycon (World) (Aftermarket) (Homebrew)" - rom ( name "Skycon (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 195dbc30 sha1 df29ece6c5f5b14840663495230135dd79c5163a ) + name "Skycon (World) (Aftermarket) (Unl)" + description "Skycon (World) (Aftermarket) (Unl)" + rom ( name "Skycon (World) (Aftermarket) (Unl).gbc" size 524288 crc 195dbc30 sha1 df29ece6c5f5b14840663495230135dd79c5163a ) ) game ( @@ -42300,6 +43395,12 @@ game ( rom ( name "Spider-Man 3 - Movie Version (USA) (Unl).gbc" size 2097152 crc 2c0d43a9 sha1 8a6b6a1300db59b86ddf87599cfc8edd2e52e2b0 ) ) +game ( + name "Spiky Harold (World) (Aftermarket) (Unl)" + description "Spiky Harold (World) (Aftermarket) (Unl)" + rom ( name "Spiky Harold (World) (Aftermarket) (Unl).gbc" size 262144 crc 7e9eda35 sha1 486d294b8cf226876581d543e3a08018321bc402 ) +) + game ( name "Spirou - The Robot Invasion (Europe) (En,Fr,De,Es,It,Nl,Da)" description "Spirou - The Robot Invasion (Europe) (En,Fr,De,Es,It,Nl,Da)" @@ -42403,9 +43504,9 @@ game ( ) game ( - name "Stellar Wars (World) (Aftermarket) (Homebrew)" - description "Stellar Wars (World) (Aftermarket) (Homebrew)" - rom ( name "Stellar Wars (World) (Aftermarket) (Homebrew).gbc" size 262144 crc e0b96256 sha1 ba2d10a2a5f3f073aea11e26ae5c53c9cf7866f8 ) + name "Stellar Wars (World) (Aftermarket) (Unl)" + description "Stellar Wars (World) (Aftermarket) (Unl)" + rom ( name "Stellar Wars (World) (Aftermarket) (Unl).gbc" size 262144 crc e0b96256 sha1 ba2d10a2a5f3f073aea11e26ae5c53c9cf7866f8 ) ) game ( @@ -42456,6 +43557,12 @@ game ( rom ( name "Stuart Little - The Journey Home (USA, Europe).gbc" size 1048576 crc eb273887 sha1 d3c31e41709c54af328787036db1b98997f508ea ) ) +game ( + name "Suicide Run (World) (GB Compatible) (Aftermarket) (Unl)" + description "Suicide Run (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Suicide Run (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 2dc3fdae sha1 a551d904c4af661bd4b6e88063cb5541900117b0 ) +) + game ( name "Super 16 in 1 (Taiwan) (En) (Sachen) (Unl)" description "Super 16 in 1 (Taiwan) (En) (Sachen) (Unl)" @@ -42541,9 +43648,21 @@ game ( ) game ( - name "Super Jacked Up Tomato Face Johnson (World) (Aftermarket) (Homebrew)" - description "Super Jacked Up Tomato Face Johnson (World) (Aftermarket) (Homebrew)" - rom ( name "Super Jacked Up Tomato Face Johnson (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 87956ddf sha1 b23320d5d3fde2a55b301f7bea324833c31c32d5 ) + name "Super Gran (World) (Aftermarket) (Unl)" + description "Super Gran (World) (Aftermarket) (Unl)" + rom ( name "Super Gran (World) (Aftermarket) (Unl).gbc" size 262144 crc 98ee2e8a sha1 5afe9c7ab1b9d86048a570e7a37a0c244cb84e43 ) +) + +game ( + name "Super Jacked Up Tomato Face Johnson (World) (Aftermarket) (Unl)" + description "Super Jacked Up Tomato Face Johnson (World) (Aftermarket) (Unl)" + rom ( name "Super Jacked Up Tomato Face Johnson (World) (Aftermarket) (Unl).gbc" size 524288 crc 87956ddf sha1 b23320d5d3fde2a55b301f7bea324833c31c32d5 ) +) + +game ( + name "Super JetPak DX (World) (GB Compatible) (Aftermarket)" + description "Super JetPak DX (World) (GB Compatible) (Aftermarket)" + rom ( name "Super JetPak DX (World) (GB Compatible) (Aftermarket).gbc" size 131072 crc 22def6f9 sha1 e7438db01fbbdeea2404dbf3d093370421ec4e1c ) ) game ( @@ -42571,9 +43690,9 @@ game ( ) game ( - name "Super Mario Bros. Deluxe (Japan) (En) (Rev 1) (NP)" - description "Super Mario Bros. Deluxe (Japan) (En) (Rev 1) (NP)" - rom ( name "Super Mario Bros. Deluxe (Japan) (En) (Rev 1) (NP).gbc" size 1048576 crc f4e91f63 sha1 e61d564e1ff19eb4b7c62a6cd96214f2bca4b01d ) + name "Super Mario Bros. Deluxe (Japan) (Rev 1) (NP)" + description "Super Mario Bros. Deluxe (Japan) (Rev 1) (NP)" + rom ( name "Super Mario Bros. Deluxe (Japan) (Rev 1) (NP).gbc" size 1048576 crc f4e91f63 sha1 e61d564e1ff19eb4b7c62a6cd96214f2bca4b01d ) ) game ( @@ -42673,9 +43792,9 @@ game ( ) game ( - name "Survival Kids 2 - Dasshutsu!! Futago-Jima! (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP)" - description "Survival Kids 2 - Dasshutsu!! Futago-Jima! (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP)" - rom ( name "Survival Kids 2 - Dasshutsu!! Futago-Jima! (Japan) (Rev 1) (SGB Enhanced, GB Compatible) (NP).gbc" size 1048576 crc bd366d22 sha1 505ffc930057719cf28560333342dfd6944c15da ) + name "Survival Kids 2 - Dasshutsu!! Futago-Jima! (Japan) (Rev 1) (Possible Proto) (SGB Enhanced, GB Compatible) (NP)" + description "Survival Kids 2 - Dasshutsu!! Futago-Jima! (Japan) (Rev 1) (Possible Proto) (SGB Enhanced, GB Compatible) (NP)" + rom ( name "Survival Kids 2 - Dasshutsu!! Futago-Jima! (Japan) (Rev 1) (Possible Proto) (SGB Enhanced, GB Compatible) (NP).gbc" size 1048576 crc bd366d22 sha1 505ffc930057719cf28560333342dfd6944c15da ) ) game ( @@ -42745,9 +43864,9 @@ game ( ) game ( - name "Sylvanian Families 2 - Irozuku Mori no Fantasy (Japan) (Rev 1) (NP)" - description "Sylvanian Families 2 - Irozuku Mori no Fantasy (Japan) (Rev 1) (NP)" - rom ( name "Sylvanian Families 2 - Irozuku Mori no Fantasy (Japan) (Rev 1) (NP).gbc" size 2097152 crc ad6f3bdf sha1 c96044d57a2367ffb7b2325aa8d0c9b659e0aaba ) + name "Sylvanian Families 2 - Irozuku Mori no Fantasy (Japan) (Rev 1) (Possible Proto)" + description "Sylvanian Families 2 - Irozuku Mori no Fantasy (Japan) (Rev 1) (Possible Proto)" + rom ( name "Sylvanian Families 2 - Irozuku Mori no Fantasy (Japan) (Rev 1) (Possible Proto).gbc" size 2097152 crc ad6f3bdf sha1 c96044d57a2367ffb7b2325aa8d0c9b659e0aaba ) ) game ( @@ -42828,10 +43947,16 @@ game ( rom ( name "Tanimura Hitoshi Ryuu Pachinko Kouryaku Daisakusen - Don Quijote ga Iku (Japan) (SGB Enhanced) (GB Compatible).gbc" size 2097152 crc ce8ae58c sha1 f752e96602274a29006425a06086d8ab45e1075c ) ) +game ( + name "Tank Attack (World) (Aftermarket) (Unl)" + description "Tank Attack (World) (Aftermarket) (Unl)" + rom ( name "Tank Attack (World) (Aftermarket) (Unl).gbc" size 262144 crc c77dc518 sha1 4751a6999f4dbe7e66b997372588f6bd15e4d9d0 ) +) + game ( name "Tarzan (France)" description "Tarzan (France)" - rom ( name "Tarzan (France).gbc" size 2097152 crc c503afbb sha1 9a4821570f565f7a1b3c7aba3c7cd2e1c1a0b570 ) + rom ( name "Tarzan (France).gbc" size 2097152 crc c503afbb sha1 9a4821570f565f7a1b3c7aba3c7cd2e1c1a0b570 flags verified ) ) game ( @@ -42882,6 +44007,18 @@ game ( rom ( name "Taxi 3 (France).gbc" size 1048576 crc 2838996f sha1 e43817c673d47b7587f542dcc9f74190c63629ff ) ) +game ( + name "Tazz (World) (2022-10-14) (Aftermarket) (Unl)" + description "Tazz (World) (2022-10-14) (Aftermarket) (Unl)" + rom ( name "Tazz (World) (2022-10-14) (Aftermarket) (Unl).gbc" size 262144 crc 1d97b754 sha1 39226ed715ae8d94f9187c3e5e8a849eac92ca26 ) +) + +game ( + name "Tazz (World) (2022-10-18) (Aftermarket) (Unl)" + description "Tazz (World) (2022-10-18) (Aftermarket) (Unl)" + rom ( name "Tazz (World) (2022-10-18) (Aftermarket) (Unl).gbc" size 262144 crc 6fb0ba23 sha1 543118e54b166ea2b8b5979f8468cc2f5662161f ) +) + game ( name "Tech Deck Skateboarding (USA, Europe)" description "Tech Deck Skateboarding (USA, Europe)" @@ -42990,18 +44127,6 @@ game ( rom ( name "TG Rally 2 (United Kingdom).gbc" size 1048576 crc 795a9992 sha1 906f886c19c4800a7ac2612951e03a092665b22c ) ) -game ( - name "The Powerpuff Girls - Il Terribile Mojo Jojo (Italy) (Proto)" - description "The Powerpuff Girls - Il Terribile Mojo Jojo (Italy) (Proto)" - rom ( name "The Powerpuff Girls - Il Terribile Mojo Jojo (Italy) (Proto).gbc" size 2097152 crc 5c265103 sha1 5ca837632930689fad9086b772928bf5995193f9 ) -) - -game ( - name "The Second Edition Harry Boy - The Secret of the Chamber of Secrets (USA) (Unl)" - description "The Second Edition Harry Boy - The Secret of the Chamber of Secrets (USA) (Unl)" - rom ( name "The Second Edition Harry Boy - The Secret of the Chamber of Secrets (USA) (Unl).gbc" size 2097152 crc cba2c784 sha1 99d852998fa84c8c0160cc9dfb2e2165a94756ca ) -) - game ( name "Three Lions (United Kingdom) (En,Fr,De,Es,It,Nl,Sv) (GB Compatible)" description "Three Lions (United Kingdom) (En,Fr,De,Es,It,Nl,Sv) (GB Compatible)" @@ -43074,6 +44199,12 @@ game ( rom ( name "Tiny Toon Adventures - Dizzy's Candy Quest (USA) (Proto).gbc" size 1048576 crc 23bb87a5 sha1 4eb0359a278173ae6c12e9452a7a2a9573a58c77 ) ) +game ( + name "Tir Na Nog (World) (Aftermarket) (Unl)" + description "Tir Na Nog (World) (Aftermarket) (Unl)" + rom ( name "Tir Na Nog (World) (Aftermarket) (Unl).gbc" size 1048576 crc 44524c90 sha1 ac712228460e9d4268f90ec4cf9ac2137fa667c4 ) +) + game ( name "Titi - Le Tour du Monde en 80 Chats (France)" description "Titi - Le Tour du Monde en 80 Chats (France)" @@ -43099,9 +44230,9 @@ game ( ) game ( - name "Tobu Tobu Girl Deluxe (World) (GB Compatible) (Aftermarket) (Homebrew)" - description "Tobu Tobu Girl Deluxe (World) (GB Compatible) (Aftermarket) (Homebrew)" - rom ( name "Tobu Tobu Girl Deluxe (World) (GB Compatible) (Aftermarket) (Homebrew).gbc" size 262144 crc 16650a8b sha1 fe6eef70d48dda741f7ad3b6cc5e753e8cd13239 ) + name "Tobu Tobu Girl Deluxe (World) (GB Compatible) (Aftermarket) (Unl)" + description "Tobu Tobu Girl Deluxe (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Tobu Tobu Girl Deluxe (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 262144 crc 16650a8b sha1 fe6eef70d48dda741f7ad3b6cc5e753e8cd13239 ) ) game ( @@ -43381,9 +44512,9 @@ game ( ) game ( - name "Tower of Evil (World) (Aftermarket) (Homebrew)" - description "Tower of Evil (World) (Aftermarket) (Homebrew)" - rom ( name "Tower of Evil (World) (Aftermarket) (Homebrew).gbc" size 524288 crc aeb6e36f sha1 e9a6a007e935afb522c67d355e0354b0fb9af3c6 ) + name "Tower of Evil (World) (Aftermarket) (Unl)" + description "Tower of Evil (World) (Aftermarket) (Unl)" + rom ( name "Tower of Evil (World) (Aftermarket) (Unl).gbc" size 524288 crc aeb6e36f sha1 e9a6a007e935afb522c67d355e0354b0fb9af3c6 ) ) game ( @@ -43441,9 +44572,9 @@ game ( ) game ( - name "Treasure Island Color (World) (Aftermarket) (Homebrew)" - description "Treasure Island Color (World) (Aftermarket) (Homebrew)" - rom ( name "Treasure Island Color (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 4ab0eb5a sha1 3ebddd4e1f5b3a87726ef34a5b7d36de8c722f36 ) + name "Treasure Island Color (World) (Aftermarket) (Unl)" + description "Treasure Island Color (World) (Aftermarket) (Unl)" + rom ( name "Treasure Island Color (World) (Aftermarket) (Unl).gbc" size 262144 crc 4ab0eb5a sha1 3ebddd4e1f5b3a87726ef34a5b7d36de8c722f36 ) ) game ( @@ -43530,6 +44661,12 @@ game ( rom ( name "Turok 3 - Shadow of Oblivion (USA, Europe) (En,Fr,De,Es).gbc" size 1048576 crc 6d48765e sha1 4b240f6b8e3648f2cbafa2fd6ee4e5b508950122 flags verified ) ) +game ( + name "Tutti Frutti (World) (Aftermarket) (Unl)" + description "Tutti Frutti (World) (Aftermarket) (Unl)" + rom ( name "Tutti Frutti (World) (Aftermarket) (Unl).gbc" size 262144 crc ca377ca6 sha1 53d97b93b9d190de40ed2d81e18b4cbe44f99a23 ) +) + game ( name "Tutty (Europe) (Demo)" description "Tutty (Europe) (Demo)" @@ -43573,9 +44710,9 @@ game ( ) game ( - name "Tycoon Tex (World) (Aftermarket) (Homebrew)" - description "Tycoon Tex (World) (Aftermarket) (Homebrew)" - rom ( name "Tycoon Tex (World) (Aftermarket) (Homebrew).gbc" size 262144 crc fa8436ba sha1 d37bcd93d647ac7efb761c539785a4ef570cdcc8 ) + name "Tycoon Tex (World) (Aftermarket) (Unl)" + description "Tycoon Tex (World) (Aftermarket) (Unl)" + rom ( name "Tycoon Tex (World) (Aftermarket) (Unl).gbc" size 262144 crc fa8436ba sha1 d37bcd93d647ac7efb761c539785a4ef570cdcc8 ) ) game ( @@ -43656,6 +44793,12 @@ game ( rom ( name "Uno (USA) (GB Compatible).gbc" size 1048576 crc f026d509 sha1 20868148461618d1195570775b183a065781ce35 ) ) +game ( + name "UXB (World) (Aftermarket) (Unl)" + description "UXB (World) (Aftermarket) (Unl)" + rom ( name "UXB (World) (Aftermarket) (Unl).gbc" size 262144 crc dd42be4c sha1 6ef7abb689b1c75f613ed253644fbb0f377b7a95 ) +) + game ( name "V-Rally - Championship Edition (Europe) (En,Fr,De,Es)" description "V-Rally - Championship Edition (Europe) (En,Fr,De,Es)" @@ -43674,6 +44817,12 @@ game ( rom ( name "V-Rally - Edition 99 (USA) (En,Fr,Es).gbc" size 1048576 crc da300c6c sha1 638266c9d2d16486c2ed00510176112071b05e2c ) ) +game ( + name "Varmit (World) (Aftermarket) (Unl)" + description "Varmit (World) (Aftermarket) (Unl)" + rom ( name "Varmit (World) (Aftermarket) (Unl).gbc" size 2097152 crc e4827006 sha1 b61876c714c47f62a77304b79f6e34b8d0e7f8a2 ) +) + game ( name "Vegas Games (Europe) (En,Fr,De)" description "Vegas Games (Europe) (En,Fr,De)" @@ -43716,12 +44865,24 @@ game ( rom ( name "Visiteurs, Les (France) (GB Compatible).gbc" size 1048576 crc d843f898 sha1 307b5d80fd7def049d446bf3406ec8d57dfee93d ) ) +game ( + name "VOX (World) (Aftermarket) (Unl)" + description "VOX (World) (Aftermarket) (Unl)" + rom ( name "VOX (World) (Aftermarket) (Unl).gbc" size 262144 crc 346561fb sha1 54a03c77653a19eb1b0d8a6ac82f498f83f35ef2 ) +) + game ( name "VR Sports - Powerboat Racing (USA) (Proto)" description "VR Sports - Powerboat Racing (USA) (Proto)" rom ( name "VR Sports - Powerboat Racing (USA) (Proto).gbc" size 1048576 crc cff671f1 sha1 1c77f032cdd373bfa108ea82c463f8f9f6874c71 ) ) +game ( + name "Wacky Painter (World) (Aftermarket) (Unl)" + description "Wacky Painter (World) (Aftermarket) (Unl)" + rom ( name "Wacky Painter (World) (Aftermarket) (Unl).gbc" size 262144 crc 09daa18d sha1 5425dc675101d63bea03b1c8f46f86fe200ad392 ) +) + game ( name "Wacky Races (Europe) (En,Fr,De,Es,It,Nl)" description "Wacky Races (Europe) (En,Fr,De,Es,It,Nl)" @@ -43734,6 +44895,12 @@ game ( rom ( name "Wacky Races (USA) (En,Fr,Es).gbc" size 1048576 crc 543abb1b sha1 3ec148027d0e5a075d7a4597232e64215c060fa7 ) ) +game ( + name "Wai Xing Tanxian Zhi Xingqiu Dazhan (Taiwan) (Unl)" + description "Wai Xing Tanxian Zhi Xingqiu Dazhan (Taiwan) (Unl)" + rom ( name "Wai Xing Tanxian Zhi Xingqiu Dazhan (Taiwan) (Unl).gbc" size 2097152 crc 4e600093 sha1 32067be39f0bdc354c31a7a02f24ba00b61ae4f1 flags verified ) +) + game ( name "Walt Disney World Quest - Magical Racing Tour (Europe) (Fr,De,Es)" description "Walt Disney World Quest - Magical Racing Tour (Europe) (Fr,De,Es)" @@ -43746,6 +44913,12 @@ game ( rom ( name "Walt Disney World Quest - Magical Racing Tour (USA, Europe).gbc" size 2097152 crc 56beb694 sha1 529289ccfd21397405d08305409c5d9b2119505e ) ) +game ( + name "Wangzu Tiantang (Taiwan) (Unl)" + description "Wangzu Tiantang (Taiwan) (Unl)" + rom ( name "Wangzu Tiantang (Taiwan) (Unl).gbc" size 2097152 crc dee597fc sha1 4e01c94ab55f2d299bd0bea83c0ef500fb9e738c ) +) + game ( name "Warau Inu no Bouken - Silly Go Lucky! (Japan)" description "Warau Inu no Bouken - Silly Go Lucky! (Japan)" @@ -43819,21 +44992,21 @@ game ( ) game ( - name "Waternet (World) (Aftermarket) (Homebrew)" - description "Waternet (World) (Aftermarket) (Homebrew)" - rom ( name "Waternet (World) (Aftermarket) (Homebrew).gbc" size 32768 crc ef202121 sha1 a11f931b18ba42f48a91e817b7117fa0e3e79518 ) + name "Waternet (World) (GB Compatible) (Aftermarket) (Unl)" + description "Waternet (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Waternet (World) (GB Compatible) (Aftermarket) (Unl).gbc" size 32768 crc ef202121 sha1 a11f931b18ba42f48a91e817b7117fa0e3e79518 ) ) game ( - name "Waternet (World) (Batteryless Save Flash Cartridge Version) (Aftermarket) (Homebrew)" - description "Waternet (World) (Batteryless Save Flash Cartridge Version) (Aftermarket) (Homebrew)" - rom ( name "Waternet (World) (Batteryless Save Flash Cartridge Version) (Aftermarket) (Homebrew).gbc" size 131072 crc 75591760 sha1 e04b01ada17e6924503029cd09a107b7f5d06f17 ) + name "Waternet (World) (Batteryless Save Flash Cartridge Version) (GB Compatible) (Aftermarket) (Unl)" + description "Waternet (World) (Batteryless Save Flash Cartridge Version) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Waternet (World) (Batteryless Save Flash Cartridge Version) (GB Compatible) (Aftermarket) (Unl).gbc" size 131072 crc 75591760 sha1 e04b01ada17e6924503029cd09a107b7f5d06f17 ) ) game ( - name "Waternet (World) (Batteryless Generic Save Flash Cartridge Version) (Aftermarket) (Homebrew)" - description "Waternet (World) (Batteryless Generic Save Flash Cartridge Version) (Aftermarket) (Homebrew)" - rom ( name "Waternet (World) (Batteryless Generic Save Flash Cartridge Version) (Aftermarket) (Homebrew).gbc" size 131072 crc f8402dc5 sha1 6feb4178589e87c6267683078201f0630cca23d6 ) + name "Waternet (World) (Batteryless Generic Save Flash Cartridge Version) (GB Compatible) (Aftermarket) (Unl)" + description "Waternet (World) (Batteryless Generic Save Flash Cartridge Version) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Waternet (World) (Batteryless Generic Save Flash Cartridge Version) (GB Compatible) (Aftermarket) (Unl).gbc" size 131072 crc f8402dc5 sha1 6feb4178589e87c6267683078201f0630cca23d6 ) ) game ( @@ -43884,6 +45057,12 @@ game ( rom ( name "Wetrix GB (Japan) (SGB Enhanced) (GB Compatible).gbc" size 1048576 crc 6215c5b3 sha1 92944052b6e4448abf0103f085998662e0140825 ) ) +game ( + name "Where's My Cake (World) (GB Compatible) (Aftermarket) (Unl)" + description "Where's My Cake (World) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Where's My Cake (World) (GB Compatible) (Aftermarket) (Unl).GBC" size 65536 crc 28114b89 sha1 05b90f0a7e92d42b3008c749d5edeab2a2eae973 ) +) + game ( name "Who Wants to Be a Millionaire - 2nd Edition (USA)" description "Who Wants to Be a Millionaire - 2nd Edition (USA)" @@ -43897,9 +45076,9 @@ game ( ) game ( - name "Wing Warriors (USA) (En,Fr,Es) (Aftermarket) (Homebrew)" - description "Wing Warriors (USA) (En,Fr,Es) (Aftermarket) (Homebrew)" - rom ( name "Wing Warriors (USA) (En,Fr,Es) (Aftermarket) (Homebrew).gbc" size 131072 crc 04e04980 sha1 574cf911d6d8f73699203cd6db42ceec777f7f93 ) + name "Wing Warriors (World) (En,Fr,Es) (GB Compatible) (Aftermarket) (Unl)" + description "Wing Warriors (World) (En,Fr,Es) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Wing Warriors (World) (En,Fr,Es) (GB Compatible) (Aftermarket) (Unl).gbc" size 131072 crc 7328cfb6 sha1 039874148f27ffe533de4ef086f29e724a3217a9 ) ) game ( @@ -43927,9 +45106,9 @@ game ( ) game ( - name "Wizard of Wor (World) (Aftermarket) (Homebrew)" - description "Wizard of Wor (World) (Aftermarket) (Homebrew)" - rom ( name "Wizard of Wor (World) (Aftermarket) (Homebrew).gbc" size 524288 crc 492a9529 sha1 a391646d259a39e7026574145e0a2bacf5f6937b ) + name "Wizard of Wor (World) (Aftermarket) (Unl)" + description "Wizard of Wor (World) (Aftermarket) (Unl)" + rom ( name "Wizard of Wor (World) (Aftermarket) (Unl).gbc" size 524288 crc 492a9529 sha1 a391646d259a39e7026574145e0a2bacf5f6937b ) ) game ( @@ -43999,9 +45178,9 @@ game ( ) game ( - name "World Cup (World) (Aftermarket) (Homebrew)" - description "World Cup (World) (Aftermarket) (Homebrew)" - rom ( name "World Cup (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 56b3ab5f sha1 075890510b3ef205ca50ebbbcf7c0a0c56032cb9 ) + name "World Cup (World) (Aftermarket) (Unl)" + description "World Cup (World) (Aftermarket) (Unl)" + rom ( name "World Cup (World) (Aftermarket) (Unl).gbc" size 262144 crc 56b3ab5f sha1 075890510b3ef205ca50ebbbcf7c0a0c56032cb9 ) ) game ( @@ -44095,27 +45274,27 @@ game ( ) game ( - name "Xi You Ji (Taiwan) (Unl)" - description "Xi You Ji (Taiwan) (Unl)" - rom ( name "Xi You Ji (Taiwan) (Unl).gbc" size 524288 crc f2fc4884 sha1 1f1bb35c47b8bd51edf9511513bb5e5e5e07eec1 ) + name "Xian Dan Chaoren - Ultraman (Taiwan) (Unl)" + description "Xian Dan Chaoren - Ultraman (Taiwan) (Unl)" + rom ( name "Xian Dan Chaoren - Ultraman (Taiwan) (Unl).gbc" size 2097152 crc e81701e8 sha1 2a96a6f2164d80aa81eebdebc0d8f2ff45c3180f ) ) game ( - name "Xiao Tai Ji - Shen Hua Li Xian (Taiwan) (Unl)" - description "Xiao Tai Ji - Shen Hua Li Xian (Taiwan) (Unl)" - rom ( name "Xiao Tai Ji - Shen Hua Li Xian (Taiwan) (Unl).gbc" size 2097152 crc 322c3816 sha1 c89f8d19a52ab5f183a03d65680bf80d1af4a0ee ) + name "Xiao Taiji - Shenhua Lixian (Taiwan) (Unl)" + description "Xiao Taiji - Shenhua Lixian (Taiwan) (Unl)" + rom ( name "Xiao Taiji - Shenhua Lixian (Taiwan) (Unl).gbc" size 2097152 crc 322c3816 sha1 c89f8d19a52ab5f183a03d65680bf80d1af4a0ee ) ) game ( - name "Xin Feng Shen Bang (Taiwan) (Unl)" - description "Xin Feng Shen Bang (Taiwan) (Unl)" - rom ( name "Xin Feng Shen Bang (Taiwan) (Unl).gbc" size 2097152 crc cf8bd780 sha1 99344231bfe3f73dfb95766e0d02a43fbf2e4acc ) + name "Xin Fengshenbang (Taiwan) (Unl)" + description "Xin Fengshenbang (Taiwan) (Unl)" + rom ( name "Xin Fengshenbang (Taiwan) (Unl).gbc" size 2097152 crc cf8bd780 sha1 99344231bfe3f73dfb95766e0d02a43fbf2e4acc ) ) game ( - name "Xin Guangming Yu Hei'an 2 - Zhushen De Yichan (Taiwan) (Unl)" - description "Xin Guangming Yu Hei'an 2 - Zhushen De Yichan (Taiwan) (Unl)" - rom ( name "Xin Guangming Yu Hei'an 2 - Zhushen De Yichan (Taiwan) (Unl).gbc" size 1048576 crc 0881ed84 sha1 c1985e8a7a241af5a35748528b3e6a9d49b788e7 ) + name "Xin Guangming Yu Hei'an 2 - Zhushen de Yichan (Taiwan) (Unl)" + description "Xin Guangming Yu Hei'an 2 - Zhushen de Yichan (Taiwan) (Unl)" + rom ( name "Xin Guangming Yu Hei'an 2 - Zhushen de Yichan (Taiwan) (Unl).gbc" size 1048576 crc 0881ed84 sha1 c1985e8a7a241af5a35748528b3e6a9d49b788e7 ) ) game ( @@ -44125,15 +45304,21 @@ game ( ) game ( - name "Xin Tiao Hui Yi (Taiwan) (Unl)" - description "Xin Tiao Hui Yi (Taiwan) (Unl)" - rom ( name "Xin Tiao Hui Yi (Taiwan) (Unl).gbc" size 4194304 crc 104968e7 sha1 86e2b619976d22818e4a7c23d7f8a97e9358e51e ) + name "Xingqiu Dazhan II - Kelong Ren Zhanyi (Taiwan) (Unl)" + description "Xingqiu Dazhan II - Kelong Ren Zhanyi (Taiwan) (Unl)" + rom ( name "Xingqiu Dazhan II - Kelong Ren Zhanyi (Taiwan) (Unl).gbc" size 2097152 crc 6df86db6 sha1 b6089f2d1064d1851a89ffb20f8e2f8aeb5fe732 ) ) game ( - name "Xing Qiu Da Zhan II - Ke Long Ren Zhan Yi (Taiwan) (Unl)" - description "Xing Qiu Da Zhan II - Ke Long Ren Zhan Yi (Taiwan) (Unl)" - rom ( name "Xing Qiu Da Zhan II - Ke Long Ren Zhan Yi (Taiwan) (Unl).gbc" size 2097152 crc 6df86db6 sha1 b6089f2d1064d1851a89ffb20f8e2f8aeb5fe732 ) + name "Xintiao Huiyi (Taiwan) (Unl)" + description "Xintiao Huiyi (Taiwan) (Unl)" + rom ( name "Xintiao Huiyi (Taiwan) (Unl).gbc" size 4194304 crc 104968e7 sha1 86e2b619976d22818e4a7c23d7f8a97e9358e51e ) +) + +game ( + name "Xiyou Ji (Taiwan) (Unl)" + description "Xiyou Ji (Taiwan) (Unl)" + rom ( name "Xiyou Ji (Taiwan) (Unl).gbc" size 524288 crc f2fc4884 sha1 1f1bb35c47b8bd51edf9511513bb5e5e5e07eec1 ) ) game ( @@ -44167,9 +45352,9 @@ game ( ) game ( - name "Xtreme Wheels (Japan) (NP)" - description "Xtreme Wheels (Japan) (NP)" - rom ( name "Xtreme Wheels (Japan) (NP).gbc" size 1048576 crc 30132ab8 sha1 a9f67640d20771e64b1474144fcca9beebdde85d ) + name "Xtreme Wheels (Japan) (Possible Proto) (NP)" + description "Xtreme Wheels (Japan) (Possible Proto) (NP)" + rom ( name "Xtreme Wheels (Japan) (Possible Proto) (NP).gbc" size 1048576 crc 30132ab8 sha1 a9f67640d20771e64b1474144fcca9beebdde85d ) ) game ( @@ -44184,6 +45369,12 @@ game ( rom ( name "Yars' Revenge (USA, Europe) (GB Compatible).gbc" size 1048576 crc d6a26444 sha1 45fb176d539ae4a65af1f6340a9bd398dd7956d2 ) ) +game ( + name "Year After, The (World) (En,Fr,Pt) (GB Compatible) (Aftermarket) (Unl)" + description "Year After, The (World) (En,Fr,Pt) (GB Compatible) (Aftermarket) (Unl)" + rom ( name "Year After, The (World) (En,Fr,Pt) (GB Compatible) (Aftermarket) (Unl).gbc" size 2097152 crc b304833d sha1 c2be46b8230bfcf509d031902c9bf144de97ef58 ) +) + game ( name "Yin Ban Zhongwen RPG Zhanlve + Dongzuo + Yizhi 12 in 1 (Taiwan) (Unl)" description "Yin Ban Zhongwen RPG Zhanlve + Dongzuo + Yizhi 12 in 1 (Taiwan) (Unl)" @@ -44202,6 +45393,12 @@ game ( rom ( name "Yingxiong Tianxia (Taiwan) (Zh) (Unl).gbc" size 2097152 crc 5f2d6317 sha1 41f5f0c11e4e99b7d2d1433b09922a0fd2c48d7f ) ) +game ( + name "Yixing VS Tiexue Zhanshi (Taiwan) (Unl)" + description "Yixing VS Tiexue Zhanshi (Taiwan) (Unl)" + rom ( name "Yixing VS Tiexue Zhanshi (Taiwan) (Unl).gbc" size 2097152 crc 45b048ee sha1 5ab50c386a0d9644f0d0337b6a1db22f5cb6ceb9 ) +) + game ( name "Yogi Bear - Great Balloon Blast (USA)" description "Yogi Bear - Great Balloon Blast (USA)" @@ -44209,9 +45406,9 @@ game ( ) game ( - name "Yong Zhe Dou E Long VIII (Taiwan) (Unl)" - description "Yong Zhe Dou E Long VIII (Taiwan) (Unl)" - rom ( name "Yong Zhe Dou E Long VIII (Taiwan) (Unl).gbc" size 2097152 crc 8b8b84ec sha1 d71788eabb4d2535e31773338523a721e2784bb5 ) + name "Yongzhe Dou E Long VIII (Taiwan) (Unl)" + description "Yongzhe Dou E Long VIII (Taiwan) (Unl)" + rom ( name "Yongzhe Dou E Long VIII (Taiwan) (Unl).gbc" size 2097152 crc 8b8b84ec sha1 d71788eabb4d2535e31773338523a721e2784bb5 ) ) game ( @@ -44298,18 +45495,18 @@ game ( rom ( name "Yu-Gi-Oh! Duel Monsters III - Tri Holy God Advant (Japan).gbc" size 4194304 crc 9f9dbab4 sha1 dc4753a2f12360d921a147287673b4448394a9d3 flags verified ) ) -game ( - name "Yue Nan Zhan Yi 3 (Taiwan) (Unl)" - description "Yue Nan Zhan Yi 3 (Taiwan) (Unl)" - rom ( name "Yue Nan Zhan Yi 3 (Taiwan) (Unl).gbc" size 2097152 crc 9268bb9f sha1 17ceb35582c89303f206eb7e93cbfe8a4c2b5aaa ) -) - game ( name "Yuenan Zhanyi - Chong Jian Tian Ri (Taiwan) (Unl)" description "Yuenan Zhanyi - Chong Jian Tian Ri (Taiwan) (Unl)" rom ( name "Yuenan Zhanyi - Chong Jian Tian Ri (Taiwan) (Unl).gbc" size 2097152 crc c19b912a sha1 09b0f8fe8c7948e847ad01f24d05513c49b77ad5 ) ) +game ( + name "Yuenan Zhanyi 3 (Taiwan) (Unl)" + description "Yuenan Zhanyi 3 (Taiwan) (Unl)" + rom ( name "Yuenan Zhanyi 3 (Taiwan) (Unl).gbc" size 2097152 crc 9268bb9f sha1 17ceb35582c89303f206eb7e93cbfe8a4c2b5aaa ) +) + game ( name "Yuenan Zhanyi X - Shenru Dihou (Taiwan) (Unl)" description "Yuenan Zhanyi X - Shenru Dihou (Taiwan) (Unl)" @@ -44317,9 +45514,15 @@ game ( ) game ( - name "Zap'em (World) (Aftermarket) (Homebrew)" - description "Zap'em (World) (Aftermarket) (Homebrew)" - rom ( name "Zap'em (World) (Aftermarket) (Homebrew).gbc" size 262144 crc 66f17c5e sha1 63d44c8c2492dc32bcf632feb2d994e9af25fc97 ) + name "Zagan Warrior (World) (Aftermarket) (Unl)" + description "Zagan Warrior (World) (Aftermarket) (Unl)" + rom ( name "Zagan Warrior (World) (Aftermarket) (Unl).gbc" size 262144 crc e770f71a sha1 99d9267946e7bf26942a8276863ba50ef0b8f90e ) +) + +game ( + name "Zap'em (World) (Aftermarket) (Unl)" + description "Zap'em (World) (Aftermarket) (Unl)" + rom ( name "Zap'em (World) (Aftermarket) (Unl).gbc" size 262144 crc 66f17c5e sha1 63d44c8c2492dc32bcf632feb2d994e9af25fc97 ) ) game ( @@ -44395,21 +45598,21 @@ game ( ) game ( - name "Zhen San Guo Wu Shuang 2 - Shin Sangokumusou (Taiwan) (Unl)" - description "Zhen San Guo Wu Shuang 2 - Shin Sangokumusou (Taiwan) (Unl)" - rom ( name "Zhen San Guo Wu Shuang 2 - Shin Sangokumusou (Taiwan) (Unl).gbc" size 2097152 crc 95c322c9 sha1 e6ab58d212de83cb18e9eadac4cee5d0f6e4e31f ) + name "Zhen Sanguo Wushuang 2 - Shin Sangokumusou (Taiwan) (Unl)" + description "Zhen Sanguo Wushuang 2 - Shin Sangokumusou (Taiwan) (Unl)" + rom ( name "Zhen Sanguo Wushuang 2 - Shin Sangokumusou (Taiwan) (Unl).gbc" size 2097152 crc 95c322c9 sha1 e6ab58d212de83cb18e9eadac4cee5d0f6e4e31f ) ) game ( - name "Zhi Huan Wang - Shou Bu Qu (Taiwan) (Zh) (Unl)" - description "Zhi Huan Wang - Shou Bu Qu (Taiwan) (Zh) (Unl)" - rom ( name "Zhi Huan Wang - Shou Bu Qu (Taiwan) (Zh) (Unl).gbc" size 2097152 crc 8160b3f5 sha1 97d7e0d05205b02c6cb69e68978a3f0fd2e3fde0 ) + name "Zhihuan Wang - Shoubu Qu (Taiwan) (Zh) (Unl)" + description "Zhihuan Wang - Shoubu Qu (Taiwan) (Zh) (Unl)" + rom ( name "Zhihuan Wang - Shoubu Qu (Taiwan) (Zh) (Unl).gbc" size 2097152 crc 8160b3f5 sha1 97d7e0d05205b02c6cb69e68978a3f0fd2e3fde0 ) ) game ( - name "Zhi Zhu Xia III - Dian Ying Ban (Taiwan) (Unl)" - description "Zhi Zhu Xia III - Dian Ying Ban (Taiwan) (Unl)" - rom ( name "Zhi Zhu Xia III - Dian Ying Ban (Taiwan) (Unl).gbc" size 2097152 crc d311efcc sha1 9eb0c468d5f898ef7b4d6ff74d7ff497b8c7818d ) + name "Zhizhu Xia III - Dianying Ban (Taiwan) (Unl)" + description "Zhizhu Xia III - Dianying Ban (Taiwan) (Unl)" + rom ( name "Zhizhu Xia III - Dianying Ban (Taiwan) (Unl).gbc" size 2097152 crc d311efcc sha1 9eb0c468d5f898ef7b4d6ff74d7ff497b8c7818d ) ) game ( @@ -44418,12 +45621,24 @@ game ( rom ( name "Zidane - Football Generation (Europe) (En,Fr,De,Es,It).gbc" size 1048576 crc e96dbfb5 sha1 06385470bb0b4ef6c743ab6a5e7f8d7a97f6100b ) ) +game ( + name "Zidane - Football Generation (Europe) (En,Fr,De,Es,It) (Beta)" + description "Zidane - Football Generation (Europe) (En,Fr,De,Es,It) (Beta)" + rom ( name "Zidane - Football Generation (Europe) (En,Fr,De,Es,It) (Beta).gbc" size 1048576 crc c2fde3c4 sha1 cba5d2fc5dbbe184e9ed58afe8525044bd8ea040 ) +) + game ( name "Zoboomafoo - Playtime in Zobooland (USA)" description "Zoboomafoo - Playtime in Zobooland (USA)" rom ( name "Zoboomafoo - Playtime in Zobooland (USA).gbc" size 1048576 crc 38d91885 sha1 a85a113bc266325f807f110daaf30feeea4b2738 ) ) +game ( + name "Zodiac (World) (Aftermarket) (Unl)" + description "Zodiac (World) (Aftermarket) (Unl)" + rom ( name "Zodiac (World) (Aftermarket) (Unl).gbc" size 262144 crc c4d86c05 sha1 73bd9e6e3bede8de489680ba84129e8fd17abbc0 ) +) + game ( name "Zoids - Jashin Fukkatsu! Genobreaker Hen (Japan) (SGB Enhanced) (GB Compatible)" description "Zoids - Jashin Fukkatsu! Genobreaker Hen (Japan) (SGB Enhanced) (GB Compatible)" From 4fdebd5dd556a7dcda5de033adae4e97471484cc Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 23 Apr 2023 22:16:38 -0700 Subject: [PATCH 182/290] CHANGES: Update for 0.10.2 --- CHANGES | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 58a465c51..9bb1489a9 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,17 @@ Features: Emulation fixes: - GB Serialize: Add missing Pocket Cam state to savestates - GB Video: Implement DMG-style sprite ordering + - GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) +Other fixes: + - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) +Misc: + - Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826) + - GB Serialize: Add missing savestate support for MBC6 and NT (newer) + - GBA: Improve detection of valid ELF ROMs + - Scripting: Add `callbacks:oneshot` for single-call callbacks + +0.10.2: (2023-04-23) +Emulation fixes: - GBA Audio: Fix improperly deserializing GB audio registers (fixes mgba.io/i/2793) - GBA Audio: Clear GB audio state when disabled - GBA Memory: Make VRAM access stalls only apply to BG RAM @@ -15,14 +26,12 @@ Emulation fixes: - 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) - GBA Video: Fix interpolation issues with OpenGL renderer Other fixes: - Core: Allow sending thread requests to a crashed core (fixes mgba.io/i/2784) - FFmpeg: Force lower sample rate for codecs not supporting high rates (fixes mgba.io/i/2869) - Qt: Fix crash when attempting to use OpenGL 2.1 to 3.1 (fixes mgba.io/i/2794) - Qt: Disable sync while running scripts from main thread (fixes mgba.io/i/2738) - - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) - Qt: Properly cap number of attached players by platform (fixes mgba.io/i/2807) - Qt: Disable attempted linking betwen incompatible platforms (fixes mgba.io/i/2702) - Qt: Fix modifier key names in shortcut editor (fixes mgba.io/i/2817) @@ -32,19 +41,16 @@ Other fixes: - Qt: Fix black screen when starting with a game (fixes mgba.io/i/2781) - Qt: Fix OSD on modern macOS (fixes mgba.io/i/2736) - Qt: Fix checked state of mute menu option at load (fixes mgba.io/i/2701) + - Qt: Remove OpenGL proxy thread and override SwapInterval directly instead - Scripting: Fix receiving packets for client sockets - Scripting: Fix empty receive calls returning unknown error on Windows - Scripting: Return proper callback ID from socket.add - Vita: Work around broken mktime implementation in Vita SDK (fixes mgba.io/i/2876) Misc: - - Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826) - - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - - GBA: Improve detection of valid ELF ROMs - Qt: Include wayland QPA in AppImage (fixes mgba.io/i/2796) - Qt: Stop eating boolean action key events (fixes mgba.io/i/2636) - Qt: Automatically change video file extension as appropriate - Qt: Swap P1 and other player's save if P1 loaded it first (closes mgba.io/i/2750) - - Scripting: Add `callbacks:oneshot` for single-call callbacks 0.10.1: (2023-01-10) Emulation fixes: From 3a59d9cb833bcfa41f5eaa222143e763d0bd0ed3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 25 Apr 2023 03:24:03 -0700 Subject: [PATCH 183/290] Res: Add script for doing fake analog input by PWM-ing the d-pad --- res/scripts/analog-interpolate.lua | 77 ++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 res/scripts/analog-interpolate.lua diff --git a/res/scripts/analog-interpolate.lua b/res/scripts/analog-interpolate.lua new file mode 100644 index 000000000..d9d985a20 --- /dev/null +++ b/res/scripts/analog-interpolate.lua @@ -0,0 +1,77 @@ +local state = {} +state.period = 4 +state.phase = 0 +state.x = 0 +state.y = 0 + +function state.update() + state.phase = state.phase + 1 + if state.phase == state.period then + state.phase = 0 + end + if state.phase == 0 then + if input.activeGamepad then + local x = input.activeGamepad.axes[1] / 30000 + local y = input.activeGamepad.axes[2] / 30000 + -- Map the circle onto a square, since we don't + -- want to have a duty of 1/sqrt(2) on the angles + local theta = math.atan(y, x) + local r = math.sqrt(x * x + y * y) + if theta < math.pi * -3 / 4 then + r = -r / math.cos(theta) + elseif theta < math.pi * -1 / 4 then + r = -r / math.sin(theta) + elseif theta < math.pi * 1 / 4 then + r = r / math.cos(theta) + elseif theta < math.pi * 3 / 4 then + r = r / math.sin(theta) + elseif theta < math.pi * 5 / 4 then + r = -r / math.cos(theta) + end + state.x = math.cos(theta) * r + state.y = math.sin(theta) * r + else + state.x = 0 + state.y = 0 + end + end +end + +function state.read() + emu:clearKeys(0xF0) + if math.floor(math.abs(state.x) * state.period) > state.phase then + if state.x > 0 then + emu:addKey(C.GB_KEY.RIGHT) + else + emu:addKey(C.GB_KEY.LEFT) + end + end + if math.floor(math.abs(state.y) * state.period) > state.phase then + if state.y > 0 then + emu:addKey(C.GB_KEY.DOWN) + else + emu:addKey(C.GB_KEY.UP) + end + end + + -- The duty cycle approach can confuse menus and the like, + -- so the POV hat setting should force a direction on + if input.activeGamepad and #input.activeGamepad.hats > 0 then + local hat = input.activeGamepad.hats[1] + if hat & C.INPUT_DIR.UP ~= 0 then + emu:addKey(C.GB_KEY.UP) + end + if hat & C.INPUT_DIR.DOWN ~= 0 then + emu:addKey(C.GB_KEY.DOWN) + end + if hat & C.INPUT_DIR.LEFT ~= 0 then + emu:addKey(C.GB_KEY.LEFT) + end + if hat & C.INPUT_DIR.RIGHT ~= 0 then + emu:addKey(C.GB_KEY.RIGHT) + end + end +end + +callbacks:add("frame", state.update) +callbacks:add("keysRead", state.read) From 622c2491b9836c966eb3a34ab3f9385683690e59 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 26 Apr 2023 00:48:12 -0700 Subject: [PATCH 184/290] Debugger: Reject traces with negative trace amounts (fixes #2900) --- src/debugger/cli-debugger.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index 12cd7af18..3dce8e255 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -809,10 +809,14 @@ static void _listWatchpoints(struct CLIDebugger* debugger, struct CLIDebugVector } static void _trace(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - if (!dv || dv->type != CLIDV_INT_TYPE) { + if (!dv) { debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); return; } + if (dv->type != CLIDV_INT_TYPE || dv->intValue < 0) { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + return; + } debugger->traceRemaining = dv->intValue; if (debugger->traceVf) { From ba6865b570666bf63e0fe8f8954d108cd4addf8f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 26 Apr 2023 01:05:11 -0700 Subject: [PATCH 185/290] mGUI: Enable auto-softpatching (closes #2899) --- CHANGES | 1 + src/feature/gui/gui-runner.c | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 9bb1489a9..ed9e04162 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,7 @@ Misc: - Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826) - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs + - mGUI: Enable auto-softpatching (closes mgba.io/i/2899) - Scripting: Add `callbacks:oneshot` for single-call callbacks 0.10.2: (2023-04-23) diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 140e5f637..28c3bd74e 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -456,6 +456,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { mLOG(GUI_RUNNER, DEBUG, "Loading save..."); mCoreAutoloadSave(runner->core); mCoreAutoloadCheats(runner->core); + mCoreAutoloadPatch(runner->core); if (runner->setup) { mLOG(GUI_RUNNER, DEBUG, "Setting up runner..."); runner->setup(runner); From 6dbd977c7376eef8fb92f197b9006817c27f7573 Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Tue, 11 Apr 2023 17:48:34 -0500 Subject: [PATCH 186/290] Scripting: shim print()/warn() in Lua --- src/script/engines/lua.c | 45 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index 25d3b621b..ae749b711 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -60,6 +60,7 @@ static int _luaGetList(lua_State* lua); static int _luaLenList(lua_State* lua); static int _luaRequireShim(lua_State* lua); +static int _luaPrintShim(lua_State* lua); static const char* _socketLuaSource = "socket = {\n" @@ -414,6 +415,14 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS lua_getglobal(luaContext->lua, "require"); luaContext->require = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX); + lua_pushliteral(luaContext->lua, "log"); + lua_pushcclosure(luaContext->lua, _luaPrintShim, 1); + lua_setglobal(luaContext->lua, "print"); + + lua_pushliteral(luaContext->lua, "warn"); + lua_pushcclosure(luaContext->lua, _luaPrintShim, 1); + lua_setglobal(luaContext->lua, "warn"); + HashTableInit(&luaContext->d.docroot, 0, (void (*)(void*)) mScriptValueDeref); int status = luaL_dostring(luaContext->lua, _socketLuaSource); @@ -1289,7 +1298,7 @@ int _luaGetTable(lua_State* lua) { } lua_pop(lua, 2); - obj = mScriptContextAccessWeakref(luaContext->d.context, obj); + obj = mScriptContextAccessWeakref(luaContext->d.context, obj); if (obj->type->base == mSCRIPT_TYPE_WRAPPER) { obj = mScriptValueUnwrap(obj); } @@ -1527,3 +1536,37 @@ static int _luaRequireShim(lua_State* lua) { int newtop = lua_gettop(luaContext->lua); return newtop - oldtop + 1; } + +static int _luaPrintShim(lua_State* lua) { + int n = lua_gettop(lua); + + lua_getglobal(lua, "console"); + lua_insert(lua, 1); + + // The first upvalue is either "log" or "warn" + lua_getglobal(lua, "console"); + lua_pushvalue(lua, lua_upvalueindex(1)); + lua_gettable(lua, -2); + + lua_insert(lua, 1); + lua_pop(lua, 1); + + // TODO when console:log is variadic and stringifies by itself: + // lua_call(lua, n + 1, 0); + + // Until then, stringify and concatenate: + for (int i = 0; i < n; i++) { + luaL_tolstring(lua, i * 2 + 3, NULL); + lua_replace(lua, i * 2 + 3); + if (i == 0) { + lua_pushliteral(lua, ""); + } else { + lua_pushliteral(lua, "\t"); + } + lua_insert(lua, i * 2 + 3); + } + n = n * 2 - 1; + lua_concat(lua, n + 1); + lua_call(lua, 2, 0); + return 0; +} From 75910bcdabaeb1846ecc248f6725d6b650fb43a2 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 26 Apr 2023 23:19:50 -0700 Subject: [PATCH 187/290] Qt: Add QPA info to report view --- src/platform/qt/ReportView.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/qt/ReportView.cpp b/src/platform/qt/ReportView.cpp index a380d04de..04944ef5c 100644 --- a/src/platform/qt/ReportView.cpp +++ b/src/platform/qt/ReportView.cpp @@ -127,6 +127,7 @@ void ReportView::generateReport() { swReport << QString("Build architecture: %1").arg(QSysInfo::buildCpuArchitecture()); swReport << QString("Run architecture: %1").arg(QSysInfo::currentCpuArchitecture()); swReport << QString("Qt version: %1").arg(QLatin1String(qVersion())); + swReport << QString("Qt QPA platform: %1").arg(QGuiApplication::platformName()); #ifdef USE_FFMPEG QStringList libavVers; libavVers << QLatin1String(LIBAVCODEC_IDENT); From 65a0b63c8253dbfb967be5f9a874a183a66cf193 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 28 Apr 2023 19:35:03 -0700 Subject: [PATCH 188/290] GB Audio: Fix channels 1/2 staying muted if restarted after long silence --- CHANGES | 1 + src/gb/audio.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index ed9e04162..ae611c1cf 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,7 @@ Features: - New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81 - Debugger: Add range watchpoints Emulation fixes: + - GB Audio: Fix channels 1/2 staying muted if restarted after long silence - GB Serialize: Add missing Pocket Cam state to savestates - GB Video: Implement DMG-style sprite ordering - GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) diff --git a/src/gb/audio.c b/src/gb/audio.c index acd57d6d6..9130c8711 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -202,6 +202,7 @@ void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) { --audio->ch1.control.length; } } + audio->ch1.lastUpdate = mTimingCurrentTime(audio->timing); _updateSquareSample(&audio->ch1); } *audio->nr52 &= ~0x0001; @@ -249,6 +250,7 @@ void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) { --audio->ch2.control.length; } } + audio->ch2.lastUpdate = mTimingCurrentTime(audio->timing); _updateSquareSample(&audio->ch2); } *audio->nr52 &= ~0x0002; From eb26b3c387400d6a5d4bcc2d3e4ab5b2541980fb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 28 Apr 2023 22:45:39 -0700 Subject: [PATCH 189/290] GBA BIOS: Fix clobbering registers with word-sized CpuSet --- CHANGES | 1 + src/gba/hle-bios.c | 8 ++++---- src/gba/hle-bios.s | 12 ++++++------ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index ae611c1cf..9496d97a1 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,7 @@ Emulation fixes: - GB Audio: Fix channels 1/2 staying muted if restarted after long silence - GB Serialize: Add missing Pocket Cam state to savestates - GB Video: Implement DMG-style sprite ordering + - GBA BIOS: Fix clobbering registers with word-sized CpuSet - GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) Other fixes: - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) diff --git a/src/gba/hle-bios.c b/src/gba/hle-bios.c index 84737afd4..f82d01f64 100644 --- a/src/gba/hle-bios.c +++ b/src/gba/hle-bios.c @@ -50,13 +50,13 @@ const uint8_t hleBios[GBA_SIZE_BIOS] = { 0x0c, 0x80, 0xbd, 0xe8, 0x30, 0x40, 0x2d, 0xe9, 0x02, 0x46, 0xa0, 0xe1, 0x00, 0xc0, 0xa0, 0xe1, 0x01, 0x50, 0xa0, 0xe1, 0x01, 0x04, 0x12, 0xe3, 0x0f, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x12, 0xe3, 0x05, 0x00, 0x00, 0x0a, - 0x24, 0x45, 0x85, 0xe0, 0x08, 0x00, 0xbc, 0xe8, 0x04, 0x00, 0x55, 0xe1, - 0x08, 0x00, 0xa5, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x14, 0x00, 0x00, 0xea, + 0x24, 0x45, 0x85, 0xe0, 0x08, 0x00, 0xb0, 0xe8, 0x04, 0x00, 0x51, 0xe1, + 0x08, 0x00, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x14, 0x00, 0x00, 0xea, 0x01, 0xc0, 0xcc, 0xe3, 0x01, 0x50, 0xc5, 0xe3, 0xa4, 0x45, 0x85, 0xe0, 0xb0, 0x30, 0xdc, 0xe1, 0x04, 0x00, 0x55, 0xe1, 0xb2, 0x30, 0xc5, 0xb0, 0xfc, 0xff, 0xff, 0xba, 0x0c, 0x00, 0x00, 0xea, 0x01, 0x03, 0x12, 0xe3, - 0x05, 0x00, 0x00, 0x0a, 0x24, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x55, 0xe1, - 0x08, 0x00, 0xbc, 0xb8, 0x08, 0x00, 0xa5, 0xb8, 0xfb, 0xff, 0xff, 0xba, + 0x05, 0x00, 0x00, 0x0a, 0x24, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x51, 0xe1, + 0x08, 0x00, 0xb0, 0xb8, 0x08, 0x00, 0xa1, 0xb8, 0xfb, 0xff, 0xff, 0xba, 0x04, 0x00, 0x00, 0xea, 0xa4, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x55, 0xe1, 0xb2, 0x30, 0xdc, 0xb0, 0xb2, 0x30, 0xc5, 0xb0, 0xfb, 0xff, 0xff, 0xba, 0x17, 0x3e, 0xa0, 0xe3, 0x30, 0x80, 0xbd, 0xe8, 0xf0, 0x47, 0x2d, 0xe9, diff --git a/src/gba/hle-bios.s b/src/gba/hle-bios.s index c891479a5..36e18790b 100644 --- a/src/gba/hle-bios.s +++ b/src/gba/hle-bios.s @@ -209,10 +209,10 @@ tst r2, #0x04000000 beq 1f @ Word add r4, r5, r4, lsr #10 -ldmia r12!, {r3} +ldmia r0!, {r3} 2: -cmp r5, r4 -stmltia r5!, {r3} +cmp r1, r4 +stmltia r1!, {r3} blt 2b b 3f @ Halfword @@ -233,9 +233,9 @@ beq 1f @ Word add r4, r5, r4, lsr #10 2: -cmp r5, r4 -ldmltia r12!, {r3} -stmltia r5!, {r3} +cmp r1, r4 +ldmltia r0!, {r3} +stmltia r1!, {r3} blt 2b b 3f @ Halfword From a7ffcee399822f8cf5c667a4b97fdf27f4d03559 Mon Sep 17 00:00:00 2001 From: sam-gupta-git <89882137+sam-gupta-git@users.noreply.github.com> Date: Sat, 29 Apr 2023 02:33:16 -0500 Subject: [PATCH 190/290] Qt: Add shortcuts to increment fast forward speed (#2903) Co-authored-by: Vicki Pfau --- src/platform/qt/Window.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index f10bdebb6..c39f3a8f8 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1429,6 +1429,20 @@ void Window::setupMenu(QMenuBar* menubar) { } m_config->updateOption("fastForwardRatio"); + addGameAction(tr("Increase fast forward speed"), "fastForwardUp", [this] { + float newRatio = m_config->getOption("fastForwardRatio", 1.0f).toFloat() + 1.0f; + if (newRatio >= 3.0f) { + m_config->setOption("fastForwardRatio", QVariant(newRatio)); + } + }, "emu"); + + addGameAction(tr("Decrease fast forward speed"), "fastForwardDown", [this] { + float newRatio = m_config->getOption("fastForwardRatio").toFloat() - 1.0f; + if (newRatio >= 2.0f) { + m_config->setOption("fastForwardRatio", QVariant(newRatio)); + } + }, "emu"); + Action* rewindHeld = m_actions.addHeldAction(tr("Rewind (held)"), "holdRewind", [this](bool held) { if (m_controller) { m_controller->setRewinding(held); From c9585b8abe707b694908e0486503f0279762cabd Mon Sep 17 00:00:00 2001 From: Martin Murtiono <43623881+martinmurtiono@users.noreply.github.com> Date: Sat, 29 Apr 2023 02:36:01 -0500 Subject: [PATCH 191/290] Qt: Make rewind speed adjustable (#2902) --- include/mgba/core/config.h | 1 + include/mgba/core/rewind.h | 1 + src/core/config.c | 2 ++ src/core/rewind.c | 1 + src/core/thread.c | 6 +++- src/platform/qt/ConfigController.cpp | 1 + src/platform/qt/SettingsView.cpp | 2 ++ src/platform/qt/SettingsView.ui | 42 ++++++++++++++++++++++++---- src/platform/qt/Window.cpp | 5 ++++ src/platform/sdl/main.c | 1 + 10 files changed, 55 insertions(+), 7 deletions(-) diff --git a/include/mgba/core/config.h b/include/mgba/core/config.h index 80184876b..ef7437487 100644 --- a/include/mgba/core/config.h +++ b/include/mgba/core/config.h @@ -33,6 +33,7 @@ struct mCoreOptions { int frameskip; bool rewindEnable; int rewindBufferCapacity; + int rewindBufferInterval; float fpsTarget; size_t audioBuffers; unsigned sampleRate; diff --git a/include/mgba/core/rewind.h b/include/mgba/core/rewind.h index 04549761d..9d2595557 100644 --- a/include/mgba/core/rewind.h +++ b/include/mgba/core/rewind.h @@ -24,6 +24,7 @@ struct mCoreRewindContext { size_t size; struct VFile* previousState; struct VFile* currentState; + int rewindFrameCounter; #ifndef DISABLE_THREADING bool onThread; diff --git a/src/core/config.c b/src/core/config.c index c0892d33e..78a527332 100644 --- a/src/core/config.c +++ b/src/core/config.c @@ -406,6 +406,7 @@ void mCoreConfigMap(const struct mCoreConfig* config, struct mCoreOptions* opts) _lookupIntValue(config, "frameskip", &opts->frameskip); _lookupIntValue(config, "volume", &opts->volume); _lookupIntValue(config, "rewindBufferCapacity", &opts->rewindBufferCapacity); + _lookupIntValue(config, "rewindBufferInterval", &opts->rewindBufferInterval); _lookupFloatValue(config, "fpsTarget", &opts->fpsTarget); unsigned audioBuffers; if (_lookupUIntValue(config, "audioBuffers", &audioBuffers)) { @@ -448,6 +449,7 @@ void mCoreConfigLoadDefaults(struct mCoreConfig* config, const struct mCoreOptio ConfigurationSetIntValue(&config->defaultsTable, 0, "frameskip", opts->frameskip); ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindEnable", opts->rewindEnable); ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferCapacity", opts->rewindBufferCapacity); + ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindBufferInterval", opts->rewindBufferInterval); ConfigurationSetFloatValue(&config->defaultsTable, 0, "fpsTarget", opts->fpsTarget); ConfigurationSetUIntValue(&config->defaultsTable, 0, "audioBuffers", opts->audioBuffers); ConfigurationSetUIntValue(&config->defaultsTable, 0, "sampleRate", opts->sampleRate); diff --git a/src/core/rewind.c b/src/core/rewind.c index 95f47ece4..d571c407a 100644 --- a/src/core/rewind.c +++ b/src/core/rewind.c @@ -30,6 +30,7 @@ void mCoreRewindContextInit(struct mCoreRewindContext* context, size_t entries, context->previousState = VFileMemChunk(0, 0); context->currentState = VFileMemChunk(0, 0); context->size = 0; + context->rewindFrameCounter = 0; #ifndef DISABLE_THREADING context->onThread = onThread; context->ready = false; diff --git a/src/core/thread.c b/src/core/thread.c index 9f4be082b..860c981dd 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -157,7 +157,11 @@ void _frameStarted(void* context) { } if (thread->core->opts.rewindEnable && thread->core->opts.rewindBufferCapacity > 0) { if (!thread->impl->rewinding || !mCoreRewindRestore(&thread->impl->rewind, thread->core)) { - mCoreRewindAppend(&thread->impl->rewind, thread->core); + if (thread->impl->rewind.rewindFrameCounter == 0) { + mCoreRewindAppend(&thread->impl->rewind, thread->core); + thread->impl->rewind.rewindFrameCounter = thread->core->opts.rewindBufferInterval; + } + thread->impl->rewind.rewindFrameCounter--; } } } diff --git a/src/platform/qt/ConfigController.cpp b/src/platform/qt/ConfigController.cpp index 73097df09..9665439b5 100644 --- a/src/platform/qt/ConfigController.cpp +++ b/src/platform/qt/ConfigController.cpp @@ -125,6 +125,7 @@ ConfigController::ConfigController(QObject* parent) m_opts.logLevel = mLOG_WARN | mLOG_ERROR | mLOG_FATAL; m_opts.rewindEnable = false; m_opts.rewindBufferCapacity = 300; + m_opts.rewindBufferInterval = 1; m_opts.useBios = true; m_opts.suspendScreensaver = true; m_opts.lockAspectRatio = true; diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index 75a3908dc..977a149ca 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -480,6 +480,7 @@ void SettingsView::updateConfig() { saveSetting("fastForwardMute", m_ui.muteFf); saveSetting("rewindEnable", m_ui.rewind); saveSetting("rewindBufferCapacity", m_ui.rewindCapacity); + saveSetting("rewindBufferInterval", m_ui.rewindBufferInterval); saveSetting("resampleVideo", m_ui.resampleVideo); saveSetting("allowOpposingDirections", m_ui.allowOpposingDirections); saveSetting("suspendScreensaver", m_ui.suspendScreensaver); @@ -708,6 +709,7 @@ void SettingsView::reloadConfig() { loadSetting("fastForwardMute", m_ui.muteFf, m_ui.mute->isChecked()); loadSetting("rewindEnable", m_ui.rewind); loadSetting("rewindBufferCapacity", m_ui.rewindCapacity); + loadSetting("rewindBufferInterval", m_ui.rewindBufferInterval); loadSetting("resampleVideo", m_ui.resampleVideo); loadSetting("allowOpposingDirections", m_ui.allowOpposingDirections); loadSetting("suspendScreensaver", m_ui.suspendScreensaver); diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 03050e607..85094fcd4 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -1195,21 +1195,51 @@ - + + + + Rewind speed: + + + + + + + + + × + + + 1 + + + 10 + + + 1 + + + 1 + + + + + + Qt::Horizontal - + Idle loops: - + @@ -1228,21 +1258,21 @@ - + Preload entire ROM into memory - + Enable Game Boy Player features by default - + Enable VBA bug compatibility in ROM hacks diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index c39f3a8f8..c223acdee 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1827,6 +1827,11 @@ void Window::setupOptions() { reloadConfig(); }, this); + ConfigOption* rewindBufferInterval = m_config->addOption("rewindBufferInterval"); + rewindBufferInterval->connect([this](const QVariant&) { + reloadConfig(); + }, this); + ConfigOption* allowOpposingDirections = m_config->addOption("allowOpposingDirections"); allowOpposingDirections->connect([this](const QVariant&) { reloadConfig(); diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index e82a00483..92a1c63c4 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -61,6 +61,7 @@ int main(int argc, char** argv) { .useBios = true, .rewindEnable = true, .rewindBufferCapacity = 600, + .rewindBufferInterval = 1, .audioBuffers = 1024, .videoSync = false, .audioSync = true, From 44ab21ab35d2ede6f8fc1e9391c0271bc0e58146 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 1 May 2023 04:44:03 -0700 Subject: [PATCH 192/290] Scripting: Allow callbacks to access weakrefs --- src/script/context.c | 23 +++++++++++++++------ src/script/test/stdlib.c | 44 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/src/script/context.c b/src/script/context.c index 352c4d278..85c3de485 100644 --- a/src/script/context.c +++ b/src/script/context.c @@ -235,12 +235,15 @@ void mScriptContextTriggerCallback(struct mScriptContext* context, const char* c do { struct mScriptFrame frame; struct mScriptCallbackInfo* info = TableIteratorGetValue(table, &iter); - mScriptFrameInit(&frame); - if (args) { - mScriptListCopy(&frame.arguments, args); + struct mScriptValue* fn = mScriptContextAccessWeakref(context, info->fn); + if (fn) { + mScriptFrameInit(&frame); + if (args) { + mScriptListCopy(&frame.arguments, args); + } + mScriptInvoke(fn, &frame); + mScriptFrameDeinit(&frame); } - mScriptInvoke(info->fn, &frame); - mScriptFrameDeinit(&frame); if (info->oneshot) { *UInt32ListAppend(&oneshots) = info->id; @@ -255,7 +258,15 @@ void mScriptContextTriggerCallback(struct mScriptContext* context, const char* c } static uint32_t mScriptContextAddCallbackInternal(struct mScriptContext* context, const char* callback, struct mScriptValue* fn, bool oneshot) { - if (fn->type->base != mSCRIPT_TYPE_FUNCTION) { + if (fn->type == mSCRIPT_TYPE_MS_WEAKREF) { + struct mScriptValue* weakref = mScriptContextAccessWeakref(context, fn); + if (!weakref) { + return 0; + } + if (weakref->type->base != mSCRIPT_TYPE_FUNCTION) { + return 0; + } + } else if (fn->type->base != mSCRIPT_TYPE_FUNCTION) { return 0; } struct Table* table = HashTableLookup(&context->callbacks, callback); diff --git a/src/script/test/stdlib.c b/src/script/test/stdlib.c index 852894a34..eda6deb7b 100644 --- a/src/script/test/stdlib.c +++ b/src/script/test/stdlib.c @@ -126,9 +126,53 @@ M_TEST_DEFINE(oneshot) { mScriptContextDeinit(&context); } +static void _tableIncrement(struct mScriptValue* table) { + assert_non_null(table); + struct mScriptValue* value = mScriptTableLookup(table, &mSCRIPT_MAKE_CHARP("key")); + assert_non_null(value); + assert_ptr_equal(value->type, mSCRIPT_TYPE_MS_S32); + ++value->value.s32; +} + +mSCRIPT_BIND_VOID_FUNCTION(tableIncrement, _tableIncrement, 1, WTABLE, table); + +M_TEST_DEFINE(callbackWeakref) { + SETUP_LUA; + + struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE); + struct mScriptList args; + mScriptListInit(&args, 1); + mScriptValueWrap(table, mScriptListAppend(&args)); + struct mScriptValue* lambda = mScriptLambdaCreate0(&tableIncrement, &args); + mScriptListDeinit(&args); + struct mScriptValue* weakref = mScriptContextMakeWeakref(&context, lambda); + mScriptContextAddCallback(&context, "test", weakref); + + struct mScriptValue* key = mScriptStringCreateFromUTF8("key"); + struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S32); + value->value.s32 = 1; + mScriptTableInsert(table, key, value); + + mScriptContextTriggerCallback(&context, "test", NULL); + assert_int_equal(value->value.s32, 2); + + mScriptContextClearWeakref(&context, weakref->value.u32); + mScriptValueDeref(weakref); + + mScriptContextTriggerCallback(&context, "test", NULL); + assert_int_equal(value->value.s32, 2); + + mScriptValueDeref(table); + mScriptValueDeref(key); + mScriptValueDeref(value); + + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStdlib, cmocka_unit_test(bitMask), cmocka_unit_test(bitUnmask), cmocka_unit_test(callbacks), cmocka_unit_test(oneshot), + cmocka_unit_test(callbackWeakref), ) From b8261a0c660d035306c77e8fb3dcb5bcf9db0d8d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 24 Apr 2023 03:27:43 -0700 Subject: [PATCH 193/290] Scripting: Add lambdas with 0 arguments and 0 return values --- include/mgba/script/types.h | 2 + src/script/test/types.c | 28 +++++++++++ src/script/types.c | 92 +++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index faf4ca750..8e3b0b560 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -335,6 +335,8 @@ bool mScriptTableIteratorLookup(struct mScriptValue* table, struct TableIterator void mScriptFrameInit(struct mScriptFrame* frame); void mScriptFrameDeinit(struct mScriptFrame* frame); +struct mScriptValue* mScriptLambdaCreate0(struct mScriptValue* fn, struct mScriptList* args); + void mScriptClassInit(struct mScriptTypeClass* cls); void mScriptClassDeinit(struct mScriptTypeClass* cls); diff --git a/src/script/test/types.c b/src/script/test/types.c index 25ec29809..5c66b3f76 100644 --- a/src/script/test/types.c +++ b/src/script/test/types.c @@ -82,6 +82,10 @@ static bool isNullStruct(struct Test* arg) { return !arg; } +static void increment(struct Test* t) { + ++t->a; +} + mSCRIPT_BIND_FUNCTION(boundVoidOne, S32, voidOne, 0); mSCRIPT_BIND_VOID_FUNCTION(boundDiscard, discard, 1, S32, ignored); mSCRIPT_BIND_FUNCTION(boundIdentityInt, S32, identityInt, 1, S32, in); @@ -95,6 +99,7 @@ mSCRIPT_BIND_FUNCTION(boundIsSequential, S32, isSequential, 1, LIST, list); mSCRIPT_BIND_FUNCTION(boundIsNullCharp, BOOL, isNullCharp, 1, CHARP, arg); mSCRIPT_BIND_FUNCTION(boundIsNullStruct, BOOL, isNullStruct, 1, S(Test), arg); mSCRIPT_BIND_FUNCTION_WITH_DEFAULTS(boundAddIntWithDefaults, S32, addInts, 2, S32, a, S32, b); +mSCRIPT_BIND_VOID_FUNCTION(boundIncrement, increment, 1, S(Test), this); mSCRIPT_DEFINE_FUNCTION_BINDING_DEFAULTS(boundAddIntWithDefaults) mSCRIPT_NO_DEFAULT, @@ -1336,6 +1341,28 @@ M_TEST_DEFINE(nullStruct) { mScriptFrameDeinit(&frame); } +M_TEST_DEFINE(lambda0) { + struct mScriptList args; + struct Test t = { + .a = 0 + }; + + mScriptListInit(&args, 1); + mSCRIPT_PUSH(&args, S(Test), &t); + struct mScriptValue* fn = mScriptLambdaCreate0(&boundIncrement, &args); + assert_non_null(fn); + mScriptListDeinit(&args); + + struct mScriptFrame frame; + mScriptFrameInit(&frame); + assert_int_equal(t.a, 0); + assert_true(mScriptInvoke(fn, &frame)); + assert_int_equal(t.a, 1); + mScriptFrameDeinit(&frame); + + mScriptValueDeref(fn); +} + M_TEST_SUITE_DEFINE(mScript, cmocka_unit_test(voidArgs), cmocka_unit_test(voidFunc), @@ -1373,4 +1400,5 @@ M_TEST_SUITE_DEFINE(mScript, cmocka_unit_test(invokeList), cmocka_unit_test(nullString), cmocka_unit_test(nullStruct), + cmocka_unit_test(lambda0), ) diff --git a/src/script/types.c b/src/script/types.c index 34529a0df..e80c018fb 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -12,6 +12,11 @@ #include #include +struct mScriptLambda { + struct mScriptValue* fn; + struct mScriptList arguments; +}; + static void _allocList(struct mScriptValue*); static void _freeList(struct mScriptValue*); @@ -47,6 +52,10 @@ static bool _boolEqual(const struct mScriptValue*, const struct mScriptValue*); static bool _charpEqual(const struct mScriptValue*, const struct mScriptValue*); static bool _stringEqual(const struct mScriptValue*, const struct mScriptValue*); +static void _lambdaAlloc(struct mScriptValue* val); +static void _lambdaFree(struct mScriptValue* val); +static bool _callLambda0(struct mScriptFrame* frame, void* context); + const struct mScriptType mSTVoid = { .base = mSCRIPT_TYPE_VOID, .size = 0, @@ -268,6 +277,25 @@ const struct mScriptType mSTWeakref = { .hash = NULL, }; +const struct mScriptType mSTLambda0 = { + .base = mSCRIPT_TYPE_FUNCTION, + .size = sizeof(struct mScriptLambda), + .name = "lambda", + .details = { + .function = { + .parameters = { + .count = 0, + }, + .returnType = { + .count = 0, + }, + }, + }, + .alloc = _lambdaAlloc, + .free = _lambdaFree, + .hash = NULL, +}; + struct mScriptValue mScriptValueNull = { .type = &mSTVoid, .refs = mSCRIPT_VALUE_UNREF @@ -826,6 +854,32 @@ bool _stringEqual(const struct mScriptValue* a, const struct mScriptValue* b) { return strncmp(valA, valB, lenA) == 0; } +void _lambdaAlloc(struct mScriptValue* value) { + struct mScriptLambda* lambda = calloc(1, sizeof(*lambda)); + struct mScriptFunction* fn = calloc(1, sizeof(*fn)); + fn->context = lambda; + mScriptListInit(&lambda->arguments, 0); + value->value.opaque = fn; +} + +void _lambdaFree(struct mScriptValue* value) { + struct mScriptFunction* fn = value->value.opaque; + struct mScriptLambda* lambda = fn->context; + size_t i; + for (i = 0; i < mScriptListSize(&lambda->arguments); ++i) { + struct mScriptValue* val = mScriptListGetPointer(&lambda->arguments, i); + if (val->type->base != mSCRIPT_TYPE_WRAPPER) { + continue; + } + val = mScriptValueUnwrap(val); + mScriptValueDeref(val); + } + mScriptListDeinit(&lambda->arguments); + mScriptValueDeref(lambda->fn); + free(lambda); + free(fn); +} + struct mScriptValue* mScriptValueAlloc(const struct mScriptType* type) { // TODO: Use an arena instead of just the generic heap struct mScriptValue* val = malloc(sizeof(*val)); @@ -1074,6 +1128,44 @@ void mScriptFrameDeinit(struct mScriptFrame* frame) { mScriptListDeinit(&frame->arguments); } +struct mScriptValue* mScriptLambdaCreate0(struct mScriptValue* fn, struct mScriptList* args) { + struct mScriptValue* value = mScriptValueAlloc(&mSTLambda0); + struct mScriptFunction* lfn = value->value.opaque; + struct mScriptLambda* lambda = lfn->context; + lfn->call = _callLambda0; + lambda->fn = fn; + mScriptValueRef(fn); + if (args) { + mScriptListCopy(&lambda->arguments, args); + size_t i; + for (i = 0; i < mScriptListSize(args); ++i) { + struct mScriptValue* val = mScriptListGetPointer(args, i); + if (val->type->base != mSCRIPT_TYPE_WRAPPER) { + continue; + } + val = mScriptValueUnwrap(val); + mScriptValueRef(val); + } + } + return value; +} + +bool _callLambda0(struct mScriptFrame* frame, void* context) { + if (mScriptListSize(&frame->arguments)) { + return false; + } + struct mScriptLambda* lambda = context; + struct mScriptFrame subframe; + mScriptFrameInit(&subframe); + mScriptListCopy(&subframe.arguments, &lambda->arguments); + bool ok = mScriptInvoke(lambda->fn, &subframe); + if (mScriptListSize(&subframe.returnValues)) { + ok = false; + } + mScriptFrameDeinit(&subframe); + return ok; +} + static void _mScriptClassInit(struct mScriptTypeClass* cls, const struct mScriptClassInitDetails* details, bool child) { const char* docstring = NULL; From 0b79184bf36f580e5638d871c0d76fbafa0b17ea Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 24 Apr 2023 03:41:33 -0700 Subject: [PATCH 194/290] Scripting: Add lambdas that bind an object method call --- include/mgba/script/types.h | 1 + src/script/types.c | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index 8e3b0b560..d5a6d9947 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -345,6 +345,7 @@ bool mScriptObjectGetConst(const struct mScriptValue* obj, const char* member, s bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScriptValue*); bool mScriptObjectCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) ; void mScriptObjectFree(struct mScriptValue* obj); +struct mScriptValue* mScriptObjectBindLambda(struct mScriptValue* obj, const char* member, struct mScriptList* args); bool mScriptPopS32(struct mScriptList* list, int32_t* out); bool mScriptPopU32(struct mScriptList* list, uint32_t* out); diff --git a/src/script/types.c b/src/script/types.c index e80c018fb..16ef78df4 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -1682,6 +1682,40 @@ void mScriptObjectFree(struct mScriptValue* value) { } } +struct mScriptValue* mScriptObjectBindLambda(struct mScriptValue* obj, const char* member, struct mScriptList* args) { + if (obj->type->base != mSCRIPT_TYPE_OBJECT) { + return false; + } + + struct mScriptTypeClass* cls = obj->type->details.cls; + if (!cls) { + return false; + } + + mScriptClassInit(cls); + + struct mScriptList arguments; + struct mScriptValue fn; + if (!mScriptObjectGetConst(obj, member, &fn)) { + return NULL; + } + + mScriptListInit(&arguments, 0); + mScriptValueWrap(obj, mScriptListAppend(&arguments)); + if (args) { + size_t i; + for (i = 0; i < mScriptListSize(args); ++i) { + memcpy(mScriptListAppend(&arguments), mScriptListGetConstPointer(args, i), sizeof(struct mScriptValue)); + } + } + + struct mScriptValue* value = mScriptValueAlloc(fn.type); + struct mScriptValue* lambda = mScriptLambdaCreate0(value, &arguments); + mScriptValueDeref(value); + mScriptListDeinit(&arguments); + return lambda; +} + bool mScriptPopS32(struct mScriptList* list, int32_t* out) { mSCRIPT_POP(list, S32, val); *out = val; From 2e5751ef6f48c105486d50dab2530fb630584784 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 24 Apr 2023 04:47:07 -0700 Subject: [PATCH 195/290] OpenGL: Fix GLES2 overlay drawing --- src/platform/opengl/gles2.c | 46 +++++++++++++++++++++++++++++-------- src/platform/opengl/gles2.h | 1 + 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 2beea3a78..8ef11e958 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -81,6 +81,15 @@ static const char* const _nullFragmentShader = " gl_FragColor = color;\n" "}"; +static const char* const _thruFragmentShader = + "varying vec2 texCoord;\n" + "uniform sampler2D tex;\n" + + "void main() {\n" + " vec4 color = texture2D(tex, texCoord);\n" + " gl_FragColor = color;\n" + "}"; + static const char* const _interframeFragmentShader = "varying vec2 texCoord;\n" "uniform sampler2D tex;\n" @@ -159,8 +168,9 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) { uniforms[3].max.fvec3[1] = 1.0f; uniforms[3].max.fvec3[2] = 1.0f; mGLES2ShaderInit(&context->initialShader, _vertexShader, _fragmentShader, -1, -1, false, uniforms, 4); - mGLES2ShaderInit(&context->finalShader, 0, 0, 0, 0, false, 0, 0); - mGLES2ShaderInit(&context->interframeShader, 0, _interframeFragmentShader, -1, -1, false, 0, 0); + mGLES2ShaderInit(&context->finalShader, 0, 0, 0, 0, false, NULL, 0); + mGLES2ShaderInit(&context->interframeShader, 0, _interframeFragmentShader, -1, -1, false, NULL, 0); + mGLES2ShaderInit(&context->overlayShader, _vertexShader, _thruFragmentShader, -1, -1, false, NULL, 0); #ifdef BUILD_GLES3 if (context->initialShader.vao != (GLuint) -1) { @@ -170,10 +180,17 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) { glBindBuffer(GL_ARRAY_BUFFER, context->vbo); glBindVertexArray(context->interframeShader.vao); glBindBuffer(GL_ARRAY_BUFFER, context->vbo); + glBindVertexArray(context->overlayShader.vao); + glBindBuffer(GL_ARRAY_BUFFER, context->vbo); glBindVertexArray(0); } #endif + glDeleteFramebuffers(1, &context->overlayShader.fbo); + glDeleteTextures(1, &context->overlayShader.tex); + context->overlayShader.fbo = context->initialShader.fbo; + context->overlayShader.tex = context->initialShader.tex; + glDeleteFramebuffers(1, &context->finalShader.fbo); glDeleteTextures(1, &context->finalShader.tex); context->finalShader.fbo = 0; @@ -221,8 +238,8 @@ static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLa context->shaders[n].dirty = true; } } - context->initialShader.dirty = true; - context->interframeShader.dirty = true; + glBindTexture(GL_TEXTURE_2D, context->initialShader.tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, frame.width, frame.height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); context->width = frame.width; context->height = frame.height; } @@ -245,6 +262,8 @@ static void mGLES2ContextDeinit(struct VideoBackend* v) { mGLES2ShaderDeinit(&context->initialShader); mGLES2ShaderDeinit(&context->finalShader); mGLES2ShaderDeinit(&context->interframeShader); + context->overlayShader.fbo = 0; + mGLES2ShaderDeinit(&context->overlayShader); free(context->initialShader.uniforms); } @@ -321,7 +340,7 @@ static void _drawShaderEx(struct mGLES2Context* context, struct mGLES2Shader* sh } if (layer >= 0 && layer < VIDEO_LAYER_MAX) { - glViewport(context->layerDims[layer].x - context->x, context->layerDims[layer].y - context->y, context->layerDims[layer].width, context->layerDims[layer].height); + glViewport(context->layerDims[layer].x - context->x, context->height - context->layerDims[layer].y - context->layerDims[layer].height + context->y, context->layerDims[layer].width, context->layerDims[layer].height); } else { glViewport(padW, padH, drawW, drawH); } @@ -423,12 +442,17 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) { context->finalShader.filter = v->filter; int layer; - for (layer = 0; layer <= VIDEO_LAYER_IMAGE; ++layer) { + for (layer = 0; layer < VIDEO_LAYER_MAX; ++layer) { if (context->layerDims[layer].width < 1 || context->layerDims[layer].height < 1) { continue; } glBindTexture(GL_TEXTURE_2D, context->tex[layer]); - _drawShaderEx(context, &context->initialShader, layer); + if (layer != VIDEO_LAYER_IMAGE) { + context->overlayShader.blend = layer > VIDEO_LAYER_BACKGROUND; + _drawShaderEx(context, &context->overlayShader, layer); + } else { + _drawShaderEx(context, &context->initialShader, layer); + } if (layer != VIDEO_LAYER_IMAGE) { continue; } @@ -659,10 +683,14 @@ void mGLES2ShaderInit(struct mGLES2Shader* shader, const char* vs, const char* f } void mGLES2ShaderDeinit(struct mGLES2Shader* shader) { - glDeleteTextures(1, &shader->tex); + if (shader->tex) { + glDeleteTextures(1, &shader->tex); + } glDeleteShader(shader->fragmentShader); glDeleteProgram(shader->program); - glDeleteFramebuffers(1, &shader->fbo); + if (shader->fbo) { + glDeleteFramebuffers(1, &shader->fbo); + } #ifdef BUILD_GLES3 if (shader->vao != (GLuint) -1) { glDeleteVertexArrays(1, &shader->vao); diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h index 498cbdc86..d98ff22c4 100644 --- a/src/platform/opengl/gles2.h +++ b/src/platform/opengl/gles2.h @@ -92,6 +92,7 @@ struct mGLES2Context { struct mGLES2Shader initialShader; struct mGLES2Shader finalShader; struct mGLES2Shader interframeShader; + struct mGLES2Shader overlayShader; struct mGLES2Shader* shaders; size_t nShaders; From 90420586e6b11abdd6f33abd7fda6a5c71945b8c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 30 Apr 2023 00:50:02 -0700 Subject: [PATCH 196/290] OpenGL: Fix GL1 overlay drawing --- src/platform/opengl/gl.c | 48 ++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/platform/opengl/gl.c b/src/platform/opengl/gl.c index ec477b954..fe24d04cf 100644 --- a/src/platform/opengl/gl.c +++ b/src/platform/opengl/gl.c @@ -145,13 +145,33 @@ static void _setFrame(struct mRectangle* dims, struct mRectangle* frame) { GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); glScissor(viewport[0] + (dims->x - frame->x) * viewport[2] / frame->width, - viewport[1] + (dims->y - frame->y) * viewport[3] / frame->height, + viewport[1] + (frame->height + frame->y - dims->height - dims->y) * viewport[3] / frame->height, dims->width * viewport[2] / frame->width, dims->height * viewport[3] / frame->height); + glLoadIdentity(); glTranslatef(dims->x, dims->y, 0); glScalef(toPow2(dims->width), toPow2(dims->height), 1); } +static void _drawLayers(struct mGLContext* context, int start, int end) { + struct mRectangle frame; + VideoBackendGetFrame(&context->d, &frame); + + int layer; + for (layer = start; layer < end; ++layer) { + if (context->layerDims[layer].width < 1 || context->layerDims[layer].height < 1) { + continue; + } + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBindTexture(GL_TEXTURE_2D, context->layers[layer]); + _setFilter(&context->d); + _setFrame(&context->layerDims[layer], &frame); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + } +} + void mGLContextDrawFrame(struct VideoBackend* v) { struct mGLContext* context = (struct mGLContext*) v; glEnable(GL_TEXTURE_2D); @@ -166,36 +186,26 @@ void mGLContextDrawFrame(struct VideoBackend* v) { VideoBackendGetFrame(v, &frame); glOrtho(frame.x, frame.x + frame.width, frame.y + frame.height, frame.y, 0, 1); glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - int layer; - for (layer = 0; layer < VIDEO_LAYER_IMAGE; ++layer) { - if (context->layerDims[layer].width < 1 || context->layerDims[layer].height < 1) { - continue; - } - - glDisable(GL_BLEND); - glBindTexture(GL_TEXTURE_2D, context->layers[layer]); - _setFilter(v); - glPushMatrix(); - _setFrame(&context->layerDims[layer], &frame); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glPopMatrix(); - } + _drawLayers(context, 0, VIDEO_LAYER_IMAGE); _setFrame(&context->layerDims[VIDEO_LAYER_IMAGE], &frame); + glDisable(GL_BLEND); if (v->interframeBlending) { - glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); - glBlendColor(1, 1, 1, 0.5); glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex ^ 1]); _setFilter(v); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glEnable(GL_BLEND); + glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); + glBlendColor(1, 1, 1, 0.5); } + glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]); _setFilter(v); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glDisable(GL_BLEND); + + _drawLayers(context, VIDEO_LAYER_IMAGE + 1, VIDEO_LAYER_MAX); } static void mGLContextSetImageSize(struct VideoBackend* v, enum VideoLayer layer, int width, int height) { From dc6639b30bf18cbf2fafffc054cd967b3d19d34b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 12 Apr 2023 22:27:26 -0700 Subject: [PATCH 197/290] Video: Add backend call proxying for cross-thread interaction --- include/mgba/feature/proxy-backend.h | 81 ++++++++ include/mgba/feature/video-backend.h | 3 + src/feature/CMakeLists.txt | 1 + src/feature/proxy-backend.c | 284 +++++++++++++++++++++++++++ src/feature/video-backend.c | 2 + 5 files changed, 371 insertions(+) create mode 100644 include/mgba/feature/proxy-backend.h create mode 100644 src/feature/proxy-backend.c diff --git a/include/mgba/feature/proxy-backend.h b/include/mgba/feature/proxy-backend.h new file mode 100644 index 000000000..8c0b9e427 --- /dev/null +++ b/include/mgba/feature/proxy-backend.h @@ -0,0 +1,81 @@ +/* Copyright (c) 2013-2023 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 PROXY_BACKEND_H +#define PROXY_BACKEND_H + +#include + +CXX_GUARD_START + +#include +#include +#include + +enum mVideoBackendCommandType { + mVB_CMD_DUMMY = 0, + mVB_CMD_INIT, + mVB_CMD_DEINIT, + mVB_CMD_SET_LAYER_DIMENSIONS, + mVB_CMD_LAYER_DIMENSIONS, + mVB_CMD_SWAP, + mVB_CMD_CLEAR, + mVB_CMD_CONTEXT_RESIZED, + mVB_CMD_SET_IMAGE_SIZE, + mVB_CMD_IMAGE_SIZE, + mVB_CMD_SET_IMAGE, + mVB_CMD_DRAW_FRAME, +}; + +union mVideoBackendCommandData { + struct mRectangle dims; + struct { + int width; + int height; + } s; + struct { + unsigned width; + unsigned height; + } u; + const void* image; +}; + +struct mVideoBackendCommand { + enum mVideoBackendCommandType cmd; + + union { + WHandle handle; + enum VideoLayer layer; + }; + union mVideoBackendCommandData data; +}; + +struct mVideoProxyBackend { + struct VideoBackend d; + struct VideoBackend* backend; + + struct RingFIFO in; + struct RingFIFO out; + + Mutex inLock; + Mutex outLock; + Condition inWait; + Condition outWait; + + void (*wakeupCb)(struct mVideoProxyBackend*, void* context); + void* context; +}; + +void mVideoProxyBackendInit(struct mVideoProxyBackend* proxy, struct VideoBackend* backend); +void mVideoProxyBackendDeinit(struct mVideoProxyBackend* proxy); + +void mVideoProxyBackendSubmit(struct mVideoProxyBackend* proxy, const struct mVideoBackendCommand* cmd, union mVideoBackendCommandData* out); +bool mVideoProxyBackendRun(struct mVideoProxyBackend* proxy, bool block); + +bool mVideoProxyBackendCommandIsBlocking(enum mVideoBackendCommandType); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/feature/video-backend.h b/include/mgba/feature/video-backend.h index 17584fea4..7baf82483 100644 --- a/include/mgba/feature/video-backend.h +++ b/include/mgba/feature/video-backend.h @@ -11,6 +11,7 @@ CXX_GUARD_START #include +#include #ifdef _WIN32 #include @@ -19,6 +20,8 @@ typedef HWND WHandle; typedef void* WHandle; #endif +mLOG_DECLARE_CATEGORY(VIDEO); + enum VideoLayer { VIDEO_LAYER_BACKGROUND = 0, VIDEO_LAYER_BEZEL, diff --git a/src/feature/CMakeLists.txt b/src/feature/CMakeLists.txt index f18028ff0..b692338f4 100644 --- a/src/feature/CMakeLists.txt +++ b/src/feature/CMakeLists.txt @@ -1,6 +1,7 @@ include(ExportDirectory) set(SOURCE_FILES commandline.c + proxy-backend.c thread-proxy.c updater.c video-backend.c diff --git a/src/feature/proxy-backend.c b/src/feature/proxy-backend.c new file mode 100644 index 000000000..fb6e555ed --- /dev/null +++ b/src/feature/proxy-backend.c @@ -0,0 +1,284 @@ +/* Copyright (c) 2013-2023 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 + +static void _mVideoProxyBackendInit(struct VideoBackend* v, WHandle handle) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_INIT, + .handle = handle + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendDeinit(struct VideoBackend* v) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_DEINIT, + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct mRectangle* dims) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_SET_LAYER_DIMENSIONS, + .layer = layer, + .data = { + .dims = *dims + } + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct mRectangle* dims) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_LAYER_DIMENSIONS, + .layer = layer, + }; + union mVideoBackendCommandData out; + mVideoProxyBackendSubmit(proxy, &cmd, &out); + memcpy(dims, &out.dims, sizeof(*dims)); +} + +static void _mVideoProxyBackendSwap(struct VideoBackend* v) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_SWAP, + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendClear(struct VideoBackend* v) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_CLEAR, + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendContextResized(struct VideoBackend* v, unsigned w, unsigned h) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_CONTEXT_RESIZED, + .data = { + .u = { + .width = w, + .height = h, + } + } + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendSetImageSize(struct VideoBackend* v, enum VideoLayer layer, int w, int h) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_SET_IMAGE_SIZE, + .layer = layer, + .data = { + .s = { + .width = w, + .height = h, + } + } + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendImageSize(struct VideoBackend* v, enum VideoLayer layer, int* w, int* h) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_IMAGE_SIZE, + .layer = layer, + }; + union mVideoBackendCommandData out; + mVideoProxyBackendSubmit(proxy, &cmd, &out); + *w = out.s.width; + *h = out.s.height; +} + +static void _mVideoProxyBackendSetImage(struct VideoBackend* v, enum VideoLayer layer, const void* frame) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_SET_IMAGE, + .layer = layer, + .data = { + .image = frame + } + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendDrawFrame(struct VideoBackend* v) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_DRAW_FRAME, + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static bool mVideoProxyBackendReadIn(struct mVideoProxyBackend* proxy, struct mVideoBackendCommand* cmd, bool block); +static void mVideoProxyBackendWriteOut(struct mVideoProxyBackend* proxy, const union mVideoBackendCommandData* out); + +void mVideoProxyBackendInit(struct mVideoProxyBackend* proxy, struct VideoBackend* backend) { + proxy->d.init = _mVideoProxyBackendInit; + proxy->d.deinit = _mVideoProxyBackendDeinit; + proxy->d.setLayerDimensions = _mVideoProxyBackendSetLayerDimensions; + proxy->d.layerDimensions = _mVideoProxyBackendLayerDimensions; + proxy->d.swap = _mVideoProxyBackendSwap; + proxy->d.clear = _mVideoProxyBackendClear; + proxy->d.contextResized = _mVideoProxyBackendContextResized; + proxy->d.setImageSize = _mVideoProxyBackendSetImageSize; + proxy->d.imageSize = _mVideoProxyBackendImageSize; + proxy->d.setImage = _mVideoProxyBackendSetImage; + proxy->d.drawFrame = _mVideoProxyBackendDrawFrame; + proxy->backend = backend; + + RingFIFOInit(&proxy->in, 0x400); + RingFIFOInit(&proxy->out, 0x400); + MutexInit(&proxy->inLock); + MutexInit(&proxy->outLock); + ConditionInit(&proxy->inWait); + ConditionInit(&proxy->outWait); + + proxy->wakeupCb = NULL; + proxy->context = NULL; +} + +void mVideoProxyBackendDeinit(struct mVideoProxyBackend* proxy) { + ConditionDeinit(&proxy->inWait); + ConditionDeinit(&proxy->outWait); + MutexDeinit(&proxy->inLock); + MutexDeinit(&proxy->outLock); + RingFIFODeinit(&proxy->in); + RingFIFODeinit(&proxy->out); +} + +void mVideoProxyBackendSubmit(struct mVideoProxyBackend* proxy, const struct mVideoBackendCommand* cmd, union mVideoBackendCommandData* out) { + MutexLock(&proxy->inLock); + while (!RingFIFOWrite(&proxy->in, cmd, sizeof(*cmd))) { + mLOG(VIDEO, DEBUG, "Can't write command. Proxy thread asleep?"); + ConditionWait(&proxy->inWait, &proxy->inLock); + } + MutexUnlock(&proxy->inLock); + if (proxy->wakeupCb) { + proxy->wakeupCb(proxy, proxy->context); + } + + if (!mVideoProxyBackendCommandIsBlocking(cmd->cmd)) { + return; + } + + MutexLock(&proxy->outLock); + while (!RingFIFORead(&proxy->out, out, sizeof(*out))) { + ConditionWait(&proxy->outWait, &proxy->outLock); + } + MutexUnlock(&proxy->outLock); +} + +bool mVideoProxyBackendRun(struct mVideoProxyBackend* proxy, bool block) { + bool ok = false; + do { + struct mVideoBackendCommand cmd; + union mVideoBackendCommandData out; + if (mVideoProxyBackendReadIn(proxy, &cmd, block)) { + switch (cmd.cmd) { + case mVB_CMD_DUMMY: + break; + case mVB_CMD_INIT: + proxy->backend->init(proxy->backend, cmd.handle); + break; + case mVB_CMD_DEINIT: + proxy->backend->deinit(proxy->backend); + break; + case mVB_CMD_SET_LAYER_DIMENSIONS: + proxy->backend->setLayerDimensions(proxy->backend, cmd.layer, &cmd.data.dims); + break; + case mVB_CMD_LAYER_DIMENSIONS: + proxy->backend->layerDimensions(proxy->backend, cmd.layer, &out.dims); + break; + case mVB_CMD_SWAP: + proxy->backend->swap(proxy->backend); + break; + case mVB_CMD_CLEAR: + proxy->backend->clear(proxy->backend); + break; + case mVB_CMD_CONTEXT_RESIZED: + proxy->backend->contextResized(proxy->backend, cmd.data.u.width, cmd.data.u.height); + break; + case mVB_CMD_SET_IMAGE_SIZE: + proxy->backend->setImageSize(proxy->backend, cmd.layer, cmd.data.s.width, cmd.data.s.height); + break; + case mVB_CMD_IMAGE_SIZE: + proxy->backend->imageSize(proxy->backend, cmd.layer, &out.s.width, &out.s.height); + break; + case mVB_CMD_SET_IMAGE: + proxy->backend->setImage(proxy->backend, cmd.layer, cmd.data.image); + break; + case mVB_CMD_DRAW_FRAME: + proxy->backend->drawFrame(proxy->backend); + break; + } + if (mVideoProxyBackendCommandIsBlocking(cmd.cmd)) { + mVideoProxyBackendWriteOut(proxy, &out); + } + ok = true; + } + } while (block); + return ok; +} + +bool mVideoProxyBackendReadIn(struct mVideoProxyBackend* proxy, struct mVideoBackendCommand* cmd, bool block) { + bool gotCmd = false; + MutexLock(&proxy->inLock); + do { + gotCmd = RingFIFORead(&proxy->in, cmd, sizeof(*cmd)); + ConditionWake(&proxy->inWait); + // TODO: interlock? + if (block && !gotCmd) { + mLOG(VIDEO, DEBUG, "Can't read command. Runner thread asleep?"); + } + } while (block && !gotCmd); + MutexUnlock(&proxy->inLock); + return gotCmd; +} + +void mVideoProxyBackendWriteOut(struct mVideoProxyBackend* proxy, const union mVideoBackendCommandData* out) { + bool gotReply = false; + MutexLock(&proxy->outLock); + while (!gotReply) { + gotReply = RingFIFOWrite(&proxy->out, out, sizeof(*out)); + ConditionWake(&proxy->outWait); + // TOOD: interlock? + if (!gotReply) { + mLOG(VIDEO, DEBUG, "Can't write reply. Runner thread asleep?"); + } + } + MutexUnlock(&proxy->outLock); +} + +bool mVideoProxyBackendCommandIsBlocking(enum mVideoBackendCommandType cmd) { + switch (cmd) { + case mVB_CMD_DUMMY: + case mVB_CMD_CONTEXT_RESIZED: + case mVB_CMD_SET_LAYER_DIMENSIONS: + case mVB_CMD_CLEAR: + case mVB_CMD_SET_IMAGE_SIZE: + case mVB_CMD_DRAW_FRAME: + return false; + case mVB_CMD_INIT: + case mVB_CMD_DEINIT: + case mVB_CMD_LAYER_DIMENSIONS: + case mVB_CMD_SWAP: + case mVB_CMD_IMAGE_SIZE: + case mVB_CMD_SET_IMAGE: + return true; + } + + return true; +} diff --git a/src/feature/video-backend.c b/src/feature/video-backend.c index ffe916edc..ae2f0257d 100644 --- a/src/feature/video-backend.c +++ b/src/feature/video-backend.c @@ -5,6 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +mLOG_DEFINE_CATEGORY(VIDEO, "Video backend", "video"); + void VideoBackendGetFrame(const struct VideoBackend* v, struct mRectangle* frame) { memset(frame, 0, sizeof(*frame)); int i; From 399ace760cbc1d9fd5f200a8b2f0b678cc690957 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 20 Apr 2023 20:08:27 -0700 Subject: [PATCH 198/290] Qt: Hook up proxy backend to DisplayGL --- src/platform/qt/DisplayGL.cpp | 3 +++ src/platform/qt/VideoProxy.cpp | 21 +++++++++++++++++++++ src/platform/qt/VideoProxy.h | 18 ++++++++++++++++++ src/platform/qt/Window.cpp | 26 +++++++++++++++----------- 4 files changed, 57 insertions(+), 11 deletions(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index b9bd72a19..bf9f6a867 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -944,6 +944,9 @@ void PainterGL::dequeueAll(bool keep) { void PainterGL::setVideoProxy(std::shared_ptr proxy) { m_videoProxy = proxy; + if (proxy) { + proxy->setProxiedBackend(m_backend); + } } void PainterGL::interrupt() { diff --git a/src/platform/qt/VideoProxy.cpp b/src/platform/qt/VideoProxy.cpp index cf674e1e8..cdd78f0dd 100644 --- a/src/platform/qt/VideoProxy.cpp +++ b/src/platform/qt/VideoProxy.cpp @@ -29,7 +29,19 @@ VideoProxy::VideoProxy() { m_logger.readData = &callback::func<&VideoProxy::readData>; m_logger.postEvent = &callback::func<&VideoProxy::postEvent>; + mVideoProxyBackendInit(&m_backend, nullptr); + m_backend.context = this; + m_backend.wakeupCb = [](struct mVideoProxyBackend*, void* context) { + VideoProxy* self = static_cast(context); + QMetaObject::invokeMethod(self, "commandAvailable"); + }; + connect(this, &VideoProxy::dataAvailable, this, &VideoProxy::processData); + connect(this, &VideoProxy::commandAvailable, this, &VideoProxy::processCommands); +} + +VideoProxy::~VideoProxy() { + mVideoProxyBackendDeinit(&m_backend); } void VideoProxy::attach(CoreController* controller) { @@ -44,11 +56,20 @@ void VideoProxy::detach(CoreController* controller) { } } +void VideoProxy::setProxiedBackend(VideoBackend* backend) { + // TODO: This needs some safety around it + m_backend.backend = backend; +} + void VideoProxy::processData() { mVideoLoggerRendererRun(&m_logger, false); m_fromThreadCond.wakeAll(); } +void VideoProxy::processCommands() { + mVideoProxyBackendRun(&m_backend, false); +} + void VideoProxy::init() { RingFIFOInit(&m_dirtyQueue, 0x80000); } diff --git a/src/platform/qt/VideoProxy.h b/src/platform/qt/VideoProxy.h index 4e16c1f90..43bb280c3 100644 --- a/src/platform/qt/VideoProxy.h +++ b/src/platform/qt/VideoProxy.h @@ -7,9 +7,12 @@ #include #include +#include +#include #include #include +#include #include namespace QGBA { @@ -21,16 +24,22 @@ Q_OBJECT public: VideoProxy(); + ~VideoProxy(); void attach(CoreController*); void detach(CoreController*); void setBlocking(bool block) { m_logger.waitOnFlush = block; } + VideoBackend* backend() { return &m_backend.d; } + void setProxiedBackend(VideoBackend*); + signals: void dataAvailable(); + void commandAvailable(); public slots: void processData(); + void processCommands(); void reset(); void handleEvent(int); @@ -62,10 +71,19 @@ private: VideoProxy* p; } m_logger; + struct mVideoProxyBackend m_backend; + RingFIFO m_dirtyQueue; QMutex m_mutex; QWaitCondition m_toThreadCond; QWaitCondition m_fromThreadCond; + + QReadWriteLock m_backendInLock; + QReadWriteLock m_backendOutLock; + QQueue m_backendIn; + QQueue m_backendOut; + QWaitCondition m_toBackendThreadCond; + QWaitCondition m_fromBackendThreadCond; }; } diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index f10bdebb6..a94d839a7 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -633,6 +633,11 @@ void Window::scriptingOpen() { m_scripting->setController(m_controller); m_display->installEventFilter(m_scripting.get()); } + + std::shared_ptr proxy = m_display->videoProxy(); + if (proxy) { + m_scripting->setVideoBackend(proxy->backend()); + } } ScriptingView* view = new ScriptingView(m_scripting.get(), m_config); openView(view); @@ -1055,6 +1060,9 @@ void Window::reloadDisplayDriver() { #endif m_display->setBackgroundImage(QImage{m_config->getOption("backgroundImage")}); + + std::shared_ptr proxy = std::make_shared(); + m_display->setVideoProxy(proxy); } void Window::reloadAudioDriver() { @@ -1079,12 +1087,7 @@ void Window::changeRenderer() { CoreController::Interrupter interrupter(m_controller); if (m_config->getOption("hwaccelVideo").toInt() && m_display->supportsShaders() && m_controller->supportsFeature(CoreController::Feature::OPENGL)) { - std::shared_ptr proxy = m_display->videoProxy(); - if (!proxy) { - proxy = std::make_shared(); - } - m_display->setVideoProxy(proxy); - proxy->attach(m_controller.get()); + m_display->videoProxy()->attach(m_controller.get()); int fb = m_display->framebufferHandle(); if (fb >= 0) { @@ -1092,11 +1095,7 @@ void Window::changeRenderer() { m_config->updateOption("videoScale"); } } else { - std::shared_ptr proxy = m_display->videoProxy(); - if (proxy) { - proxy->detach(m_controller.get()); - m_display->setVideoProxy({}); - } + m_display->videoProxy()->detach(m_controller.get()); m_controller->setFramebufferHandle(-1); } } @@ -2113,6 +2112,11 @@ void Window::setController(CoreController* controller, const QString& fname) { #ifdef ENABLE_SCRIPTING if (m_scripting) { m_scripting->setController(m_controller); + + std::shared_ptr proxy = m_display->videoProxy(); + if (proxy) { + m_scripting->setVideoBackend(proxy->backend()); + } } #endif From 18d0ad6ff9c9eb616cd9bf7e180c48891a79480e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 25 Apr 2023 01:37:01 -0700 Subject: [PATCH 199/290] Qt: Just don't tear down proxy ring FIFO until the object is destroyed --- src/platform/qt/VideoProxy.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/VideoProxy.cpp b/src/platform/qt/VideoProxy.cpp index cdd78f0dd..17168da0e 100644 --- a/src/platform/qt/VideoProxy.cpp +++ b/src/platform/qt/VideoProxy.cpp @@ -24,6 +24,7 @@ VideoProxy::VideoProxy() { m_logger.unlock = &cbind<&VideoProxy::unlock>; m_logger.wait = &cbind<&VideoProxy::wait>; m_logger.wake = &callback::func<&VideoProxy::wake>; + RingFIFOInit(&m_dirtyQueue, 0x80000); m_logger.writeData = &callback::func<&VideoProxy::writeData>; m_logger.readData = &callback::func<&VideoProxy::readData>; @@ -42,6 +43,7 @@ VideoProxy::VideoProxy() { VideoProxy::~VideoProxy() { mVideoProxyBackendDeinit(&m_backend); + RingFIFODeinit(&m_dirtyQueue); } void VideoProxy::attach(CoreController* controller) { @@ -71,7 +73,6 @@ void VideoProxy::processCommands() { } void VideoProxy::init() { - RingFIFOInit(&m_dirtyQueue, 0x80000); } void VideoProxy::reset() { @@ -82,7 +83,6 @@ void VideoProxy::reset() { } void VideoProxy::deinit() { - RingFIFODeinit(&m_dirtyQueue); } bool VideoProxy::writeData(const void* data, size_t length) { From dda5634189007653f0f5a79c7d4686c3c5be724d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 12 Apr 2023 22:28:04 -0700 Subject: [PATCH 200/290] Scripting: Add canvas API --- include/mgba/feature/video-backend.h | 13 +- include/mgba/script.h | 1 + include/mgba/script/canvas.h | 23 ++ src/platform/qt/Display.h | 1 + src/platform/qt/Window.cpp | 24 +- .../qt/scripting/ScriptingController.cpp | 10 +- .../qt/scripting/ScriptingController.h | 3 + src/script/CMakeLists.txt | 1 + src/script/canvas.c | 269 ++++++++++++++++++ 9 files changed, 341 insertions(+), 4 deletions(-) create mode 100644 include/mgba/script/canvas.h create mode 100644 src/script/canvas.c diff --git a/include/mgba/feature/video-backend.h b/include/mgba/feature/video-backend.h index 7baf82483..292dc9c82 100644 --- a/include/mgba/feature/video-backend.h +++ b/include/mgba/feature/video-backend.h @@ -26,10 +26,21 @@ enum VideoLayer { VIDEO_LAYER_BACKGROUND = 0, VIDEO_LAYER_BEZEL, VIDEO_LAYER_IMAGE, - VIDEO_LAYER_OVERLAY, + VIDEO_LAYER_OVERLAY0, + VIDEO_LAYER_OVERLAY1, + VIDEO_LAYER_OVERLAY2, + VIDEO_LAYER_OVERLAY3, + VIDEO_LAYER_OVERLAY4, + VIDEO_LAYER_OVERLAY5, + VIDEO_LAYER_OVERLAY6, + VIDEO_LAYER_OVERLAY7, + VIDEO_LAYER_OVERLAY8, + VIDEO_LAYER_OVERLAY9, VIDEO_LAYER_MAX }; +#define VIDEO_LAYER_OVERLAY_COUNT VIDEO_LAYER_MAX - VIDEO_LAYER_OVERLAY0 + struct VideoBackend { void (*init)(struct VideoBackend*, WHandle handle); void (*deinit)(struct VideoBackend*); diff --git a/include/mgba/script.h b/include/mgba/script.h index 3497cd0d0..747dfedc1 100644 --- a/include/mgba/script.h +++ b/include/mgba/script.h @@ -7,6 +7,7 @@ #define M_SCRIPT_H #include +#include #include #include #include diff --git a/include/mgba/script/canvas.h b/include/mgba/script/canvas.h new file mode 100644 index 000000000..39c8edfd1 --- /dev/null +++ b/include/mgba/script/canvas.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2013-2023 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 M_SCRIPT_CANVAS_H +#define M_SCRIPT_CANVAS_H + +#include + +CXX_GUARD_START + +#include +#include + +struct VideoBackend; +void mScriptContextAttachCanvas(struct mScriptContext* context); +void mScriptCanvasUpdate(struct mScriptContext* context); +void mScriptCanvasUpdateBackend(struct mScriptContext* context, struct VideoBackend*); + +CXX_GUARD_END + +#endif diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index 6830e8515..e9c37aa55 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -14,6 +14,7 @@ #include "MessagePainter.h" struct VDir; +struct VideoBackend; struct VideoShader; namespace QGBA { diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index a94d839a7..26d5d4255 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -970,6 +970,12 @@ void Window::gameStopped() { updateTitle(); if (m_pendingClose) { +#ifdef ENABLE_SCRIPTING + std::shared_ptr proxy = m_display->videoProxy(); + if (m_scripting && proxy) { + m_scripting->setVideoBackend(nullptr); + } +#endif m_display.reset(); close(); } @@ -1020,6 +1026,15 @@ void Window::reloadDisplayDriver() { m_display->stopDrawing(); detachWidget(); } +#ifdef ENABLE_SCRIPTING + if (m_scripting) { + m_scripting->setVideoBackend(nullptr); + } +#endif + std::shared_ptr proxy; + if (m_display) { + proxy = m_display->videoProxy(); + } m_display = std::unique_ptr(Display::create(this)); if (!m_display) { LOG(QT, ERROR) << tr("Failed to create an appropriate display device, falling back to software display. " @@ -1061,8 +1076,15 @@ void Window::reloadDisplayDriver() { m_display->setBackgroundImage(QImage{m_config->getOption("backgroundImage")}); - std::shared_ptr proxy = std::make_shared(); + if (!proxy) { + proxy = std::make_shared(); + } m_display->setVideoProxy(proxy); +#ifdef ENABLE_SCRIPTING + if (m_scripting) { + m_scripting->setVideoBackend(proxy->backend()); + } +#endif } void Window::reloadAudioDriver() { diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index 3062dc96a..3542bf28b 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -87,6 +87,10 @@ void ScriptingController::setInputController(InputController* input) { connect(m_inputController, &InputController::updated, this, &ScriptingController::updateGamepad); } +void ScriptingController::setVideoBackend(VideoBackend* backend) { + mScriptCanvasUpdateBackend(&m_scriptContext, backend); +} + bool ScriptingController::loadFile(const QString& path) { VFileDevice vf(path, QIODevice::ReadOnly); if (!vf.isOpen()) { @@ -304,11 +308,13 @@ void ScriptingController::detachGamepad() { void ScriptingController::init() { mScriptContextInit(&m_scriptContext); mScriptContextAttachStdlib(&m_scriptContext); + mScriptContextAttachCanvas(&m_scriptContext); + mScriptContextAttachImage(&m_scriptContext); + mScriptContextAttachInput(&m_scriptContext); + mScriptContextAttachSocket(&m_scriptContext); #ifdef USE_JSON_C mScriptContextAttachStorage(&m_scriptContext); #endif - mScriptContextAttachSocket(&m_scriptContext); - mScriptContextAttachInput(&m_scriptContext); mScriptContextRegisterEngines(&m_scriptContext); mScriptContextAttachLogger(&m_scriptContext, &m_logger); diff --git a/src/platform/qt/scripting/ScriptingController.h b/src/platform/qt/scripting/ScriptingController.h index e34b34542..da99a5785 100644 --- a/src/platform/qt/scripting/ScriptingController.h +++ b/src/platform/qt/scripting/ScriptingController.h @@ -20,6 +20,8 @@ class QKeyEvent; class QTextDocument; +struct VideoBackend; + namespace QGBA { class CoreController; @@ -36,6 +38,7 @@ public: void setController(std::shared_ptr controller); void setInputController(InputController* controller); + void setVideoBackend(VideoBackend* backend); bool loadFile(const QString& path); bool load(VFileDevice& vf, const QString& name); diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index a451ff6d8..0f64514c0 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -1,5 +1,6 @@ include(ExportDirectory) set(SOURCE_FILES + canvas.c context.c input.c image.c diff --git a/src/script/canvas.c b/src/script/canvas.c new file mode 100644 index 000000000..c8b0441ac --- /dev/null +++ b/src/script/canvas.c @@ -0,0 +1,269 @@ +/* Copyright (c) 2013-2023 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 + +#include +#include +#include + +struct mScriptCanvasContext; +struct mScriptCanvasLayer { + struct VideoBackend* backend; + enum VideoLayer layer; + struct mImage* image; + int x; + int y; + bool dirty; + bool sizeDirty; + bool dimsDirty; + bool contentsDirty; +}; + +struct mScriptCanvasContext { + struct mScriptCanvasLayer overlays[VIDEO_LAYER_OVERLAY_COUNT]; + struct VideoBackend* backend; + struct mScriptContext* context; + uint32_t frameCbid; +}; + +mSCRIPT_DECLARE_STRUCT(mScriptCanvasContext); +mSCRIPT_DECLARE_STRUCT(mScriptCanvasLayer); + +static void mScriptCanvasLayerDestroy(struct mScriptCanvasLayer* layer); +static void mScriptCanvasLayerUpdate(struct mScriptCanvasLayer* layer); + +static int _getNextAvailableOverlay(struct mScriptCanvasContext* context) { + if (!context->backend) { + return -1; + } + size_t i; + for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) { + int w = -1, h = -1; + context->backend->imageSize(context->backend, VIDEO_LAYER_OVERLAY0 + i, &w, &h); + if (w <= 0 && h <= 0) { + return i; + } + } + return -1; +} + +static void mScriptCanvasContextDeinit(struct mScriptCanvasContext* context) { + size_t i; + for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) { + if (context->overlays[i].image) { + mScriptCanvasLayerDestroy(&context->overlays[i]); + } + } +} + +void mScriptCanvasUpdateBackend(struct mScriptContext* context, struct VideoBackend* backend) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "canvas"); + if (!value) { + return; + } + struct mScriptCanvasContext* canvas = value->value.opaque; + canvas->backend = backend; + size_t i; + for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) { + struct mScriptCanvasLayer* layer = &canvas->overlays[i]; + layer->backend = backend; + layer->dirty = true; + layer->dimsDirty = true; + layer->sizeDirty = true; + layer->contentsDirty = true; + } +} + +static void _mScriptCanvasUpdate(struct mScriptCanvasContext* canvas) { + size_t i; + for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) { + mScriptCanvasLayerUpdate(&canvas->overlays[i]); + } +} + +static unsigned _mScriptCanvasWidth(struct mScriptCanvasContext* canvas) { + if (!canvas->backend) { + return 0; + } + unsigned w, h; + VideoBackendGetFrameSize(canvas->backend, &w, &h); + return w; +} + +static unsigned _mScriptCanvasHeight(struct mScriptCanvasContext* canvas) { + if (!canvas->backend) { + return 0; + } + unsigned w, h; + VideoBackendGetFrameSize(canvas->backend, &w, &h); + return h; +} + +static int _mScriptCanvasScreenWidth(struct mScriptCanvasContext* canvas) { + if (!canvas->backend) { + return 0; + } + struct mRectangle dims; + canvas->backend->layerDimensions(canvas->backend, VIDEO_LAYER_IMAGE, &dims); + return dims.width; +} + +static int _mScriptCanvasScreenHeight(struct mScriptCanvasContext* canvas) { + if (!canvas->backend) { + return 0; + } + struct mRectangle dims; + canvas->backend->layerDimensions(canvas->backend, VIDEO_LAYER_IMAGE, &dims); + return dims.height; +} + +void mScriptCanvasUpdate(struct mScriptContext* context) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "canvas"); + if (!value) { + return; + } + struct mScriptCanvasContext* canvas = value->value.opaque; + _mScriptCanvasUpdate(canvas); +} + +static struct mScriptValue* mScriptCanvasLayerCreate(struct mScriptCanvasContext* context, int w, int h) { + if (w <= 0 || h <= 0) { + return NULL; + } + int next = _getNextAvailableOverlay(context); + if (next < 0) { + return NULL; + } + + struct mScriptCanvasLayer* layer = &context->overlays[next]; + if (layer->image) { + // This shouldn't exist yet + abort(); + } + + layer->image = mImageCreate(w, h, mCOLOR_ABGR8); + layer->dirty = true; + layer->dimsDirty = true; + layer->sizeDirty = true; + layer->contentsDirty = true; + mScriptCanvasLayerUpdate(layer); + + struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptCanvasLayer)); + value->value.opaque = layer; + return value; +} + +static void mScriptCanvasLayerDestroy(struct mScriptCanvasLayer* layer) { + struct mRectangle frame = {0}; + if (layer->backend) { + layer->backend->setLayerDimensions(layer->backend, layer->layer, &frame); + layer->backend->setImageSize(layer->backend, layer->layer, 0, 0); + } + mImageDestroy(layer->image); + layer->image = NULL; +} + +static void mScriptCanvasLayerUpdate(struct mScriptCanvasLayer* layer) { + if (!layer->dirty || !layer->image || !layer->backend) { + return; + } + + struct VideoBackend* backend = layer->backend; + if (layer->sizeDirty) { + backend->setImageSize(backend, layer->layer, layer->image->width, layer->image->height); + layer->sizeDirty = false; + // Resizing the image invalidates the contents in many backends + layer->contentsDirty = true; + } + if (layer->dimsDirty) { + struct mRectangle frame = { + .x = layer->x, + .y = layer->y, + .width = layer->image->width, + .height = layer->image->height, + }; + backend->setLayerDimensions(backend, layer->layer, &frame); + layer->dimsDirty = false; + } + if (layer->contentsDirty) { + backend->setImage(backend, layer->layer, layer->image->data); + layer->contentsDirty = false; + } + layer->dirty = false; +} + + +static void mScriptCanvasLayerSetPosition(struct mScriptCanvasLayer* layer, int32_t x, int32_t y) { + layer->x = x; + layer->y = y; + layer->dimsDirty = true; + layer->dirty = true; +} + +static void mScriptCanvasLayerInvalidate(struct mScriptCanvasLayer* layer) { + layer->contentsDirty = true; + layer->dirty = true; +} + +void mScriptContextAttachCanvas(struct mScriptContext* context) { + struct mScriptCanvasContext* canvas = calloc(1, sizeof(*canvas)); + size_t i; + for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) { + canvas->overlays[i].layer = VIDEO_LAYER_OVERLAY0 + i; + } + struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptCanvasContext)); + value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER; + value->value.opaque = canvas; + + canvas->context = context; + struct mScriptValue* lambda = mScriptObjectBindLambda(value, "update", NULL); + canvas->frameCbid = mScriptContextAddCallback(context, "frame", lambda); + mScriptValueDeref(lambda); + + mScriptContextSetGlobal(context, "canvas", value); + mScriptContextSetDocstring(context, "canvas", "Singleton instance of struct::mScriptCanvasContext"); +} + +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCanvasContext, _deinit, mScriptCanvasContextDeinit, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, W(mScriptCanvasLayer), newLayer, mScriptCanvasLayerCreate, 2, S32, width, S32, height); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCanvasContext, update, _mScriptCanvasUpdate, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, U32, width, _mScriptCanvasWidth, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, U32, height, _mScriptCanvasHeight, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, S32, screenWidth, _mScriptCanvasScreenWidth, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, S32, screenHeight, _mScriptCanvasScreenHeight, 0); + +mSCRIPT_DEFINE_STRUCT(mScriptCanvasContext) + mSCRIPT_DEFINE_CLASS_DOCSTRING( + "A canvas that can be used for drawing images on or around the screen." + ) + mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptCanvasContext) + mSCRIPT_DEFINE_DOCSTRING("Create a new layer of a given size. If multiple layers overlap, the most recently created one takes priority.") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, newLayer) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, update) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, width) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, height) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, screenWidth) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, screenHeight) +mSCRIPT_DEFINE_END; + +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCanvasLayer, update, mScriptCanvasLayerInvalidate, 0); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCanvasLayer, setPosition, mScriptCanvasLayerSetPosition, 2, S32, x, S32, y); + +mSCRIPT_DEFINE_STRUCT(mScriptCanvasLayer) + mSCRIPT_DEFINE_CLASS_DOCSTRING( + "An individual layer of a drawable canvas." + ) + mSCRIPT_DEFINE_DOCSTRING("Mark the contents of the layer as needed to be repainted") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasLayer, update) + mSCRIPT_DEFINE_DOCSTRING("Set the position of the layer in the canvas") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasLayer, setPosition) + mSCRIPT_DEFINE_DOCSTRING("The image that has the pixel contents of the image") + mSCRIPT_DEFINE_STRUCT_MEMBER(mScriptCanvasLayer, PS(mImage), image) + mSCRIPT_DEFINE_DOCSTRING("The current x (horizontal) position of this layer") + mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mScriptCanvasLayer, S32, x) + mSCRIPT_DEFINE_DOCSTRING("The current y (vertical) position of this layer") + mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mScriptCanvasLayer, S32, y) +mSCRIPT_DEFINE_END; From 428a29dae3b4a7ad88652578d826b4de8f648424 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 1 May 2023 02:45:43 -0700 Subject: [PATCH 201/290] Qt: Expose DisplayQt as a VideoBackend --- src/platform/qt/Display.cpp | 8 ++ src/platform/qt/Display.h | 1 + src/platform/qt/DisplayQt.cpp | 225 +++++++++++++++++++++------------- src/platform/qt/DisplayQt.h | 25 +++- src/platform/qt/Window.cpp | 12 +- 5 files changed, 175 insertions(+), 96 deletions(-) diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index f8b803558..93b4950ef 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -10,6 +10,7 @@ #include "DisplayGL.h" #include "DisplayQt.h" #include "LogController.h" +#include "VideoProxy.h" #include "utils.h" #include @@ -124,6 +125,13 @@ void QGBA::Display::configure(ConfigController* config) { #endif } +VideoBackend* QGBA::Display::videoBackend() { + if (m_videoProxy) { + return m_videoProxy->backend(); + } + return nullptr; +} + void QGBA::Display::resizeEvent(QResizeEvent*) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) m_messagePainter.resize(size(), devicePixelRatioF()); diff --git a/src/platform/qt/Display.h b/src/platform/qt/Display.h index e9c37aa55..3c723ba91 100644 --- a/src/platform/qt/Display.h +++ b/src/platform/qt/Display.h @@ -60,6 +60,7 @@ public: virtual void setVideoProxy(std::shared_ptr proxy) { m_videoProxy = proxy; } std::shared_ptr videoProxy() { return m_videoProxy; } + virtual VideoBackend* videoBackend(); signals: void drawingStarted(); diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index dee0d1244..783f56efc 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau +/* Copyright (c) 2013-2023 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 @@ -19,13 +19,28 @@ using namespace QGBA; DisplayQt::DisplayQt(QWidget* parent) : Display(parent) { + m_backend.init = &DisplayQt::init; + m_backend.deinit = &DisplayQt::deinit; + m_backend.setLayerDimensions = &DisplayQt::setLayerDimensions; + m_backend.layerDimensions = &DisplayQt::layerDimensions; + m_backend.swap = &DisplayQt::swap; + m_backend.clear = &DisplayQt::clear; + m_backend.contextResized = &DisplayQt::contextResized; + m_backend.setImageSize = &DisplayQt::setImageSize; + m_backend.imageSize = &DisplayQt::imageSize; + m_backend.setImage = &DisplayQt::setImage; + m_backend.drawFrame = &DisplayQt::drawFrame; + m_backend.filter = isFiltered(); + m_backend.lockAspectRatio = isAspectRatioLocked(); + m_backend.lockIntegerScaling = isIntegerScalingLocked(); + m_backend.interframeBlending = hasInterframeBlending(); + m_backend.user = this; } void DisplayQt::startDrawing(std::shared_ptr controller) { QSize size = controller->screenDimensions(); m_width = size.width(); m_height = size.height(); - m_backing = QImage(); m_oldBacking = QImage(); m_isDrawing = true; m_context = controller; @@ -39,44 +54,51 @@ void DisplayQt::stopDrawing() { void DisplayQt::lockAspectRatio(bool lock) { Display::lockAspectRatio(lock); + m_backend.lockAspectRatio = lock; update(); } void DisplayQt::lockIntegerScaling(bool lock) { Display::lockIntegerScaling(lock); + m_backend.lockIntegerScaling = lock; update(); } void DisplayQt::interframeBlending(bool lock) { Display::interframeBlending(lock); + m_backend.interframeBlending = lock; update(); } void DisplayQt::filter(bool filter) { Display::filter(filter); + m_backend.filter = filter; update(); } void DisplayQt::framePosted() { update(); const color_t* buffer = m_context->drawContext(); - if (const_cast(m_backing).bits() == reinterpret_cast(buffer)) { + if (const_cast(m_layers[VIDEO_LAYER_IMAGE]).bits() == reinterpret_cast(buffer)) { return; } - m_oldBacking = m_backing; + m_oldBacking = m_layers[VIDEO_LAYER_IMAGE]; #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - m_backing = QImage(reinterpret_cast(buffer), m_width, m_height, QImage::Format_RGB16); + m_layers[VIDEO_LAYER_IMAGE] = QImage(reinterpret_cast(buffer), m_width, m_height, QImage::Format_RGB16); #else - m_backing = QImage(reinterpret_cast(buffer), m_width, m_height, QImage::Format_RGB555); + m_layers[VIDEO_LAYER_IMAGE] = QImage(reinterpret_cast(buffer), m_width, m_height, QImage::Format_RGB555); #endif #else - m_backing = QImage(reinterpret_cast(buffer), m_width, m_height, QImage::Format_ARGB32); - m_backing = m_backing.convertToFormat(QImage::Format_RGB32); + m_layers[VIDEO_LAYER_IMAGE] = QImage(reinterpret_cast(buffer), m_width, m_height, QImage::Format_ARGB32); + m_layers[VIDEO_LAYER_IMAGE] = m_layers[VIDEO_LAYER_IMAGE].convertToFormat(QImage::Format_RGB32); #endif #ifndef COLOR_5_6_5 - m_backing = m_backing.rgbSwapped(); + m_layers[VIDEO_LAYER_IMAGE] = m_layers[VIDEO_LAYER_IMAGE].rgbSwapped(); #endif + m_layerDims[VIDEO_LAYER_IMAGE].setWidth(m_width); + m_layerDims[VIDEO_LAYER_IMAGE].setHeight(m_height); + redoBounds(); } void DisplayQt::resizeContext() { @@ -88,12 +110,13 @@ void DisplayQt::resizeContext() { m_width = size.width(); m_height = size.height(); m_oldBacking = QImage(); - m_backing = QImage(); + m_layers[VIDEO_LAYER_IMAGE] = QImage(); } } void DisplayQt::setBackgroundImage(const QImage& image) { - m_background = image; + m_layers[VIDEO_LAYER_BACKGROUND] = image; + redoBounds(); update(); } @@ -104,91 +127,125 @@ void DisplayQt::paintEvent(QPaintEvent*) { painter.setRenderHint(QPainter::SmoothPixmapTransform); } - QRect bgRect(0, 0, m_background.width(), m_background.height()); - QRect imRect(0, 0, m_width, m_height); - QSize outerFrame = contentSize(); + struct mRectangle frame; + VideoBackendGetFrame(&m_backend, &frame); + QPoint origin(-frame.x, -frame.y); + QRect full(clampSize(contentSize(), size(), isAspectRatioLocked(), isIntegerScalingLocked())); + painter.save(); + painter.translate(full.topLeft()); + painter.scale(full.width() / static_cast(frame.width), full.height() / static_cast(frame.height)); - if (bgRect.width() > imRect.width()) { - imRect.moveLeft(bgRect.width() - imRect.width()); - } else { - bgRect.moveLeft(imRect.width() - bgRect.width()); - } - - if (bgRect.height() > imRect.height()) { - imRect.moveTop(bgRect.height() - imRect.height()); - } else { - bgRect.moveTop(imRect.height() - bgRect.height()); - } - - QRect full(clampSize(outerFrame, size(), isAspectRatioLocked(), isIntegerScalingLocked())); - - if (m_background.isNull()) { - imRect = full; - } else { - if (imRect.x()) { - imRect.moveLeft(imRect.x() * full.width() / bgRect.width() / 2); - imRect.setWidth(imRect.width() * full.width() / bgRect.width()); - bgRect.setWidth(full.width()); - } else { - bgRect.moveLeft(bgRect.x() * full.width() / imRect.width() / 2); - bgRect.setWidth(bgRect.width() * full.width() / imRect.width()); - imRect.setWidth(full.width()); - } - if (imRect.y()) { - imRect.moveTop(imRect.y() * full.height() / bgRect.height() / 2); - imRect.setHeight(imRect.height() * full.height() / bgRect.height()); - bgRect.setHeight(full.height()); - } else { - bgRect.moveTop(bgRect.y() * full.height() / imRect.height() / 2); - bgRect.setHeight(bgRect.height() * full.height() / imRect.height()); - imRect.setHeight(full.height()); - } - - if (bgRect.right() > imRect.right()) { - if (bgRect.right() < full.right()) { - imRect.translate((full.right() - bgRect.right()), 0); - bgRect.translate((full.right() - bgRect.right()), 0); - } - } else { - if (imRect.right() < full.right()) { - bgRect.translate((full.right() - imRect.right()), 0); - imRect.translate((full.right() - imRect.right()), 0); - } - } - - if (bgRect.bottom() > imRect.bottom()) { - if (bgRect.bottom() < full.bottom()) { - imRect.translate(0, (full.bottom() - bgRect.bottom())); - bgRect.translate(0, (full.bottom() - bgRect.bottom())); - } - } else { - if (imRect.bottom() < full.bottom()) { - bgRect.translate(0, (full.bottom() - imRect.bottom())); - imRect.translate(0, (full.bottom() - imRect.bottom())); - } - } - painter.drawImage(bgRect, m_background); + if (!m_layers[VIDEO_LAYER_BACKGROUND].isNull()) { + painter.drawImage(m_layerDims[VIDEO_LAYER_BACKGROUND].translated(origin), m_layers[VIDEO_LAYER_BACKGROUND]); } if (hasInterframeBlending()) { - painter.drawImage(imRect, m_oldBacking, QRect(0, 0, m_width, m_height)); + painter.drawImage(m_layerDims[VIDEO_LAYER_IMAGE].translated(origin), m_oldBacking, QRect(0, 0, m_width, m_height)); painter.setOpacity(0.5); } - painter.drawImage(imRect, m_backing, QRect(0, 0, m_width, m_height)); + painter.drawImage(m_layerDims[VIDEO_LAYER_IMAGE].translated(origin), m_layers[VIDEO_LAYER_IMAGE], QRect(0, 0, m_width, m_height)); + + for (int i = VIDEO_LAYER_IMAGE + 1; i < VIDEO_LAYER_MAX; ++i) { + if (m_layers[i].isNull()) { + continue; + } + + painter.drawImage(m_layerDims[i].translated(origin), m_layers[i]); + } + + painter.restore(); painter.setOpacity(1); if (isShowOSD() || isShowFrameCounter()) { messagePainter()->paint(&painter); } } -QSize DisplayQt::contentSize() const { - QSize outerFrame(m_width, m_height); +void DisplayQt::redoBounds() { + const static std::initializer_list centeredLayers{VIDEO_LAYER_BACKGROUND}; + mRectangle frame = {0}; + frame.width = m_width; + frame.height = m_height; - if (m_background.width() > outerFrame.width()) { - outerFrame.setWidth(m_background.width()); + for (VideoLayer l : centeredLayers) { + mRectangle dims{}; + dims.width = m_layers[l].width(); + dims.height = m_layers[l].height(); + mRectangleCenter(&frame, &dims); + m_layerDims[l].setX(dims.x); + m_layerDims[l].setY(dims.y); + m_layerDims[l].setWidth(dims.width); + m_layerDims[l].setHeight(dims.height); } - if (m_background.height() > outerFrame.height()) { - outerFrame.setHeight(m_background.height()); - } - return outerFrame; +} + +QSize DisplayQt::contentSize() const { + unsigned w, h; + VideoBackendGetFrameSize(&m_backend, &w, &h); + return {w, h}; +} + +void DisplayQt::init(struct VideoBackend*, WHandle) { +} + +void DisplayQt::deinit(struct VideoBackend*) { +} + +void DisplayQt::setLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct mRectangle* dims) { + DisplayQt* self = static_cast(v->user); + if (layer > self->m_layerDims.size()) { + return; + } + self->m_layerDims[layer] = QRect(dims->x, dims->y, dims->width, dims->height); +} + +void DisplayQt::layerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct mRectangle* dims) { + DisplayQt* self = static_cast(v->user); + if (layer > self->m_layerDims.size()) { + return; + } + QRect rect = self->m_layerDims[layer]; + dims->x = rect.x(); + dims->y = rect.y(); + dims->width = rect.width(); + dims->height = rect.height(); +} + +void DisplayQt::swap(struct VideoBackend*) { +} + +void DisplayQt::clear(struct VideoBackend*) { +} + +void DisplayQt::contextResized(struct VideoBackend*, unsigned, unsigned) { +} + +void DisplayQt::setImageSize(struct VideoBackend* v, enum VideoLayer layer, int w, int h) { + DisplayQt* self = static_cast(v->user); + if (layer > self->m_layers.size()) { + return; + } + self->m_layers[layer] = QImage(w, h, QImage::Format_ARGB32); +} + +void DisplayQt::imageSize(struct VideoBackend* v, enum VideoLayer layer, int* w, int* h) { + DisplayQt* self = static_cast(v->user); + if (layer > self->m_layers.size()) { + return; + } + *w = self->m_layers[layer].width(); + *h = self->m_layers[layer].height(); +} + +void DisplayQt::setImage(struct VideoBackend* v, enum VideoLayer layer, const void* frame) { + DisplayQt* self = static_cast(v->user); + if (layer > self->m_layers.size()) { + return; + } + QImage image = self->m_layers[layer]; + image = QImage(static_cast(frame), image.width(), image.height(), QImage::Format_ARGB32).rgbSwapped(); + self->m_layers[layer] = image; +} + +void DisplayQt::drawFrame(struct VideoBackend* v) { + QMetaObject::invokeMethod(static_cast(v->user), "update"); } diff --git a/src/platform/qt/DisplayQt.h b/src/platform/qt/DisplayQt.h index 78deb2042..c1363dede 100644 --- a/src/platform/qt/DisplayQt.h +++ b/src/platform/qt/DisplayQt.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau +/* Copyright (c) 2013-2023 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 @@ -10,6 +10,9 @@ #include #include +#include +#include + namespace QGBA { class DisplayQt : public Display { @@ -23,6 +26,7 @@ public: bool supportsShaders() const override { return false; } VideoShader* shaders() override { return nullptr; } QSize contentSize() const override; + VideoBackend* videoBackend() override { return &m_backend; } public slots: void stopDrawing() override; @@ -44,12 +48,27 @@ protected: virtual void paintEvent(QPaintEvent*) override; private: + void redoBounds(); + + static void init(struct VideoBackend*, WHandle); + static void deinit(struct VideoBackend*); + static void setLayerDimensions(struct VideoBackend*, enum VideoLayer, const struct mRectangle*); + static void layerDimensions(const struct VideoBackend*, enum VideoLayer, struct mRectangle*); + static void swap(struct VideoBackend*); + static void clear(struct VideoBackend*); + static void contextResized(struct VideoBackend*, unsigned w, unsigned h); + static void setImageSize(struct VideoBackend*, enum VideoLayer, int w, int h); + static void imageSize(struct VideoBackend*, enum VideoLayer, int* w, int* h); + static void setImage(struct VideoBackend*, enum VideoLayer, const void* frame); + static void drawFrame(struct VideoBackend*); + + VideoBackend m_backend{}; + std::array m_layerDims; + std::array m_layers; bool m_isDrawing = false; int m_width = -1; int m_height = -1; - QImage m_backing{nullptr}; QImage m_oldBacking{nullptr}; - QImage m_background; std::shared_ptr m_context = nullptr; }; diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 26d5d4255..c0fd9e339 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -634,10 +634,7 @@ void Window::scriptingOpen() { m_display->installEventFilter(m_scripting.get()); } - std::shared_ptr proxy = m_display->videoProxy(); - if (proxy) { - m_scripting->setVideoBackend(proxy->backend()); - } + m_scripting->setVideoBackend(m_display->videoBackend()); } ScriptingView* view = new ScriptingView(m_scripting.get(), m_config); openView(view); @@ -1082,7 +1079,7 @@ void Window::reloadDisplayDriver() { m_display->setVideoProxy(proxy); #ifdef ENABLE_SCRIPTING if (m_scripting) { - m_scripting->setVideoBackend(proxy->backend()); + m_scripting->setVideoBackend(m_display->videoBackend()); } #endif } @@ -2135,10 +2132,7 @@ void Window::setController(CoreController* controller, const QString& fname) { if (m_scripting) { m_scripting->setController(m_controller); - std::shared_ptr proxy = m_display->videoProxy(); - if (proxy) { - m_scripting->setVideoBackend(proxy->backend()); - } + m_scripting->setVideoBackend(m_display->videoBackend()); } #endif From 55dd3e28db466b329974a3bff335515743819b6f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 29 Apr 2023 03:22:30 -0700 Subject: [PATCH 202/290] Scripting: Add canvas internal scaling factor --- include/mgba/script/canvas.h | 1 + src/platform/qt/Window.cpp | 5 +++ .../qt/scripting/ScriptingController.cpp | 11 ++++++ .../qt/scripting/ScriptingController.h | 1 + src/script/canvas.c | 36 ++++++++++++++----- 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/include/mgba/script/canvas.h b/include/mgba/script/canvas.h index 39c8edfd1..e48cd4e5d 100644 --- a/include/mgba/script/canvas.h +++ b/include/mgba/script/canvas.h @@ -17,6 +17,7 @@ struct VideoBackend; void mScriptContextAttachCanvas(struct mScriptContext* context); void mScriptCanvasUpdate(struct mScriptContext* context); void mScriptCanvasUpdateBackend(struct mScriptContext* context, struct VideoBackend*); +void mScriptCanvasSetInternalScale(struct mScriptContext* context, unsigned scale); CXX_GUARD_END diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index c0fd9e339..6cdace391 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1888,6 +1888,11 @@ void Window::setupOptions() { videoScale->connect([this](const QVariant& value) { if (m_display) { m_display->setVideoScale(value.toInt()); +#ifdef ENABLE_SCRIPTING + if (m_controller && m_scripting) { + m_scripting->updateVideoScale(); + } +#endif } }, this); diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index 3542bf28b..701243d40 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -70,12 +70,16 @@ void ScriptingController::setController(std::shared_ptr controll return; } clearController(); + if (!controller) { + return; + } m_controller = controller; CoreController::Interrupter interrupter(m_controller); m_controller->thread()->scriptContext = &m_scriptContext; if (m_controller->hasStarted()) { mScriptContextAttachCore(&m_scriptContext, m_controller->thread()->core); } + updateVideoScale(); connect(m_controller.get(), &CoreController::stopping, this, &ScriptingController::clearController); } @@ -135,6 +139,13 @@ void ScriptingController::clearController() { m_controller.reset(); } +void ScriptingController::updateVideoScale() { + if (!m_controller) { + return; + } + mScriptCanvasSetInternalScale(&m_scriptContext, m_controller->videoScale()); +} + void ScriptingController::reset() { CoreController::Interrupter interrupter(m_controller); m_bufferModel->reset(); diff --git a/src/platform/qt/scripting/ScriptingController.h b/src/platform/qt/scripting/ScriptingController.h index da99a5785..f2e698d76 100644 --- a/src/platform/qt/scripting/ScriptingController.h +++ b/src/platform/qt/scripting/ScriptingController.h @@ -56,6 +56,7 @@ signals: public slots: void clearController(); + void updateVideoScale(); void reset(); void runCode(const QString& code); diff --git a/src/script/canvas.c b/src/script/canvas.c index c8b0441ac..7d8a725d1 100644 --- a/src/script/canvas.c +++ b/src/script/canvas.c @@ -16,6 +16,7 @@ struct mScriptCanvasLayer { struct mImage* image; int x; int y; + unsigned scale; bool dirty; bool sizeDirty; bool dimsDirty; @@ -27,6 +28,7 @@ struct mScriptCanvasContext { struct VideoBackend* backend; struct mScriptContext* context; uint32_t frameCbid; + unsigned scale; }; mSCRIPT_DECLARE_STRUCT(mScriptCanvasContext); @@ -77,6 +79,22 @@ void mScriptCanvasUpdateBackend(struct mScriptContext* context, struct VideoBack } } +void mScriptCanvasSetInternalScale(struct mScriptContext* context, unsigned scale) { + struct mScriptValue* value = mScriptContextGetGlobal(context, "canvas"); + if (!value) { + return; + } + struct mScriptCanvasContext* canvas = value->value.opaque; + if (scale < 1) { + scale = 1; + } + canvas->scale = scale; + size_t i; + for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) { + canvas->overlays[i].scale = scale; + } +} + static void _mScriptCanvasUpdate(struct mScriptCanvasContext* canvas) { size_t i; for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) { @@ -90,7 +108,7 @@ static unsigned _mScriptCanvasWidth(struct mScriptCanvasContext* canvas) { } unsigned w, h; VideoBackendGetFrameSize(canvas->backend, &w, &h); - return w; + return w / canvas->scale; } static unsigned _mScriptCanvasHeight(struct mScriptCanvasContext* canvas) { @@ -99,7 +117,7 @@ static unsigned _mScriptCanvasHeight(struct mScriptCanvasContext* canvas) { } unsigned w, h; VideoBackendGetFrameSize(canvas->backend, &w, &h); - return h; + return h / canvas->scale; } static int _mScriptCanvasScreenWidth(struct mScriptCanvasContext* canvas) { @@ -108,7 +126,7 @@ static int _mScriptCanvasScreenWidth(struct mScriptCanvasContext* canvas) { } struct mRectangle dims; canvas->backend->layerDimensions(canvas->backend, VIDEO_LAYER_IMAGE, &dims); - return dims.width; + return dims.width / canvas->scale; } static int _mScriptCanvasScreenHeight(struct mScriptCanvasContext* canvas) { @@ -117,7 +135,7 @@ static int _mScriptCanvasScreenHeight(struct mScriptCanvasContext* canvas) { } struct mRectangle dims; canvas->backend->layerDimensions(canvas->backend, VIDEO_LAYER_IMAGE, &dims); - return dims.height; + return dims.height / canvas->scale; } void mScriptCanvasUpdate(struct mScriptContext* context) { @@ -149,6 +167,7 @@ static struct mScriptValue* mScriptCanvasLayerCreate(struct mScriptCanvasContext layer->dimsDirty = true; layer->sizeDirty = true; layer->contentsDirty = true; + layer->scale = context->scale; mScriptCanvasLayerUpdate(layer); struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptCanvasLayer)); @@ -180,10 +199,10 @@ static void mScriptCanvasLayerUpdate(struct mScriptCanvasLayer* layer) { } if (layer->dimsDirty) { struct mRectangle frame = { - .x = layer->x, - .y = layer->y, - .width = layer->image->width, - .height = layer->image->height, + .x = layer->x * layer->scale, + .y = layer->y * layer->scale, + .width = layer->image->width * layer->scale, + .height = layer->image->height * layer->scale, }; backend->setLayerDimensions(backend, layer->layer, &frame); layer->dimsDirty = false; @@ -210,6 +229,7 @@ static void mScriptCanvasLayerInvalidate(struct mScriptCanvasLayer* layer) { void mScriptContextAttachCanvas(struct mScriptContext* context) { struct mScriptCanvasContext* canvas = calloc(1, sizeof(*canvas)); + canvas->scale = 1; size_t i; for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) { canvas->overlays[i].layer = VIDEO_LAYER_OVERLAY0 + i; From 2b3855f2e4933a6965ff8c258429fc6a56a08f5a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 24 Apr 2023 20:20:49 -0700 Subject: [PATCH 203/290] Res: Add a bouncing logo demo script --- res/scripts/logo-bounce.lua | 44 ++++++++++++++++++++++++++++++++++++ res/scripts/logo.png | Bin 0 -> 1217 bytes 2 files changed, 44 insertions(+) create mode 100644 res/scripts/logo-bounce.lua create mode 100644 res/scripts/logo.png diff --git a/res/scripts/logo-bounce.lua b/res/scripts/logo-bounce.lua new file mode 100644 index 000000000..c8192638c --- /dev/null +++ b/res/scripts/logo-bounce.lua @@ -0,0 +1,44 @@ +math.randomseed(os.time()) +local state = {} +state.logo = image.load(script.dir .. "/logo.png") +state.overlay = canvas:newLayer(state.logo.width, state.logo.height) +state.overlay.image:drawImageOpaque(state.logo, 0, 0) +state.x = math.random() * (canvas:screenWidth() - state.logo.width) +state.y = math.random() * (canvas:screenHeight() - state.logo.height) +state.direction = math.floor(math.random() * 3) +state.speed = 0.5 + +state.overlay:setPosition(math.floor(state.x), math.floor(state.y)) +state.overlay:update() + +function state.update() + if state.direction & 1 == 1 then + state.x = state.x + 1 + if state.x > canvas:screenWidth() - state.logo.width then + state.x = (canvas:screenWidth() - state.logo.width) * 2 - state.x + state.direction = state.direction ~ 1 + end + else + state.x = state.x - 1 + if state.x < 0 then + state.x = -state.x + state.direction = state.direction ~ 1 + end + end + if state.direction & 2 == 2 then + state.y = state.y + 1 + if state.y > canvas:screenHeight() - state.logo.height then + state.y = (canvas:screenHeight() - state.logo.height) * 2 - state.y + state.direction = state.direction ~ 2 + end + else + state.y = state.y - 1 + if state.y < 0 then + state.y = -state.y + state.direction = state.direction ~ 2 + end + end + state.overlay:setPosition(math.floor(state.x), math.floor(state.y)) +end + +callbacks:add("frame", state.update) diff --git a/res/scripts/logo.png b/res/scripts/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b986e7597a288642bed747211651d629aad7cfe2 GIT binary patch literal 1217 zcmV;y1U~zTP)6L+p=2uCI$)5R!OASy_}9q*p!LD&K39h^Pedw1XO^Ld`CKHk61;B$jLLSM*SCqSHj9 zM5dnZfpeJ&x|`%++(V zZ5(V%CZZmq2BLRUqe|n_Rpp*b0^lNr!`3#Hd56N=eG~j14Tsz>8PNf`(1yLgEU^8> zC-q%zSCzH_X0CJILV8d}#w)0=uSZr^7X1DFA(2QR5{a?O*%Rv?4rEaA^0#dQ7#c60 z!_4R++D+uP7(gL(@KI&h`Fz#f!nkL4qoAMwmSbdO1kKIOC@LyKTwEMnU0n%~2-1~q z3~1{{9!a6a^8k3IfM2@@S}eBthlpEv0~cR?;V_$hJ?&GrP_a1^`T6;0z(Zo>FuZ!1|cZLz$8i1~;pH@W(B)~r?1^L|C;(hE1m4Wavi06PcXv(Uo|+6a|nQf zhduGKP3O#rKQQ|5_~dWIyhW6%EIqZ*VDq}|NK8z02!MQ71o~<1hUkf-Mk6sdTFnC# zAT~DEA%K{e7;N!<)-FJeaJqw-{~{uJKGkxf@$+^BtWQ8tP>@3aa=Cmi60#BxvQVV| z-^ncM|gs8p>kw;*13uBK&tiDwU$Ku@RG#lgk2-$z&{hzO|zcG`d>P z^ss%0|Gv%U3v<3;&$X3?3p7EoWS~~-9waX-iOiH*6Iz0mD;{Er#@*eWWkZ9(fT^h| zy8vEZUU+H|Ae|GTfd1C(aytJ4WB`C<`Rcb&( ziDo$pF)I%b4`gO$qN%9~lC?fmW+UZEYV*EDRVq~}@Wilp zwl=Ks+yb#!vIvBV7OLRjV5Fp^uxD%Oc%ja>{C<`I=xo+%+WqhmGy;-XIsQL8T-r4gm>kkU@0*cONPl^SaER!u^q5Nhf#UD$ ft_=)rpI!4G(XC=y#0#gu00000NkvXXu0mjfBR@;6 literal 0 HcmV?d00001 From ac9ffdd765c84e1728df41d767fc00bfa92a4486 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 1 May 2023 22:22:51 -0700 Subject: [PATCH 204/290] Qt: Add a saturateCast template --- src/platform/qt/DisplayGL.cpp | 5 ++- src/platform/qt/DisplayQt.cpp | 2 +- .../qt/scripting/ScriptingTextBuffer.cpp | 10 ++--- src/platform/qt/utils.h | 40 +++++++++++++++++++ 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index bf9f6a867..c90c19aac 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -60,6 +60,7 @@ typedef struct _XDisplay Display; #endif #include "OpenGLBug.h" +#include "utils.h" using namespace QGBA; @@ -1003,8 +1004,8 @@ VideoShader* PainterGL::shaders() { QSize PainterGL::contentSize() const { unsigned width, height; VideoBackendGetFrameSize(m_backend, &width, &height); - return {static_cast(width > static_cast(INT_MAX) ? INT_MAX : width), - static_cast(height > static_cast(INT_MAX) ? INT_MAX : height)}; + return {saturateCast(width), + saturateCast(height)}; } int PainterGL::glTex() { diff --git a/src/platform/qt/DisplayQt.cpp b/src/platform/qt/DisplayQt.cpp index 783f56efc..51874cb40 100644 --- a/src/platform/qt/DisplayQt.cpp +++ b/src/platform/qt/DisplayQt.cpp @@ -181,7 +181,7 @@ void DisplayQt::redoBounds() { QSize DisplayQt::contentSize() const { unsigned w, h; VideoBackendGetFrameSize(&m_backend, &w, &h); - return {w, h}; + return {saturateCast(w), saturateCast(h)}; } void DisplayQt::init(struct VideoBackend*, WHandle) { diff --git a/src/platform/qt/scripting/ScriptingTextBuffer.cpp b/src/platform/qt/scripting/ScriptingTextBuffer.cpp index 7a4785d01..fe85eda0e 100644 --- a/src/platform/qt/scripting/ScriptingTextBuffer.cpp +++ b/src/platform/qt/scripting/ScriptingTextBuffer.cpp @@ -6,6 +6,7 @@ #include "ScriptingTextBuffer.h" #include "GBAApp.h" +#include "utils.h" #include #include @@ -205,13 +206,8 @@ void ScriptingTextBuffer::setSize(struct mScriptTextBuffer* buffer, uint32_t col void ScriptingTextBuffer::moveCursor(struct mScriptTextBuffer* buffer, uint32_t x, uint32_t y) { ScriptingTextBuffer* self = static_cast(buffer)->p; - if (x > INT_MAX) { - x = INT_MAX; - } - if (y > INT_MAX) { - y = INT_MAX; - } - QMetaObject::invokeMethod(self, "moveCursor", Q_ARG(QPoint, QPoint(x, y))); + QPoint point(saturateCast(x), saturateCast(y)); + QMetaObject::invokeMethod(self, "moveCursor", Q_ARG(QPoint, point)); } void ScriptingTextBuffer::advance(struct mScriptTextBuffer* buffer, int32_t adv) { diff --git a/src/platform/qt/utils.h b/src/platform/qt/utils.h index 0ce974bc5..6e2f6ec96 100644 --- a/src/platform/qt/utils.h +++ b/src/platform/qt/utils.h @@ -72,6 +72,46 @@ constexpr const T& clamp(const T& v, const T& lo, const T& hi) { } #endif +template +constexpr T saturateCast(U value) { + if (std::numeric_limits::is_signed == std::numeric_limits::is_signed) { + if (value > std::numeric_limits::max()) { + return std::numeric_limits::max(); + } + if (value < std::numeric_limits::min()) { + return std::numeric_limits::min(); + } + } else if (std::numeric_limits::is_signed) { + if (value > static_cast(std::numeric_limits::max())) { + std::numeric_limits::max(); + } + } else { + if (value < 0) { + return 0; + } + if (static_cast(value) > std::numeric_limits::max()) { + std::numeric_limits::max(); + } + } + return static_cast(value); +} + +template<> +constexpr unsigned saturateCast(int value) { + if (value < 0) { + return 0; + } + return static_cast(value); +} + +template<> +constexpr int saturateCast(unsigned value) { + if (value > static_cast(std::numeric_limits::max())) { + return std::numeric_limits::max(); + } + return static_cast(value); +} + QString romFilters(bool includeMvl = false); bool extractMatchingFile(VDir* dir, std::function filter); From 608029e930ec0010e58d01b0fc98545a572dff82 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 1 May 2023 22:27:16 -0700 Subject: [PATCH 205/290] Qt: Promote -Wnarrowing to an error --- src/platform/qt/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 44a208b81..2a7092ff6 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -64,7 +64,7 @@ if(APPLE) endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations -Werror=narrowing") endif() get_target_property(QT_TYPE ${QT}::Core TYPE) From 74a72a5c071bbefa90c7d67a0b1d4c6227f17d9a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 3 May 2023 01:37:56 -0700 Subject: [PATCH 206/290] Scripting: Add missing docs to canvas --- src/script/canvas.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/script/canvas.c b/src/script/canvas.c index 7d8a725d1..5e931943d 100644 --- a/src/script/canvas.c +++ b/src/script/canvas.c @@ -262,10 +262,15 @@ mSCRIPT_DEFINE_STRUCT(mScriptCanvasContext) mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptCanvasContext) mSCRIPT_DEFINE_DOCSTRING("Create a new layer of a given size. If multiple layers overlap, the most recently created one takes priority.") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, newLayer) + mSCRIPT_DEFINE_DOCSTRING("Update all layers marked as having pending changes") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, update) + mSCRIPT_DEFINE_DOCSTRING("Get the width of the canvas") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, width) + mSCRIPT_DEFINE_DOCSTRING("Get the height of the canvas") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, height) + mSCRIPT_DEFINE_DOCSTRING("Get the width of the emulated screen") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, screenWidth) + mSCRIPT_DEFINE_DOCSTRING("Get the height of the emulated screen") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, screenHeight) mSCRIPT_DEFINE_END; From 2b8bb4baf791072022e9ccc7c7e66c2878c26f42 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 3 May 2023 01:44:56 -0700 Subject: [PATCH 207/290] Qt: Mark QtMultimedia as optional --- src/platform/qt/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 2a7092ff6..41f3f8614 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -25,7 +25,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(QT_LIBRARIES) -find_package(Qt5 COMPONENTS Core Widgets Network Multimedia) +find_package(Qt5 COMPONENTS Core Widgets Network OPTIONAL_COMPONENTS Multimedia) set(QT Qt5) if(NOT BUILD_GL AND NOT BUILD_GLES2 AND NOT BUILD_GLES3) From d8643870d6dda25157c09afe0622cf88480e1058 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 3 May 2023 02:21:55 -0700 Subject: [PATCH 208/290] Qt: A bit more Qt 6 porting progress --- src/platform/qt/CMakeLists.txt | 21 +++++++++++++++------ src/platform/qt/InputController.h | 4 ++++ src/platform/qt/InputProfile.cpp | 6 +++--- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 41f3f8614..0818fefbc 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -25,8 +25,12 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(QT_LIBRARIES) -find_package(Qt5 COMPONENTS Core Widgets Network OPTIONAL_COMPONENTS Multimedia) -set(QT Qt5) +set(QT_V 5) +find_package(Qt${QT_V} COMPONENTS Core Widgets Network OPTIONAL_COMPONENTS Multimedia) +if(QT_V GREATER_EQUAL 6) + find_package(Qt${QT_V} COMPONENTS OpenGL OpenGLWidgets) +endif() +set(QT Qt${QT_V}) if(NOT BUILD_GL AND NOT BUILD_GLES2 AND NOT BUILD_GLES3) message(WARNING "OpenGL is recommended to build the Qt port") @@ -199,7 +203,7 @@ set(GB_SRC GBOverride.cpp PrinterView.cpp) -set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5") +set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt${QT_V}widgets${QT_V}") set(AUDIO_SRC) if(BUILD_SDL) @@ -219,15 +223,17 @@ if(${QT}Multimedia_FOUND) list(APPEND AUDIO_SRC AudioProcessorQt.cpp AudioDevice.cpp) - list(APPEND SOURCE_FILES - VideoDumper.cpp) + if(QT_V LESS 6) + list(APPEND SOURCE_FILES + VideoDumper.cpp) + endif() if (WIN32 AND QT_STATIC) list(APPEND QT_LIBRARIES ${QT}::QWindowsAudioPlugin ${QT}::DSServicePlugin ${QT}::QWindowsVistaStylePlugin ${QT}::QJpegPlugin strmiids mfuuid mfplat mf ksguid dxva2 evr d3d9) endif() list(APPEND QT_LIBRARIES ${QT}::Multimedia) list(APPEND QT_DEFINES BUILD_QT_MULTIMEDIA) - set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5multimedia5") + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt${QT_V}multimedia${QT_V}") endif() if(NOT AUDIO_SRC) @@ -403,6 +409,9 @@ if(WIN32) endif() list(APPEND QT_LIBRARIES ${QT}::Widgets ${QT}::Network) +if(${QT_V} GREATER_EQUAL 6) + list(APPEND QT_LIBRARIES ${QT}::OpenGL ${QT}::OpenGLWidgets) +endif() if(BUILD_GL OR BUILD_GLES2 OR BUILD_EPOXY) list(APPEND QT_LIBRARIES ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) endif() diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h index 12f4e8915..936b211eb 100644 --- a/src/platform/qt/InputController.h +++ b/src/platform/qt/InputController.h @@ -25,7 +25,9 @@ #include #ifdef BUILD_QT_MULTIMEDIA +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) #include "VideoDumper.h" +#endif #include #endif @@ -165,7 +167,9 @@ private: bool m_cameraActive = false; QByteArray m_cameraDevice; std::unique_ptr m_camera; +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) VideoDumper m_videoDumper; +#endif #endif mInputMap m_inputMap; diff --git a/src/platform/qt/InputProfile.cpp b/src/platform/qt/InputProfile.cpp index e8f7f1711..852dbb413 100644 --- a/src/platform/qt/InputProfile.cpp +++ b/src/platform/qt/InputProfile.cpp @@ -8,7 +8,7 @@ #include "input/InputMapper.h" #include "InputController.h" -#include +#include using namespace QGBA; @@ -202,8 +202,8 @@ constexpr InputProfile::InputProfile(const char* name, const InputProfile* InputProfile::findProfile(const QString& name) { for (size_t i = 0; i < sizeof(s_defaultMaps) / sizeof(*s_defaultMaps); ++i) { - QRegExp re(s_defaultMaps[i].m_profileName); - if (re.exactMatch(name)) { + QRegularExpression re(QString("^%1$").arg(s_defaultMaps[i].m_profileName)); + if (re.match(name).hasMatch()) { return &s_defaultMaps[i]; } } From 00d1c0dc9df98b642a6b1f3bb7f8f58ceda1d956 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 3 May 2023 06:55:17 -0700 Subject: [PATCH 209/290] Qt: Fix saturateCast signed check --- src/platform/qt/utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/qt/utils.h b/src/platform/qt/utils.h index 6e2f6ec96..7f317f499 100644 --- a/src/platform/qt/utils.h +++ b/src/platform/qt/utils.h @@ -74,7 +74,7 @@ constexpr const T& clamp(const T& v, const T& lo, const T& hi) { template constexpr T saturateCast(U value) { - if (std::numeric_limits::is_signed == std::numeric_limits::is_signed) { + if (std::numeric_limits::is_signed == std::numeric_limits::is_signed) { if (value > std::numeric_limits::max()) { return std::numeric_limits::max(); } From 0a36069315b0f6ae7cabb4d6a6dad5dff0f7944c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 5 May 2023 02:00:03 -0700 Subject: [PATCH 210/290] Util: Fix handling of SocketPoll of n > 1 --- include/mgba-util/socket.h | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/include/mgba-util/socket.h b/include/mgba-util/socket.h index 96edc3d92..ff9d89626 100644 --- a/include/mgba-util/socket.h +++ b/include/mgba-util/socket.h @@ -434,9 +434,9 @@ static inline int SocketPoll(size_t nSockets, Socket* reads, Socket* writes, Soc #else int result = select(maxFd, &rset, &wset, &eset, timeoutMillis < 0 ? 0 : &tv); #endif - int r = 0; - int w = 0; - int e = 0; + size_t r = 0; + size_t w = 0; + size_t e = 0; Socket j; for (j = 0; j < maxFd; ++j) { if (reads && FD_ISSET(j, &rset)) { @@ -452,6 +452,21 @@ static inline int SocketPoll(size_t nSockets, Socket* reads, Socket* writes, Soc ++e; } } + if (reads) { + for (; r < nSockets; ++r) { + reads[r] = INVALID_SOCKET; + } + } + if (writes) { + for (; w < nSockets; ++w) { + writes[w] = INVALID_SOCKET; + } + } + if (errors) { + for (; e < nSockets; ++e) { + errors[e] = INVALID_SOCKET; + } + } return result; } From abf08484214284b9b93fe669c41c157667605551 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 8 May 2023 00:41:58 -0700 Subject: [PATCH 211/290] mGUI: Make "bios" name check case-insensitive --- src/feature/gui/gui-config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/feature/gui/gui-config.c b/src/feature/gui/gui-config.c index 8ab4dd7dc..4a6c79871 100644 --- a/src/feature/gui/gui-config.c +++ b/src/feature/gui/gui-config.c @@ -31,7 +31,7 @@ static bool _biosNamed(const char* name) { char ext[PATH_MAX + 1] = {}; separatePath(name, NULL, NULL, ext); - if (strstr(name, "bios")) { + if (strcasestr(name, "bios")) { return true; } if (!strncmp(ext, "bin", PATH_MAX)) { From 36e62c085d4768665c5ac07eb47d9dcf79341784 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 8 May 2023 04:42:08 -0700 Subject: [PATCH 212/290] GB SIO: Disabling SIO should cancel pending transfers (fixes #2537) --- CHANGES | 1 + src/gb/sio.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 9496d97a1..ca460464c 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,7 @@ Features: Emulation fixes: - GB Audio: Fix channels 1/2 staying muted if restarted after long silence - GB Serialize: Add missing Pocket Cam state to savestates + - GB SIO: Disabling SIO should cancel pending transfers (fixes mgba.io/i/2537) - GB Video: Implement DMG-style sprite ordering - GBA BIOS: Fix clobbering registers with word-sized CpuSet - GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) diff --git a/src/gb/sio.c b/src/gb/sio.c index d327b606d..649213168 100644 --- a/src/gb/sio.c +++ b/src/gb/sio.c @@ -91,11 +91,13 @@ void GBSIOWriteSB(struct GBSIO* sio, uint8_t sb) { void GBSIOWriteSC(struct GBSIO* sio, uint8_t sc) { sio->period = GBSIOCyclesPerTransfer[GBRegisterSCGetClockSpeed(sc)]; // TODO Shift Clock if (GBRegisterSCIsEnable(sc)) { - mTimingDeschedule(&sio->p->timing, &sio->event); if (GBRegisterSCIsShiftClock(sc)) { + mTimingDeschedule(&sio->p->timing, &sio->event); mTimingSchedule(&sio->p->timing, &sio->event, sio->period * (2 - sio->p->doubleSpeed)); sio->remainingBits = 8; } + } else { + mTimingDeschedule(&sio->p->timing, &sio->event); } if (sio->driver) { sio->driver->writeSC(sio->driver, sc); From 4f58f85572a2a3db5d43f992a613366d6b6b2770 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 8 May 2023 05:59:36 -0700 Subject: [PATCH 213/290] Res: Use a slightly larger image for the logo-bounce demo --- res/scripts/logo.png | Bin 1217 -> 1592 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/res/scripts/logo.png b/res/scripts/logo.png index b986e7597a288642bed747211651d629aad7cfe2..77ec0693a60e8eee9bf539e425f9beb5d7ca3df6 100644 GIT binary patch literal 1592 zcmV-82FLk{P)XhBxP1_Egp!k|j#;PXrHg<_u?-b3bTkab zPC!|J2_2BKj%2U^A(f?xhSEeWFU3U$8&A*sy6rI7aFh9-^FQa?zVChZJJ0t%w>gD} zhdUVs09=$8YNlSV->9y=mc1{zsF(>+Hb_N^`>1+Uj#Mb$#E2DPQGyh&p8Fq{2$(DC zAnFbn)_-@~e%lFMdfFbwc` zJTvIQ92XevISpovUjd5)H(7;zWzn9NwpMLDn6Elipe4v|vgu^eS@`>sWatnNC@n1odsj4z=Q(0MA4uK2^2!NRr8Ji-1^}L-O=V}^H z8@AphkhjQeNM&TH2gwb6^Ql@BlJ7IoTnQ91aJ3y#;nbo+zyS znFNoMRH#hoeY3nw4Qs=9Qb?0Ubo&<9~t3}i4u|WbWe}Y7{Ai2F1x$CLu_m; z6c-nRR;#rcva+(0?jJF19JQN~@*U{)-Eg+5k$!&lrM*yFaRtuRTs8A#8P=bkr_q>+Cx6an?sX&Pgo;xOiT>q=jTIvdwai-B_$B_|p}D!a4`fbGjsbG4UD18%yoQ{~i&h&= zNob-gBa|OBc1bH|ZwEi#JZyg1R$+-)EEWU?20}tY0#sF1L3ejIBqb%$dtQb!X{)YZ zy{$sX@`twW_;4_)*a%BT2|qjngV`?q!W!l6?M*#RuE&fFunY3uowizp%(HnIiV!O% zV0B8z&G(3bh~N|uOo~MBOrWXC7T~Eq3+;k5bVb|kp4`aPhv_OFjZ_%~G>#;!gO=Svi1YRQ*D?USjvIioam+~mNCjWHc^(DOi q2TSCg)Q#V$&s1LCkAOZ#8~+2_Np?77%b5iL0000pP~ literal 1217 zcmV;y1U~zTP)6L+p=2uCI$)5R!OASy_}9q*p!LD&K39h^Pedw1XO^Ld`CKHk61;B$jLLSM*SCqSHj9 zM5dnZfpeJ&x|`%++(V zZ5(V%CZZmq2BLRUqe|n_Rpp*b0^lNr!`3#Hd56N=eG~j14Tsz>8PNf`(1yLgEU^8> zC-q%zSCzH_X0CJILV8d}#w)0=uSZr^7X1DFA(2QR5{a?O*%Rv?4rEaA^0#dQ7#c60 z!_4R++D+uP7(gL(@KI&h`Fz#f!nkL4qoAMwmSbdO1kKIOC@LyKTwEMnU0n%~2-1~q z3~1{{9!a6a^8k3IfM2@@S}eBthlpEv0~cR?;V_$hJ?&GrP_a1^`T6;0z(Zo>FuZ!1|cZLz$8i1~;pH@W(B)~r?1^L|C;(hE1m4Wavi06PcXv(Uo|+6a|nQf zhduGKP3O#rKQQ|5_~dWIyhW6%EIqZ*VDq}|NK8z02!MQ71o~<1hUkf-Mk6sdTFnC# zAT~DEA%K{e7;N!<)-FJeaJqw-{~{uJKGkxf@$+^BtWQ8tP>@3aa=Cmi60#BxvQVV| z-^ncM|gs8p>kw;*13uBK&tiDwU$Ku@RG#lgk2-$z&{hzO|zcG`d>P z^ss%0|Gv%U3v<3;&$X3?3p7EoWS~~-9waX-iOiH*6Iz0mD;{Er#@*eWWkZ9(fT^h| zy8vEZUU+H|Ae|GTfd1C(aytJ4WB`C<`Rcb&( ziDo$pF)I%b4`gO$qN%9~lC?fmW+UZEYV*EDRVq~}@Wilp zwl=Ks+yb#!vIvBV7OLRjV5Fp^uxD%Oc%ja>{C<`I=xo+%+WqhmGy;-XIsQL8T-r4gm>kkU@0*cONPl^SaER!u^q5Nhf#UD$ ft_=)rpI!4G(XC=y#0#gu00000NkvXXu0mjfBR@;6 From b1f991bf9431c99e2ba3b2486d4d38b3d74d77b3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 9 May 2023 14:50:57 -0700 Subject: [PATCH 214/290] GB I/O: Read back proper SVBK value after writing 0 (fixes #2921) --- CHANGES | 1 + src/gb/io.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index ca460464c..d45c14d5e 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,7 @@ Features: - Debugger: Add range watchpoints Emulation fixes: - GB Audio: Fix channels 1/2 staying muted if restarted after long silence + - GB I/O: Read back proper SVBK value after writing 0 (fixes mgba.io/i/2921) - GB Serialize: Add missing Pocket Cam state to savestates - GB SIO: Disabling SIO should cancel pending transfers (fixes mgba.io/i/2537) - GB Video: Implement DMG-style sprite ordering diff --git a/src/gb/io.c b/src/gb/io.c index ba4473550..560ad7e62 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -491,7 +491,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { return; case GB_REG_SVBK: GBMemorySwitchWramBank(&gb->memory, value); - value = gb->memory.wramCurrentBank; + value &= 7; break; default: goto failed; From b21a6158f43f60592e0901c6f34a4e7fc0830010 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 11 Dec 2022 20:30:47 -0800 Subject: [PATCH 215/290] Debugger: Break out debugger functionality into modules --- include/mgba/debugger/debugger.h | 47 +++-- include/mgba/internal/debugger/cli-debugger.h | 2 +- include/mgba/internal/debugger/gdb-stub.h | 2 +- src/arm/debugger/cli-debugger.c | 14 +- src/arm/debugger/debugger.c | 3 - src/core/thread.c | 9 +- src/debugger/cli-debugger.c | 136 +++++++------- src/debugger/debugger.c | 170 +++++++++++++++--- src/debugger/gdb-stub.c | 72 ++++---- src/feature/editline/cli-el-backend.c | 5 +- src/gb/debugger/cli.c | 5 +- src/gb/debugger/debugger.c | 2 +- src/gba/debugger/cli.c | 5 +- src/platform/qt/CoreController.cpp | 41 ++++- src/platform/qt/CoreController.h | 11 +- src/platform/qt/DebuggerConsoleController.cpp | 8 +- src/platform/qt/DebuggerController.cpp | 10 +- src/platform/qt/DebuggerController.h | 6 +- src/platform/qt/GDBController.cpp | 4 - src/platform/qt/GDBController.h | 1 - src/platform/sdl/main.c | 24 ++- src/sm83/debugger/cli-debugger.c | 10 +- src/sm83/debugger/debugger.c | 4 - 23 files changed, 396 insertions(+), 195 deletions(-) diff --git a/include/mgba/debugger/debugger.h b/include/mgba/debugger/debugger.h index b460623e3..c73508b5f 100644 --- a/include/mgba/debugger/debugger.h +++ b/include/mgba/debugger/debugger.h @@ -56,6 +56,7 @@ enum mDebuggerEntryReason { DEBUGGER_ENTER_STACK }; +struct mDebuggerModule; struct mDebuggerEntryInfo { uint32_t address; union { @@ -76,6 +77,7 @@ struct mDebuggerEntryInfo { } st; } type; ssize_t pointId; + struct mDebuggerModule* target; }; struct mBreakpoint { @@ -97,6 +99,7 @@ struct mWatchpoint { DECLARE_VECTOR(mBreakpointList, struct mBreakpoint); DECLARE_VECTOR(mWatchpointList, struct mWatchpoint); +DECLARE_VECTOR(mDebuggerModuleList, struct mDebuggerModule*); struct mDebugger; struct ParseTree; @@ -130,28 +133,50 @@ struct mDebugger { struct mCPUComponent d; struct mDebuggerPlatform* platform; enum mDebuggerState state; - enum mDebuggerType type; struct mCore* core; struct mScriptBridge* bridge; struct mStackTrace stackTrace; - void (*init)(struct mDebugger*); - void (*deinit)(struct mDebugger*); - - void (*paused)(struct mDebugger*); - void (*update)(struct mDebugger*); - void (*entered)(struct mDebugger*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*); - void (*custom)(struct mDebugger*); - - void (*interrupt)(struct mDebugger*); + struct mDebuggerModuleList modules; }; -struct mDebugger* mDebuggerCreate(enum mDebuggerType type, struct mCore*); +struct mDebuggerModule { + struct mDebugger* p; + enum mDebuggerType type; + bool isPaused; + bool needsCallback; + + void (*init)(struct mDebuggerModule*); + void (*deinit)(struct mDebuggerModule*); + + void (*paused)(struct mDebuggerModule*); + void (*update)(struct mDebuggerModule*); + void (*entered)(struct mDebuggerModule*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*); + void (*custom)(struct mDebuggerModule*); + + void (*interrupt)(struct mDebuggerModule*); +}; + +void mDebuggerInit(struct mDebugger*); +void mDebuggerDeinit(struct mDebugger*); + void mDebuggerAttach(struct mDebugger*, struct mCore*); +void mDebuggerAttachModule(struct mDebugger*, struct mDebuggerModule*); +void mDebuggerDetachModule(struct mDebugger*, struct mDebuggerModule*); void mDebuggerRun(struct mDebugger*); void mDebuggerRunFrame(struct mDebugger*); void mDebuggerEnter(struct mDebugger*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*); +void mDebuggerInterrupt(struct mDebugger*); +void mDebuggerUpdatePaused(struct mDebugger*); +void mDebuggerShutdown(struct mDebugger*); +void mDebuggerUpdate(struct mDebugger*); + +bool mDebuggerIsShutdown(const struct mDebugger*); + +struct mDebuggerModule* mDebuggerCreateModule(enum mDebuggerType type, struct mCore*); +void mDebuggerModuleSetNeedsCallback(struct mDebuggerModule*); + bool mDebuggerLookupIdentifier(struct mDebugger* debugger, const char* name, int32_t* value, int* segment); CXX_GUARD_END diff --git a/include/mgba/internal/debugger/cli-debugger.h b/include/mgba/internal/debugger/cli-debugger.h index 1fc5df369..f87bffd7e 100644 --- a/include/mgba/internal/debugger/cli-debugger.h +++ b/include/mgba/internal/debugger/cli-debugger.h @@ -81,7 +81,7 @@ struct CLIDebuggerBackend { }; struct CLIDebugger { - struct mDebugger d; + struct mDebuggerModule d; struct CLIDebuggerSystem* system; struct CLIDebuggerBackend* backend; diff --git a/include/mgba/internal/debugger/gdb-stub.h b/include/mgba/internal/debugger/gdb-stub.h index 1086f7b43..0d6d24b29 100644 --- a/include/mgba/internal/debugger/gdb-stub.h +++ b/include/mgba/internal/debugger/gdb-stub.h @@ -31,7 +31,7 @@ enum GDBWatchpointsBehvaior { }; struct GDBStub { - struct mDebugger d; + struct mDebuggerModule d; char line[GDB_STUB_MAX_LINE]; char outgoing[GDB_STUB_MAX_LINE]; diff --git a/src/arm/debugger/cli-debugger.c b/src/arm/debugger/cli-debugger.c index 4df448151..dd3323dc9 100644 --- a/src/arm/debugger/cli-debugger.c +++ b/src/arm/debugger/cli-debugger.c @@ -52,7 +52,7 @@ static inline void _printPSR(struct CLIDebuggerBackend* be, union PSR psr) { } static void _disassemble(struct CLIDebuggerSystem* debugger, struct CLIDebugVector* dv) { - struct ARMCore* cpu = debugger->p->d.core->cpu; + struct ARMCore* cpu = debugger->p->d.p->core->cpu; _disassembleMode(debugger->p, dv, cpu->executionMode); } @@ -65,7 +65,7 @@ static void _disassembleThumb(struct CLIDebugger* debugger, struct CLIDebugVecto } static void _disassembleMode(struct CLIDebugger* debugger, struct CLIDebugVector* dv, enum ExecutionMode mode) { - struct ARMCore* cpu = debugger->d.core->cpu; + struct ARMCore* cpu = debugger->d.p->core->cpu; uint32_t address; int size; int wordSize; @@ -98,7 +98,7 @@ static void _disassembleMode(struct CLIDebugger* debugger, struct CLIDebugVector static inline uint32_t _printLine(struct CLIDebugger* debugger, uint32_t address, enum ExecutionMode mode) { struct CLIDebuggerBackend* be = debugger->backend; - struct mCore* core = debugger->d.core; + struct mCore* core = debugger->d.p->core; char disassembly[64]; struct ARMInstructionInfo info; address &= ~(WORD_SIZE_THUMB - 1); @@ -130,7 +130,7 @@ static inline uint32_t _printLine(struct CLIDebugger* debugger, uint32_t address static void _printStatus(struct CLIDebuggerSystem* debugger) { struct CLIDebuggerBackend* be = debugger->p->backend; - struct ARMCore* cpu = debugger->p->d.core->cpu; + struct ARMCore* cpu = debugger->p->d.p->core->cpu; int r; for (r = 0; r < 16; r += 4) { be->printf(be, "%sr%i: %08X %sr%i: %08X %sr%i: %08X %sr%i: %08X\n", @@ -141,7 +141,7 @@ static void _printStatus(struct CLIDebuggerSystem* debugger) { } be->printf(be, "cpsr: "); _printPSR(be, cpu->cpsr); - be->printf(be, "Cycle: %" PRIu64 "\n", mTimingGlobalTime(debugger->p->d.core->timing)); + be->printf(be, "Cycle: %" PRIu64 "\n", mTimingGlobalTime(debugger->p->d.p->core->timing)); int instructionLength; enum ExecutionMode mode = cpu->cpsr.t; if (mode == MODE_ARM) { @@ -159,7 +159,7 @@ static void _setBreakpointARM(struct CLIDebugger* debugger, struct CLIDebugVecto return; } uint32_t address = dv->intValue; - ssize_t id = ARMDebuggerSetSoftwareBreakpoint(debugger->d.platform, address, MODE_ARM); + ssize_t id = ARMDebuggerSetSoftwareBreakpoint(debugger->d.p->platform, address, MODE_ARM); if (id > 0) { debugger->backend->printf(debugger->backend, INFO_BREAKPOINT_ADDED, id); } @@ -172,7 +172,7 @@ static void _setBreakpointThumb(struct CLIDebugger* debugger, struct CLIDebugVec return; } uint32_t address = dv->intValue; - ssize_t id = ARMDebuggerSetSoftwareBreakpoint(debugger->d.platform, address, MODE_THUMB); + ssize_t id = ARMDebuggerSetSoftwareBreakpoint(debugger->d.p->platform, address, MODE_THUMB); if (id > 0) { debugger->backend->printf(debugger->backend, INFO_BREAKPOINT_ADDED, id); } diff --git a/src/arm/debugger/debugger.c b/src/arm/debugger/debugger.c index afc1e0c49..0f0f5a020 100644 --- a/src/arm/debugger/debugger.c +++ b/src/arm/debugger/debugger.c @@ -323,9 +323,6 @@ static void ARMDebuggerEnter(struct mDebuggerPlatform* platform, enum mDebuggerE } } } - if (debugger->d.p->entered) { - debugger->d.p->entered(debugger->d.p, reason, info); - } } ssize_t ARMDebuggerSetSoftwareBreakpoint(struct mDebuggerPlatform* d, uint32_t address, enum ExecutionMode mode) { diff --git a/src/core/thread.c b/src/core/thread.c index 860c981dd..983395c12 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -109,10 +109,7 @@ static void _wait(struct mCoreThreadInternal* threadContext) { #ifdef USE_DEBUGGERS if (threadContext->core && threadContext->core->debugger) { - struct mDebugger* debugger = threadContext->core->debugger; - if (debugger->interrupt) { - debugger->interrupt(debugger); - } + mDebuggerInterrupt(threadContext->core->debugger); } #endif @@ -352,8 +349,8 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { while (impl->state >= mTHREAD_MIN_WAITING && impl->state <= mTHREAD_MAX_WAITING) { #ifdef USE_DEBUGGERS - if (debugger && debugger->update && debugger->state != DEBUGGER_SHUTDOWN) { - debugger->update(debugger); + if (debugger && debugger->state != DEBUGGER_SHUTDOWN) { + mDebuggerUpdate(debugger); ConditionWaitTimed(&impl->stateCond, &impl->stateMutex, 10); } else #endif diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index 3dce8e255..275c99bbc 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -191,7 +191,7 @@ static void _breakInto(struct CLIDebugger* debugger, struct CLIDebugVector* dv) #endif static bool CLIDebuggerCheckTraceMode(struct CLIDebugger* debugger, bool requireEnabled) { - struct mDebuggerPlatform* platform = debugger->d.platform; + struct mDebuggerPlatform* platform = debugger->d.p->platform; if (!platform->getStackTraceMode) { debugger->backend->printf(debugger->backend, "Stack tracing is not supported by this platform.\n"); return false; @@ -204,13 +204,14 @@ static bool CLIDebuggerCheckTraceMode(struct CLIDebugger* debugger, bool require static void _continue(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); - debugger->d.state = debugger->traceRemaining != 0 ? DEBUGGER_CALLBACK : DEBUGGER_RUNNING; + debugger->d.needsCallback = debugger->traceRemaining != 0; + debugger->d.isPaused = false; } static void _next(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); - struct mDebuggerPlatform* platform = debugger->d.platform; - debugger->d.core->step(debugger->d.core); + struct mDebuggerPlatform* platform = debugger->d.p->platform; + debugger->d.p->core->step(debugger->d.p->core); if (platform->getStackTraceMode && platform->getStackTraceMode(platform) != STACK_TRACE_DISABLED) { platform->updateStackTrace(platform); } @@ -221,7 +222,7 @@ static void _disassemble(struct CLIDebugger* debugger, struct CLIDebugVector* dv debugger->system->disassemble(debugger->system, dv); } -static bool _parseExpression(struct mDebugger* debugger, struct CLIDebugVector* dv, int32_t* intValue, int* segmentValue) { +static bool _parseExpression(struct mDebuggerModule* debugger, struct CLIDebugVector* dv, int32_t* intValue, int* segmentValue) { size_t args = 0; struct CLIDebugVector* accum; for (accum = dv; accum; accum = accum->next) { @@ -240,7 +241,7 @@ static bool _parseExpression(struct mDebugger* debugger, struct CLIDebugVector* if (!tree) { return false; } - if (!mDebuggerEvaluateParseTree(debugger, tree, intValue, segmentValue)) { + if (!mDebuggerEvaluateParseTree(debugger->p, tree, intValue, segmentValue)) { parseFree(tree); return false; } @@ -366,7 +367,7 @@ static void _printHelp(struct CLIDebugger* debugger, struct CLIDebugVector* dv) static void _quit(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); - debugger->d.state = DEBUGGER_SHUTDOWN; + mDebuggerShutdown(debugger->d.p); } static void _readByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { @@ -377,17 +378,17 @@ static void _readByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { uint32_t address = dv->intValue; uint8_t value; if (dv->segmentValue >= 0) { - value = debugger->d.core->rawRead8(debugger->d.core, address, dv->segmentValue); + value = debugger->d.p->core->rawRead8(debugger->d.p->core, address, dv->segmentValue); } else { - value = debugger->d.core->busRead8(debugger->d.core, address); + value = debugger->d.p->core->busRead8(debugger->d.p->core, address); } debugger->backend->printf(debugger->backend, " 0x%02X\n", value); } static void _reset(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); - mStackTraceClear(&debugger->d.stackTrace); - debugger->d.core->reset(debugger->d.core); + mStackTraceClear(&debugger->d.p->stackTrace); + debugger->d.p->core->reset(debugger->d.p->core); _printStatus(debugger, 0); } @@ -399,9 +400,9 @@ static void _readHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* d uint32_t address = dv->intValue; uint16_t value; if (dv->segmentValue >= 0) { - value = debugger->d.core->rawRead16(debugger->d.core, address & -1, dv->segmentValue); + value = debugger->d.p->core->rawRead16(debugger->d.p->core, address & -1, dv->segmentValue); } else { - value = debugger->d.core->busRead16(debugger->d.core, address & ~1); + value = debugger->d.p->core->busRead16(debugger->d.p->core, address & ~1); } debugger->backend->printf(debugger->backend, " 0x%04X\n", value); } @@ -414,9 +415,9 @@ static void _readWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { uint32_t address = dv->intValue; uint32_t value; if (dv->segmentValue >= 0) { - value = debugger->d.core->rawRead32(debugger->d.core, address & -3, dv->segmentValue); + value = debugger->d.p->core->rawRead32(debugger->d.p->core, address & -3, dv->segmentValue); } else { - value = debugger->d.core->busRead32(debugger->d.core, address & ~3); + value = debugger->d.p->core->busRead32(debugger->d.p->core, address & ~3); } debugger->backend->printf(debugger->backend, " 0x%08X\n", value); } @@ -437,9 +438,9 @@ static void _writeByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) return; } if (dv->segmentValue >= 0) { - debugger->d.core->rawWrite8(debugger->d.core, address, value, dv->segmentValue); + debugger->d.p->core->rawWrite8(debugger->d.p->core, address, value, dv->segmentValue); } else { - debugger->d.core->busWrite8(debugger->d.core, address, value); + debugger->d.p->core->busWrite8(debugger->d.p->core, address, value); } } @@ -459,9 +460,9 @@ static void _writeHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* return; } if (dv->segmentValue >= 0) { - debugger->d.core->rawWrite16(debugger->d.core, address, value, dv->segmentValue); + debugger->d.p->core->rawWrite16(debugger->d.p->core, address, value, dv->segmentValue); } else { - debugger->d.core->busWrite16(debugger->d.core, address, value); + debugger->d.p->core->busWrite16(debugger->d.p->core, address, value); } } @@ -474,7 +475,7 @@ static void _writeRegister(struct CLIDebugger* debugger, struct CLIDebugVector* debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); return; } - if (!debugger->d.core->writeRegister(debugger->d.core, dv->charValue, &dv->next->intValue)) { + if (!debugger->d.p->core->writeRegister(debugger->d.p->core, dv->charValue, &dv->next->intValue)) { debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); } } @@ -491,9 +492,9 @@ static void _writeWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) uint32_t address = dv->intValue; uint32_t value = dv->next->intValue; if (dv->segmentValue >= 0) { - debugger->d.core->rawWrite32(debugger->d.core, address, value, dv->segmentValue); + debugger->d.p->core->rawWrite32(debugger->d.p->core, address, value, dv->segmentValue); } else { - debugger->d.core->busWrite32(debugger->d.core, address, value); + debugger->d.p->core->busWrite32(debugger->d.p->core, address, value); } } @@ -516,9 +517,9 @@ static void _dumpByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { for (; line > 0; --line, ++address, --words) { uint32_t value; if (dv->segmentValue >= 0) { - value = debugger->d.core->rawRead8(debugger->d.core, address, dv->segmentValue); + value = debugger->d.p->core->rawRead8(debugger->d.p->core, address, dv->segmentValue); } else { - value = debugger->d.core->busRead8(debugger->d.core, address); + value = debugger->d.p->core->busRead8(debugger->d.p->core, address); } debugger->backend->printf(debugger->backend, " %02X", value); } @@ -545,9 +546,9 @@ static void _dumpHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* d for (; line > 0; --line, address += 2, --words) { uint32_t value; if (dv->segmentValue >= 0) { - value = debugger->d.core->rawRead16(debugger->d.core, address, dv->segmentValue); + value = debugger->d.p->core->rawRead16(debugger->d.p->core, address, dv->segmentValue); } else { - value = debugger->d.core->busRead16(debugger->d.core, address); + value = debugger->d.p->core->busRead16(debugger->d.p->core, address); } debugger->backend->printf(debugger->backend, " %04X", value); } @@ -574,9 +575,9 @@ static void _dumpWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { for (; line > 0; --line, address += 4, --words) { uint32_t value; if (dv->segmentValue >= 0) { - value = debugger->d.core->rawRead32(debugger->d.core, address, dv->segmentValue); + value = debugger->d.p->core->rawRead32(debugger->d.p->core, address, dv->segmentValue); } else { - value = debugger->d.core->busRead32(debugger->d.core, address); + value = debugger->d.p->core->busRead32(debugger->d.p->core, address); } debugger->backend->printf(debugger->backend, " %08X", value); } @@ -590,8 +591,8 @@ static void _source(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { debugger->backend->printf(debugger->backend, "Needs a filename\n"); return; } - if (debugger->d.bridge && mScriptBridgeLoadScript(debugger->d.bridge, dv->charValue)) { - mScriptBridgeRun(debugger->d.bridge); + if (debugger->d.p->bridge && mScriptBridgeLoadScript(debugger->d.p->bridge, dv->charValue)) { + mScriptBridgeRun(debugger->d.p->bridge); } else { debugger->backend->printf(debugger->backend, "Failed to load script\n"); } @@ -647,7 +648,7 @@ static void _setBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* return; } } - ssize_t id = debugger->d.platform->setBreakpoint(debugger->d.platform, &breakpoint); + ssize_t id = debugger->d.p->platform->setBreakpoint(debugger->d.p->platform, &breakpoint); if (id > 0) { debugger->backend->printf(debugger->backend, INFO_BREAKPOINT_ADDED, id); } @@ -658,7 +659,7 @@ static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); return; } - if (!debugger->d.platform->setWatchpoint) { + if (!debugger->d.p->platform->setWatchpoint) { debugger->backend->printf(debugger->backend, "Watchpoints are not supported by this platform.\n"); return; } @@ -677,7 +678,7 @@ static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* return; } } - ssize_t id = debugger->d.platform->setWatchpoint(debugger->d.platform, &watchpoint); + ssize_t id = debugger->d.p->platform->setWatchpoint(debugger->d.p->platform, &watchpoint); if (id > 0) { debugger->backend->printf(debugger->backend, INFO_WATCHPOINT_ADDED, id); } @@ -692,7 +693,7 @@ static void _setRangeWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVec debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS); return; } - if (!debugger->d.platform->setWatchpoint) { + if (!debugger->d.p->platform->setWatchpoint) { debugger->backend->printf(debugger->backend, "Watchpoints are not supported by this platform.\n"); return; } @@ -719,7 +720,7 @@ static void _setRangeWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVec return; } } - ssize_t id = debugger->d.platform->setWatchpoint(debugger->d.platform, &watchpoint); + ssize_t id = debugger->d.p->platform->setWatchpoint(debugger->d.p->platform, &watchpoint); if (id > 0) { debugger->backend->printf(debugger->backend, INFO_WATCHPOINT_ADDED, id); } @@ -763,14 +764,14 @@ static void _clearBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector return; } uint64_t id = dv->intValue; - debugger->d.platform->clearBreakpoint(debugger->d.platform, id); + debugger->d.p->platform->clearBreakpoint(debugger->d.p->platform, id); } static void _listBreakpoints(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); struct mBreakpointList breakpoints; mBreakpointListInit(&breakpoints, 0); - debugger->d.platform->listBreakpoints(debugger->d.platform, &breakpoints); + debugger->d.p->platform->listBreakpoints(debugger->d.p->platform, &breakpoints); size_t i; for (i = 0; i < mBreakpointListSize(&breakpoints); ++i) { struct mBreakpoint* breakpoint = mBreakpointListGetPointer(&breakpoints, i); @@ -787,7 +788,7 @@ static void _listWatchpoints(struct CLIDebugger* debugger, struct CLIDebugVector UNUSED(dv); struct mWatchpointList watchpoints; mWatchpointListInit(&watchpoints, 0); - debugger->d.platform->listWatchpoints(debugger->d.platform, &watchpoints); + debugger->d.p->platform->listWatchpoints(debugger->d.p->platform, &watchpoints); size_t i; for (i = 0; i < mWatchpointListSize(&watchpoints); ++i) { struct mWatchpoint* watchpoint = mWatchpointListGetPointer(&watchpoints, i); @@ -823,6 +824,7 @@ static void _trace(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { debugger->traceVf->close(debugger->traceVf); debugger->traceVf = NULL; } + debugger->d.needsCallback = debugger->traceRemaining != 0; if (debugger->traceRemaining == 0) { return; } @@ -830,7 +832,7 @@ static void _trace(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { debugger->traceVf = VFileOpen(dv->next->charValue, O_CREAT | O_WRONLY | O_APPEND); } if (_doTrace(debugger)) { - debugger->d.state = DEBUGGER_CALLBACK; + mDebuggerUpdatePaused(debugger->d.p); } else { debugger->system->printStatus(debugger->system); } @@ -840,7 +842,7 @@ static bool _doTrace(struct CLIDebugger* debugger) { char trace[1024]; trace[sizeof(trace) - 1] = '\0'; size_t traceSize = sizeof(trace) - 2; - debugger->d.platform->trace(debugger->d.platform, trace, &traceSize); + debugger->d.p->platform->trace(debugger->d.p->platform, trace, &traceSize); if (traceSize + 2 <= sizeof(trace)) { trace[traceSize] = '\n'; trace[traceSize + 1] = '\0'; @@ -858,6 +860,7 @@ static bool _doTrace(struct CLIDebugger* debugger) { debugger->traceVf->close(debugger->traceVf); debugger->traceVf = NULL; } + debugger->d.needsCallback = false; return false; } return true; @@ -870,7 +873,7 @@ static void _printStatus(struct CLIDebugger* debugger, struct CLIDebugVector* dv static void _events(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); - struct mTiming* timing = debugger->d.core->timing; + struct mTiming* timing = debugger->d.p->core->timing; struct mTimingEvent* next = timing->root; for (; next; next = next->next) { debugger->backend->printf(debugger->backend, "%s in %i cycles\n", next->name, mTimingUntil(timing, next)); @@ -895,7 +898,7 @@ struct CLIDebugVector* CLIDVParse(struct CLIDebugger* debugger, const char* stri if (!parseLexedExpression(tree, &lv)) { dvTemp.type = CLIDV_ERROR_TYPE; } else { - if (!mDebuggerEvaluateParseTree(&debugger->d, tree, &dvTemp.intValue, &dvTemp.segmentValue)) { + if (!mDebuggerEvaluateParseTree(debugger->d.p, tree, &dvTemp.intValue, &dvTemp.segmentValue)) { dvTemp.type = CLIDV_ERROR_TYPE; } } @@ -1089,7 +1092,7 @@ bool CLIDebuggerRunCommand(struct CLIDebugger* debugger, const char* line, size_ return false; } -static void _commandLine(struct mDebugger* debugger) { +static void _commandLine(struct mDebuggerModule* debugger) { struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; const char* line; size_t len; @@ -1098,10 +1101,10 @@ static void _commandLine(struct mDebugger* debugger) { } else { _printStatus(cliDebugger, 0); } - while (debugger->state == DEBUGGER_PAUSED) { + while (debugger->isPaused && !mDebuggerIsShutdown(debugger->p)) { line = cliDebugger->backend->readline(cliDebugger->backend, &len); if (!line || len == 0) { - debugger->state = DEBUGGER_SHUTDOWN; + mDebuggerShutdown(debugger->p); return; } if (line[0] == '\033') { @@ -1124,7 +1127,7 @@ static void _commandLine(struct mDebugger* debugger) { } } -static void _reportEntry(struct mDebugger* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) { +static void _reportEntry(struct mDebuggerModule* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) { struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; if (cliDebugger->traceRemaining > 0) { cliDebugger->traceRemaining = 0; @@ -1166,7 +1169,7 @@ static void _reportEntry(struct mDebugger* debugger, enum mDebuggerEntryReason r case DEBUGGER_ENTER_STACK: if (info) { if (info->type.st.traceType == STACK_TRACE_BREAK_ON_CALL) { - struct mStackTrace* stack = &cliDebugger->d.stackTrace; + struct mStackTrace* stack = &cliDebugger->d.p->stackTrace; struct mStackFrame* frame = mStackTraceGetFrame(stack, 0); if (frame->interrupt) { cliDebugger->backend->printf(cliDebugger->backend, "Hit interrupt at at 0x%08X\n", info->address); @@ -1184,7 +1187,7 @@ static void _reportEntry(struct mDebugger* debugger, enum mDebuggerEntryReason r } } -static void _cliDebuggerInit(struct mDebugger* debugger) { +static void _cliDebuggerInit(struct mDebuggerModule* debugger) { struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; cliDebugger->traceRemaining = 0; cliDebugger->traceVf = NULL; @@ -1195,7 +1198,7 @@ static void _cliDebuggerInit(struct mDebugger* debugger) { } } -static void _cliDebuggerDeinit(struct mDebugger* debugger) { +static void _cliDebuggerDeinit(struct mDebuggerModule* debugger) { struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; if (cliDebugger->traceVf) { cliDebugger->traceVf->close(cliDebugger->traceVf); @@ -1215,23 +1218,22 @@ static void _cliDebuggerDeinit(struct mDebugger* debugger) { } } -static void _cliDebuggerCustom(struct mDebugger* debugger) { +static void _cliDebuggerCustom(struct mDebuggerModule* debugger) { struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; - bool retain = true; - enum mDebuggerState next = DEBUGGER_RUNNING; if (cliDebugger->traceRemaining) { - retain = _doTrace(cliDebugger) && retain; - next = DEBUGGER_PAUSED; + if (!_doTrace(cliDebugger)) { + debugger->isPaused = true; + debugger->needsCallback = false; + } } - if (cliDebugger->system) { - retain = cliDebugger->system->custom(cliDebugger->system) && retain; - } - if (!retain && debugger->state == DEBUGGER_CALLBACK) { - debugger->state = next; + if (cliDebugger->system && cliDebugger->system->custom) { + debugger->needsCallback = cliDebugger->system->custom(cliDebugger->system) || debugger->needsCallback; } + + mDebuggerUpdatePaused(debugger->p); } -static void _cliDebuggerInterrupt(struct mDebugger* debugger) { +static void _cliDebuggerInterrupt(struct mDebuggerModule* debugger) { struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; if (cliDebugger->backend->interrupt) { cliDebugger->backend->interrupt(cliDebugger->backend); @@ -1327,13 +1329,13 @@ static void _backtrace(struct CLIDebugger* debugger, struct CLIDebugVector* dv) if (!CLIDebuggerCheckTraceMode(debugger, true)) { return; } - struct mStackTrace* stack = &debugger->d.stackTrace; + struct mStackTrace* stack = &debugger->d.p->stackTrace; ssize_t frames = mStackTraceGetDepth(stack); if (dv && dv->type == CLIDV_INT_TYPE && dv->intValue < frames) { frames = dv->intValue; } ssize_t i; - struct mDebuggerSymbols* symbolTable = debugger->d.core->symbolTable; + struct mDebuggerSymbols* symbolTable = debugger->d.p->core->symbolTable; for (i = 0; i < frames; ++i) { char trace[1024]; size_t traceSize = sizeof(trace) - 2; @@ -1347,7 +1349,7 @@ static void _finish(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { if (!CLIDebuggerCheckTraceMode(debugger, true)) { return; } - struct mStackTrace* stack = &debugger->d.stackTrace; + struct mStackTrace* stack = &debugger->d.p->stackTrace; struct mStackFrame* frame = mStackTraceGetFrame(stack, 0); if (!frame) { debugger->backend->printf(debugger->backend, "No current stack frame.\n"); @@ -1373,7 +1375,7 @@ static void _setStackTraceMode(struct CLIDebugger* debugger, struct CLIDebugVect debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); return; } - struct mDebuggerPlatform* platform = debugger->d.platform; + struct mDebuggerPlatform* platform = debugger->d.p->platform; if (strcmp(dv->charValue, "off") == 0) { platform->setStackTraceMode(platform, STACK_TRACE_DISABLED); } else if (strcmp(dv->charValue, "trace-only") == 0) { @@ -1390,7 +1392,7 @@ static void _setStackTraceMode(struct CLIDebugger* debugger, struct CLIDebugVect } static void _loadSymbols(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - struct mDebuggerSymbols* symbolTable = debugger->d.core->symbolTable; + struct mDebuggerSymbols* symbolTable = debugger->d.p->core->symbolTable; if (!symbolTable) { debugger->backend->printf(debugger->backend, "No symbol table available.\n"); return; @@ -1424,7 +1426,7 @@ static void _loadSymbols(struct CLIDebugger* debugger, struct CLIDebugVector* dv } static void _setSymbol(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - struct mDebuggerSymbols* symbolTable = debugger->d.core->symbolTable; + struct mDebuggerSymbols* symbolTable = debugger->d.p->core->symbolTable; if (!symbolTable) { debugger->backend->printf(debugger->backend, "No symbol table available.\n"); return; @@ -1441,7 +1443,7 @@ static void _setSymbol(struct CLIDebugger* debugger, struct CLIDebugVector* dv) } static void _findSymbol(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { - struct mDebuggerSymbols* symbolTable = debugger->d.core->symbolTable; + struct mDebuggerSymbols* symbolTable = debugger->d.p->core->symbolTable; if (!symbolTable) { debugger->backend->printf(debugger->backend, "No symbol table available.\n"); return; diff --git a/src/debugger/debugger.c b/src/debugger/debugger.c index cc6beab9c..9273723ac 100644 --- a/src/debugger/debugger.c +++ b/src/debugger/debugger.c @@ -24,17 +24,18 @@ mLOG_DEFINE_CATEGORY(DEBUGGER, "Debugger", "core.debugger"); DEFINE_VECTOR(mBreakpointList, struct mBreakpoint); DEFINE_VECTOR(mWatchpointList, struct mWatchpoint); +DEFINE_VECTOR(mDebuggerModuleList, struct mDebuggerModule*); -static void mDebuggerInit(void* cpu, struct mCPUComponent* component); -static void mDebuggerDeinit(struct mCPUComponent* component); +static void _mDebuggerInit(void* cpu, struct mCPUComponent* component); +static void _mDebuggerDeinit(struct mCPUComponent* component); -struct mDebugger* mDebuggerCreate(enum mDebuggerType type, struct mCore* core) { +struct mDebuggerModule* mDebuggerCreateModule(enum mDebuggerType type, struct mCore* core) { if (!core->supportsDebuggerType(core, type)) { return NULL; } union DebugUnion { - struct mDebugger d; + struct mDebuggerModule d; struct CLIDebugger cli; #ifdef USE_GDB_STUB struct GDBStub gdb; @@ -67,10 +68,19 @@ struct mDebugger* mDebuggerCreate(enum mDebuggerType type, struct mCore* core) { return &debugger->d; } +void mDebuggerInit(struct mDebugger* debugger) { + memset(debugger, 0, sizeof(*debugger)); + mDebuggerModuleListInit(&debugger->modules, 4); +} + +void mDebuggerDeinit(struct mDebugger* debugger) { + mDebuggerModuleListDeinit(&debugger->modules); +} + void mDebuggerAttach(struct mDebugger* debugger, struct mCore* core) { debugger->d.id = DEBUGGER_ID; - debugger->d.init = mDebuggerInit; - debugger->d.deinit = mDebuggerDeinit; + debugger->d.init = _mDebuggerInit; + debugger->d.deinit = _mDebuggerDeinit; debugger->core = core; if (!debugger->core->symbolTable) { debugger->core->loadSymbols(debugger->core, NULL); @@ -80,7 +90,20 @@ void mDebuggerAttach(struct mDebugger* debugger, struct mCore* core) { core->attachDebugger(core, debugger); } +void mDebuggerAttachModule(struct mDebugger* debugger, struct mDebuggerModule* module) { + module->p = debugger; + *mDebuggerModuleListAppend(&debugger->modules) = module; +} + +void mDebuggerDetachModule(struct mDebugger* debugger, struct mDebuggerModule* module) { + // TODO + abort(); +} + void mDebuggerRun(struct mDebugger* debugger) { + size_t i; + size_t anyPaused = 0; + switch (debugger->state) { case DEBUGGER_RUNNING: if (!debugger->platform->hasBreakpoints(debugger->platform)) { @@ -93,12 +116,28 @@ void mDebuggerRun(struct mDebugger* debugger) { case DEBUGGER_CALLBACK: debugger->core->step(debugger->core); debugger->platform->checkBreakpoints(debugger->platform); - debugger->custom(debugger); + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); + if (module->needsCallback) { + module->custom(module); + } + } break; case DEBUGGER_PAUSED: - if (debugger->paused) { - debugger->paused(debugger); - } else { + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); + if (module->isPaused) { + if (module->paused) { + module->paused(module); + } + if (module->isPaused) { + ++anyPaused; + } + } else if (module->needsCallback) { + module->custom(module); + } + } + if (debugger->state == DEBUGGER_PAUSED && !anyPaused) { debugger->state = DEBUGGER_RUNNING; } break; @@ -115,30 +154,114 @@ void mDebuggerRunFrame(struct mDebugger* debugger) { } void mDebuggerEnter(struct mDebugger* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) { - debugger->state = DEBUGGER_PAUSED; if (debugger->platform->entered) { debugger->platform->entered(debugger->platform, reason, info); } + + size_t i; + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); + if (info && info->target) { + // This check needs to be in the loop to make sure we don't + // accidentally enter a module that isn't registered. + // This is an error by the caller, but it's good to check for. + if (info->target != module) { + continue; + } + // Make this the last loop so we don't hit this one twice + i = mDebuggerModuleListSize(&debugger->modules) - 1; + } + module->isPaused = true; + if (module->entered) { + module->entered(module, reason, info); + } + } + #ifdef ENABLE_SCRIPTING if (debugger->bridge) { mScriptBridgeDebuggerEntered(debugger->bridge, reason, info); } #endif + + mDebuggerUpdatePaused(debugger); } -static void mDebuggerInit(void* cpu, struct mCPUComponent* component) { - struct mDebugger* debugger = (struct mDebugger*) component; - debugger->state = DEBUGGER_RUNNING; - debugger->platform->init(cpu, debugger->platform); - if (debugger->init) { - debugger->init(debugger); +void mDebuggerInterrupt(struct mDebugger* debugger) { + size_t i; + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); + if (module->interrupt) { + module->interrupt(module); + } } } -static void mDebuggerDeinit(struct mCPUComponent* component) { +void mDebuggerUpdatePaused(struct mDebugger* debugger) { + if (debugger->state == DEBUGGER_SHUTDOWN) { + return; + } + + size_t anyPaused = 0; + size_t anyCallback = 0; + size_t i; + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); + if (module->isPaused) { + ++anyPaused; + } + if (module->needsCallback) { + ++anyCallback; + } + } + if (anyPaused) { + debugger->state = DEBUGGER_PAUSED; + } else if (anyCallback) { + debugger->state = DEBUGGER_CALLBACK; + } else { + debugger->state = DEBUGGER_RUNNING; + } +} + +void mDebuggerShutdown(struct mDebugger* debugger) { + debugger->state = DEBUGGER_SHUTDOWN; +} + +bool mDebuggerIsShutdown(const struct mDebugger* debugger) { + return debugger->state == DEBUGGER_SHUTDOWN; +} + +void mDebuggerUpdate(struct mDebugger* debugger) { + size_t i; + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); + if (module->update) { + module->update(module); + } + } +} + +static void _mDebuggerInit(void* cpu, struct mCPUComponent* component) { struct mDebugger* debugger = (struct mDebugger*) component; - if (debugger->deinit) { - debugger->deinit(debugger); + debugger->state = DEBUGGER_RUNNING; + debugger->platform->init(cpu, debugger->platform); + + size_t i; + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); + if (module->init) { + module->init(module); + } + } +} + +static void _mDebuggerDeinit(struct mCPUComponent* component) { + struct mDebugger* debugger = (struct mDebugger*) component; + size_t i; + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); + if (module->deinit) { + module->deinit(module); + } } debugger->platform->deinit(debugger->platform); } @@ -161,3 +284,10 @@ bool mDebuggerLookupIdentifier(struct mDebugger* debugger, const char* name, int } return false; } + +void mDebuggerModuleSetNeedsCallback(struct mDebuggerModule* debugger) { + debugger->needsCallback = true; + if (debugger->p->state == DEBUGGER_RUNNING) { + debugger->p->state = DEBUGGER_CALLBACK; + } +} diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index 34b7f2000..3136e9cad 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -66,14 +66,14 @@ static const char* TARGET_XML = "" static void _sendMessage(struct GDBStub* stub); -static void _gdbStubDeinit(struct mDebugger* debugger) { +static void _gdbStubDeinit(struct mDebuggerModule* debugger) { struct GDBStub* stub = (struct GDBStub*) debugger; if (!SOCKET_FAILED(stub->socket)) { GDBStubShutdown(stub); } } -static void _gdbStubEntered(struct mDebugger* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) { +static void _gdbStubEntered(struct mDebuggerModule* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) { struct GDBStub* stub = (struct GDBStub*) debugger; switch (reason) { case DEBUGGER_ENTER_MANUAL: @@ -128,7 +128,7 @@ static void _gdbStubEntered(struct mDebugger* debugger, enum mDebuggerEntryReaso _sendMessage(stub); } -static void _gdbStubPoll(struct mDebugger* debugger) { +static void _gdbStubPoll(struct mDebuggerModule* debugger) { struct GDBStub* stub = (struct GDBStub*) debugger; --stub->untilPoll; if (stub->untilPoll > 0) { @@ -139,13 +139,13 @@ static void _gdbStubPoll(struct mDebugger* debugger) { GDBStubUpdate(stub); } -static void _gdbStubWait(struct mDebugger* debugger) { +static void _gdbStubWait(struct mDebuggerModule* debugger) { struct GDBStub* stub = (struct GDBStub*) debugger; stub->shouldBlock = true; GDBStubUpdate(stub); } -static void _gdbStubUpdate(struct mDebugger* debugger) { +static void _gdbStubUpdate(struct mDebuggerModule* debugger) { struct GDBStub* stub = (struct GDBStub*) debugger; stub->shouldBlock = false; GDBStubUpdate(stub); @@ -252,14 +252,14 @@ static void _writeHostInfo(struct GDBStub* stub) { } static void _continue(struct GDBStub* stub, const char* message) { - stub->d.state = DEBUGGER_CALLBACK; + mDebuggerModuleSetNeedsCallback(&stub->d); stub->untilPoll = GDB_STUB_INTERVAL; // TODO: parse message UNUSED(message); } static void _step(struct GDBStub* stub, const char* message) { - stub->d.core->step(stub->d.core); + stub->d.p->core->step(stub->d.p->core); snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP); _sendMessage(stub); // TODO: parse message @@ -281,7 +281,7 @@ static void _writeMemoryBinary(struct GDBStub* stub, const char* message) { return; } - struct ARMCore* cpu = stub->d.core->cpu; + struct ARMCore* cpu = stub->d.p->core->cpu; for (i = 0; i < size; i++) { uint8_t byte = *readAddress; ++readAddress; @@ -315,7 +315,7 @@ static void _writeMemory(struct GDBStub* stub, const char* message) { return; } - struct ARMCore* cpu = stub->d.core->cpu; + struct ARMCore* cpu = stub->d.p->core->cpu; for (i = 0; i < size; ++i, readAddress += 2) { uint8_t byte = _hex2int(readAddress, 2); GBAPatch8(cpu, address + i, byte, 0); @@ -335,7 +335,7 @@ static void _readMemory(struct GDBStub* stub, const char* message) { _error(stub, GDB_BAD_ARGUMENTS); return; } - struct ARMCore* cpu = stub->d.core->cpu; + struct ARMCore* cpu = stub->d.p->core->cpu; int writeAddress = 0; for (i = 0; i < size; ++i, writeAddress += 2) { uint8_t byte = cpu->memory.load8(cpu, address + i, 0); @@ -346,7 +346,7 @@ static void _readMemory(struct GDBStub* stub, const char* message) { } static void _writeGPRs(struct GDBStub* stub, const char* message) { - struct ARMCore* cpu = stub->d.core->cpu; + struct ARMCore* cpu = stub->d.p->core->cpu; const char* readAddress = message; int r; @@ -365,7 +365,7 @@ static void _writeGPRs(struct GDBStub* stub, const char* message) { } static void _readGPRs(struct GDBStub* stub, const char* message) { - struct ARMCore* cpu = stub->d.core->cpu; + struct ARMCore* cpu = stub->d.p->core->cpu; UNUSED(message); int r; int i = 0; @@ -389,7 +389,7 @@ static void _readGPRs(struct GDBStub* stub, const char* message) { } static void _writeRegister(struct GDBStub* stub, const char* message) { - struct ARMCore* cpu = stub->d.core->cpu; + struct ARMCore* cpu = stub->d.p->core->cpu; const char* readAddress = message; unsigned i = 0; @@ -422,7 +422,7 @@ static void _writeRegister(struct GDBStub* stub, const char* message) { } static void _readRegister(struct GDBStub* stub, const char* message) { - struct ARMCore* cpu = stub->d.core->cpu; + struct ARMCore* cpu = stub->d.p->core->cpu; const char* readAddress = message; unsigned i = 0; uint32_t reg = _readHex(readAddress, &i); @@ -507,7 +507,7 @@ static void _generateMemoryMapXml(struct GDBStub* stub, char* memoryMap) { strncpy(memoryMap, "", 27); index += strlen(memoryMap); const struct mCoreMemoryBlock* blocks; - size_t nBlocks = stub->d.core->listMemoryBlocks(stub->d.core, &blocks); + size_t nBlocks = stub->d.p->core->listMemoryBlocks(stub->d.p->core, &blocks); size_t i; for (i = 0; i < nBlocks; ++i) { if (!(blocks[i].flags & mCORE_MEMORY_MAPPED)) { @@ -569,7 +569,11 @@ static void _processVReadCommand(struct GDBStub* stub, const char* message) { stub->outgoing[0] = '\0'; if (!strncmp("Attach", message, 6)) { strncpy(stub->outgoing, "1", GDB_STUB_MAX_LINE - 4); - mDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL, 0); + stub->d.isPaused = true; + struct mDebuggerEntryInfo info = { + .target = &stub->d + }; + mDebuggerEnter(stub->d.p, DEBUGGER_ENTER_MANUAL, &info); } _sendMessage(stub); } @@ -592,19 +596,19 @@ static void _setBreakpoint(struct GDBStub* stub, const char* message) { switch (message[0]) { case '0': case '1': - stub->d.platform->setBreakpoint(stub->d.platform, &breakpoint); + stub->d.p->platform->setBreakpoint(stub->d.p->platform, &breakpoint); break; case '2': watchpoint.type = stub->watchpointsBehavior == GDB_WATCHPOINT_OVERRIDE_LOGIC_ANY_WRITE ? WATCHPOINT_WRITE : WATCHPOINT_WRITE_CHANGE; - stub->d.platform->setWatchpoint(stub->d.platform, &watchpoint); + stub->d.p->platform->setWatchpoint(stub->d.p->platform, &watchpoint); break; case '3': watchpoint.type = WATCHPOINT_READ; - stub->d.platform->setWatchpoint(stub->d.platform, &watchpoint); + stub->d.p->platform->setWatchpoint(stub->d.p->platform, &watchpoint); break; case '4': watchpoint.type = WATCHPOINT_RW; - stub->d.platform->setWatchpoint(stub->d.platform, &watchpoint); + stub->d.p->platform->setWatchpoint(stub->d.p->platform, &watchpoint); break; default: stub->outgoing[0] = '\0'; @@ -627,12 +631,12 @@ static void _clearBreakpoint(struct GDBStub* stub, const char* message) { case '0': case '1': mBreakpointListInit(&breakpoints, 0); - stub->d.platform->listBreakpoints(stub->d.platform, &breakpoints); + stub->d.p->platform->listBreakpoints(stub->d.p->platform, &breakpoints); for (index = 0; index < mBreakpointListSize(&breakpoints); ++index) { if (mBreakpointListGetPointer(&breakpoints, index)->address != address) { continue; } - stub->d.platform->clearBreakpoint(stub->d.platform, mBreakpointListGetPointer(&breakpoints, index)->id); + stub->d.p->platform->clearBreakpoint(stub->d.p->platform, mBreakpointListGetPointer(&breakpoints, index)->id); } mBreakpointListDeinit(&breakpoints); break; @@ -640,13 +644,13 @@ static void _clearBreakpoint(struct GDBStub* stub, const char* message) { case '3': case '4': mWatchpointListInit(&watchpoints, 0); - stub->d.platform->listWatchpoints(stub->d.platform, &watchpoints); + stub->d.p->platform->listWatchpoints(stub->d.p->platform, &watchpoints); for (index = 0; index < mWatchpointListSize(&watchpoints); ++index) { struct mWatchpoint* watchpoint = mWatchpointListGetPointer(&watchpoints, index); if (address >= watchpoint->minAddress && address < watchpoint->maxAddress) { continue; } - stub->d.platform->clearBreakpoint(stub->d.platform, watchpoint->id); + stub->d.p->platform->clearBreakpoint(stub->d.p->platform, watchpoint->id); } mWatchpointListDeinit(&watchpoints); break; @@ -660,6 +664,9 @@ static void _clearBreakpoint(struct GDBStub* stub, const char* message) { size_t _parseGDBMessage(struct GDBStub* stub, const char* message) { uint8_t checksum = 0; int parsed = 1; + struct mDebuggerEntryInfo info = { + .target = &stub->d + }; switch (*message) { case '+': stub->lineAck = GDB_ACK_RECEIVED; @@ -671,7 +678,8 @@ size_t _parseGDBMessage(struct GDBStub* stub, const char* message) { ++message; break; case '\x03': - mDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL, 0); + stub->d.isPaused = true; + mDebuggerEnter(stub->d.p, DEBUGGER_ENTER_MANUAL, &info); return parsed; default: _nak(stub); @@ -820,9 +828,9 @@ void GDBStubHangup(struct GDBStub* stub) { SocketClose(stub->connection); stub->connection = INVALID_SOCKET; } - if (stub->d.state == DEBUGGER_PAUSED) { - stub->d.state = DEBUGGER_RUNNING; - } + stub->d.needsCallback = false; + stub->d.isPaused = false; + mDebuggerUpdatePaused(stub->d.p); } void GDBStubShutdown(struct GDBStub* stub) { @@ -835,9 +843,9 @@ void GDBStubShutdown(struct GDBStub* stub) { void GDBStubUpdate(struct GDBStub* stub) { if (stub->socket == INVALID_SOCKET) { - if (stub->d.state == DEBUGGER_PAUSED) { - stub->d.state = DEBUGGER_RUNNING; - } + stub->d.needsCallback = false; + stub->d.isPaused = false; + mDebuggerUpdatePaused(stub->d.p); return; } if (stub->connection == INVALID_SOCKET) { @@ -850,7 +858,7 @@ void GDBStubUpdate(struct GDBStub* stub) { if (!SocketSetBlocking(stub->connection, false)) { goto connectionLost; } - mDebuggerEnter(&stub->d, DEBUGGER_ENTER_ATTACHED, 0); + mDebuggerEnter(stub->d.p, DEBUGGER_ENTER_ATTACHED, 0); } else if (SocketWouldBlock()) { return; } else { diff --git a/src/feature/editline/cli-el-backend.c b/src/feature/editline/cli-el-backend.c index 30cb9399f..b87ecb6bf 100644 --- a/src/feature/editline/cli-el-backend.c +++ b/src/feature/editline/cli-el-backend.c @@ -20,7 +20,10 @@ static char* _prompt(EditLine* el) { static void _breakIntoDefault(int signal) { UNUSED(signal); - mDebuggerEnter(&_activeDebugger->d, DEBUGGER_ENTER_MANUAL, 0); + struct mDebuggerEntryInfo info = { + .target = &_activeDebugger->d + }; + mDebuggerEnter(_activeDebugger->d.p, DEBUGGER_ENTER_MANUAL, &info); } static unsigned char _tabComplete(EditLine* elstate, int ch) { diff --git a/src/gb/debugger/cli.c b/src/gb/debugger/cli.c index d4089d40f..03aa2a992 100644 --- a/src/gb/debugger/cli.c +++ b/src/gb/debugger/cli.c @@ -58,7 +58,7 @@ static bool _GBCLIDebuggerCustom(struct CLIDebuggerSystem* debugger) { if (gbDebugger->frameAdvance) { if (!gbDebugger->inVblank && GBRegisterSTATGetMode(((struct GB*) gbDebugger->core->board)->memory.io[GB_REG_STAT]) == 1) { - mDebuggerEnter(&gbDebugger->d.p->d, DEBUGGER_ENTER_MANUAL, 0); + mDebuggerEnter(gbDebugger->d.p->d.p, DEBUGGER_ENTER_MANUAL, 0); gbDebugger->frameAdvance = false; return false; } @@ -70,7 +70,8 @@ static bool _GBCLIDebuggerCustom(struct CLIDebuggerSystem* debugger) { static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); - debugger->d.state = DEBUGGER_CALLBACK; + debugger->d.needsCallback = true; + mDebuggerUpdatePaused(debugger->d.p); struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger->system; gbDebugger->frameAdvance = true; diff --git a/src/gb/debugger/debugger.c b/src/gb/debugger/debugger.c index e1dda7a8d..cc05bb7a5 100644 --- a/src/gb/debugger/debugger.c +++ b/src/gb/debugger/debugger.c @@ -28,7 +28,7 @@ static const struct SM83Segment _GBCSegments[] = { static void _printStatus(struct CLIDebuggerSystem* debugger) { struct CLIDebuggerBackend* be = debugger->p->backend; - struct GB* gb = debugger->p->d.core->board; + struct GB* gb = debugger->p->d.p->core->board; be->printf(be, "IE: %02X IF: %02X IME: %i\n", gb->memory.ie, gb->memory.io[GB_REG_IF], gb->memory.ime); be->printf(be, "LCDC: %02X STAT: %02X LY: %02X\n", gb->memory.io[GB_REG_LCDC], gb->memory.io[GB_REG_STAT] | 0x80, gb->memory.io[GB_REG_LY]); be->printf(be, "Next video mode: %i\n", mTimingUntil(&gb->timing, &gb->video.modeEvent) / 4); diff --git a/src/gba/debugger/cli.c b/src/gba/debugger/cli.c index 5349911ab..fd36c2dbb 100644 --- a/src/gba/debugger/cli.c +++ b/src/gba/debugger/cli.c @@ -57,7 +57,7 @@ static bool _GBACLIDebuggerCustom(struct CLIDebuggerSystem* debugger) { if (gbaDebugger->frameAdvance) { if (!gbaDebugger->inVblank && GBARegisterDISPSTATIsInVblank(((struct GBA*) gbaDebugger->core->board)->memory.io[REG_DISPSTAT >> 1])) { - mDebuggerEnter(&gbaDebugger->d.p->d, DEBUGGER_ENTER_MANUAL, 0); + mDebuggerEnter(gbaDebugger->d.p->d.p, DEBUGGER_ENTER_MANUAL, 0); gbaDebugger->frameAdvance = false; return false; } @@ -69,7 +69,8 @@ static bool _GBACLIDebuggerCustom(struct CLIDebuggerSystem* debugger) { static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); - debugger->d.state = DEBUGGER_CALLBACK; + debugger->d.needsCallback = true; + mDebuggerUpdatePaused(debugger->d.p); struct GBACLIDebugger* gbaDebugger = (struct GBACLIDebugger*) debugger->system; gbaDebugger->frameAdvance = true; diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 7cc8d7e1f..6bcbeffb3 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -49,6 +49,10 @@ CoreController::CoreController(mCore* core, QObject* parent) GBASIODolphinCreate(&m_dolphin); #endif +#ifdef USE_DEBUGGERS + mDebuggerInit(&m_debugger); +#endif + m_resetActions.append([this]() { if (m_autoload) { mCoreLoadState(m_threadContext.core, 0, m_loadStateFlags); @@ -214,6 +218,10 @@ CoreController::~CoreController() { mCoreThreadJoin(&m_threadContext); +#ifdef USE_DEBUGGERS + mDebuggerDeinit(&m_debugger); +#endif + if (m_cacheSet) { mCacheSetDeinit(m_cacheSet.get()); m_cacheSet.reset(); @@ -319,14 +327,33 @@ void CoreController::loadConfig(ConfigController* config) { } #ifdef USE_DEBUGGERS -void CoreController::setDebugger(mDebugger* debugger) { +void CoreController::attachDebugger(bool interrupt) { Interrupter interrupter(this); - if (debugger) { - mDebuggerAttach(debugger, m_threadContext.core); - mDebuggerEnter(debugger, DEBUGGER_ENTER_ATTACHED, 0); - } else { - m_threadContext.core->detachDebugger(m_threadContext.core); + if (!m_threadContext.core->debugger) { + mDebuggerAttach(&m_debugger, m_threadContext.core); } + if (interrupt) { + mDebuggerEnter(&m_debugger, DEBUGGER_ENTER_ATTACHED, 0); + } +} + +void CoreController::detachDebugger() { + Interrupter interrupter(this); + if (!m_threadContext.core->debugger) { + return; + } + m_threadContext.core->detachDebugger(m_threadContext.core); +} + +void CoreController::attachDebuggerModule(mDebuggerModule* module, bool interrupt) { + Interrupter interrupter(this); + mDebuggerAttachModule(&m_debugger, module); + attachDebugger(interrupt); +} + +void CoreController::detachDebuggerModule(mDebuggerModule* module) { + Interrupter interrupter(this); + mDebuggerDetachModule(&m_debugger, module); } #endif @@ -445,7 +472,7 @@ void CoreController::start() { void CoreController::stop() { setSync(false); #ifdef USE_DEBUGGERS - setDebugger(nullptr); + detachDebugger(); #endif setPaused(false); mCoreThreadEnd(&m_threadContext); diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index 74d9a7b05..1dd211d44 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -103,8 +103,11 @@ public: mCheatDevice* cheatDevice() { return m_threadContext.core->cheatDevice(m_threadContext.core); } #ifdef USE_DEBUGGERS - mDebugger* debugger() { return m_threadContext.core->debugger; } - void setDebugger(mDebugger*); + mDebugger* debugger() { return &m_debugger; } + void attachDebugger(bool interrupt = true); + void detachDebugger(); + void attachDebuggerModule(mDebuggerModule*, bool interrupt = true); + void detachDebuggerModule(mDebuggerModule*); #endif void setMultiplayerController(MultiplayerController*); @@ -293,6 +296,10 @@ private: bool m_autoload; int m_autosaveCounter = 0; +#ifdef USE_DEBUGGERS + struct mDebugger m_debugger; +#endif + int m_fastForward = false; int m_fastForwardForced = false; int m_fastForwardVolume = -1; diff --git a/src/platform/qt/DebuggerConsoleController.cpp b/src/platform/qt/DebuggerConsoleController.cpp index bacc97104..a0bfe1999 100644 --- a/src/platform/qt/DebuggerConsoleController.cpp +++ b/src/platform/qt/DebuggerConsoleController.cpp @@ -37,8 +37,8 @@ void DebuggerConsoleController::enterLine(const QString& line) { CoreController::Interrupter interrupter(m_gameController); QMutexLocker lock(&m_mutex); m_lines.append(line); - if (m_cliDebugger.d.state == DEBUGGER_RUNNING) { - mDebuggerEnter(&m_cliDebugger.d, DEBUGGER_ENTER_MANUAL, nullptr); + if (m_cliDebugger.d.p->state == DEBUGGER_RUNNING) { + mDebuggerEnter(m_cliDebugger.d.p, DEBUGGER_ENTER_MANUAL, nullptr); } m_cond.wakeOne(); } @@ -47,7 +47,7 @@ void DebuggerConsoleController::detach() { { CoreController::Interrupter interrupter(m_gameController); QMutexLocker lock(&m_mutex); - if (m_cliDebugger.d.state != DEBUGGER_SHUTDOWN) { + if (m_cliDebugger.d.p->state != DEBUGGER_SHUTDOWN) { m_lines.append(QString()); m_cond.wakeOne(); } @@ -82,7 +82,7 @@ void DebuggerConsoleController::init(struct CLIDebuggerBackend* be) { void DebuggerConsoleController::deinit(struct CLIDebuggerBackend* be) { Backend* consoleBe = reinterpret_cast(be); DebuggerConsoleController* self = consoleBe->self; - if (QThread::currentThread() == self->thread() && be->p->d.state != DEBUGGER_SHUTDOWN) { + if (QThread::currentThread() == self->thread() && be->p->d.p->state != DEBUGGER_SHUTDOWN) { self->m_lines.append(QString()); self->m_cond.wakeOne(); } diff --git a/src/platform/qt/DebuggerController.cpp b/src/platform/qt/DebuggerController.cpp index 7291b62fa..105549309 100644 --- a/src/platform/qt/DebuggerController.cpp +++ b/src/platform/qt/DebuggerController.cpp @@ -9,7 +9,7 @@ using namespace QGBA; -DebuggerController::DebuggerController(mDebugger* debugger, QObject* parent) +DebuggerController::DebuggerController(mDebuggerModule* debugger, QObject* parent) : QObject(parent) , m_debugger(debugger) { @@ -19,7 +19,7 @@ bool DebuggerController::isAttached() { if (!m_gameController) { return false; } - return m_gameController->debugger() == m_debugger; + return m_gameController->debugger() == m_debugger->p; } void DebuggerController::setController(std::shared_ptr controller) { @@ -45,7 +45,7 @@ void DebuggerController::attach() { } if (m_gameController) { attachInternal(); - m_gameController->setDebugger(m_debugger); + m_gameController->attachDebuggerModule(m_debugger); } else { m_autoattach = true; } @@ -58,7 +58,7 @@ void DebuggerController::detach() { if (m_gameController) { CoreController::Interrupter interrupter(m_gameController); shutdownInternal(); - m_gameController->setDebugger(nullptr); + m_gameController->detachDebuggerModule(m_debugger); } else { m_autoattach = false; } @@ -69,7 +69,7 @@ void DebuggerController::breakInto() { return; } CoreController::Interrupter interrupter(m_gameController); - mDebuggerEnter(m_debugger, DEBUGGER_ENTER_MANUAL, 0); + mDebuggerEnter(m_debugger->p, DEBUGGER_ENTER_MANUAL, 0); } void DebuggerController::shutdown() { diff --git a/src/platform/qt/DebuggerController.h b/src/platform/qt/DebuggerController.h index 45ac434da..722282f27 100644 --- a/src/platform/qt/DebuggerController.h +++ b/src/platform/qt/DebuggerController.h @@ -9,7 +9,7 @@ #include -struct mDebugger; +struct mDebuggerModule; namespace QGBA { @@ -19,7 +19,7 @@ class DebuggerController : public QObject { Q_OBJECT public: - DebuggerController(mDebugger* debugger, QObject* parent = nullptr); + DebuggerController(mDebuggerModule* debugger, QObject* parent = nullptr); public: bool isAttached(); @@ -35,7 +35,7 @@ protected: virtual void attachInternal(); virtual void shutdownInternal(); - mDebugger* const m_debugger; + mDebuggerModule* const m_debugger; std::shared_ptr m_gameController; private: diff --git a/src/platform/qt/GDBController.cpp b/src/platform/qt/GDBController.cpp index 5fd2a1768..583ffed62 100644 --- a/src/platform/qt/GDBController.cpp +++ b/src/platform/qt/GDBController.cpp @@ -20,10 +20,6 @@ ushort GDBController::port() { return m_port; } -bool GDBController::isAttached() { - return m_gameController && m_gameController->debugger() == &m_gdbStub.d; -} - void GDBController::setPort(ushort port) { m_port = port; } diff --git a/src/platform/qt/GDBController.h b/src/platform/qt/GDBController.h index d83f00028..6bf0a7132 100644 --- a/src/platform/qt/GDBController.h +++ b/src/platform/qt/GDBController.h @@ -23,7 +23,6 @@ public: public: ushort port(); - bool isAttached(); public slots: void setPort(ushort port); diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index 92a1c63c4..3f2065c15 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -243,18 +243,23 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { #endif #ifdef USE_DEBUGGERS - struct mDebugger* debugger = mDebuggerCreate(args->debuggerType, renderer->core); - if (debugger) { + struct mDebuggerModule* module = mDebuggerCreateModule(args->debuggerType, renderer->core); + struct mDebugger debugger; + + mDebuggerInit(&debugger); + if (module) { #ifdef USE_EDITLINE if (args->debuggerType == DEBUGGER_CLI) { - struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; + struct CLIDebugger* cliDebugger = (struct CLIDebugger*) module; CLIDebuggerAttachBackend(cliDebugger, CLIDebuggerEditLineBackendCreate()); } #endif - mDebuggerAttach(debugger, renderer->core); - mDebuggerEnter(debugger, DEBUGGER_ENTER_MANUAL, NULL); + module->isPaused = true; + mDebuggerAttachModule(&debugger, module); + mDebuggerAttach(&debugger, renderer->core); + mDebuggerEnter(&debugger, DEBUGGER_ENTER_MANUAL, NULL); #ifdef ENABLE_SCRIPTING - mScriptBridgeSetDebugger(bridge, debugger); + mScriptBridgeSetDebugger(bridge, &debugger); #endif } #endif @@ -322,6 +327,13 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { mScriptBridgeDestroy(bridge); #endif +#ifdef USE_DEBUGGERS + if (module) { + renderer->core->detachDebugger(renderer->core); + mDebuggerDeinit(&debugger); + } +#endif + return didFail; } diff --git a/src/sm83/debugger/cli-debugger.c b/src/sm83/debugger/cli-debugger.c index dfc56dc23..fd5a3084d 100644 --- a/src/sm83/debugger/cli-debugger.c +++ b/src/sm83/debugger/cli-debugger.c @@ -26,7 +26,7 @@ static inline void _printFlags(struct CLIDebuggerBackend* be, union FlagRegister } static void _disassemble(struct CLIDebuggerSystem* debugger, struct CLIDebugVector* dv) { - struct SM83Core* cpu = debugger->p->d.core->cpu; + struct SM83Core* cpu = debugger->p->d.p->core->cpu; uint16_t address; int segment = -1; @@ -64,7 +64,7 @@ static inline uint16_t _printLine(struct CLIDebugger* debugger, uint16_t address uint8_t instruction; size_t bytesRemaining = 1; for (bytesRemaining = 1; bytesRemaining; --bytesRemaining) { - instruction = debugger->d.core->rawRead8(debugger->d.core, address, segment); + instruction = debugger->d.p->core->rawRead8(debugger->d.p->core, address, segment); disPtr += snprintf(disPtr, sizeof(disassembly) - (disPtr - disassembly), "%02X", instruction); ++address; bytesRemaining += SM83Decode(instruction, &info); @@ -78,16 +78,16 @@ static inline uint16_t _printLine(struct CLIDebugger* debugger, uint16_t address static void _printStatus(struct CLIDebuggerSystem* debugger) { struct CLIDebuggerBackend* be = debugger->p->backend; - struct SM83Core* cpu = debugger->p->d.core->cpu; + struct SM83Core* cpu = debugger->p->d.p->core->cpu; be->printf(be, "A: %02X F: %02X (AF: %04X)\n", cpu->a, cpu->f.packed, cpu->af); be->printf(be, "B: %02X C: %02X (BC: %04X)\n", cpu->b, cpu->c, cpu->bc); be->printf(be, "D: %02X E: %02X (DE: %04X)\n", cpu->d, cpu->e, cpu->de); be->printf(be, "H: %02X L: %02X (HL: %04X)\n", cpu->h, cpu->l, cpu->hl); be->printf(be, "PC: %04X SP: %04X\n", cpu->pc, cpu->sp); _printFlags(be, cpu->f); - be->printf(be, "T-cycle: %" PRIu64 "\n", mTimingGlobalTime(debugger->p->d.core->timing)); + be->printf(be, "T-cycle: %" PRIu64 "\n", mTimingGlobalTime(debugger->p->d.p->core->timing)); - struct SM83Debugger* platDebugger = (struct SM83Debugger*) debugger->p->d.platform; + struct SM83Debugger* platDebugger = (struct SM83Debugger*) debugger->p->d.p->platform; size_t i; for (i = 0; platDebugger->segments[i].name; ++i) { be->printf(be, "%s%s: %02X", i ? " " : "", platDebugger->segments[i].name, cpu->memory.currentSegment(cpu, platDebugger->segments[i].start)); diff --git a/src/sm83/debugger/debugger.c b/src/sm83/debugger/debugger.c index f6a6f690e..c52fe857c 100644 --- a/src/sm83/debugger/debugger.c +++ b/src/sm83/debugger/debugger.c @@ -120,10 +120,6 @@ static void SM83DebuggerEnter(struct mDebuggerPlatform* platform, enum mDebugger struct SM83Debugger* debugger = (struct SM83Debugger*) platform; struct SM83Core* cpu = debugger->cpu; cpu->nextEvent = cpu->cycles; - - if (debugger->d.p->entered) { - debugger->d.p->entered(debugger->d.p, reason, info); - } } static ssize_t SM83DebuggerSetBreakpoint(struct mDebuggerPlatform* d, const struct mBreakpoint* info) { From 8efb3fb5dffeab0f9f905af467ddf56c7f4ccf4c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 11 Dec 2022 22:08:41 -0800 Subject: [PATCH 216/290] Debugger: Make created debug items, e.g. breakpoints, owned by modules --- include/mgba/debugger/debugger.h | 10 +-- include/mgba/internal/arm/debugger/debugger.h | 2 +- src/arm/debugger/cli-debugger.c | 4 +- src/arm/debugger/debugger.c | 65 +++++++++++++------ src/arm/debugger/memory-debugger.c | 1 + src/debugger/cli-debugger.c | 10 +-- src/debugger/debugger.c | 2 + src/debugger/gdb-stub.c | 12 ++-- src/sm83/debugger/debugger.c | 62 +++++++++++++----- src/sm83/debugger/memory-debugger.c | 1 + 10 files changed, 113 insertions(+), 56 deletions(-) diff --git a/include/mgba/debugger/debugger.h b/include/mgba/debugger/debugger.h index c73508b5f..fa9ca58d6 100644 --- a/include/mgba/debugger/debugger.h +++ b/include/mgba/debugger/debugger.h @@ -12,6 +12,7 @@ CXX_GUARD_START #include #include +#include #include #include @@ -114,11 +115,11 @@ struct mDebuggerPlatform { void (*checkBreakpoints)(struct mDebuggerPlatform*); bool (*clearBreakpoint)(struct mDebuggerPlatform*, ssize_t id); - ssize_t (*setBreakpoint)(struct mDebuggerPlatform*, const struct mBreakpoint*); - void (*listBreakpoints)(struct mDebuggerPlatform*, struct mBreakpointList*); + ssize_t (*setBreakpoint)(struct mDebuggerPlatform*, struct mDebuggerModule*, const struct mBreakpoint*); + void (*listBreakpoints)(struct mDebuggerPlatform*, struct mDebuggerModule*, struct mBreakpointList*); - ssize_t (*setWatchpoint)(struct mDebuggerPlatform*, const struct mWatchpoint*); - void (*listWatchpoints)(struct mDebuggerPlatform*, struct mWatchpointList*); + ssize_t (*setWatchpoint)(struct mDebuggerPlatform*, struct mDebuggerModule*, const struct mWatchpoint*); + void (*listWatchpoints)(struct mDebuggerPlatform*, struct mDebuggerModule*, struct mWatchpointList*); void (*trace)(struct mDebuggerPlatform*, char* out, size_t* length); @@ -138,6 +139,7 @@ struct mDebugger { struct mStackTrace stackTrace; struct mDebuggerModuleList modules; + struct Table pointOwner; }; struct mDebuggerModule { diff --git a/include/mgba/internal/arm/debugger/debugger.h b/include/mgba/internal/arm/debugger/debugger.h index cbce4f120..e2f8e3858 100644 --- a/include/mgba/internal/arm/debugger/debugger.h +++ b/include/mgba/internal/arm/debugger/debugger.h @@ -45,7 +45,7 @@ struct ARMDebugger { }; struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void); -ssize_t ARMDebuggerSetSoftwareBreakpoint(struct mDebuggerPlatform* debugger, uint32_t address, enum ExecutionMode mode); +ssize_t ARMDebuggerSetSoftwareBreakpoint(struct mDebuggerPlatform* debugger, struct mDebuggerModule* owner, uint32_t address, enum ExecutionMode mode); CXX_GUARD_END diff --git a/src/arm/debugger/cli-debugger.c b/src/arm/debugger/cli-debugger.c index dd3323dc9..60caa7a21 100644 --- a/src/arm/debugger/cli-debugger.c +++ b/src/arm/debugger/cli-debugger.c @@ -159,7 +159,7 @@ static void _setBreakpointARM(struct CLIDebugger* debugger, struct CLIDebugVecto return; } uint32_t address = dv->intValue; - ssize_t id = ARMDebuggerSetSoftwareBreakpoint(debugger->d.p->platform, address, MODE_ARM); + ssize_t id = ARMDebuggerSetSoftwareBreakpoint(debugger->d.p->platform, &debugger->d, address, MODE_ARM); if (id > 0) { debugger->backend->printf(debugger->backend, INFO_BREAKPOINT_ADDED, id); } @@ -172,7 +172,7 @@ static void _setBreakpointThumb(struct CLIDebugger* debugger, struct CLIDebugVec return; } uint32_t address = dv->intValue; - ssize_t id = ARMDebuggerSetSoftwareBreakpoint(debugger->d.p->platform, address, MODE_THUMB); + ssize_t id = ARMDebuggerSetSoftwareBreakpoint(debugger->d.p->platform, &debugger->d, address, MODE_THUMB); if (id > 0) { debugger->backend->printf(debugger->backend, INFO_BREAKPOINT_ADDED, id); } diff --git a/src/arm/debugger/debugger.c b/src/arm/debugger/debugger.c index 0f0f5a020..31df1b523 100644 --- a/src/arm/debugger/debugger.c +++ b/src/arm/debugger/debugger.c @@ -187,16 +187,18 @@ static struct ARMDebugBreakpoint* _lookupBreakpoint(struct ARMDebugBreakpointLis return 0; } -static void _destroyBreakpoint(struct ARMDebugBreakpoint* breakpoint) { +static void _destroyBreakpoint(struct mDebugger* debugger, struct ARMDebugBreakpoint* breakpoint) { if (breakpoint->d.condition) { parseFree(breakpoint->d.condition); } + TableRemove(&debugger->pointOwner, breakpoint->d.id); } -static void _destroyWatchpoint(struct mWatchpoint* watchpoint) { +static void _destroyWatchpoint(struct mDebugger* debugger, struct mWatchpoint* watchpoint) { if (watchpoint->condition) { parseFree(watchpoint->condition); } + TableRemove(&debugger->pointOwner, watchpoint->id); } static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { @@ -220,7 +222,8 @@ static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { struct mDebuggerEntryInfo info = { .address = breakpoint->d.address, .type.bp.breakType = BREAKPOINT_HARDWARE, - .pointId = breakpoint->d.id + .pointId = breakpoint->d.id, + .target = TableLookup(&d->p->pointOwner, breakpoint->d.id) }; mDebuggerEnter(d->p, DEBUGGER_ENTER_BREAKPOINT, &info); } @@ -230,11 +233,11 @@ static void ARMDebuggerDeinit(struct mDebuggerPlatform* platform); static void ARMDebuggerEnter(struct mDebuggerPlatform* d, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info); -static ssize_t ARMDebuggerSetBreakpoint(struct mDebuggerPlatform*, const struct mBreakpoint*); +static ssize_t ARMDebuggerSetBreakpoint(struct mDebuggerPlatform*, struct mDebuggerModule* owner, const struct mBreakpoint*); static bool ARMDebuggerClearBreakpoint(struct mDebuggerPlatform*, ssize_t id); -static void ARMDebuggerListBreakpoints(struct mDebuggerPlatform*, struct mBreakpointList*); -static ssize_t ARMDebuggerSetWatchpoint(struct mDebuggerPlatform*, const struct mWatchpoint*); -static void ARMDebuggerListWatchpoints(struct mDebuggerPlatform*, struct mWatchpointList*); +static void ARMDebuggerListBreakpoints(struct mDebuggerPlatform*, struct mDebuggerModule* owner, struct mBreakpointList*); +static ssize_t ARMDebuggerSetWatchpoint(struct mDebuggerPlatform*, struct mDebuggerModule* owner, const struct mWatchpoint*); +static void ARMDebuggerListWatchpoints(struct mDebuggerPlatform*, struct mDebuggerModule* owner, struct mWatchpointList*); static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform*); static bool ARMDebuggerHasBreakpoints(struct mDebuggerPlatform*); static void ARMDebuggerTrace(struct mDebuggerPlatform*, char* out, size_t* length); @@ -291,12 +294,12 @@ void ARMDebuggerDeinit(struct mDebuggerPlatform* platform) { size_t i; for (i = 0; i < ARMDebugBreakpointListSize(&debugger->breakpoints); ++i) { - _destroyBreakpoint(ARMDebugBreakpointListGetPointer(&debugger->breakpoints, i)); + _destroyBreakpoint(debugger->d.p, ARMDebugBreakpointListGetPointer(&debugger->breakpoints, i)); } ARMDebugBreakpointListDeinit(&debugger->breakpoints); for (i = 0; i < mWatchpointListSize(&debugger->watchpoints); ++i) { - _destroyWatchpoint(mWatchpointListGetPointer(&debugger->watchpoints, i)); + _destroyWatchpoint(debugger->d.p, mWatchpointListGetPointer(&debugger->watchpoints, i)); } ARMDebugBreakpointListDeinit(&debugger->swBreakpoints); mWatchpointListDeinit(&debugger->watchpoints); @@ -325,7 +328,7 @@ static void ARMDebuggerEnter(struct mDebuggerPlatform* platform, enum mDebuggerE } } -ssize_t ARMDebuggerSetSoftwareBreakpoint(struct mDebuggerPlatform* d, uint32_t address, enum ExecutionMode mode) { +ssize_t ARMDebuggerSetSoftwareBreakpoint(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, uint32_t address, enum ExecutionMode mode) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; uint32_t opcode; if (!debugger->setSoftwareBreakpoint || !debugger->setSoftwareBreakpoint(debugger, address, mode, &opcode)) { @@ -342,11 +345,12 @@ ssize_t ARMDebuggerSetSoftwareBreakpoint(struct mDebuggerPlatform* d, uint32_t a breakpoint->d.type = BREAKPOINT_SOFTWARE; breakpoint->sw.opcode = opcode; breakpoint->sw.mode = mode; + TableInsert(&debugger->d.p->pointOwner, id, owner); return id; } -static ssize_t ARMDebuggerSetBreakpoint(struct mDebuggerPlatform* d, const struct mBreakpoint* info) { +static ssize_t ARMDebuggerSetBreakpoint(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, const struct mBreakpoint* info) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; struct ARMDebugBreakpoint* breakpoint = ARMDebugBreakpointListAppend(&debugger->breakpoints); ssize_t id = debugger->nextId; @@ -354,6 +358,7 @@ static ssize_t ARMDebuggerSetBreakpoint(struct mDebuggerPlatform* d, const struc breakpoint->d = *info; breakpoint->d.address &= ~1; // Clear Thumb bit since it's not part of a valid address breakpoint->d.id = id; + TableInsert(&debugger->d.p->pointOwner, id, owner); if (info->type == BREAKPOINT_SOFTWARE) { // TODO abort(); @@ -368,7 +373,7 @@ static bool ARMDebuggerClearBreakpoint(struct mDebuggerPlatform* d, ssize_t id) struct ARMDebugBreakpointList* breakpoints = &debugger->breakpoints; for (i = 0; i < ARMDebugBreakpointListSize(breakpoints); ++i) { if (ARMDebugBreakpointListGetPointer(breakpoints, i)->d.id == id) { - _destroyBreakpoint(ARMDebugBreakpointListGetPointer(breakpoints, i)); + _destroyBreakpoint(debugger->d.p, ARMDebugBreakpointListGetPointer(breakpoints, i)); ARMDebugBreakpointListShift(breakpoints, i, 1); return true; } @@ -388,7 +393,7 @@ static bool ARMDebuggerClearBreakpoint(struct mDebuggerPlatform* d, ssize_t id) struct mWatchpointList* watchpoints = &debugger->watchpoints; for (i = 0; i < mWatchpointListSize(watchpoints); ++i) { if (mWatchpointListGetPointer(watchpoints, i)->id == id) { - _destroyWatchpoint(mWatchpointListGetPointer(watchpoints, i)); + _destroyWatchpoint(debugger->d.p, mWatchpointListGetPointer(watchpoints, i)); mWatchpointListShift(watchpoints, i, 1); if (!mWatchpointListSize(&debugger->watchpoints)) { ARMDebuggerRemoveMemoryShim(debugger); @@ -399,19 +404,29 @@ static bool ARMDebuggerClearBreakpoint(struct mDebuggerPlatform* d, ssize_t id) return false; } -static void ARMDebuggerListBreakpoints(struct mDebuggerPlatform* d, struct mBreakpointList* list) { +static void ARMDebuggerListBreakpoints(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, struct mBreakpointList* list) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; mBreakpointListClear(list); size_t i, s; - for (i = 0, s = 0; i < ARMDebugBreakpointListSize(&debugger->breakpoints) || s < ARMDebugBreakpointListSize(&debugger->swBreakpoints);) { + for (i = 0; i < ARMDebugBreakpointListSize(&debugger->breakpoints) || s < ARMDebugBreakpointListSize(&debugger->swBreakpoints);) { struct ARMDebugBreakpoint* hw = NULL; struct ARMDebugBreakpoint* sw = NULL; if (i < ARMDebugBreakpointListSize(&debugger->breakpoints)) { hw = ARMDebugBreakpointListGetPointer(&debugger->breakpoints, i); + if (owner && TableLookup(&debugger->d.p->pointOwner, hw->d.id) != owner) { + hw = NULL; + } } if (s < ARMDebugBreakpointListSize(&debugger->swBreakpoints)) { sw = ARMDebugBreakpointListGetPointer(&debugger->swBreakpoints, s); + if (owner && TableLookup(&debugger->d.p->pointOwner, sw->d.id) != owner) { + sw = NULL; + } } + if (!hw && !sw) { + continue; + } + struct mBreakpoint* b = mBreakpointListAppend(list); if (hw && sw) { if (hw->d.id < sw->d.id) { @@ -427,8 +442,6 @@ static void ARMDebuggerListBreakpoints(struct mDebuggerPlatform* d, struct mBrea } else if (sw) { *b = sw->d; ++s; - } else { - abort(); // Should be unreachable } } } @@ -438,7 +451,7 @@ static bool ARMDebuggerHasBreakpoints(struct mDebuggerPlatform* d) { return ARMDebugBreakpointListSize(&debugger->breakpoints) || mWatchpointListSize(&debugger->watchpoints) || debugger->stackTraceMode != STACK_TRACE_DISABLED; } -static ssize_t ARMDebuggerSetWatchpoint(struct mDebuggerPlatform* d, const struct mWatchpoint* info) { +static ssize_t ARMDebuggerSetWatchpoint(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, const struct mWatchpoint* info) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; if (!mWatchpointListSize(&debugger->watchpoints)) { ARMDebuggerInstallMemoryShim(debugger); @@ -448,13 +461,25 @@ static ssize_t ARMDebuggerSetWatchpoint(struct mDebuggerPlatform* d, const struc ++debugger->nextId; *watchpoint = *info; watchpoint->id = id; + TableInsert(&debugger->d.p->pointOwner, id, owner); return id; } -static void ARMDebuggerListWatchpoints(struct mDebuggerPlatform* d, struct mWatchpointList* list) { +static void ARMDebuggerListWatchpoints(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, struct mWatchpointList* list) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; mWatchpointListClear(list); - mWatchpointListCopy(list, &debugger->watchpoints); + if (owner) { + size_t i; + for (i = 0; i < mWatchpointListSize(&debugger->watchpoints); ++i) { + struct mWatchpoint* point = mWatchpointListGetPointer(&debugger->watchpoints, i); + if (TableLookup(&debugger->d.p->pointOwner, point->id) != owner) { + continue; + } + memcpy(mWatchpointListAppend(list), point, sizeof(*point)); + } + } else { + mWatchpointListCopy(list, &debugger->watchpoints); + } } static void ARMDebuggerTrace(struct mDebuggerPlatform* d, char* out, size_t* length) { diff --git a/src/arm/debugger/memory-debugger.c b/src/arm/debugger/memory-debugger.c index 85c9995ae..699e36c87 100644 --- a/src/arm/debugger/memory-debugger.c +++ b/src/arm/debugger/memory-debugger.c @@ -130,6 +130,7 @@ static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, st info->type.wp.watchType = watchpoint->type; info->type.wp.accessType = type; info->pointId = watchpoint->id; + info->target = TableLookup(&debugger->d.p->pointOwner, watchpoint->id); return true; } } diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index 275c99bbc..79313d06c 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -648,7 +648,7 @@ static void _setBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* return; } } - ssize_t id = debugger->d.p->platform->setBreakpoint(debugger->d.p->platform, &breakpoint); + ssize_t id = debugger->d.p->platform->setBreakpoint(debugger->d.p->platform, &debugger->d, &breakpoint); if (id > 0) { debugger->backend->printf(debugger->backend, INFO_BREAKPOINT_ADDED, id); } @@ -678,7 +678,7 @@ static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* return; } } - ssize_t id = debugger->d.p->platform->setWatchpoint(debugger->d.p->platform, &watchpoint); + ssize_t id = debugger->d.p->platform->setWatchpoint(debugger->d.p->platform, &debugger->d, &watchpoint); if (id > 0) { debugger->backend->printf(debugger->backend, INFO_WATCHPOINT_ADDED, id); } @@ -720,7 +720,7 @@ static void _setRangeWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVec return; } } - ssize_t id = debugger->d.p->platform->setWatchpoint(debugger->d.p->platform, &watchpoint); + ssize_t id = debugger->d.p->platform->setWatchpoint(debugger->d.p->platform, &debugger->d, &watchpoint); if (id > 0) { debugger->backend->printf(debugger->backend, INFO_WATCHPOINT_ADDED, id); } @@ -771,7 +771,7 @@ static void _listBreakpoints(struct CLIDebugger* debugger, struct CLIDebugVector UNUSED(dv); struct mBreakpointList breakpoints; mBreakpointListInit(&breakpoints, 0); - debugger->d.p->platform->listBreakpoints(debugger->d.p->platform, &breakpoints); + debugger->d.p->platform->listBreakpoints(debugger->d.p->platform, &debugger->d, &breakpoints); size_t i; for (i = 0; i < mBreakpointListSize(&breakpoints); ++i) { struct mBreakpoint* breakpoint = mBreakpointListGetPointer(&breakpoints, i); @@ -788,7 +788,7 @@ static void _listWatchpoints(struct CLIDebugger* debugger, struct CLIDebugVector UNUSED(dv); struct mWatchpointList watchpoints; mWatchpointListInit(&watchpoints, 0); - debugger->d.p->platform->listWatchpoints(debugger->d.p->platform, &watchpoints); + debugger->d.p->platform->listWatchpoints(debugger->d.p->platform, &debugger->d, &watchpoints); size_t i; for (i = 0; i < mWatchpointListSize(&watchpoints); ++i) { struct mWatchpoint* watchpoint = mWatchpointListGetPointer(&watchpoints, i); diff --git a/src/debugger/debugger.c b/src/debugger/debugger.c index 9273723ac..36be88f61 100644 --- a/src/debugger/debugger.c +++ b/src/debugger/debugger.c @@ -71,10 +71,12 @@ struct mDebuggerModule* mDebuggerCreateModule(enum mDebuggerType type, struct mC void mDebuggerInit(struct mDebugger* debugger) { memset(debugger, 0, sizeof(*debugger)); mDebuggerModuleListInit(&debugger->modules, 4); + TableInit(&debugger->pointOwner, 0, NULL); } void mDebuggerDeinit(struct mDebugger* debugger) { mDebuggerModuleListDeinit(&debugger->modules); + TableDeinit(&debugger->pointOwner); } void mDebuggerAttach(struct mDebugger* debugger, struct mCore* core) { diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index 3136e9cad..88bd1e6a5 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -596,19 +596,19 @@ static void _setBreakpoint(struct GDBStub* stub, const char* message) { switch (message[0]) { case '0': case '1': - stub->d.p->platform->setBreakpoint(stub->d.p->platform, &breakpoint); + stub->d.p->platform->setBreakpoint(stub->d.p->platform, &stub->d, &breakpoint); break; case '2': watchpoint.type = stub->watchpointsBehavior == GDB_WATCHPOINT_OVERRIDE_LOGIC_ANY_WRITE ? WATCHPOINT_WRITE : WATCHPOINT_WRITE_CHANGE; - stub->d.p->platform->setWatchpoint(stub->d.p->platform, &watchpoint); + stub->d.p->platform->setWatchpoint(stub->d.p->platform, &stub->d, &watchpoint); break; case '3': watchpoint.type = WATCHPOINT_READ; - stub->d.p->platform->setWatchpoint(stub->d.p->platform, &watchpoint); + stub->d.p->platform->setWatchpoint(stub->d.p->platform, &stub->d, &watchpoint); break; case '4': watchpoint.type = WATCHPOINT_RW; - stub->d.p->platform->setWatchpoint(stub->d.p->platform, &watchpoint); + stub->d.p->platform->setWatchpoint(stub->d.p->platform, &stub->d, &watchpoint); break; default: stub->outgoing[0] = '\0'; @@ -631,7 +631,7 @@ static void _clearBreakpoint(struct GDBStub* stub, const char* message) { case '0': case '1': mBreakpointListInit(&breakpoints, 0); - stub->d.p->platform->listBreakpoints(stub->d.p->platform, &breakpoints); + stub->d.p->platform->listBreakpoints(stub->d.p->platform, &stub->d, &breakpoints); for (index = 0; index < mBreakpointListSize(&breakpoints); ++index) { if (mBreakpointListGetPointer(&breakpoints, index)->address != address) { continue; @@ -644,7 +644,7 @@ static void _clearBreakpoint(struct GDBStub* stub, const char* message) { case '3': case '4': mWatchpointListInit(&watchpoints, 0); - stub->d.p->platform->listWatchpoints(stub->d.p->platform, &watchpoints); + stub->d.p->platform->listWatchpoints(stub->d.p->platform, &stub->d, &watchpoints); for (index = 0; index < mWatchpointListSize(&watchpoints); ++index) { struct mWatchpoint* watchpoint = mWatchpointListGetPointer(&watchpoints, index); if (address >= watchpoint->minAddress && address < watchpoint->maxAddress) { diff --git a/src/sm83/debugger/debugger.c b/src/sm83/debugger/debugger.c index c52fe857c..eafed0329 100644 --- a/src/sm83/debugger/debugger.c +++ b/src/sm83/debugger/debugger.c @@ -25,16 +25,18 @@ static struct mBreakpoint* _lookupBreakpoint(struct mBreakpointList* breakpoints return NULL; } -static void _destroyBreakpoint(struct mBreakpoint* breakpoint) { +static void _destroyBreakpoint(struct mDebugger* debugger, struct mBreakpoint* breakpoint) { if (breakpoint->condition) { parseFree(breakpoint->condition); } + TableRemove(&debugger->pointOwner, breakpoint->id); } -static void _destroyWatchpoint(struct mWatchpoint* watchpoint) { +static void _destroyWatchpoint(struct mDebugger* debugger, struct mWatchpoint* watchpoint) { if (watchpoint->condition) { parseFree(watchpoint->condition); } + TableRemove(&debugger->pointOwner, watchpoint->id); } static void SM83DebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { @@ -52,7 +54,8 @@ static void SM83DebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { } struct mDebuggerEntryInfo info = { .address = breakpoint->address, - .pointId = breakpoint->id + .pointId = breakpoint->id, + .target = TableLookup(&d->p->pointOwner, breakpoint->id) }; mDebuggerEnter(d->p, DEBUGGER_ENTER_BREAKPOINT, &info); } @@ -62,11 +65,11 @@ static void SM83DebuggerDeinit(struct mDebuggerPlatform* platform); static void SM83DebuggerEnter(struct mDebuggerPlatform* d, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info); -static ssize_t SM83DebuggerSetBreakpoint(struct mDebuggerPlatform*, const struct mBreakpoint*); -static void SM83DebuggerListBreakpoints(struct mDebuggerPlatform*, struct mBreakpointList*); +static ssize_t SM83DebuggerSetBreakpoint(struct mDebuggerPlatform*, struct mDebuggerModule* owner, const struct mBreakpoint*); +static void SM83DebuggerListBreakpoints(struct mDebuggerPlatform*, struct mDebuggerModule* owner, struct mBreakpointList*); static bool SM83DebuggerClearBreakpoint(struct mDebuggerPlatform*, ssize_t id); -static ssize_t SM83DebuggerSetWatchpoint(struct mDebuggerPlatform*, const struct mWatchpoint*); -static void SM83DebuggerListWatchpoints(struct mDebuggerPlatform*, struct mWatchpointList*); +static ssize_t SM83DebuggerSetWatchpoint(struct mDebuggerPlatform*, struct mDebuggerModule* owner, const struct mWatchpoint*); +static void SM83DebuggerListWatchpoints(struct mDebuggerPlatform*, struct mDebuggerModule* owner, struct mWatchpointList*); static void SM83DebuggerCheckBreakpoints(struct mDebuggerPlatform*); static bool SM83DebuggerHasBreakpoints(struct mDebuggerPlatform*); static void SM83DebuggerTrace(struct mDebuggerPlatform*, char* out, size_t* length); @@ -104,12 +107,12 @@ void SM83DebuggerDeinit(struct mDebuggerPlatform* platform) { struct SM83Debugger* debugger = (struct SM83Debugger*) platform; size_t i; for (i = 0; i < mBreakpointListSize(&debugger->breakpoints); ++i) { - _destroyBreakpoint(mBreakpointListGetPointer(&debugger->breakpoints, i)); + _destroyBreakpoint(debugger->d.p, mBreakpointListGetPointer(&debugger->breakpoints, i)); } mBreakpointListDeinit(&debugger->breakpoints); for (i = 0; i < mWatchpointListSize(&debugger->watchpoints); ++i) { - _destroyWatchpoint(mWatchpointListGetPointer(&debugger->watchpoints, i)); + _destroyWatchpoint(debugger->d.p, mWatchpointListGetPointer(&debugger->watchpoints, i)); } mWatchpointListDeinit(&debugger->watchpoints); } @@ -122,14 +125,14 @@ static void SM83DebuggerEnter(struct mDebuggerPlatform* platform, enum mDebugger cpu->nextEvent = cpu->cycles; } -static ssize_t SM83DebuggerSetBreakpoint(struct mDebuggerPlatform* d, const struct mBreakpoint* info) { +static ssize_t SM83DebuggerSetBreakpoint(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, const struct mBreakpoint* info) { struct SM83Debugger* debugger = (struct SM83Debugger*) d; struct mBreakpoint* breakpoint = mBreakpointListAppend(&debugger->breakpoints); *breakpoint = *info; breakpoint->id = debugger->nextId; + TableInsert(&debugger->d.p->pointOwner, breakpoint->id, owner); ++debugger->nextId; return breakpoint->id; - } static bool SM83DebuggerClearBreakpoint(struct mDebuggerPlatform* d, ssize_t id) { @@ -140,7 +143,7 @@ static bool SM83DebuggerClearBreakpoint(struct mDebuggerPlatform* d, ssize_t id) for (i = 0; i < mBreakpointListSize(breakpoints); ++i) { struct mBreakpoint* breakpoint = mBreakpointListGetPointer(breakpoints, i); if (breakpoint->id == id) { - _destroyBreakpoint(breakpoint); + _destroyBreakpoint(debugger->d.p, breakpoint); mBreakpointListShift(breakpoints, i, 1); return true; } @@ -150,7 +153,7 @@ static bool SM83DebuggerClearBreakpoint(struct mDebuggerPlatform* d, ssize_t id) for (i = 0; i < mWatchpointListSize(watchpoints); ++i) { struct mWatchpoint* watchpoint = mWatchpointListGetPointer(watchpoints, i); if (watchpoint->id == id) { - _destroyWatchpoint(watchpoint); + _destroyWatchpoint(debugger->d.p, watchpoint); mWatchpointListShift(watchpoints, i, 1); if (!mWatchpointListSize(&debugger->watchpoints)) { SM83DebuggerRemoveMemoryShim(debugger); @@ -166,7 +169,7 @@ static bool SM83DebuggerHasBreakpoints(struct mDebuggerPlatform* d) { return mBreakpointListSize(&debugger->breakpoints) || mWatchpointListSize(&debugger->watchpoints); } -static ssize_t SM83DebuggerSetWatchpoint(struct mDebuggerPlatform* d, const struct mWatchpoint* info) { +static ssize_t SM83DebuggerSetWatchpoint(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, const struct mWatchpoint* info) { struct SM83Debugger* debugger = (struct SM83Debugger*) d; if (!mWatchpointListSize(&debugger->watchpoints)) { SM83DebuggerInstallMemoryShim(debugger); @@ -174,20 +177,43 @@ static ssize_t SM83DebuggerSetWatchpoint(struct mDebuggerPlatform* d, const stru struct mWatchpoint* watchpoint = mWatchpointListAppend(&debugger->watchpoints); *watchpoint = *info; watchpoint->id = debugger->nextId; + TableInsert(&debugger->d.p->pointOwner, watchpoint->id, owner); ++debugger->nextId; return watchpoint->id; } -static void SM83DebuggerListBreakpoints(struct mDebuggerPlatform* d, struct mBreakpointList* list) { +static void SM83DebuggerListBreakpoints(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, struct mBreakpointList* list) { struct SM83Debugger* debugger = (struct SM83Debugger*) d; mBreakpointListClear(list); - mBreakpointListCopy(list, &debugger->breakpoints); + if (owner) { + size_t i; + for (i = 0; i < mBreakpointListSize(&debugger->breakpoints); ++i) { + struct mBreakpoint* point = mBreakpointListGetPointer(&debugger->breakpoints, i); + if (TableLookup(&debugger->d.p->pointOwner, point->id) != owner) { + continue; + } + memcpy(mBreakpointListAppend(list), point, sizeof(*point)); + } + } else { + mBreakpointListCopy(list, &debugger->breakpoints); + } } -static void SM83DebuggerListWatchpoints(struct mDebuggerPlatform* d, struct mWatchpointList* list) { +static void SM83DebuggerListWatchpoints(struct mDebuggerPlatform* d, struct mDebuggerModule* owner, struct mWatchpointList* list) { struct SM83Debugger* debugger = (struct SM83Debugger*) d; mWatchpointListClear(list); - mWatchpointListCopy(list, &debugger->watchpoints); + if (owner) { + size_t i; + for (i = 0; i < mWatchpointListSize(&debugger->watchpoints); ++i) { + struct mWatchpoint* point = mWatchpointListGetPointer(&debugger->watchpoints, i); + if (TableLookup(&debugger->d.p->pointOwner, point->id) != owner) { + continue; + } + memcpy(mWatchpointListAppend(list), point, sizeof(*point)); + } + } else { + mWatchpointListCopy(list, &debugger->watchpoints); + } } static void SM83DebuggerTrace(struct mDebuggerPlatform* d, char* out, size_t* length) { diff --git a/src/sm83/debugger/memory-debugger.c b/src/sm83/debugger/memory-debugger.c index 142ab92c7..4a62448e8 100644 --- a/src/sm83/debugger/memory-debugger.c +++ b/src/sm83/debugger/memory-debugger.c @@ -65,6 +65,7 @@ static bool _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, s info->type.wp.watchType = watchpoint->type; info->type.wp.accessType = type; info->pointId = watchpoint->id; + info->target = TableLookup(&debugger->d.p->pointOwner, watchpoint->id); return true; } } From a00f2939ada8594f2aa287fb0a20627d2367e29f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 12 Dec 2022 01:58:01 -0800 Subject: [PATCH 217/290] Debugger: Allow attaching multiple debugger modules independently --- include/mgba/debugger/debugger.h | 1 + include/mgba/feature/commandline.h | 3 ++- src/debugger/debugger.c | 24 ++++++++++++++++++++++-- src/feature/commandline.c | 12 ++++-------- src/platform/qt/CoreController.cpp | 4 +++- src/platform/qt/Window.cpp | 17 ++++++++--------- src/platform/sdl/main.c | 26 ++++++++++++++++++++------ 7 files changed, 60 insertions(+), 27 deletions(-) diff --git a/include/mgba/debugger/debugger.h b/include/mgba/debugger/debugger.h index fa9ca58d6..87f80197b 100644 --- a/include/mgba/debugger/debugger.h +++ b/include/mgba/debugger/debugger.h @@ -29,6 +29,7 @@ enum mDebuggerType { }; enum mDebuggerState { + DEBUGGER_CREATED = 0, DEBUGGER_PAUSED, DEBUGGER_RUNNING, DEBUGGER_CALLBACK, diff --git a/include/mgba/feature/commandline.h b/include/mgba/feature/commandline.h index 0f9c543d1..964d9bdf9 100644 --- a/include/mgba/feature/commandline.h +++ b/include/mgba/feature/commandline.h @@ -25,8 +25,9 @@ struct mArguments { struct Table configOverrides; - enum mDebuggerType debuggerType; bool debugAtStart; + bool debugCli; + bool debugGdb; bool showHelp; bool showVersion; }; diff --git a/src/debugger/debugger.c b/src/debugger/debugger.c index 36be88f61..d3819a754 100644 --- a/src/debugger/debugger.c +++ b/src/debugger/debugger.c @@ -95,11 +95,27 @@ void mDebuggerAttach(struct mDebugger* debugger, struct mCore* core) { void mDebuggerAttachModule(struct mDebugger* debugger, struct mDebuggerModule* module) { module->p = debugger; *mDebuggerModuleListAppend(&debugger->modules) = module; + if (debugger->state > DEBUGGER_CREATED && debugger->state < DEBUGGER_SHUTDOWN) { + if (module->init) { + module->init(module); + } + } } void mDebuggerDetachModule(struct mDebugger* debugger, struct mDebuggerModule* module) { - // TODO - abort(); + size_t i; + for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { + if (module != *mDebuggerModuleListGetPointer(&debugger->modules, i)) { + continue; + } + if (debugger->state > DEBUGGER_CREATED && debugger->state < DEBUGGER_SHUTDOWN) { + if (module->deinit) { + module->deinit(module); + } + } + mDebuggerModuleListShift(&debugger->modules, i, 1); + break; + } } void mDebuggerRun(struct mDebugger* debugger) { @@ -143,6 +159,9 @@ void mDebuggerRun(struct mDebugger* debugger) { debugger->state = DEBUGGER_RUNNING; } break; + case DEBUGGER_CREATED: + mLOG(DEBUGGER, ERROR, "Attempted to run debugger before initializtion"); + return; case DEBUGGER_SHUTDOWN: return; } @@ -258,6 +277,7 @@ static void _mDebuggerInit(void* cpu, struct mCPUComponent* component) { static void _mDebuggerDeinit(struct mCPUComponent* component) { struct mDebugger* debugger = (struct mDebugger*) component; + debugger->state = DEBUGGER_SHUTDOWN; size_t i; for (i = 0; i < mDebuggerModuleListSize(&debugger->modules); ++i) { struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); diff --git a/src/feature/commandline.c b/src/feature/commandline.c index 72e5f6c26..e529a2eb3 100644 --- a/src/feature/commandline.c +++ b/src/feature/commandline.c @@ -137,18 +137,14 @@ bool mArgumentsParse(struct mArguments* args, int argc, char* const* argv, struc break; #ifdef USE_EDITLINE case 'd': - if (args->debuggerType != DEBUGGER_NONE) { - return false; - } - args->debuggerType = DEBUGGER_CLI; + args->debugAtStart = true; + args->debugCli = true; break; #endif #ifdef USE_GDB_STUB case 'g': - if (args->debuggerType != DEBUGGER_NONE) { - return false; - } - args->debuggerType = DEBUGGER_GDB; + args->debugAtStart = true; + args->debugGdb = true; break; #endif case 'h': diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 6bcbeffb3..8eacd4ea1 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -347,7 +347,9 @@ void CoreController::detachDebugger() { void CoreController::attachDebuggerModule(mDebuggerModule* module, bool interrupt) { Interrupter interrupter(this); - mDebuggerAttachModule(&m_debugger, module); + if (module) { + mDebuggerAttachModule(&m_debugger, module); + } attachDebugger(interrupt); } diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index c101b8c1c..dfad74ead 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -206,15 +206,14 @@ void Window::argumentsPassed() { } #ifdef USE_GDB_STUB - if (args->debuggerType == DEBUGGER_GDB) { - if (!m_gdbController) { - m_gdbController = new GDBController(this); - if (m_controller) { - m_gdbController->setController(m_controller); - } - m_gdbController->attach(); - m_gdbController->listen(); - } + if (args->debugGdb) { + gdbOpen(); + } +#endif + +#ifdef USE_DEBUGGERS + if (args->debugCli) { + consoleOpen(); } #endif diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index 3f2065c15..2d9a74ae8 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -243,19 +243,33 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { #endif #ifdef USE_DEBUGGERS - struct mDebuggerModule* module = mDebuggerCreateModule(args->debuggerType, renderer->core); struct mDebugger debugger; + bool hasDebugger = false; mDebuggerInit(&debugger); - if (module) { #ifdef USE_EDITLINE - if (args->debuggerType == DEBUGGER_CLI) { + if (args->debugCli) { + struct mDebuggerModule* module = mDebuggerCreateModule(DEBUGGER_CLI, renderer->core); + if (module) { struct CLIDebugger* cliDebugger = (struct CLIDebugger*) module; CLIDebuggerAttachBackend(cliDebugger, CLIDebuggerEditLineBackendCreate()); + mDebuggerAttachModule(&debugger, module); + hasDebugger = true; } + } #endif - module->isPaused = true; - mDebuggerAttachModule(&debugger, module); + +#ifdef USE_GDB_STUB + if (args->debugGdb) { + struct mDebuggerModule* module = mDebuggerCreateModule(DEBUGGER_GDB, renderer->core); + if (module) { + mDebuggerAttachModule(&debugger, module); + hasDebugger = true; + } + } +#endif + + if (hasDebugger) { mDebuggerAttach(&debugger, renderer->core); mDebuggerEnter(&debugger, DEBUGGER_ENTER_MANUAL, NULL); #ifdef ENABLE_SCRIPTING @@ -328,7 +342,7 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { #endif #ifdef USE_DEBUGGERS - if (module) { + if (hasDebugger) { renderer->core->detachDebugger(renderer->core); mDebuggerDeinit(&debugger); } From 257122796c4d4efc37c3a285344fcbea97e74c53 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 9 May 2023 21:26:23 -0700 Subject: [PATCH 218/290] Debugger: Add debugger polling to avoid blocking --- include/mgba/debugger/debugger.h | 3 +- include/mgba/internal/debugger/cli-debugger.h | 1 + include/mgba/internal/debugger/gdb-stub.h | 3 +- src/debugger/cli-debugger.c | 15 ++- src/debugger/debugger.c | 12 +- src/debugger/gdb-stub.c | 69 ++++++----- src/feature/editline/cli-el-backend.c | 107 +++++++++++++++--- src/feature/editline/cli-el-backend.h | 7 -- src/platform/qt/DebuggerConsoleController.cpp | 13 ++- src/platform/qt/DebuggerConsoleController.h | 1 + 10 files changed, 155 insertions(+), 76 deletions(-) diff --git a/include/mgba/debugger/debugger.h b/include/mgba/debugger/debugger.h index 87f80197b..122fdde6f 100644 --- a/include/mgba/debugger/debugger.h +++ b/include/mgba/debugger/debugger.h @@ -152,7 +152,7 @@ struct mDebuggerModule { void (*init)(struct mDebuggerModule*); void (*deinit)(struct mDebuggerModule*); - void (*paused)(struct mDebuggerModule*); + void (*paused)(struct mDebuggerModule*, int32_t timeoutMs); void (*update)(struct mDebuggerModule*); void (*entered)(struct mDebuggerModule*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*); void (*custom)(struct mDebuggerModule*); @@ -166,6 +166,7 @@ void mDebuggerDeinit(struct mDebugger*); void mDebuggerAttach(struct mDebugger*, struct mCore*); void mDebuggerAttachModule(struct mDebugger*, struct mDebuggerModule*); void mDebuggerDetachModule(struct mDebugger*, struct mDebuggerModule*); +void mDebuggerRunTimeout(struct mDebugger* debugger, int32_t timeoutMs); void mDebuggerRun(struct mDebugger*); void mDebuggerRunFrame(struct mDebugger*); void mDebuggerEnter(struct mDebugger*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*); diff --git a/include/mgba/internal/debugger/cli-debugger.h b/include/mgba/internal/debugger/cli-debugger.h index f87bffd7e..87ba9db88 100644 --- a/include/mgba/internal/debugger/cli-debugger.h +++ b/include/mgba/internal/debugger/cli-debugger.h @@ -73,6 +73,7 @@ struct CLIDebuggerBackend { ATTRIBUTE_FORMAT(printf, 2, 3) void (*printf)(struct CLIDebuggerBackend*, const char* fmt, ...); + int (*poll)(struct CLIDebuggerBackend*, int32_t timeoutMs); const char* (*readline)(struct CLIDebuggerBackend*, size_t* len); void (*lineAppend)(struct CLIDebuggerBackend*, const char* line); const char* (*historyLast)(struct CLIDebuggerBackend*, size_t* len); diff --git a/include/mgba/internal/debugger/gdb-stub.h b/include/mgba/internal/debugger/gdb-stub.h index 0d6d24b29..6110ec499 100644 --- a/include/mgba/internal/debugger/gdb-stub.h +++ b/include/mgba/internal/debugger/gdb-stub.h @@ -41,7 +41,6 @@ struct GDBStub { Socket socket; Socket connection; - bool shouldBlock; int untilPoll; bool supportsSwbreak; @@ -56,7 +55,7 @@ bool GDBStubListen(struct GDBStub*, int port, const struct Address* bindAddress, void GDBStubHangup(struct GDBStub*); void GDBStubShutdown(struct GDBStub*); -void GDBStubUpdate(struct GDBStub*); +bool GDBStubUpdate(struct GDBStub*, int timeoutMs); CXX_GUARD_END diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index 79313d06c..58ce72a25 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -1092,7 +1092,7 @@ bool CLIDebuggerRunCommand(struct CLIDebugger* debugger, const char* line, size_ return false; } -static void _commandLine(struct mDebuggerModule* debugger) { +static void _commandLine(struct mDebuggerModule* debugger, int32_t timeoutMs) { struct CLIDebugger* cliDebugger = (struct CLIDebugger*) debugger; const char* line; size_t len; @@ -1102,15 +1102,20 @@ static void _commandLine(struct mDebuggerModule* debugger) { _printStatus(cliDebugger, 0); } while (debugger->isPaused && !mDebuggerIsShutdown(debugger->p)) { + int poll = cliDebugger->backend->poll(cliDebugger->backend, timeoutMs); + if (poll <= 0) { + if (poll < 0) { + mDebuggerShutdown(debugger->p); + } else { + cliDebugger->skipStatus = true; + } + return; + } line = cliDebugger->backend->readline(cliDebugger->backend, &len); if (!line || len == 0) { mDebuggerShutdown(debugger->p); return; } - if (line[0] == '\033') { - cliDebugger->skipStatus = true; - return; - } if (line[0] == '\n') { line = cliDebugger->backend->historyLast(cliDebugger->backend, &len); if (line && len) { diff --git a/src/debugger/debugger.c b/src/debugger/debugger.c index d3819a754..cc0b32683 100644 --- a/src/debugger/debugger.c +++ b/src/debugger/debugger.c @@ -118,7 +118,7 @@ void mDebuggerDetachModule(struct mDebugger* debugger, struct mDebuggerModule* m } } -void mDebuggerRun(struct mDebugger* debugger) { +void mDebuggerRunTimeout(struct mDebugger* debugger, int32_t timeoutMs) { size_t i; size_t anyPaused = 0; @@ -146,7 +146,7 @@ void mDebuggerRun(struct mDebugger* debugger) { struct mDebuggerModule* module = *mDebuggerModuleListGetPointer(&debugger->modules, i); if (module->isPaused) { if (module->paused) { - module->paused(module); + module->paused(module, timeoutMs); } if (module->isPaused) { ++anyPaused; @@ -167,6 +167,10 @@ void mDebuggerRun(struct mDebugger* debugger) { } } +void mDebuggerRun(struct mDebugger* debugger) { + mDebuggerRunTimeout(debugger, 50); +} + void mDebuggerRunFrame(struct mDebugger* debugger) { uint32_t frame = debugger->core->frameCounter(debugger->core); do { @@ -309,7 +313,5 @@ bool mDebuggerLookupIdentifier(struct mDebugger* debugger, const char* name, int void mDebuggerModuleSetNeedsCallback(struct mDebuggerModule* debugger) { debugger->needsCallback = true; - if (debugger->p->state == DEBUGGER_RUNNING) { - debugger->p->state = DEBUGGER_CALLBACK; - } + mDebuggerUpdatePaused(debugger->p); } diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index 88bd1e6a5..8a7d400ee 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -17,8 +17,6 @@ #define SIGTRAP 5 /* Win32 Signals do not include SIGTRAP */ #endif -#define SOCKET_TIMEOUT 50 - enum GDBError { GDB_NO_ERROR = 0x00, GDB_BAD_ARGUMENTS = 0x06, @@ -135,20 +133,17 @@ static void _gdbStubPoll(struct mDebuggerModule* debugger) { return; } stub->untilPoll = GDB_STUB_INTERVAL; - stub->shouldBlock = false; - GDBStubUpdate(stub); + GDBStubUpdate(stub, 0); } -static void _gdbStubWait(struct mDebuggerModule* debugger) { +static void _gdbStubWait(struct mDebuggerModule* debugger, int32_t timeoutMs) { struct GDBStub* stub = (struct GDBStub*) debugger; - stub->shouldBlock = true; - GDBStubUpdate(stub); + GDBStubUpdate(stub, timeoutMs); } static void _gdbStubUpdate(struct mDebuggerModule* debugger) { struct GDBStub* stub = (struct GDBStub*) debugger; - stub->shouldBlock = false; - GDBStubUpdate(stub); + GDBStubUpdate(stub, 0); } static void _ack(struct GDBStub* stub) { @@ -254,6 +249,7 @@ static void _writeHostInfo(struct GDBStub* stub) { static void _continue(struct GDBStub* stub, const char* message) { mDebuggerModuleSetNeedsCallback(&stub->d); stub->untilPoll = GDB_STUB_INTERVAL; + stub->d.isPaused = false; // TODO: parse message UNUSED(message); } @@ -791,7 +787,6 @@ void GDBStubCreate(struct GDBStub* stub) { stub->d.type = DEBUGGER_GDB; stub->untilPoll = GDB_STUB_INTERVAL; stub->lineAck = GDB_ACK_PENDING; - stub->shouldBlock = false; } bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAddress, enum GDBWatchpointsBehvaior watchpointsBehavior) { @@ -841,17 +836,17 @@ void GDBStubShutdown(struct GDBStub* stub) { } } -void GDBStubUpdate(struct GDBStub* stub) { +bool GDBStubUpdate(struct GDBStub* stub, int32_t timeoutMs) { if (stub->socket == INVALID_SOCKET) { stub->d.needsCallback = false; stub->d.isPaused = false; mDebuggerUpdatePaused(stub->d.p); - return; + return false; } if (stub->connection == INVALID_SOCKET) { - if (stub->shouldBlock) { + if (timeoutMs) { Socket reads = stub->socket; - SocketPoll(1, &reads, 0, 0, SOCKET_TIMEOUT); + SocketPoll(1, &reads, 0, 0, timeoutMs); } stub->connection = SocketAccept(stub->socket, 0); if (!SOCKET_FAILED(stub->connection)) { @@ -860,36 +855,38 @@ void GDBStubUpdate(struct GDBStub* stub) { } mDebuggerEnter(stub->d.p, DEBUGGER_ENTER_ATTACHED, 0); } else if (SocketWouldBlock()) { - return; + return false; } else { goto connectionLost; } SocketSetTCPPush(stub->connection, 1); } - while (true) { - if (stub->shouldBlock) { - Socket reads = stub->connection; - SocketPoll(1, &reads, 0, 0, SOCKET_TIMEOUT); - } - ssize_t messageLen = SocketRecv(stub->connection, stub->line, GDB_STUB_MAX_LINE - 1); - if (messageLen == 0) { - goto connectionLost; - } - if (messageLen == -1) { - if (SocketWouldBlock()) { - return; - } - goto connectionLost; - } - stub->line[messageLen] = '\0'; - mLOG(DEBUGGER, DEBUG, "< %s", stub->line); - ssize_t position = 0; - while (position < messageLen) { - position += _parseGDBMessage(stub, &stub->line[position]); - } + + if (timeoutMs) { + Socket reads = stub->connection; + SocketPoll(1, &reads, 0, 0, timeoutMs); } + ssize_t messageLen = SocketRecv(stub->connection, stub->line, GDB_STUB_MAX_LINE - 1); + if (messageLen == 0) { + goto connectionLost; + } + if (messageLen == -1) { + if (SocketWouldBlock()) { + return false; + } + goto connectionLost; + } + + stub->line[messageLen] = '\0'; + mLOG(DEBUGGER, DEBUG, "< %s", stub->line); + ssize_t position = 0; + while (position < messageLen) { + position += _parseGDBMessage(stub, &stub->line[position]); + } + return true; connectionLost: mLOG(DEBUGGER, WARN, "Connection lost"); GDBStubHangup(stub); + return false; } diff --git a/src/feature/editline/cli-el-backend.c b/src/feature/editline/cli-el-backend.c index b87ecb6bf..78ae19fe1 100644 --- a/src/feature/editline/cli-el-backend.c +++ b/src/feature/editline/cli-el-backend.c @@ -7,12 +7,31 @@ #include #include +#include #include #include +struct CLIDebuggerEditLineBackend { + struct CLIDebuggerBackend d; + + EditLine* elstate; + History* histate; + + int count; + const char* prompt; + bool doPrompt; + Thread promptThread; + Mutex promptMutex; + Condition promptRead; + Condition promptWrite; + bool exitThread; +}; + static struct CLIDebugger* _activeDebugger; +static THREAD_ENTRY _promptThread(void*); + static char* _prompt(EditLine* el) { UNUSED(el); return "> "; @@ -43,7 +62,7 @@ static unsigned char _tabComplete(EditLine* elstate, int ch) { } ATTRIBUTE_FORMAT(printf, 2, 3) -void _CLIDebuggerEditLinePrintf(struct CLIDebuggerBackend* be, const char* fmt, ...) { +static void CLIDebuggerEditLinePrintf(struct CLIDebuggerBackend* be, const char* fmt, ...) { UNUSED(be); va_list args; va_start(args, fmt); @@ -51,7 +70,7 @@ void _CLIDebuggerEditLinePrintf(struct CLIDebuggerBackend* be, const char* fmt, va_end(args); } -void _CLIDebuggerEditLineInit(struct CLIDebuggerBackend* be) { +static void CLIDebuggerEditLineInit(struct CLIDebuggerBackend* be) { struct CLIDebuggerEditLineBackend* elbe = (struct CLIDebuggerEditLineBackend*) be; // TODO: get argv[0] elbe->elstate = el_init(binaryName, stdin, stdout, stderr); @@ -81,12 +100,26 @@ void _CLIDebuggerEditLineInit(struct CLIDebuggerBackend* be) { } } + MutexInit(&elbe->promptMutex); + ConditionInit(&elbe->promptRead); + ConditionInit(&elbe->promptWrite); + elbe->prompt = NULL; + elbe->exitThread = false; + elbe->doPrompt = false; + ThreadCreate(&elbe->promptThread, _promptThread, elbe); + _activeDebugger = be->p; signal(SIGINT, _breakIntoDefault); } -void _CLIDebuggerEditLineDeinit(struct CLIDebuggerBackend* be) { +static void CLIDebuggerEditLineDeinit(struct CLIDebuggerBackend* be) { struct CLIDebuggerEditLineBackend* elbe = (struct CLIDebuggerEditLineBackend*) be; + MutexLock(&elbe->promptMutex); + elbe->exitThread = true; + ConditionWake(&elbe->promptWrite); + MutexUnlock(&elbe->promptMutex); + ThreadJoin(&elbe->promptThread); + char path[PATH_MAX + 1]; mCoreConfigDirectory(path, PATH_MAX); if (path[0]) { @@ -111,11 +144,51 @@ void _CLIDebuggerEditLineDeinit(struct CLIDebuggerBackend* be) { free(elbe); } -const char* _CLIDebuggerEditLineReadLine(struct CLIDebuggerBackend* be, size_t* len) { +static THREAD_ENTRY _promptThread(void* context) { + struct CLIDebuggerEditLineBackend* elbe = context; + + MutexLock(&elbe->promptMutex); + while (!elbe->exitThread) { + if (elbe->doPrompt) { + MutexUnlock(&elbe->promptMutex); + elbe->prompt = el_gets(elbe->elstate, &elbe->count); + MutexLock(&elbe->promptMutex); + elbe->doPrompt = false; + ConditionWake(&elbe->promptRead); + } + ConditionWait(&elbe->promptWrite, &elbe->promptMutex); + } + MutexUnlock(&elbe->promptMutex); +} + +static int CLIDebuggerEditLinePoll(struct CLIDebuggerBackend* be, int32_t timeoutMs) { + struct CLIDebuggerEditLineBackend* elbe = (struct CLIDebuggerEditLineBackend*) be; + int gotPrompt = 0; + MutexLock(&elbe->promptMutex); + if (!elbe->prompt) { + elbe->doPrompt = true; + ConditionWake(&elbe->promptWrite); + ConditionWaitTimed(&elbe->promptRead, &elbe->promptMutex, timeoutMs); + } + if (elbe->prompt) { + gotPrompt = 1; + } + MutexUnlock(&elbe->promptMutex); + + return gotPrompt; +} + +static const char* CLIDebuggerEditLineReadLine(struct CLIDebuggerBackend* be, size_t* len) { struct CLIDebuggerEditLineBackend* elbe = (struct CLIDebuggerEditLineBackend*) be; - int count; *len = 0; - const char* line = el_gets(elbe->elstate, &count); + if (CLIDebuggerEditLinePoll(be, -1) != 1) { + return NULL; + } + MutexLock(&elbe->promptMutex); + int count = elbe->count; + const char* line = elbe->prompt; + elbe->prompt = NULL; + MutexUnlock(&elbe->promptMutex); if (line) { if (count > 1) { // Crop off newline @@ -126,12 +199,13 @@ const char* _CLIDebuggerEditLineReadLine(struct CLIDebuggerBackend* be, size_t* } return line; } -void _CLIDebuggerEditLineLineAppend(struct CLIDebuggerBackend* be, const char* line) { + +static void CLIDebuggerEditLineLineAppend(struct CLIDebuggerBackend* be, const char* line) { struct CLIDebuggerEditLineBackend* elbe = (struct CLIDebuggerEditLineBackend*) be; el_insertstr(elbe->elstate, line); } -const char* _CLIDebuggerEditLineHistoryLast(struct CLIDebuggerBackend* be, size_t* len) { +static const char* CLIDebuggerEditLineHistoryLast(struct CLIDebuggerBackend* be, size_t* len) { struct CLIDebuggerEditLineBackend* elbe = (struct CLIDebuggerEditLineBackend*) be; HistEvent ev; if (history(elbe->histate, &ev, H_FIRST) < 0) { @@ -148,7 +222,7 @@ const char* _CLIDebuggerEditLineHistoryLast(struct CLIDebuggerBackend* be, size_ return ev.str; } -void _CLIDebuggerEditLineHistoryAppend(struct CLIDebuggerBackend* be, const char* line) { +static void CLIDebuggerEditLineHistoryAppend(struct CLIDebuggerBackend* be, const char* line) { struct CLIDebuggerEditLineBackend* elbe = (struct CLIDebuggerEditLineBackend*) be; HistEvent ev; history(elbe->histate, &ev, H_ENTER, line); @@ -156,13 +230,14 @@ void _CLIDebuggerEditLineHistoryAppend(struct CLIDebuggerBackend* be, const char struct CLIDebuggerBackend* CLIDebuggerEditLineBackendCreate(void) { struct CLIDebuggerEditLineBackend* elbe = calloc(1, sizeof(*elbe)); - elbe->d.printf = _CLIDebuggerEditLinePrintf; - elbe->d.init = _CLIDebuggerEditLineInit; - elbe->d.deinit = _CLIDebuggerEditLineDeinit; - elbe->d.readline = _CLIDebuggerEditLineReadLine; - elbe->d.lineAppend = _CLIDebuggerEditLineLineAppend; - elbe->d.historyLast = _CLIDebuggerEditLineHistoryLast; - elbe->d.historyAppend = _CLIDebuggerEditLineHistoryAppend; + elbe->d.printf = CLIDebuggerEditLinePrintf; + elbe->d.init = CLIDebuggerEditLineInit; + elbe->d.deinit = CLIDebuggerEditLineDeinit; + elbe->d.poll = CLIDebuggerEditLinePoll; + elbe->d.readline = CLIDebuggerEditLineReadLine; + elbe->d.lineAppend = CLIDebuggerEditLineLineAppend; + elbe->d.historyLast = CLIDebuggerEditLineHistoryLast; + elbe->d.historyAppend = CLIDebuggerEditLineHistoryAppend; elbe->d.interrupt = NULL; return &elbe->d; } diff --git a/src/feature/editline/cli-el-backend.h b/src/feature/editline/cli-el-backend.h index 89ceec641..312798a38 100644 --- a/src/feature/editline/cli-el-backend.h +++ b/src/feature/editline/cli-el-backend.h @@ -14,13 +14,6 @@ CXX_GUARD_START #include -struct CLIDebuggerEditLineBackend { - struct CLIDebuggerBackend d; - - EditLine* elstate; - History* histate; -}; - struct CLIDebuggerBackend* CLIDebuggerEditLineBackendCreate(void); CXX_GUARD_END diff --git a/src/platform/qt/DebuggerConsoleController.cpp b/src/platform/qt/DebuggerConsoleController.cpp index a0bfe1999..55d44a0ae 100644 --- a/src/platform/qt/DebuggerConsoleController.cpp +++ b/src/platform/qt/DebuggerConsoleController.cpp @@ -22,6 +22,7 @@ DebuggerConsoleController::DebuggerConsoleController(QObject* parent) m_backend.printf = printf; m_backend.init = init; m_backend.deinit = deinit; + m_backend.poll = poll; m_backend.readline = readLine; m_backend.lineAppend = lineAppend; m_backend.historyLast = historyLast; @@ -88,6 +89,14 @@ void DebuggerConsoleController::deinit(struct CLIDebuggerBackend* be) { } } +int DebuggerConsoleController::poll(struct CLIDebuggerBackend* be, int32_t timeoutMs) { + Backend* consoleBe = reinterpret_cast(be); + DebuggerConsoleController* self = consoleBe->self; + QMutexLocker lock(&self->m_mutex); + self->m_cond.wait(&self->m_mutex, timeoutMs < 0 ? ULONG_MAX : static_cast(timeoutMs)); + return !self->m_lines.isEmpty(); +} + const char* DebuggerConsoleController::readLine(struct CLIDebuggerBackend* be, size_t* len) { Backend* consoleBe = reinterpret_cast(be); DebuggerConsoleController* self = consoleBe->self; @@ -137,10 +146,6 @@ void DebuggerConsoleController::interrupt(struct CLIDebuggerBackend* be) { DebuggerConsoleController* self = consoleBe->self; QMutexLocker lock(&self->m_mutex); self->m_cond.wakeOne(); - if (!self->m_lines.isEmpty()) { - return; - } - self->m_lines.append("\033"); } void DebuggerConsoleController::historyLoad() { diff --git a/src/platform/qt/DebuggerConsoleController.h b/src/platform/qt/DebuggerConsoleController.h index c3d98d35c..b4530185b 100644 --- a/src/platform/qt/DebuggerConsoleController.h +++ b/src/platform/qt/DebuggerConsoleController.h @@ -42,6 +42,7 @@ private: static void printf(struct CLIDebuggerBackend* be, const char* fmt, ...); static void init(struct CLIDebuggerBackend* be); static void deinit(struct CLIDebuggerBackend* be); + static int poll(struct CLIDebuggerBackend* be, int32_t timeoutMs); static const char* readLine(struct CLIDebuggerBackend* be, size_t* len); static void lineAppend(struct CLIDebuggerBackend* be, const char* line); static const char* historyLast(struct CLIDebuggerBackend* be, size_t* len); From 213a534f4b8a312156fc9ae5502fbd3e5a6177d4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 9 May 2023 21:44:58 -0700 Subject: [PATCH 219/290] GBA: Remove disused variable --- src/gba/gba.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gba/gba.c b/src/gba/gba.c index 588a138f3..e98f0102f 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -663,7 +663,6 @@ bool GBAIsROM(struct VFile* vf) { #ifdef USE_ELF struct ELF* elf = ELFOpen(vf); if (elf) { - uint32_t entry = ELFEntry(elf); bool isGBA = true; isGBA = isGBA && ELFMachine(elf) == EM_ARM; isGBA = isGBA && (GBAVerifyELFEntry(elf, GBA_BASE_ROM0) || GBAVerifyELFEntry(elf, GBA_BASE_EWRAM + 0xC0)); From 7d6a8a86a889e76dd6bcbf30b7eaec20aa09f15f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 9 May 2023 22:13:09 -0700 Subject: [PATCH 220/290] ARM Debugger: Fix unitialized stack variable --- src/arm/debugger/debugger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arm/debugger/debugger.c b/src/arm/debugger/debugger.c index 31df1b523..640e2849a 100644 --- a/src/arm/debugger/debugger.c +++ b/src/arm/debugger/debugger.c @@ -408,7 +408,7 @@ static void ARMDebuggerListBreakpoints(struct mDebuggerPlatform* d, struct mDebu struct ARMDebugger* debugger = (struct ARMDebugger*) d; mBreakpointListClear(list); size_t i, s; - for (i = 0; i < ARMDebugBreakpointListSize(&debugger->breakpoints) || s < ARMDebugBreakpointListSize(&debugger->swBreakpoints);) { + for (i = 0, s = 0; i < ARMDebugBreakpointListSize(&debugger->breakpoints) || s < ARMDebugBreakpointListSize(&debugger->swBreakpoints);) { struct ARMDebugBreakpoint* hw = NULL; struct ARMDebugBreakpoint* sw = NULL; if (i < ARMDebugBreakpointListSize(&debugger->breakpoints)) { From bb6613888a57a7746e127f44dad447e3a9cf5f5e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 9 May 2023 22:21:55 -0700 Subject: [PATCH 221/290] Util: Add THREAD_EXIT macro --- include/mgba-util/platform/3ds/threading.h | 1 + include/mgba-util/platform/posix/threading.h | 1 + include/mgba-util/platform/psp2/threading.h | 1 + include/mgba-util/platform/switch/threading.h | 1 + include/mgba-util/platform/windows/threading.h | 1 + src/core/rewind.c | 2 +- src/feature/editline/cli-el-backend.c | 1 + src/feature/gui/gui-runner.c | 1 + src/feature/thread-proxy.c | 2 +- src/platform/psp2/psp2-context.c | 2 +- src/platform/test/cinema-main.c | 1 + 11 files changed, 11 insertions(+), 3 deletions(-) diff --git a/include/mgba-util/platform/3ds/threading.h b/include/mgba-util/platform/3ds/threading.h index dfc03342d..8d5555317 100644 --- a/include/mgba-util/platform/3ds/threading.h +++ b/include/mgba-util/platform/3ds/threading.h @@ -12,6 +12,7 @@ #include #define THREAD_ENTRY void +#define THREAD_EXIT(RES) return typedef ThreadFunc ThreadEntry; typedef LightLock Mutex; diff --git a/include/mgba-util/platform/posix/threading.h b/include/mgba-util/platform/posix/threading.h index c8d42ed73..ad6e99690 100644 --- a/include/mgba-util/platform/posix/threading.h +++ b/include/mgba-util/platform/posix/threading.h @@ -20,6 +20,7 @@ CXX_GUARD_START #define THREAD_ENTRY void* typedef THREAD_ENTRY (*ThreadEntry)(void*); +#define THREAD_EXIT(RES) return RES typedef pthread_t Thread; typedef pthread_mutex_t Mutex; diff --git a/include/mgba-util/platform/psp2/threading.h b/include/mgba-util/platform/psp2/threading.h index ba4827229..4c084804c 100644 --- a/include/mgba-util/platform/psp2/threading.h +++ b/include/mgba-util/platform/psp2/threading.h @@ -17,6 +17,7 @@ typedef struct { } Condition; #define THREAD_ENTRY int typedef THREAD_ENTRY (*ThreadEntry)(void*); +#define THREAD_EXIT(RES) return RES static inline int MutexInit(Mutex* mutex) { Mutex id = sceKernelCreateMutex("mutex", 0, 0, 0); diff --git a/include/mgba-util/platform/switch/threading.h b/include/mgba-util/platform/switch/threading.h index 76f1ae8b5..52419fa28 100644 --- a/include/mgba-util/platform/switch/threading.h +++ b/include/mgba-util/platform/switch/threading.h @@ -11,6 +11,7 @@ #include #define THREAD_ENTRY void +#define THREAD_EXIT(RES) return typedef ThreadFunc ThreadEntry; typedef CondVar Condition; diff --git a/include/mgba-util/platform/windows/threading.h b/include/mgba-util/platform/windows/threading.h index 9be6fe53a..3f5de5a77 100644 --- a/include/mgba-util/platform/windows/threading.h +++ b/include/mgba-util/platform/windows/threading.h @@ -12,6 +12,7 @@ #include #define THREAD_ENTRY DWORD WINAPI typedef THREAD_ENTRY ThreadEntry(LPVOID); +#define THREAD_EXIT(RES) return RES typedef HANDLE Thread; typedef CRITICAL_SECTION Mutex; diff --git a/src/core/rewind.c b/src/core/rewind.c index d571c407a..21120ec21 100644 --- a/src/core/rewind.c +++ b/src/core/rewind.c @@ -176,7 +176,7 @@ THREAD_ENTRY _rewindThread(void* context) { rewindContext->ready = false; } MutexUnlock(&rewindContext->mutex); - return 0; + THREAD_EXIT(0); } #endif diff --git a/src/feature/editline/cli-el-backend.c b/src/feature/editline/cli-el-backend.c index 78ae19fe1..c76bb5d67 100644 --- a/src/feature/editline/cli-el-backend.c +++ b/src/feature/editline/cli-el-backend.c @@ -159,6 +159,7 @@ static THREAD_ENTRY _promptThread(void* context) { ConditionWait(&elbe->promptWrite, &elbe->promptMutex); } MutexUnlock(&elbe->promptMutex); + THREAD_EXIT(0); } static int CLIDebuggerEditLinePoll(struct CLIDebuggerBackend* be, int32_t timeoutMs) { diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 28c3bd74e..22518579b 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -841,5 +841,6 @@ THREAD_ENTRY mGUIAutosaveThread(void* context) { } } MutexUnlock(&autosave->mutex); + THREAD_EXIT(0); } #endif diff --git a/src/feature/thread-proxy.c b/src/feature/thread-proxy.c index d2956ed95..c3878e896 100644 --- a/src/feature/thread-proxy.c +++ b/src/feature/thread-proxy.c @@ -207,7 +207,7 @@ static THREAD_ENTRY _proxyThread(void* logger) { } } MutexUnlock(&proxyRenderer->mutex); - return 0; + THREAD_EXIT(0); } #endif diff --git a/src/platform/psp2/psp2-context.c b/src/platform/psp2/psp2-context.c index 749ff4589..a7504f7cc 100644 --- a/src/platform/psp2/psp2-context.c +++ b/src/platform/psp2/psp2-context.c @@ -129,7 +129,7 @@ static THREAD_ENTRY _audioThread(void* context) { sceAudioOutOutput(audioPort, buffer); } sceAudioOutReleasePort(audioPort); - return 0; + THREAD_EXIT(0); } static void _sampleRotation(struct mRotationSource* source) { diff --git a/src/platform/test/cinema-main.c b/src/platform/test/cinema-main.c index 0644c4408..01d864bd8 100644 --- a/src/platform/test/cinema-main.c +++ b/src/platform/test/cinema-main.c @@ -1365,6 +1365,7 @@ static THREAD_ENTRY CInemaJob(void* context) { CIflush(&stream.err, stderr); StringListDeinit(&stream.err.lines); StringListDeinit(&stream.err.partial); + THREAD_EXIT(0); } void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args) { From 196f507d3ba8478f102faf2ec7b863ebefd5ff07 Mon Sep 17 00:00:00 2001 From: May Date: Tue, 23 May 2023 18:16:46 -0400 Subject: [PATCH 222/290] [UI bug] mGBA doesn't update savestate screenshots until you move the cursor over other savestates (#2929) --- CHANGES | 1 + src/feature/gui/gui-runner.c | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index d45c14d5e..609a1495d 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,7 @@ Emulation fixes: - GBA BIOS: Fix clobbering registers with word-sized CpuSet - GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) Other fixes: + - mGUI: Fix cases where an older save state screenshot would be shown. (fixes mgba.io/i/2183) - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) Misc: - Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826) diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 22518579b..267b0e0b0 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -656,6 +656,9 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { runner->core->reset(runner->core); break; case RUNNER_SAVE_STATE: + // If we are saving state, then the screenshot stored for the state previously should no longer be considered up-to-date. + // Therefore, mark it as stale so that at draw time we load the new save state's screenshot. + ((struct mGUIBackground*) stateSaveMenu.background)->screenshotId |= SCREENSHOT_INVALID; mCoreSaveState(runner->core, item->data.v.u >> 16, SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA); break; case RUNNER_LOAD_STATE: From a7d63cde543e1e1f74a376e3ff7194abe33a6894 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 10 May 2023 23:08:29 -0700 Subject: [PATCH 223/290] Core: Reattaching the same debugger is a no-op --- src/gb/core.c | 3 +++ src/gba/core.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/gb/core.c b/src/gb/core.c index b35616f2b..f1358ee69 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -1061,6 +1061,9 @@ static struct CLIDebuggerSystem* _GBCoreCliDebuggerSystem(struct mCore* core) { static void _GBCoreAttachDebugger(struct mCore* core, struct mDebugger* debugger) { struct SM83Core* cpu = core->cpu; + if (core->debugger == debugger) { + return; + } if (core->debugger) { SM83HotplugDetach(cpu, CPU_COMPONENT_DEBUGGER); } diff --git a/src/gba/core.c b/src/gba/core.c index 851a9dee6..7a4650a33 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -1129,6 +1129,9 @@ static struct CLIDebuggerSystem* _GBACoreCliDebuggerSystem(struct mCore* core) { } static void _GBACoreAttachDebugger(struct mCore* core, struct mDebugger* debugger) { + if (core->debugger == debugger) { + return; + } if (core->debugger) { GBADetachDebugger(core->board); } From 4b7223c3ffb98e7fa85b02d7676d520b5d07b010 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sat, 27 May 2023 10:53:13 -0700 Subject: [PATCH 224/290] Core: Only attempt to open symbol file if basedir exists --- src/gb/core.c | 2 +- src/gba/core.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gb/core.c b/src/gb/core.c index f1358ee69..68c2d2985 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -1084,7 +1084,7 @@ static void _GBCoreDetachDebugger(struct mCore* core) { static void _GBCoreLoadSymbols(struct mCore* core, struct VFile* vf) { core->symbolTable = mDebuggerSymbolTableCreate(); #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 - if (!vf) { + if (!vf && core->dirs.base) { vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".sym", O_RDONLY); } #endif diff --git a/src/gba/core.c b/src/gba/core.c index 7a4650a33..7c1e19d21 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -1149,12 +1149,12 @@ static void _GBACoreLoadSymbols(struct mCore* core, struct VFile* vf) { core->symbolTable = mDebuggerSymbolTableCreate(); #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 #ifdef USE_ELF - if (!vf) { + if (!vf && core->dirs.base) { closeAfter = true; vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".elf", O_RDONLY); } #endif - if (!vf) { + if (!vf && core->dirs.base) { closeAfter = true; vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".sym", O_RDONLY); } From b80797a5785494a647f9e1444653ab86c6d9e24f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 28 May 2023 19:10:41 -0700 Subject: [PATCH 225/290] CMake: Add -Werror=incompatible-pointer-types --- CMakeLists.txt | 2 +- src/feature/updater-main.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4766eb521..689b8795b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ if(NOT MSVC) # mingw32 likes to complain about using the "wrong" format strings despite them actually working set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-format") endif() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration -Werror=implicit-int -fwrapv") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration -Werror=implicit-int -Werror=incompatible-pointer-types -fwrapv") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS} -Woverloaded-virtual") else() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146 /wd4267 /Zc:preprocessor-") diff --git a/src/feature/updater-main.c b/src/feature/updater-main.c index cd72fc4f3..e24fef593 100644 --- a/src/feature/updater-main.c +++ b/src/feature/updater-main.c @@ -274,7 +274,7 @@ int main(int argc, char* argv[]) { const char* argv[] = { qbin, NULL }; _execv(bin, argv); #elif defined(_POSIX_C_SOURCE) || defined(__APPLE__) - const char* argv[] = { bin, NULL }; + char* const argv[] = { bin, NULL }; execv(bin, argv); #endif } From d432ec34e130a61205cefa5414a322b7aff220a3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 29 May 2023 00:37:11 -0700 Subject: [PATCH 226/290] CMake: Fix build on clang --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 689b8795b..2bf638253 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,11 @@ if(NOT MSVC) # mingw32 likes to complain about using the "wrong" format strings despite them actually working set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-format") endif() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration -Werror=implicit-int -Werror=incompatible-pointer-types -fwrapv") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=implicit-function-declaration -Werror=implicit-int -fwrapv") + if(CMAKE_C_COMPILER_ID STREQUAL "GNU") + # TODO: Remove this once mScript KV pairs support const correctness + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=incompatible-pointer-types") + endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS} -Woverloaded-virtual") else() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_CRT_SECURE_NO_WARNINGS /wd4003 /wd4244 /wd4146 /wd4267 /Zc:preprocessor-") From 46c44120c96719bb02abd26d8bc4cef5e6b4edc6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 29 May 2023 00:53:38 -0700 Subject: [PATCH 227/290] 3DS: Fix build --- src/feature/updater.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/feature/updater.c b/src/feature/updater.c index e9f47f440..e50ed0fa2 100644 --- a/src/feature/updater.c +++ b/src/feature/updater.c @@ -148,7 +148,7 @@ bool mUpdateLoad(const struct mCoreConfig* config, const char* prefix, struct mU snprintf(key, sizeof(key), "%s.path", prefix); update->path = mCoreConfigGetValue(config, key); snprintf(key, sizeof(key), "%s.size", prefix); - uint32_t size = 0; + unsigned size = 0; mCoreConfigGetUIntValue(config, key, &size); if (!update->path && !size) { return false; From b94cd7f1dc521945ec56ab25f06cee141da14ea4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 28 May 2023 21:11:11 -0700 Subject: [PATCH 228/290] Core: Remove duplicated include --- src/core/input.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/input.c b/src/core/input.c index 787e31f86..1c06d48a9 100644 --- a/src/core/input.c +++ b/src/core/input.c @@ -9,8 +9,6 @@ #include #include -#include - #define SECTION_NAME_MAX 128 #define KEY_NAME_MAX 32 #define KEY_VALUE_MAX 16 From a161dfeb31710b91731457a13f44f06c2fb92451 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 28 May 2023 21:11:48 -0700 Subject: [PATCH 229/290] Debugger: Fill in segment in debugger entry --- include/mgba/debugger/debugger.h | 1 + src/sm83/debugger/debugger.c | 1 + src/sm83/debugger/memory-debugger.c | 1 + 3 files changed, 3 insertions(+) diff --git a/include/mgba/debugger/debugger.h b/include/mgba/debugger/debugger.h index 122fdde6f..4ce7c3cf9 100644 --- a/include/mgba/debugger/debugger.h +++ b/include/mgba/debugger/debugger.h @@ -61,6 +61,7 @@ enum mDebuggerEntryReason { struct mDebuggerModule; struct mDebuggerEntryInfo { uint32_t address; + int segment; union { struct { uint32_t oldValue; diff --git a/src/sm83/debugger/debugger.c b/src/sm83/debugger/debugger.c index eafed0329..d24a75463 100644 --- a/src/sm83/debugger/debugger.c +++ b/src/sm83/debugger/debugger.c @@ -54,6 +54,7 @@ static void SM83DebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { } struct mDebuggerEntryInfo info = { .address = breakpoint->address, + .segment = debugger->cpu->memory.currentSegment(debugger->cpu, breakpoint->address), .pointId = breakpoint->id, .target = TableLookup(&d->p->pointOwner, breakpoint->id) }; diff --git a/src/sm83/debugger/memory-debugger.c b/src/sm83/debugger/memory-debugger.c index 4a62448e8..9022a3f07 100644 --- a/src/sm83/debugger/memory-debugger.c +++ b/src/sm83/debugger/memory-debugger.c @@ -62,6 +62,7 @@ static bool _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, s info->type.wp.oldValue = oldValue; info->type.wp.newValue = newValue; info->address = address; + info->segment = debugger->originalMemory.currentSegment(debugger->cpu, address); info->type.wp.watchType = watchpoint->type; info->type.wp.accessType = type; info->pointId = watchpoint->id; From cbc8e4f11a90351c5c4cdad4b9662f4a6835c1ac Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 28 May 2023 21:29:11 -0700 Subject: [PATCH 230/290] Debugger: Fix writing to specific segment in command-line debugger --- CHANGES | 1 + src/debugger/cli-debugger.c | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 609a1495d..41e46b939 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,7 @@ Emulation fixes: - GBA BIOS: Fix clobbering registers with word-sized CpuSet - GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) Other fixes: + - Debugger: Fix writing to specific segment in command-line debugger - mGUI: Fix cases where an older save state screenshot would be shown. (fixes mgba.io/i/2183) - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) Misc: diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index 58ce72a25..398a993e5 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -438,7 +438,7 @@ static void _writeByte(struct CLIDebugger* debugger, struct CLIDebugVector* dv) return; } if (dv->segmentValue >= 0) { - debugger->d.p->core->rawWrite8(debugger->d.p->core, address, value, dv->segmentValue); + debugger->d.p->core->rawWrite8(debugger->d.p->core, address, dv->segmentValue, value); } else { debugger->d.p->core->busWrite8(debugger->d.p->core, address, value); } @@ -460,7 +460,7 @@ static void _writeHalfword(struct CLIDebugger* debugger, struct CLIDebugVector* return; } if (dv->segmentValue >= 0) { - debugger->d.p->core->rawWrite16(debugger->d.p->core, address, value, dv->segmentValue); + debugger->d.p->core->rawWrite16(debugger->d.p->core, address, dv->segmentValue, value); } else { debugger->d.p->core->busWrite16(debugger->d.p->core, address, value); } @@ -492,7 +492,7 @@ static void _writeWord(struct CLIDebugger* debugger, struct CLIDebugVector* dv) uint32_t address = dv->intValue; uint32_t value = dv->next->intValue; if (dv->segmentValue >= 0) { - debugger->d.p->core->rawWrite32(debugger->d.p->core, address, value, dv->segmentValue); + debugger->d.p->core->rawWrite32(debugger->d.p->core, address, dv->segmentValue, value); } else { debugger->d.p->core->busWrite32(debugger->d.p->core, address, value); } From c1421afccb0351c5f16a3b41167ae40db10e43fe Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 29 May 2023 00:11:31 -0700 Subject: [PATCH 231/290] Debugger: Support calling into multiple debuggers per watchpoint --- src/arm/debugger/memory-debugger.c | 38 ++++++++++++----------------- src/sm83/debugger/memory-debugger.c | 29 ++++++++++------------ 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/src/arm/debugger/memory-debugger.c b/src/arm/debugger/memory-debugger.c index 699e36c87..d8fe42eda 100644 --- a/src/arm/debugger/memory-debugger.c +++ b/src/arm/debugger/memory-debugger.c @@ -12,7 +12,7 @@ #include -static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct mDebuggerEntryInfo* info, enum mWatchpointType type, uint32_t newValue, int width); +static void _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, enum mWatchpointType type, uint32_t newValue, int width); #define FIND_DEBUGGER(DEBUGGER, CPU) \ do { \ @@ -39,10 +39,7 @@ static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, st static RETURN DebuggerShim_ ## NAME TYPES { \ struct ARMDebugger* debugger; \ FIND_DEBUGGER(debugger, cpu); \ - struct mDebuggerEntryInfo info; \ - if (_checkWatchpoints(debugger, address, &info, WATCHPOINT_READ, 0, WIDTH)) { \ - mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); \ - } \ + _checkWatchpoints(debugger, address, WATCHPOINT_READ, 0, WIDTH); \ return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \ } @@ -50,10 +47,7 @@ static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, st static RETURN DebuggerShim_ ## NAME TYPES { \ struct ARMDebugger* debugger; \ FIND_DEBUGGER(debugger, cpu); \ - struct mDebuggerEntryInfo info; \ - if (_checkWatchpoints(debugger, address, &info, WATCHPOINT_WRITE, value, WIDTH)) { \ - mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); \ - } \ + _checkWatchpoints(debugger, address, WATCHPOINT_WRITE, value, WIDTH); \ return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \ } @@ -73,10 +67,7 @@ static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, st } \ unsigned i; \ for (i = 0; i < popcount; ++i) { \ - struct mDebuggerEntryInfo info; \ - if (_checkWatchpoints(debugger, base + 4 * i, &info, ACCESS_TYPE, 0, 4)) { \ - mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); \ - } \ + _checkWatchpoints(debugger, base + 4 * i, ACCESS_TYPE, 0, 4); \ } \ return debugger->originalMemory.NAME(cpu, address, mask, direction, cycleCounter); \ } @@ -91,7 +82,7 @@ CREATE_MULTIPLE_WATCHPOINT_SHIM(loadMultiple, WATCHPOINT_READ) CREATE_MULTIPLE_WATCHPOINT_SHIM(storeMultiple, WATCHPOINT_WRITE) CREATE_SHIM(setActiveRegion, void, (struct ARMCore* cpu, uint32_t address), address) -static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct mDebuggerEntryInfo* info, enum mWatchpointType type, uint32_t newValue, int width) { +static void _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, enum mWatchpointType type, uint32_t newValue, int width) { struct mWatchpoint* watchpoint; size_t i; uint32_t minAddress = address & ~(width - 1); @@ -124,17 +115,18 @@ static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, st if ((watchpoint->type & WATCHPOINT_CHANGE) && newValue == oldValue) { continue; } - info->type.wp.oldValue = oldValue; - info->type.wp.newValue = newValue; - info->address = address; - info->type.wp.watchType = watchpoint->type; - info->type.wp.accessType = type; - info->pointId = watchpoint->id; - info->target = TableLookup(&debugger->d.p->pointOwner, watchpoint->id); - return true; + + struct mDebuggerEntryInfo info; + info.type.wp.oldValue = oldValue; + info.type.wp.newValue = newValue; + info.address = address; + info.type.wp.watchType = watchpoint->type; + info.type.wp.accessType = type; + info.pointId = watchpoint->id; + info.target = TableLookup(&debugger->d.p->pointOwner, watchpoint->id); + mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); } } - return false; } void ARMDebuggerInstallMemoryShim(struct ARMDebugger* debugger) { diff --git a/src/sm83/debugger/memory-debugger.c b/src/sm83/debugger/memory-debugger.c index 9022a3f07..23966c9a3 100644 --- a/src/sm83/debugger/memory-debugger.c +++ b/src/sm83/debugger/memory-debugger.c @@ -12,7 +12,7 @@ #include -static bool _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, struct mDebuggerEntryInfo* info, enum mWatchpointType type, uint8_t newValue); +static void _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, enum mWatchpointType type, uint8_t newValue); #define FIND_DEBUGGER(DEBUGGER, CPU) \ do { \ @@ -32,17 +32,14 @@ static bool _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, s static RETURN DebuggerShim_ ## NAME TYPES { \ struct SM83Debugger* debugger; \ FIND_DEBUGGER(debugger, cpu); \ - struct mDebuggerEntryInfo info; \ - if (_checkWatchpoints(debugger, address, &info, WATCHPOINT_ ## RW, VALUE)) { \ - mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); \ - } \ + _checkWatchpoints(debugger, address, WATCHPOINT_ ## RW, VALUE); \ return debugger->originalMemory.NAME(cpu, __VA_ARGS__); \ } CREATE_WATCHPOINT_SHIM(load8, READ, 0, uint8_t, (struct SM83Core* cpu, uint16_t address), address) CREATE_WATCHPOINT_SHIM(store8, WRITE, value, void, (struct SM83Core* cpu, uint16_t address, int8_t value), address, value) -static bool _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, struct mDebuggerEntryInfo* info, enum mWatchpointType type, uint8_t newValue) { +static void _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, enum mWatchpointType type, uint8_t newValue) { struct mWatchpoint* watchpoint; size_t i; for (i = 0; i < mWatchpointListSize(&debugger->watchpoints); ++i) { @@ -59,18 +56,18 @@ static bool _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, s if ((watchpoint->type & WATCHPOINT_CHANGE) && newValue == oldValue) { continue; } - info->type.wp.oldValue = oldValue; - info->type.wp.newValue = newValue; - info->address = address; - info->segment = debugger->originalMemory.currentSegment(debugger->cpu, address); - info->type.wp.watchType = watchpoint->type; - info->type.wp.accessType = type; - info->pointId = watchpoint->id; - info->target = TableLookup(&debugger->d.p->pointOwner, watchpoint->id); - return true; + struct mDebuggerEntryInfo info; + info.type.wp.oldValue = oldValue; + info.type.wp.newValue = newValue; + info.address = address; + info.segment = debugger->originalMemory.currentSegment(debugger->cpu, address); + info.type.wp.watchType = watchpoint->type; + info.type.wp.accessType = type; + info.pointId = watchpoint->id; + info.target = TableLookup(&debugger->d.p->pointOwner, watchpoint->id); + mDebuggerEnter(debugger->d.p, DEBUGGER_ENTER_WATCHPOINT, &info); } } - return false; } void SM83DebuggerInstallMemoryShim(struct SM83Debugger* debugger) { From 656122353666d4fd86808069dd7aba534858f5cd Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 29 May 2023 00:18:53 -0700 Subject: [PATCH 232/290] Scripting: Add debugger integration --- CHANGES | 1 + src/core/scripting.c | 299 +++++++++++++++++++++++++++ src/core/test/scripting.c | 422 +++++++++++++++++++++++++++++++++++++- src/script/stdlib.c | 9 + 4 files changed, 722 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 41e46b939..11f97dc2b 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Features: - Scripting: New `input` API for getting raw keyboard/mouse/controller state - Scripting: New `storage` API for saving data for a script, e.g. settings + - Scripting: Debugger integration to allow for breakpoints and watchpoints - New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81 - Debugger: Add range watchpoints Emulation fixes: diff --git a/src/core/scripting.c b/src/core/scripting.c index ba0549dae..a49ef4fe0 100644 --- a/src/core/scripting.c +++ b/src/core/scripting.c @@ -156,10 +156,39 @@ struct mScriptMemoryDomain { struct mCoreMemoryBlock block; }; +#ifdef USE_DEBUGGERS +struct mScriptBreakpointName { + uint32_t address; + uint32_t maxAddress; + int segment : 9; + int type : 1; + int subtype : 3; +}; + +struct mScriptBreakpoint { + ssize_t id; + struct mScriptBreakpointName name; + struct Table callbacks; +}; + +struct mScriptCoreAdapter; +struct mScriptDebugger { + struct mDebuggerModule d; + struct mScriptCoreAdapter* p; + struct Table breakpoints; + struct Table cbidMap; + struct Table bpidMap; + int64_t nextBreakpoint; +}; +#endif + struct mScriptCoreAdapter { struct mCore* core; struct mScriptContext* context; struct mScriptValue memory; +#ifdef USE_DEBUGGERS + struct mScriptDebugger debugger; +#endif }; struct mScriptConsole { @@ -659,9 +688,239 @@ static void _rebuildMemoryMap(struct mScriptContext* context, struct mScriptCore } } +#ifdef USE_DEBUGGERS +static void _freeBreakpoint(void* bp) { + struct mScriptBreakpoint* point = bp; + HashTableDeinit(&point->callbacks); + free(bp); +} + +static struct mScriptBreakpoint* _ensureBreakpoint(struct mScriptDebugger* debugger, struct mBreakpoint* breakpoint) { + struct mDebuggerModule* module = &debugger->d; + struct mScriptBreakpointName name = { + .address = breakpoint->address, + .maxAddress = 0, + .segment = breakpoint->segment, + .type = 0, + .subtype = breakpoint->type + }; + struct mScriptBreakpoint* point = HashTableLookupBinary(&debugger->breakpoints, &name, sizeof(name)); + if (point) { + return point; + } + point = calloc(1, sizeof(*point)); + point->id = module->p->platform->setBreakpoint(module->p->platform, module, breakpoint); + point->name = name; + HashTableInit(&point->callbacks, 0, (void (*)(void*)) mScriptValueDeref); + HashTableInsertBinary(&debugger->bpidMap, &point->id, sizeof(point->id), point); + HashTableInsertBinary(&debugger->breakpoints, &name, sizeof(name), point); + return point; +} + +static struct mScriptBreakpoint* _ensureWatchpoint(struct mScriptDebugger* debugger, struct mWatchpoint* watchpoint) { + struct mDebuggerModule* module = &debugger->d; + struct mScriptBreakpointName name = { + .address = watchpoint->minAddress, + .maxAddress = watchpoint->maxAddress, + .segment = watchpoint->segment, + .type = 1, + .subtype = watchpoint->type + }; + struct mScriptBreakpoint* point = HashTableLookupBinary(&debugger->breakpoints, &name, sizeof(name)); + if (point) { + return point; + } + point = calloc(1, sizeof(*point)); + point->id = module->p->platform->setWatchpoint(module->p->platform, module, watchpoint); + point->name = name; + HashTableInit(&point->callbacks, 0, (void (*)(void*)) mScriptValueDeref); + HashTableInsertBinary(&debugger->bpidMap, &point->id, sizeof(point->id), point); + HashTableInsertBinary(&debugger->breakpoints, &name, sizeof(name), point); + return point; +} + +static int64_t _addCallbackToBreakpoint(struct mScriptDebugger* debugger, struct mScriptBreakpoint* point, struct mScriptValue* callback) { + int64_t cbid = debugger->nextBreakpoint; + ++debugger->nextBreakpoint; + HashTableInsertBinary(&debugger->cbidMap, &cbid, sizeof(cbid), point); + mScriptValueRef(callback); + HashTableInsertBinary(&point->callbacks, &cbid, sizeof(cbid), callback); + return cbid; +} + +static void _runCallbacks(struct mScriptBreakpoint* point) { + struct TableIterator iter; + if (!HashTableIteratorStart(&point->callbacks, &iter)) { + return; + } + do { + struct mScriptValue* fn = HashTableIteratorGetValue(&point->callbacks, &iter); + struct mScriptFrame frame; + mScriptFrameInit(&frame); + mScriptInvoke(fn, &frame); + mScriptFrameDeinit(&frame); + } while (HashTableIteratorNext(&point->callbacks, &iter)); +} + +static void _scriptDebuggerInit(struct mDebuggerModule* debugger) { + struct mScriptDebugger* scriptDebugger = (struct mScriptDebugger*) debugger; + debugger->isPaused = false; + debugger->needsCallback = false; + + HashTableInit(&scriptDebugger->breakpoints, 0, _freeBreakpoint); + HashTableInit(&scriptDebugger->cbidMap, 0, NULL); + HashTableInit(&scriptDebugger->bpidMap, 0, NULL); +} + +static void _scriptDebuggerDeinit(struct mDebuggerModule* debugger) { + struct mScriptDebugger* scriptDebugger = (struct mScriptDebugger*) debugger; + HashTableDeinit(&scriptDebugger->cbidMap); + HashTableDeinit(&scriptDebugger->bpidMap); + HashTableDeinit(&scriptDebugger->breakpoints); +} + +static void _scriptDebuggerPaused(struct mDebuggerModule* debugger, int32_t timeoutMs) { + UNUSED(debugger); + UNUSED(timeoutMs); +} + +static void _scriptDebuggerUpdate(struct mDebuggerModule* debugger) { + UNUSED(debugger); +} + +static void _scriptDebuggerEntered(struct mDebuggerModule* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) { + struct mScriptDebugger* scriptDebugger = (struct mScriptDebugger*) debugger; + struct mScriptBreakpoint* point; + switch (reason) { + case DEBUGGER_ENTER_BREAKPOINT: + case DEBUGGER_ENTER_WATCHPOINT: + point = HashTableLookupBinary(&scriptDebugger->bpidMap, &info->pointId, sizeof(info->pointId)); + break; + default: + return; + } + _runCallbacks(point); + debugger->isPaused = false; +} + +static void _scriptDebuggerCustom(struct mDebuggerModule* debugger) { + UNUSED(debugger); +} + +static void _scriptDebuggerInterrupt(struct mDebuggerModule* debugger) { + UNUSED(debugger); +} + +static bool _setupDebugger(struct mScriptCoreAdapter* adapter) { + if (!adapter->core->debugger) { + return false; + } + + if (adapter->debugger.d.p) { + return true; + } + adapter->debugger.p = adapter; + adapter->debugger.d.type = DEBUGGER_CUSTOM; + adapter->debugger.d.init = _scriptDebuggerInit; + adapter->debugger.d.deinit = _scriptDebuggerDeinit; + adapter->debugger.d.paused = _scriptDebuggerPaused; + adapter->debugger.d.update = _scriptDebuggerUpdate; + adapter->debugger.d.entered = _scriptDebuggerEntered; + adapter->debugger.d.custom = _scriptDebuggerCustom; + adapter->debugger.d.interrupt = _scriptDebuggerInterrupt; + adapter->debugger.d.isPaused = false; + adapter->debugger.d.needsCallback = false; + adapter->debugger.nextBreakpoint = 1; + mDebuggerAttachModule(adapter->core->debugger, &adapter->debugger.d); + return true; +} + +static int64_t _mScriptCoreAdapterSetBreakpoint(struct mScriptCoreAdapter* adapter, struct mScriptValue* callback, uint32_t address, int32_t segment) { + if (!_setupDebugger(adapter)) { + return -1; + } + struct mBreakpoint breakpoint = { + .address = address, + .segment = segment, + .type = BREAKPOINT_HARDWARE + }; + + struct mDebuggerModule* module = &adapter->debugger.d; + if (!module->p->platform->setBreakpoint) { + return -1; + } + struct mScriptBreakpoint* point = _ensureBreakpoint(&adapter->debugger, &breakpoint); + return _addCallbackToBreakpoint(&adapter->debugger, point, callback); +} + +static int64_t _mScriptCoreAdapterSetWatchpoint(struct mScriptCoreAdapter* adapter, struct mScriptValue* callback, uint32_t address, int type, int32_t segment) { + if (!_setupDebugger(adapter)) { + return -1; + } + + struct mWatchpoint watchpoint = { + .minAddress = address, + .maxAddress = address + 1, + .segment = segment, + .type = type, + }; + struct mDebuggerModule* module = &adapter->debugger.d; + if (!module->p->platform->setWatchpoint) { + return -1; + } + struct mScriptBreakpoint* point = _ensureWatchpoint(&adapter->debugger, &watchpoint); + return _addCallbackToBreakpoint(&adapter->debugger, point, callback); +} + +static int64_t _mScriptCoreAdapterSetRangeWatchpoint(struct mScriptCoreAdapter* adapter, struct mScriptValue* callback, uint32_t minAddress, uint32_t maxAddress, int type, int32_t segment) { + if (!_setupDebugger(adapter)) { + return -1; + } + + struct mWatchpoint watchpoint = { + .minAddress = minAddress, + .maxAddress = maxAddress, + .segment = segment, + .type = type, + }; + struct mDebuggerModule* module = &adapter->debugger.d; + if (!module->p->platform->setWatchpoint) { + return -1; + } + struct mScriptBreakpoint* point = _ensureWatchpoint(&adapter->debugger, &watchpoint); + return _addCallbackToBreakpoint(&adapter->debugger, point, callback); +} + +static bool _mScriptCoreAdapterClearBreakpoint(struct mScriptCoreAdapter* adapter, int64_t cbid) { + if (!_setupDebugger(adapter)) { + return false; + } + struct mScriptBreakpoint* point = HashTableLookupBinary(&adapter->debugger.cbidMap, &cbid, sizeof(cbid)); + if (!point) { + return false; + } + HashTableRemoveBinary(&adapter->debugger.cbidMap, &cbid, sizeof(cbid)); + HashTableRemoveBinary(&point->callbacks, &cbid, sizeof(cbid)); + + if (!HashTableSize(&point->callbacks)) { + struct mDebuggerModule* module = &adapter->debugger.d; + module->p->platform->clearBreakpoint(module->p->platform, point->id); + + struct mScriptBreakpointName name = point->name; + HashTableRemoveBinary(&adapter->debugger.breakpoints, &name, sizeof(name)); + } + return true; +} +#endif + static void _mScriptCoreAdapterDeinit(struct mScriptCoreAdapter* adapter) { _clearMemoryMap(adapter->context, adapter, false); adapter->memory.type->free(&adapter->memory); +#ifdef USE_DEBUGGERS + if (adapter->core->debugger) { + mDebuggerDetachModule(adapter->core->debugger, &adapter->debugger.d); + } +#endif } static struct mScriptValue* _mScriptCoreAdapterGet(struct mScriptCoreAdapter* adapter, const char* name) { @@ -686,6 +945,33 @@ mSCRIPT_DECLARE_STRUCT(mScriptCoreAdapter); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCoreAdapter, W(mCore), _get, _mScriptCoreAdapterGet, 1, CHARP, name); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCoreAdapter, _deinit, _mScriptCoreAdapterDeinit, 0); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCoreAdapter, reset, _mScriptCoreAdapterReset, 0); +#ifdef USE_DEBUGGERS +mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptCoreAdapter, S64, setBreakpoint, _mScriptCoreAdapterSetBreakpoint, 3, WRAPPER, callback, U32, address, S32, segment); +mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptCoreAdapter, S64, setWatchpoint, _mScriptCoreAdapterSetWatchpoint, 4, WRAPPER, callback, U32, address, S32, type, S32, segment); +mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptCoreAdapter, S64, setRangeWatchpoint, _mScriptCoreAdapterSetRangeWatchpoint, 5, WRAPPER, callback, U32, minAddress, U32, maxAddress, S32, type, S32, segment); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCoreAdapter, BOOL, clearBreakpoint, _mScriptCoreAdapterClearBreakpoint, 1, S64, cbid); +#endif + +mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptCoreAdapter, setBreakpoint) + mSCRIPT_NO_DEFAULT, + mSCRIPT_NO_DEFAULT, + mSCRIPT_S32(-1) +mSCRIPT_DEFINE_DEFAULTS_END; + +mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptCoreAdapter, setWatchpoint) + mSCRIPT_NO_DEFAULT, + mSCRIPT_NO_DEFAULT, + mSCRIPT_NO_DEFAULT, + mSCRIPT_S32(-1) +mSCRIPT_DEFINE_DEFAULTS_END; + +mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptCoreAdapter, setRangeWatchpoint) + mSCRIPT_NO_DEFAULT, + mSCRIPT_NO_DEFAULT, + mSCRIPT_NO_DEFAULT, + mSCRIPT_NO_DEFAULT, + mSCRIPT_S32(-1) +mSCRIPT_DEFINE_DEFAULTS_END; mSCRIPT_DEFINE_STRUCT(mScriptCoreAdapter) mSCRIPT_DEFINE_CLASS_DOCSTRING( @@ -700,6 +986,19 @@ mSCRIPT_DEFINE_STRUCT(mScriptCoreAdapter) mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(mScriptCoreAdapter) mSCRIPT_DEFINE_DOCSTRING("Reset the emulation. As opposed to struct::mCore.reset, this version calls the **reset** callback") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, reset) +#ifdef USE_DEBUGGERS + mSCRIPT_DEFINE_DOCSTRING("Set a breakpoint at a given address") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, setBreakpoint) + mSCRIPT_DEFINE_DOCSTRING("Clear a breakpoint or watchpoint for a given id returned by a previous call") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, clearBreakpoint) + mSCRIPT_DEFINE_DOCSTRING("Set a watchpoint at a given address of a given type") + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, setWatchpoint) + mSCRIPT_DEFINE_DOCSTRING( + "Set a watchpoint in a given range of a given type. Note that the range is exclusive on the end, " + "as though you've added the size, i.e. a 4-byte watch would specify the maximum as the minimum address + 4" + ) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, setRangeWatchpoint) +#endif mSCRIPT_DEFINE_STRUCT_CAST_TO_MEMBER(mScriptCoreAdapter, S(mCore), _core) mSCRIPT_DEFINE_STRUCT_CAST_TO_MEMBER(mScriptCoreAdapter, CS(mCore), _core) mSCRIPT_DEFINE_END; diff --git a/src/core/test/scripting.c b/src/core/test/scripting.c index 4ed2c2abd..c2593bf14 100644 --- a/src/core/test/scripting.c +++ b/src/core/test/scripting.c @@ -9,8 +9,7 @@ #include #include #include -#include -#include +#include #include "script/test.h" @@ -143,8 +142,8 @@ M_TEST_DEFINE(globals) { LOAD_PROGRAM("assert(emu)"); assert_true(lua->run(lua)); - TEARDOWN_CORE; mScriptContextDeinit(&context); + TEARDOWN_CORE; } M_TEST_DEFINE(infoFuncs) { @@ -161,8 +160,8 @@ M_TEST_DEFINE(infoFuncs) { TEST_VALUE(S32, "frequency", core->frequency(core)); TEST_VALUE(S32, "frameCycles", core->frameCycles(core)); - TEARDOWN_CORE; mScriptContextDeinit(&context); + TEARDOWN_CORE; } M_TEST_DEFINE(detach) { @@ -195,8 +194,8 @@ M_TEST_DEFINE(detach) { ); assert_false(lua->run(lua)); - TEARDOWN_CORE; mScriptContextDeinit(&context); + TEARDOWN_CORE; } M_TEST_DEFINE(runFrame) { @@ -215,8 +214,8 @@ M_TEST_DEFINE(runFrame) { TEST_VALUE(S32, "frame", i); } - TEARDOWN_CORE; mScriptContextDeinit(&context); + TEARDOWN_CORE; } M_TEST_DEFINE(memoryRead) { @@ -250,8 +249,8 @@ M_TEST_DEFINE(memoryRead) { TEST_VALUE(S32, "b16", 0x0807); TEST_VALUE(S32, "a32", 0x0C0B0A09); - TEARDOWN_CORE; mScriptContextDeinit(&context); + TEARDOWN_CORE; } M_TEST_DEFINE(memoryWrite) { @@ -278,8 +277,8 @@ M_TEST_DEFINE(memoryWrite) { assert_int_equal(core->busRead8(core, RAM_BASE + i), i + 1); } - TEARDOWN_CORE; mScriptContextDeinit(&context); + TEARDOWN_CORE; } M_TEST_DEFINE(logging) { @@ -323,11 +322,402 @@ M_TEST_DEFINE(screenshot) { TEST_PROGRAM("assert(im.width >= 160)"); TEST_PROGRAM("assert(im.height >= 144)"); - TEARDOWN_CORE; free(buffer); mScriptContextDeinit(&context); + TEARDOWN_CORE; } +#ifdef USE_DEBUGGERS +void _setupBp(struct mCore* core) { + switch (core->platform(core)) { +#ifdef M_CORE_GBA + case mPLATFORM_GBA: + core->busWrite32(core, 0x020000C0, 0xE0000000); // nop + core->busWrite32(core, 0x020000C4, 0xE0000000); // nop + core->busWrite32(core, 0x020000C8, 0xEAFFFFFD); // b 0x020000C4 + break; +#endif +#ifdef M_CORE_GB + case mPLATFORM_GB: + core->rawWrite8(core, 0x101, 0, 0xEE); // Jump to 0xF0 + core->rawWrite8(core, 0xF0, 0, 0x00); // nop + core->rawWrite8(core, 0xF1, 0, 0x18); // Loop forecer + core->rawWrite8(core, 0xF2, 0, 0xFD); // jr $-3 + break; +#endif + } +} + +#ifdef M_CORE_GBA +M_TEST_DEFINE(basicBreakpointGBA) { + SETUP_LUA; + struct mCore* core = mCoreCreate(mPLATFORM_GBA); + struct mDebugger debugger; + assert_non_null(core); + assert_true(core->init(core)); + mCoreInitConfig(core, NULL); + core->reset(core); + _setupBp(core); + mScriptContextAttachCore(&context, core); + + mDebuggerInit(&debugger); + mDebuggerAttach(&debugger, core); + + TEST_PROGRAM( + "hit = 0\n" + "function bkpt()\n" + " hit = hit + 1\n" + "end" + ); + TEST_PROGRAM("cbid = emu:setBreakpoint(bkpt, 0x020000C4)"); + TEST_PROGRAM("assert(cbid == 1)"); + + int i; + for (i = 0; i < 20; ++i) { + mDebuggerRun(&debugger); + } + + assert_int_equal(debugger.state, DEBUGGER_RUNNING); + TEST_PROGRAM("assert(hit >= 1)"); + + mScriptContextDeinit(&context); + TEARDOWN_CORE; + mDebuggerDeinit(&debugger); +} +#endif + +#ifdef M_CORE_GB +M_TEST_DEFINE(basicBreakpointGB) { + SETUP_LUA; + struct mCore* core = mCoreCreate(mPLATFORM_GB); + struct mDebugger debugger; + assert_non_null(core); + assert_true(core->init(core)); + mCoreInitConfig(core, NULL); + assert_true(core->loadROM(core, VFileFromConstMemory(_fakeGBROM, sizeof(_fakeGBROM)))); + core->reset(core); + _setupBp(core); + mScriptContextAttachCore(&context, core); + + mDebuggerInit(&debugger); + mDebuggerAttach(&debugger, core); + + TEST_PROGRAM( + "hit = 0\n" + "function bkpt()\n" + " hit = hit + 1\n" + "end" + ); + TEST_PROGRAM("cbid = emu:setBreakpoint(bkpt, 0xF0)"); + TEST_PROGRAM("assert(cbid == 1)"); + + int i; + for (i = 0; i < 20; ++i) { + mDebuggerRun(&debugger); + } + + assert_int_equal(debugger.state, DEBUGGER_RUNNING); + TEST_PROGRAM("assert(hit >= 1)"); + + mScriptContextDeinit(&context); + TEARDOWN_CORE; + mDebuggerDeinit(&debugger); +} +#endif + +M_TEST_DEFINE(multipleBreakpoint) { + SETUP_LUA; + struct mCore* core = mCoreCreate(TEST_PLATFORM); + struct mDebugger debugger; + assert_non_null(core); + assert_true(core->init(core)); + mCoreInitConfig(core, NULL); + core->reset(core); + _setupBp(core); + mScriptContextAttachCore(&context, core); + + mDebuggerInit(&debugger); + mDebuggerAttach(&debugger, core); + + TEST_PROGRAM( + "hit = 0\n" + "function bkpt1()\n" + " hit = hit + 1\n" + "end\n" + "function bkpt2()\n" + " hit = hit + 100\n" + "end" + ); +#ifdef M_CORE_GBA + TEST_PROGRAM("cbid1 = emu:setBreakpoint(bkpt1, 0x020000C4)"); + TEST_PROGRAM("cbid2 = emu:setBreakpoint(bkpt2, 0x020000C8)"); +#else + TEST_PROGRAM("cbid1 = emu:setBreakpoint(bkpt1, 0xF0)"); + TEST_PROGRAM("cbid2 = emu:setBreakpoint(bkpt2, 0xF1)"); +#endif + TEST_PROGRAM("assert(cbid1 == 1)"); + TEST_PROGRAM("assert(cbid2 == 2)"); + + int i; + for (i = 0; i < 20; ++i) { + mDebuggerRun(&debugger); + } + + assert_int_equal(debugger.state, DEBUGGER_RUNNING); + TEST_PROGRAM("assert(hit >= 101)"); + + mScriptContextDeinit(&context); + TEARDOWN_CORE; + mDebuggerDeinit(&debugger); +} + +M_TEST_DEFINE(basicWatchpoint) { + SETUP_LUA; + mScriptContextAttachStdlib(&context); + CREATE_CORE; + struct mDebugger debugger; + core->reset(core); + mScriptContextAttachCore(&context, core); + + mDebuggerInit(&debugger); + mDebuggerAttach(&debugger, core); + + TEST_PROGRAM( + "hit = 0\n" + "function bkpt()\n" + " hit = hit + 1\n" + "end" + ); + struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE); + lua->setGlobal(lua, "base", &base); + TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.READ))"); + TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base + 1, C.WATCHPOINT_TYPE.WRITE))"); + TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base + 2, C.WATCHPOINT_TYPE.RW))"); + TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base + 3, C.WATCHPOINT_TYPE.WRITE_CHANGE))"); + TEST_PROGRAM("assert(hit == 0)"); + + uint8_t value; + + // Read + TEST_PROGRAM("hit = 0"); + value = core->rawRead8(core, RAM_BASE, -1); + TEST_PROGRAM("assert(hit == 0)"); + core->busRead8(core, RAM_BASE); + TEST_PROGRAM("assert(hit == 1)"); + core->busWrite8(core, RAM_BASE, value); + TEST_PROGRAM("assert(hit == 1)"); + core->busWrite8(core, RAM_BASE, ~value); + TEST_PROGRAM("assert(hit == 1)"); + + // Write + TEST_PROGRAM("hit = 0"); + value = core->rawRead8(core, RAM_BASE + 1, -1); + TEST_PROGRAM("assert(hit == 0)"); + core->busRead8(core, RAM_BASE + 1); + TEST_PROGRAM("assert(hit == 0)"); + core->busWrite8(core, RAM_BASE + 1, value); + TEST_PROGRAM("assert(hit == 1)"); + core->busWrite8(core, RAM_BASE + 1, ~value); + TEST_PROGRAM("assert(hit == 2)"); + + // RW + TEST_PROGRAM("hit = 0"); + value = core->rawRead8(core, RAM_BASE + 2, -1); + TEST_PROGRAM("assert(hit == 0)"); + core->busRead8(core, RAM_BASE + 2); + TEST_PROGRAM("assert(hit == 1)"); + core->busWrite8(core, RAM_BASE + 2, value); + TEST_PROGRAM("assert(hit == 2)"); + core->busWrite8(core, RAM_BASE + 2, ~value); + TEST_PROGRAM("assert(hit == 3)"); + + // Change + TEST_PROGRAM("hit = 0"); + value = core->rawRead8(core, RAM_BASE + 3, -1); + TEST_PROGRAM("assert(hit == 0)"); + core->busRead8(core, RAM_BASE + 3); + TEST_PROGRAM("assert(hit == 0)"); + core->busWrite8(core, RAM_BASE + 3, value); + TEST_PROGRAM("assert(hit == 0)"); + core->busWrite8(core, RAM_BASE + 3, ~value); + TEST_PROGRAM("assert(hit == 1)"); + + mScriptContextDeinit(&context); + TEARDOWN_CORE; + mDebuggerDeinit(&debugger); +} + +M_TEST_DEFINE(removeBreakpoint) { + SETUP_LUA; + mScriptContextAttachStdlib(&context); + CREATE_CORE; + struct mDebugger debugger; + core->reset(core); + mScriptContextAttachCore(&context, core); + + mDebuggerInit(&debugger); + mDebuggerAttach(&debugger, core); + + TEST_PROGRAM( + "hit = 0\n" + "function bkpt()\n" + " hit = hit + 1\n" + "end" + ); + struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE); + lua->setGlobal(lua, "base", &base); + TEST_PROGRAM("cbid = emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.READ)"); + + TEST_PROGRAM("assert(hit == 0)"); + core->busRead8(core, RAM_BASE); + TEST_PROGRAM("assert(hit == 1)"); + core->busRead8(core, RAM_BASE); + TEST_PROGRAM("assert(hit == 2)"); + TEST_PROGRAM("assert(emu:clearBreakpoint(cbid))"); + core->busRead8(core, RAM_BASE); + TEST_PROGRAM("assert(hit == 2)"); + + mScriptContextDeinit(&context); + TEARDOWN_CORE; + mDebuggerDeinit(&debugger); +} + + +M_TEST_DEFINE(overlappingBreakpoint) { + SETUP_LUA; + struct mCore* core = mCoreCreate(TEST_PLATFORM); + struct mDebugger debugger; + assert_non_null(core); + assert_true(core->init(core)); + mCoreInitConfig(core, NULL); + core->reset(core); + _setupBp(core); + mScriptContextAttachCore(&context, core); + + mDebuggerInit(&debugger); + mDebuggerAttach(&debugger, core); + + TEST_PROGRAM( + "hit = 0\n" + "function bkpt1()\n" + " hit = hit + 1\n" + "end\n" + "function bkpt2()\n" + " hit = hit + 100\n" + "end" + ); +#ifdef M_CORE_GBA + TEST_PROGRAM("cbid1 = emu:setBreakpoint(bkpt1, 0x020000C4)"); + TEST_PROGRAM("cbid2 = emu:setBreakpoint(bkpt2, 0x020000C4)"); +#else + TEST_PROGRAM("cbid1 = emu:setBreakpoint(bkpt1, 0xF0)"); + TEST_PROGRAM("cbid2 = emu:setBreakpoint(bkpt2, 0xF0)"); +#endif + TEST_PROGRAM("assert(cbid1 == 1)"); + TEST_PROGRAM("assert(cbid2 == 2)"); + + int i; + for (i = 0; i < 20; ++i) { + mDebuggerRun(&debugger); + } + + assert_int_equal(debugger.state, DEBUGGER_RUNNING); + TEST_PROGRAM("assert(hit >= 101)"); + TEST_PROGRAM("oldHit = hit"); + + TEST_PROGRAM("assert(emu:clearBreakpoint(cbid2))"); + + for (i = 0; i < 10; ++i) { + mDebuggerRun(&debugger); + } + TEST_PROGRAM("assert(hit - oldHit > 0)"); + TEST_PROGRAM("assert(hit - oldHit < 100)"); + + mScriptContextDeinit(&context); + TEARDOWN_CORE; + mDebuggerDeinit(&debugger); +} + +M_TEST_DEFINE(overlappingWatchpoint) { + SETUP_LUA; + mScriptContextAttachStdlib(&context); + CREATE_CORE; + struct mDebugger debugger; + core->reset(core); + mScriptContextAttachCore(&context, core); + + mDebuggerInit(&debugger); + mDebuggerAttach(&debugger, core); + + TEST_PROGRAM( + "hit = 0\n" + "function bkpt()\n" + " hit = hit + 1\n" + "end" + ); + struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE); + lua->setGlobal(lua, "base", &base); + TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.READ))"); + TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.WRITE))"); + TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.RW))"); + TEST_PROGRAM("assert(0 < emu:setWatchpoint(bkpt, base, C.WATCHPOINT_TYPE.WRITE_CHANGE))"); + TEST_PROGRAM("assert(hit == 0)"); + + uint8_t value; + + // Read + TEST_PROGRAM("hit = 0"); + value = core->rawRead8(core, RAM_BASE, -1); + TEST_PROGRAM("assert(hit == 0)"); + core->busRead8(core, RAM_BASE); + TEST_PROGRAM("assert(hit == 2)"); // Read, RW + core->busWrite8(core, RAM_BASE, value); + TEST_PROGRAM("assert(hit == 4)"); // Write, RW + core->busWrite8(core, RAM_BASE, ~value); + TEST_PROGRAM("assert(hit == 7)"); // Write, RW, change + + mScriptContextDeinit(&context); + TEARDOWN_CORE; + mDebuggerDeinit(&debugger); +} + +M_TEST_DEFINE(rangeWatchpoint) { + SETUP_LUA; + mScriptContextAttachStdlib(&context); + CREATE_CORE; + struct mDebugger debugger; + core->reset(core); + mScriptContextAttachCore(&context, core); + + mDebuggerInit(&debugger); + mDebuggerAttach(&debugger, core); + + TEST_PROGRAM( + "hit = 0\n" + "function bkpt()\n" + " hit = hit + 1\n" + "end" + ); + struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE); + lua->setGlobal(lua, "base", &base); + TEST_PROGRAM("assert(0 < emu:setRangeWatchpoint(bkpt, base, base + 2, C.WATCHPOINT_TYPE.READ))"); + TEST_PROGRAM("assert(0 < emu:setRangeWatchpoint(bkpt, base + 1, base + 3, C.WATCHPOINT_TYPE.READ))"); + + // Read + TEST_PROGRAM("assert(hit == 0)"); + core->busRead8(core, RAM_BASE); + TEST_PROGRAM("assert(hit == 1)"); + core->busRead8(core, RAM_BASE + 1); + TEST_PROGRAM("assert(hit == 3)"); + core->busRead8(core, RAM_BASE + 2); + TEST_PROGRAM("assert(hit == 4)"); + + mScriptContextDeinit(&context); + TEARDOWN_CORE; + mDebuggerDeinit(&debugger); +} +#endif + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore, cmocka_unit_test(globals), cmocka_unit_test(infoFuncs), @@ -337,4 +727,18 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore, cmocka_unit_test(memoryWrite), cmocka_unit_test(logging), cmocka_unit_test(screenshot), +#ifdef USE_DEBUGGERS +#ifdef M_CORE_GBA + cmocka_unit_test(basicBreakpointGBA), +#endif +#ifdef M_CORE_GB + cmocka_unit_test(basicBreakpointGB), +#endif + cmocka_unit_test(multipleBreakpoint), + cmocka_unit_test(basicWatchpoint), + cmocka_unit_test(removeBreakpoint), + cmocka_unit_test(overlappingBreakpoint), + cmocka_unit_test(overlappingWatchpoint), + cmocka_unit_test(rangeWatchpoint), +#endif ) diff --git a/src/script/stdlib.c b/src/script/stdlib.c index 8a64ac0c1..c6070dcb6 100644 --- a/src/script/stdlib.c +++ b/src/script/stdlib.c @@ -158,6 +158,15 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) { mSCRIPT_CONSTANT_PAIR(GB_KEY, DOWN), mSCRIPT_KV_SENTINEL }); +#endif +#ifdef USE_DEBUGGERS + mScriptContextExportConstants(context, "WATCHPOINT_TYPE", (struct mScriptKVPair[]) { + mSCRIPT_CONSTANT_PAIR(WATCHPOINT, WRITE), + mSCRIPT_CONSTANT_PAIR(WATCHPOINT, READ), + mSCRIPT_CONSTANT_PAIR(WATCHPOINT, RW), + mSCRIPT_CONSTANT_PAIR(WATCHPOINT, WRITE_CHANGE), + mSCRIPT_KV_SENTINEL + }); #endif mScriptContextSetGlobal(context, "C", context->constants); mScriptContextSetDocstring(context, "C", "A table containing the [exported constants](#constants)"); From 294470d9402762ae13d0eb9c7682d72b10271e38 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 1 Jun 2023 00:05:41 -0700 Subject: [PATCH 233/290] Core: Add getPeripheral function --- include/mgba/core/core.h | 1 + src/gb/core.c | 15 +++++++++++++++ src/gba/core.c | 15 +++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/include/mgba/core/core.h b/include/mgba/core/core.h index 7a6e89f56..e2a804258 100644 --- a/include/mgba/core/core.h +++ b/include/mgba/core/core.h @@ -120,6 +120,7 @@ struct mCore { void (*getGameCode)(const struct mCore*, char* title); void (*setPeripheral)(struct mCore*, int type, void*); + void* (*getPeripheral)(struct mCore*, int type); uint32_t (*busRead8)(struct mCore*, uint32_t address); uint32_t (*busRead16)(struct mCore*, uint32_t address); diff --git a/src/gb/core.c b/src/gb/core.c index 68c2d2985..96f9142f5 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -795,6 +795,20 @@ static void _GBCoreSetPeripheral(struct mCore* core, int type, void* periph) { } } +static void* _GBCoreGetPeripheral(struct mCore* core, int type) { + struct GB* gb = core->board; + switch (type) { + case mPERIPH_ROTATION: + return gb->memory.rotation; + case mPERIPH_RUMBLE: + return gb->memory.rumble; + case mPERIPH_IMAGE_SOURCE: + return gb->memory.cam; + default: + return NULL; + } +} + static uint32_t _GBCoreBusRead8(struct mCore* core, uint32_t address) { struct SM83Core* cpu = core->cpu; return cpu->memory.load8(cpu, address); @@ -1306,6 +1320,7 @@ struct mCore* GBCoreCreate(void) { core->getGameTitle = _GBCoreGetGameTitle; core->getGameCode = _GBCoreGetGameCode; core->setPeripheral = _GBCoreSetPeripheral; + core->getPeripheral = _GBCoreGetPeripheral; core->busRead8 = _GBCoreBusRead8; core->busRead16 = _GBCoreBusRead16; core->busRead32 = _GBCoreBusRead32; diff --git a/src/gba/core.c b/src/gba/core.c index 7c1e19d21..a53cc08d1 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -844,6 +844,20 @@ static void _GBACoreSetPeripheral(struct mCore* core, int type, void* periph) { } } +static void* _GBACoreGetPeripheral(struct mCore* core, int type) { + struct GBA* gba = core->board; + switch (type) { + case mPERIPH_ROTATION: + return gba->rotationSource; + case mPERIPH_RUMBLE: + return gba->rumble; + case mPERIPH_GBA_LUMINANCE: + return gba->luminanceSource; + default: + return NULL; + } +} + static uint32_t _GBACoreBusRead8(struct mCore* core, uint32_t address) { struct ARMCore* cpu = core->cpu; return cpu->memory.load8(cpu, address, 0); @@ -1431,6 +1445,7 @@ struct mCore* GBACoreCreate(void) { core->getGameTitle = _GBACoreGetGameTitle; core->getGameCode = _GBACoreGetGameCode; core->setPeripheral = _GBACoreSetPeripheral; + core->getPeripheral = _GBACoreGetPeripheral; core->busRead8 = _GBACoreBusRead8; core->busRead16 = _GBACoreBusRead16; core->busRead32 = _GBACoreBusRead32; From e7bbc60e7926edfa0a451cf486c62cc450f502e9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 1 Jun 2023 00:12:32 -0700 Subject: [PATCH 234/290] All: Fix several warnings --- include/mgba/internal/gb/memory.h | 2 +- src/gb/mbc.c | 2 +- src/gba/cart/ereader.c | 2 +- src/platform/qt/Window.cpp | 2 +- src/util/test/vfs.c | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/mgba/internal/gb/memory.h b/include/mgba/internal/gb/memory.h index d3a42a229..052a9f77e 100644 --- a/include/mgba/internal/gb/memory.h +++ b/include/mgba/internal/gb/memory.h @@ -292,7 +292,7 @@ struct GBMemory { int currentSramBank1; uint8_t* sramBank1; - unsigned cartBusDecay; + int cartBusDecay; uint16_t cartBusPc; uint8_t cartBus; diff --git a/src/gb/mbc.c b/src/gb/mbc.c index 27ccfe784..76c005f96 100644 --- a/src/gb/mbc.c +++ b/src/gb/mbc.c @@ -219,7 +219,7 @@ static enum GBMemoryBankControllerType _detectUnlMBC(const uint8_t* mem, size_t if (cart->type == 0x01) { // Make sure we're not using a "fixed" version return GB_UNL_LI_CHENG; } - if ((0x8000 << cart->romSize) != size) { + if ((0x8000U << cart->romSize) != size) { return GB_UNL_LI_CHENG; } break; diff --git a/src/gba/cart/ereader.c b/src/gba/cart/ereader.c index 9da25196c..6a3ce58b0 100644 --- a/src/gba/cart/ereader.c +++ b/src/gba/cart/ereader.c @@ -1499,7 +1499,7 @@ bool EReaderScanCard(struct EReaderScan* scan) { size_t i; for (i = 0; i < blocks; ++i) { EReaderScanDetectBlockThreshold(scan, i); - int errors = 36 * 36; + unsigned errors = 36 * 36; while (!EReaderScanScanBlock(scan, i, true)) { if (errors < EReaderBlockListGetPointer(&scan->blocks, i)->errors) { return false; diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index dfad74ead..0a2ebb73b 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1790,7 +1790,7 @@ void Window::setupMenu(QMenuBar* menubar) { void Window::setupOptions() { ConfigOption* videoSync = m_config->addOption("videoSync"); - videoSync->connect([this](const QVariant& variant) { + videoSync->connect([this](const QVariant&) { reloadConfig(); }, this); diff --git a/src/util/test/vfs.c b/src/util/test/vfs.c index cc623d1b0..4fd995275 100644 --- a/src/util/test/vfs.c +++ b/src/util/test/vfs.c @@ -84,7 +84,7 @@ M_TEST_DEFINE(resizeMem) { } M_TEST_DEFINE(resizeConstMem) { - uint8_t bytes[32]; + uint8_t bytes[32] = {0}; struct VFile* vf = VFileFromConstMemory(bytes, 32); assert_non_null(vf); assert_int_equal(vf->size(vf), 32); @@ -96,7 +96,7 @@ M_TEST_DEFINE(resizeConstMem) { } M_TEST_DEFINE(resizeMemChunk) { - uint8_t bytes[32]; + uint8_t bytes[32] = {0}; struct VFile* vf = VFileMemChunk(bytes, 32); assert_non_null(vf); assert_int_equal(vf->size(vf), 32); From 17a549baf2c8100f2c7e7c244996d9ac85d23198 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 4 Jun 2023 20:20:09 -0700 Subject: [PATCH 235/290] Scripting: Fix build against Lua 5.2 --- src/script/engines/lua.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/script/engines/lua.c b/src/script/engines/lua.c index ae749b711..9a5278846 100644 --- a/src/script/engines/lua.c +++ b/src/script/engines/lua.c @@ -436,6 +436,7 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS lua_getfield(luaContext->lua, -1, "ERRORS"); for (i = 0; i < _mScriptSocketNumErrors; i++) { const struct _mScriptSocketError* err = &_mScriptSocketErrors[i]; + lua_pushinteger(luaContext->lua, err->err); if (err->message) { lua_pushstring(luaContext->lua, err->message); struct mScriptValue* key = mScriptValueAlloc(mSCRIPT_TYPE_MS_S32); @@ -447,7 +448,7 @@ struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mS } else { lua_pushnil(luaContext->lua); } - lua_seti(luaContext->lua, -2, err->err); + lua_settable(luaContext->lua, -3); } lua_pop(luaContext->lua, 2); From 20ab4d27b18bccc4d99e0cf54016144e63af2630 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 4 Jun 2023 22:22:31 -0700 Subject: [PATCH 236/290] Scripting: Expose rumble callback --- include/mgba-util/common.h | 4 ++++ src/core/scripting.c | 23 +++++++++++++++++++++++ src/script/stdlib.c | 1 + 3 files changed, 28 insertions(+) diff --git a/include/mgba-util/common.h b/include/mgba-util/common.h index ae86c8b3c..0fc10be58 100644 --- a/include/mgba-util/common.h +++ b/include/mgba-util/common.h @@ -44,6 +44,10 @@ CXX_GUARD_START #define restrict __restrict #endif +#ifndef containerof +#define containerof(PTR, TYPE, MEMBER) ((TYPE*) ((uintptr_t) (PTR) - offsetof(TYPE, MEMBER))) +#endif + #ifdef _MSC_VER #include #include diff --git a/src/core/scripting.c b/src/core/scripting.c index a49ef4fe0..0b3150039 100644 --- a/src/core/scripting.c +++ b/src/core/scripting.c @@ -7,6 +7,9 @@ #include #include +#ifdef M_CORE_GBA +#include +#endif #include #include #include @@ -189,6 +192,8 @@ struct mScriptCoreAdapter { #ifdef USE_DEBUGGERS struct mScriptDebugger debugger; #endif + struct mRumble rumble; + struct mRumble* oldRumble; }; struct mScriptConsole { @@ -1003,6 +1008,20 @@ mSCRIPT_DEFINE_STRUCT(mScriptCoreAdapter) mSCRIPT_DEFINE_STRUCT_CAST_TO_MEMBER(mScriptCoreAdapter, CS(mCore), _core) mSCRIPT_DEFINE_END; +static void _setRumble(struct mRumble* rumble, int enable) { + struct mScriptCoreAdapter* adapter = containerof(rumble, struct mScriptCoreAdapter, rumble); + + if (adapter->oldRumble) { + adapter->oldRumble->setRumble(adapter->oldRumble, enable); + } + + struct mScriptList args; + mScriptListInit(&args, 1); + *mScriptListAppend(&args) = mSCRIPT_MAKE_BOOL(!!enable); + mScriptContextTriggerCallback(adapter->context, "rumble", &args); + mScriptListDeinit(&args); +} + void mScriptContextAttachCore(struct mScriptContext* context, struct mCore* core) { struct mScriptValue* coreValue = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptCoreAdapter)); struct mScriptCoreAdapter* adapter = calloc(1, sizeof(*adapter)); @@ -1014,6 +1033,10 @@ void mScriptContextAttachCore(struct mScriptContext* context, struct mCore* core adapter->memory.type = mSCRIPT_TYPE_MS_TABLE; adapter->memory.type->alloc(&adapter->memory); + adapter->rumble.setRumble = _setRumble; + adapter->oldRumble = core->getPeripheral(core, mPERIPH_RUMBLE); + core->setPeripheral(core, mPERIPH_RUMBLE, &adapter->rumble); + _rebuildMemoryMap(context, adapter); coreValue->value.opaque = adapter; diff --git a/src/script/stdlib.c b/src/script/stdlib.c index c6070dcb6..ae4cbf54f 100644 --- a/src/script/stdlib.c +++ b/src/script/stdlib.c @@ -86,6 +86,7 @@ mSCRIPT_DEFINE_STRUCT(mScriptCallbackManager) "- **frame**: The emulation finished a frame\n" "- **keysRead**: The emulation is about to read the key input\n" "- **reset**: The emulation has been reset\n" + "- **rumble**: The state of the rumble motor was changed. This callback is passed a single argument that specifies if it was turned on (true) or off (false)\n" "- **savedataUpdated**: The emulation has just finished modifying save data\n" "- **sleep**: The emulation has used the sleep feature to enter a low-power mode\n" "- **shutdown**: The emulation has been powered off\n" From 58da738647086853c304198e91b65fe424578b76 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 13 Jun 2023 16:07:28 -0700 Subject: [PATCH 237/290] Qt: Reduce minimum size of GB palette color pickers --- src/platform/qt/SettingsView.ui | 50 ++++++++++++++++----------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/platform/qt/SettingsView.ui b/src/platform/qt/SettingsView.ui index 85094fcd4..42e4af5a0 100644 --- a/src/platform/qt/SettingsView.ui +++ b/src/platform/qt/SettingsView.ui @@ -95,7 +95,7 @@ - 1 + 0 @@ -1901,8 +1901,8 @@ - 30 - 30 + 24 + 24 @@ -1920,8 +1920,8 @@ - 30 - 30 + 24 + 24 @@ -1939,8 +1939,8 @@ - 30 - 30 + 24 + 24 @@ -1958,8 +1958,8 @@ - 30 - 30 + 24 + 24 @@ -1988,8 +1988,8 @@ - 30 - 30 + 24 + 24 @@ -2007,8 +2007,8 @@ - 30 - 30 + 24 + 24 @@ -2026,8 +2026,8 @@ - 30 - 30 + 24 + 24 @@ -2045,8 +2045,8 @@ - 30 - 30 + 24 + 24 @@ -2075,8 +2075,8 @@ - 30 - 30 + 24 + 24 @@ -2094,8 +2094,8 @@ - 30 - 30 + 24 + 24 @@ -2113,8 +2113,8 @@ - 30 - 30 + 24 + 24 @@ -2132,8 +2132,8 @@ - 30 - 30 + 24 + 24 From 82f7e52fc6a9368b8c4ea4a4a64718f996a95e32 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 18 Jun 2023 15:16:15 -0700 Subject: [PATCH 238/290] Qt: Add exporting of SAV + RTC GBA saves from Save Converter to strip RTC data --- CHANGES | 1 + src/platform/qt/SaveConverter.cpp | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 11f97dc2b..3dd20b6c3 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,7 @@ Misc: - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs - mGUI: Enable auto-softpatching (closes mgba.io/i/2899) + - Qt: Add exporting of SAV + RTC GBA saves from Save Converter to strip RTC data - Scripting: Add `callbacks:oneshot` for single-call callbacks 0.10.2: (2023-04-23) diff --git a/src/platform/qt/SaveConverter.cpp b/src/platform/qt/SaveConverter.cpp index 712396517..17f28bd34 100644 --- a/src/platform/qt/SaveConverter.cpp +++ b/src/platform/qt/SaveConverter.cpp @@ -198,19 +198,24 @@ void SaveConverter::detectFromSize(std::shared_ptr vf) { #ifdef M_CORE_GBA switch (vf->size()) { case GBA_SIZE_SRAM: + case GBA_SIZE_SRAM + 16: m_validSaves.append(AnnotatedSave{SAVEDATA_SRAM, vf}); break; case GBA_SIZE_FLASH512: + case GBA_SIZE_FLASH512 + 16: m_validSaves.append(AnnotatedSave{SAVEDATA_FLASH512, vf}); break; case GBA_SIZE_FLASH1M: + case GBA_SIZE_FLASH1M + 16: m_validSaves.append(AnnotatedSave{SAVEDATA_FLASH1M, vf}); break; case GBA_SIZE_EEPROM: + case GBA_SIZE_EEPROM + 16: m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM, vf, Endian::LITTLE}); m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM, vf, Endian::BIG}); break; case GBA_SIZE_EEPROM512: + case GBA_SIZE_EEPROM512 + 16: m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM512, vf, Endian::LITTLE}); m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM512, vf, Endian::BIG}); break; @@ -478,6 +483,9 @@ SaveConverter::AnnotatedSave::operator QString() const { default: break; } + if ((size & 0xFF) == 0x10) { + typeFormat += QCoreApplication::translate("QGBA::SaveConverter", " + RTC"); + } break; #endif #ifdef M_CORE_GB @@ -618,6 +626,15 @@ QList SaveConverter::AnnotatedSave::possibleConver break; } break; +#endif +#ifdef M_CORE_GBA + case mPLATFORM_GBA: + if ((size & 0xFF) == 0x10) { + AnnotatedSave noRtc = same; + noRtc.size &= ~0xFF; + possible.append(noRtc); + } + break; #endif default: break; @@ -650,7 +667,7 @@ QByteArray SaveConverter::AnnotatedSave::convertTo(const SaveConverter::Annotate } converted.resize(target.size); buffer = backing->readAll(); - for (int i = 0; i < size; i += 8) { + for (int i = 0; i < (size & ~0xFF); i += 8) { uint64_t word; const uint64_t* in = reinterpret_cast(buffer.constData()); uint64_t* out = reinterpret_cast(converted.data()); @@ -661,6 +678,9 @@ QByteArray SaveConverter::AnnotatedSave::convertTo(const SaveConverter::Annotate default: break; } + if (endianness == target.endianness && size > target.size) { + converted = backing->read(target.size); + } break; #endif #ifdef M_CORE_GB From a82c390fe97e24e2fdf71ce781a74cf7cdcada31 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 18 Jun 2023 21:48:35 -0700 Subject: [PATCH 239/290] Scripting: Detach adapter peripherals when detaching adapter --- src/core/scripting.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/core/scripting.c b/src/core/scripting.c index 0b3150039..e2d518b3a 100644 --- a/src/core/scripting.c +++ b/src/core/scripting.c @@ -1053,7 +1053,11 @@ void mScriptContextDetachCore(struct mScriptContext* context) { if (!value) { return; } - _clearMemoryMap(context, value->value.opaque, true); + + struct mScriptCoreAdapter* adapter = value->value.opaque; + _clearMemoryMap(context, adapter, true); + adapter->core->setPeripheral(adapter->core, mPERIPH_RUMBLE, adapter->oldRumble); + mScriptContextRemoveGlobal(context, "emu"); } From 7be14fa7ccdec48476fb7d63b5fb29a6326b2b38 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 18 Jun 2023 22:06:38 -0700 Subject: [PATCH 240/290] Scripting: Add rotation callback support --- res/scripts/tilt-random-walk.lua | 20 ++++++ src/core/scripting.c | 112 +++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 res/scripts/tilt-random-walk.lua diff --git a/res/scripts/tilt-random-walk.lua b/res/scripts/tilt-random-walk.lua new file mode 100644 index 000000000..41fc7f04b --- /dev/null +++ b/res/scripts/tilt-random-walk.lua @@ -0,0 +1,20 @@ +local r = 0 +local theta = 0 +local rotation = {} + +math.randomseed() + +function rotation.sample() + theta = math.fmod(theta + math.random() / 20, math.pi * 2) + r = math.min(math.max(r + (math.random() - 0.5) / 50, -1), 1) +end + +function rotation.readTiltX() + return math.cos(theta) * r +end + +function rotation.readTiltY() + return math.sin(theta) * r +end + +emu:setRotationCallbacks(rotation) diff --git a/src/core/scripting.c b/src/core/scripting.c index e2d518b3a..9205bbe39 100644 --- a/src/core/scripting.c +++ b/src/core/scripting.c @@ -194,6 +194,9 @@ struct mScriptCoreAdapter { #endif struct mRumble rumble; struct mRumble* oldRumble; + struct mRotationSource rotation; + struct mScriptValue* rotationCbTable; + struct mRotationSource* oldRotation; }; struct mScriptConsole { @@ -946,10 +949,20 @@ static void _mScriptCoreAdapterReset(struct mScriptCoreAdapter* adapter) { mScriptContextTriggerCallback(adapter->context, "reset", NULL); } +static struct mScriptValue* _mScriptCoreAdapterSetRotationCbTable(struct mScriptCoreAdapter* adapter, struct mScriptValue* cbTable) { + if (cbTable) { + mScriptValueRef(cbTable); + } + struct mScriptValue* oldTable = adapter->rotationCbTable; + adapter->rotationCbTable = cbTable; + return oldTable; +} + mSCRIPT_DECLARE_STRUCT(mScriptCoreAdapter); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCoreAdapter, W(mCore), _get, _mScriptCoreAdapterGet, 1, CHARP, name); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCoreAdapter, _deinit, _mScriptCoreAdapterDeinit, 0); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCoreAdapter, reset, _mScriptCoreAdapterReset, 0); +mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCoreAdapter, WTABLE, setRotationCallbacks, _mScriptCoreAdapterSetRotationCbTable, 1, WTABLE, cbTable); #ifdef USE_DEBUGGERS mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptCoreAdapter, S64, setBreakpoint, _mScriptCoreAdapterSetBreakpoint, 3, WRAPPER, callback, U32, address, S32, segment); mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptCoreAdapter, S64, setWatchpoint, _mScriptCoreAdapterSetWatchpoint, 4, WRAPPER, callback, U32, address, S32, type, S32, segment); @@ -991,6 +1004,18 @@ mSCRIPT_DEFINE_STRUCT(mScriptCoreAdapter) mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(mScriptCoreAdapter) mSCRIPT_DEFINE_DOCSTRING("Reset the emulation. As opposed to struct::mCore.reset, this version calls the **reset** callback") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, reset) + mSCRIPT_DEFINE_DOCSTRING( + "Sets the table of functions to be called when the game requests rotation data, for either a gyroscope or accelerometer. " + "The following functions are supported, and if any isn't set then then default implementation for that function is called instead:\n\n" + "- `sample`: Update (\"sample\") the values returned by the other functions. The values returned shouldn't change until the next time this is called\n" + "- `readTiltX`: Return a value between -1.0 and +1.0 representing the X (left/right axis) direction of the linear acceleration vector, as for an accelerometer.\n" + "- `readTiltY`: Return a value between -1.0 and +1.0 representing the Y (up/down axis) direction of the linear acceleration vector, as for an accelerometer.\n" + "- `readGyroZ`: Return a value between -1.0 and +1.0 representing the roll (front/back axis) value of the rotational acceleration vector, as for an gyroscope.\n\n" + "Optionally, you can also set a value `context` on the table that will be passed to the callbacks. This table is copied by value, so changes made to the table " + "after being passed to this function will not be seen unless the function is called again. Therefore, the recommended usage of the `context` field is as an index " + "or key into a separate table. Use cases may vary. If this function is called more than once, the previous value of the table is returned." + ) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, setRotationCallbacks) #ifdef USE_DEBUGGERS mSCRIPT_DEFINE_DOCSTRING("Set a breakpoint at a given address") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, setBreakpoint) @@ -1022,6 +1047,82 @@ static void _setRumble(struct mRumble* rumble, int enable) { mScriptListDeinit(&args); } +static bool _callRotationCb(struct mScriptCoreAdapter* adapter, const char* cbName, struct mScriptValue* out) { + if (!adapter->rotationCbTable) { + return false; + } + struct mScriptValue* cb = mScriptTableLookup(adapter->rotationCbTable, &mSCRIPT_MAKE_CHARP(cbName)); + if (!cb || cb->type->base != mSCRIPT_TYPE_FUNCTION) { + return false; + } + struct mScriptFrame frame; + struct mScriptValue* context = mScriptTableLookup(adapter->rotationCbTable, &mSCRIPT_MAKE_CHARP("context")); + mScriptFrameInit(&frame); + if (context) { + mScriptValueWrap(context, mScriptListAppend(&frame.arguments)); + } + bool ok = mScriptInvoke(cb, &frame); + if (ok && out && mScriptListSize(&frame.returnValues) == 1) { + if (!mScriptCast(mSCRIPT_TYPE_MS_F32, mScriptListGetPointer(&frame.returnValues, 0), out)) { + ok = false; + } + } + mScriptFrameDeinit(&frame); + return ok; +} + +static void _rotationSample(struct mRotationSource* rotation) { + struct mScriptCoreAdapter* adapter = containerof(rotation, struct mScriptCoreAdapter, rotation); + + _callRotationCb(adapter, "sample", NULL); + + if (adapter->oldRotation && adapter->oldRotation->sample) { + adapter->oldRotation->sample(adapter->oldRotation); + } +} + +static int32_t _rotationReadTiltX(struct mRotationSource* rotation) { + struct mScriptCoreAdapter* adapter = containerof(rotation, struct mScriptCoreAdapter, rotation); + + struct mScriptValue out; + if (_callRotationCb(adapter, "readTiltX", &out)) { + return out.value.f32 * INT32_MAX; + } + + if (adapter->oldRotation && adapter->oldRotation->readTiltX) { + return adapter->oldRotation->readTiltX(adapter->oldRotation); + } + return 0; +} + +static int32_t _rotationReadTiltY(struct mRotationSource* rotation) { + struct mScriptCoreAdapter* adapter = containerof(rotation, struct mScriptCoreAdapter, rotation); + + struct mScriptValue out; + if (_callRotationCb(adapter, "readTiltY", &out)) { + return out.value.f32 * INT32_MAX; + } + + if (adapter->oldRotation && adapter->oldRotation->readTiltY) { + return adapter->oldRotation->readTiltY(adapter->oldRotation); + } + return 0; +} + +static int32_t _rotationReadGyroZ(struct mRotationSource* rotation) { + struct mScriptCoreAdapter* adapter = containerof(rotation, struct mScriptCoreAdapter, rotation); + + struct mScriptValue out; + if (_callRotationCb(adapter, "readGyroZ", &out)) { + return out.value.f32 * INT32_MAX; + } + + if (adapter->oldRotation && adapter->oldRotation->readGyroZ) { + return adapter->oldRotation->readGyroZ(adapter->oldRotation); + } + return 0; +} + void mScriptContextAttachCore(struct mScriptContext* context, struct mCore* core) { struct mScriptValue* coreValue = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptCoreAdapter)); struct mScriptCoreAdapter* adapter = calloc(1, sizeof(*adapter)); @@ -1034,8 +1135,15 @@ void mScriptContextAttachCore(struct mScriptContext* context, struct mCore* core adapter->memory.type->alloc(&adapter->memory); adapter->rumble.setRumble = _setRumble; + adapter->rotation.sample = _rotationSample; + adapter->rotation.readTiltX = _rotationReadTiltX; + adapter->rotation.readTiltY = _rotationReadTiltY; + adapter->rotation.readGyroZ = _rotationReadGyroZ; + adapter->oldRumble = core->getPeripheral(core, mPERIPH_RUMBLE); + adapter->oldRotation = core->getPeripheral(core, mPERIPH_ROTATION); core->setPeripheral(core, mPERIPH_RUMBLE, &adapter->rumble); + core->setPeripheral(core, mPERIPH_ROTATION, &adapter->rotation); _rebuildMemoryMap(context, adapter); @@ -1057,6 +1165,10 @@ void mScriptContextDetachCore(struct mScriptContext* context) { struct mScriptCoreAdapter* adapter = value->value.opaque; _clearMemoryMap(context, adapter, true); adapter->core->setPeripheral(adapter->core, mPERIPH_RUMBLE, adapter->oldRumble); + adapter->core->setPeripheral(adapter->core, mPERIPH_ROTATION, adapter->oldRotation); + if (adapter->rotationCbTable) { + mScriptValueDeref(adapter->rotationCbTable); + } mScriptContextRemoveGlobal(context, "emu"); } From 600b11b2845e7e35d5deacc81479870c0284852e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 18 Jun 2023 22:07:24 -0700 Subject: [PATCH 241/290] Scripting: Fix generated docs validity --- src/script/stdlib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/script/stdlib.c b/src/script/stdlib.c index ae4cbf54f..366bc4611 100644 --- a/src/script/stdlib.c +++ b/src/script/stdlib.c @@ -199,7 +199,7 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) { mScriptContextSetDocstring(context, "system", "Information about the system the script is running under"); mScriptContextSetDocstring(context, "system.version", "The current version of this build of the program"); - mScriptContextSetDocstring(context, "system.program", "The name of the program. Generally this will be \"mGBA\", but forks may change it to differentiate"); + mScriptContextSetDocstring(context, "system.program", "The name of the program. Generally this will be \\\"mGBA\\\", but forks may change it to differentiate"); mScriptContextSetDocstring(context, "system.branch", "The current git branch of this build of the program, if known"); mScriptContextSetDocstring(context, "system.commit", "The current git commit hash of this build of the program, if known"); mScriptContextSetDocstring(context, "system.revision", "The current git revision number of this build of the program, or -1 if unknown"); From c358f224031e2807b620d8bb0e6d66a092b975c7 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 18 Jun 2023 22:13:32 -0700 Subject: [PATCH 242/290] Scripting: Update docs style --- src/script/stdlib.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/script/stdlib.c b/src/script/stdlib.c index 366bc4611..33ec0b751 100644 --- a/src/script/stdlib.c +++ b/src/script/stdlib.c @@ -81,17 +81,17 @@ mSCRIPT_BIND_FUNCTION(mScriptExpandBitmask_Binding, WLIST, mScriptExpandBitmask, mSCRIPT_DEFINE_STRUCT(mScriptCallbackManager) mSCRIPT_DEFINE_CLASS_DOCSTRING( "A global singleton object `callbacks` used for managing callbacks. The following callbacks are defined:\n\n" - "- **alarm**: An in-game alarm went off\n" - "- **crashed**: The emulation crashed\n" - "- **frame**: The emulation finished a frame\n" - "- **keysRead**: The emulation is about to read the key input\n" - "- **reset**: The emulation has been reset\n" - "- **rumble**: The state of the rumble motor was changed. This callback is passed a single argument that specifies if it was turned on (true) or off (false)\n" - "- **savedataUpdated**: The emulation has just finished modifying save data\n" - "- **sleep**: The emulation has used the sleep feature to enter a low-power mode\n" - "- **shutdown**: The emulation has been powered off\n" - "- **start**: The emulation has started\n" - "- **stop**: The emulation has voluntarily shut down\n" + "- `alarm`: An in-game alarm went off\n" + "- `crashed`: The emulation crashed\n" + "- `frame`: The emulation finished a frame\n" + "- `keysRead`: The emulation is about to read the key input\n" + "- `reset`: The emulation has been reset\n" + "- `rumble`: The state of the rumble motor was changed. This callback is passed a single argument that specifies if it was turned on (true) or off (false)\n" + "- `savedataUpdated`: The emulation has just finished modifying save data\n" + "- `sleep`: The emulation has used the sleep feature to enter a low-power mode\n" + "- `shutdown`: The emulation has been powered off\n" + "- `start`: The emulation has started\n" + "- `stop`: The emulation has voluntarily shut down\n" ) mSCRIPT_DEFINE_DOCSTRING("Add a callback of the named type. The returned id can be used to remove it later") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, add) From 1c41e1e051803344f1e854f4da23c9cfa470f60b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 19 Jun 2023 22:32:27 -0700 Subject: [PATCH 243/290] GBA Audio: Fix sample timing drifting when changing sample interval --- CHANGES | 1 + src/gba/audio.c | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 3dd20b6c3..189ca1c3b 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,7 @@ Emulation fixes: - GB Serialize: Add missing Pocket Cam state to savestates - GB SIO: Disabling SIO should cancel pending transfers (fixes mgba.io/i/2537) - GB Video: Implement DMG-style sprite ordering + - GBA Audio: Fix sample timing drifting when changing sample interval - GBA BIOS: Fix clobbering registers with word-sized CpuSet - GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) Other fixes: diff --git a/src/gba/audio.c b/src/gba/audio.c index 9cbf1dc24..ed4ea8474 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -253,11 +253,16 @@ void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value) { } void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value) { + GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing)); audio->soundbias = value; int32_t oldSampleInterval = audio->sampleInterval; audio->sampleInterval = 0x200 >> GBARegisterSOUNDBIASGetResolution(value); - if (oldSampleInterval != audio->sampleInterval && audio->p->stream && audio->p->stream->audioRateChanged) { - audio->p->stream->audioRateChanged(audio->p->stream, GBA_ARM7TDMI_FREQUENCY / audio->sampleInterval); + if (oldSampleInterval != audio->sampleInterval) { + audio->lastSample += oldSampleInterval * audio->sampleIndex; + audio->sampleIndex = 0; + if (audio->p->stream && audio->p->stream->audioRateChanged) { + audio->p->stream->audioRateChanged(audio->p->stream, GBA_ARM7TDMI_FREQUENCY / audio->sampleInterval); + } } } From 1af9831fc9defaba6609fedce21793f610e3ea4e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 20 Jun 2023 03:41:52 -0700 Subject: [PATCH 244/290] Scripting: Add light sensor callback support --- src/core/scripting.c | 72 ++++++++++++++++++++++++++++++++++++++++++-- src/gba/cart/gpio.c | 4 ++- 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/src/core/scripting.c b/src/core/scripting.c index 9205bbe39..ff7f7f255 100644 --- a/src/core/scripting.c +++ b/src/core/scripting.c @@ -197,6 +197,11 @@ struct mScriptCoreAdapter { struct mRotationSource rotation; struct mScriptValue* rotationCbTable; struct mRotationSource* oldRotation; +#ifdef M_CORE_GBA + struct GBALuminanceSource luminance; + struct mScriptValue* luminanceCb; + struct GBALuminanceSource* oldLuminance; +#endif }; struct mScriptConsole { @@ -958,11 +963,25 @@ static struct mScriptValue* _mScriptCoreAdapterSetRotationCbTable(struct mScript return oldTable; } +static void _mScriptCoreAdapterSetLuminanceCb(struct mScriptCoreAdapter* adapter, struct mScriptValue* callback) { + if (callback) { + if (callback->type->base != mSCRIPT_TYPE_FUNCTION) { + return; + } + mScriptValueRef(callback); + } + if (adapter->luminanceCb) { + mScriptValueDeref(adapter->luminanceCb); + } + adapter->luminanceCb = callback; +} + mSCRIPT_DECLARE_STRUCT(mScriptCoreAdapter); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCoreAdapter, W(mCore), _get, _mScriptCoreAdapterGet, 1, CHARP, name); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCoreAdapter, _deinit, _mScriptCoreAdapterDeinit, 0); mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCoreAdapter, reset, _mScriptCoreAdapterReset, 0); mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCoreAdapter, WTABLE, setRotationCallbacks, _mScriptCoreAdapterSetRotationCbTable, 1, WTABLE, cbTable); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCoreAdapter, setSolarSensorCallback, _mScriptCoreAdapterSetLuminanceCb, 1, WRAPPER, callback); #ifdef USE_DEBUGGERS mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptCoreAdapter, S64, setBreakpoint, _mScriptCoreAdapterSetBreakpoint, 3, WRAPPER, callback, U32, address, S32, segment); mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptCoreAdapter, S64, setWatchpoint, _mScriptCoreAdapterSetWatchpoint, 4, WRAPPER, callback, U32, address, S32, type, S32, segment); @@ -1016,6 +1035,11 @@ mSCRIPT_DEFINE_STRUCT(mScriptCoreAdapter) "or key into a separate table. Use cases may vary. If this function is called more than once, the previous value of the table is returned." ) mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, setRotationCallbacks) + mSCRIPT_DEFINE_DOCSTRING( + "Set a callback that will be used to get the current value of the solar sensors between 0 (darkest) and 255 (brightest). " + "Note that the full range of values is not used by games, and the exact range depends on the calibration done by the game itself." + ) + mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, setSolarSensorCallback) #ifdef USE_DEBUGGERS mSCRIPT_DEFINE_DOCSTRING("Set a breakpoint at a given address") mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, setBreakpoint) @@ -1123,6 +1147,33 @@ static int32_t _rotationReadGyroZ(struct mRotationSource* rotation) { return 0; } +#ifdef M_CORE_GBA +static uint8_t _readLuminance(struct GBALuminanceSource* luminance) { + struct mScriptCoreAdapter* adapter = containerof(luminance, struct mScriptCoreAdapter, luminance); + + if (adapter->luminanceCb) { + struct mScriptFrame frame; + mScriptFrameInit(&frame); + bool ok = mScriptInvoke(adapter->luminanceCb, &frame); + struct mScriptValue out = {0}; + if (ok && mScriptListSize(&frame.returnValues) == 1) { + if (!mScriptCast(mSCRIPT_TYPE_MS_U8, mScriptListGetPointer(&frame.returnValues, 0), &out)) { + ok = false; + } + } + mScriptFrameDeinit(&frame); + if (ok) { + return 0xFF - out.value.u32; + } + } + if (adapter->oldLuminance) { + adapter->oldLuminance->sample(adapter->oldLuminance); + return adapter->oldLuminance->readLuminance(adapter->oldLuminance); + } + return 0; +} +#endif + void mScriptContextAttachCore(struct mScriptContext* context, struct mCore* core) { struct mScriptValue* coreValue = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptCoreAdapter)); struct mScriptCoreAdapter* adapter = calloc(1, sizeof(*adapter)); @@ -1145,6 +1196,14 @@ void mScriptContextAttachCore(struct mScriptContext* context, struct mCore* core core->setPeripheral(core, mPERIPH_RUMBLE, &adapter->rumble); core->setPeripheral(core, mPERIPH_ROTATION, &adapter->rotation); +#ifdef M_CORE_GBA + adapter->luminance.readLuminance = _readLuminance; + if (core->platform(core) == mPLATFORM_GBA) { + adapter->oldLuminance = core->getPeripheral(core, mPERIPH_GBA_LUMINANCE); + core->setPeripheral(core, mPERIPH_GBA_LUMINANCE, &adapter->luminance); + } +#endif + _rebuildMemoryMap(context, adapter); coreValue->value.opaque = adapter; @@ -1164,11 +1223,20 @@ void mScriptContextDetachCore(struct mScriptContext* context) { struct mScriptCoreAdapter* adapter = value->value.opaque; _clearMemoryMap(context, adapter, true); - adapter->core->setPeripheral(adapter->core, mPERIPH_RUMBLE, adapter->oldRumble); - adapter->core->setPeripheral(adapter->core, mPERIPH_ROTATION, adapter->oldRotation); + struct mCore* core = adapter->core; + core->setPeripheral(core, mPERIPH_RUMBLE, adapter->oldRumble); + core->setPeripheral(core, mPERIPH_ROTATION, adapter->oldRotation); if (adapter->rotationCbTable) { mScriptValueDeref(adapter->rotationCbTable); } +#ifdef M_CORE_GBA + if (core->platform(core) == mPLATFORM_GBA) { + core->setPeripheral(core, mPERIPH_GBA_LUMINANCE, adapter->oldLuminance); + } + if (adapter->luminanceCb) { + mScriptValueDeref(adapter->luminanceCb); + } +#endif mScriptContextRemoveGlobal(context, "emu"); } diff --git a/src/gba/cart/gpio.c b/src/gba/cart/gpio.c index 0bb7f3429..35b72cb3c 100644 --- a/src/gba/cart/gpio.c +++ b/src/gba/cart/gpio.c @@ -374,7 +374,9 @@ void _lightReadPins(struct GBACartridgeHardware* hw) { mLOG(GBA_HW, DEBUG, "[SOLAR] Got reset"); hw->lightCounter = 0; if (lux) { - lux->sample(lux); + if (lux->sample) { + lux->sample(lux); + } hw->lightSample = lux->readLuminance(lux); } else { hw->lightSample = 0xFF; From 76e5aa7148ff6845a45b3594ae0048190ce67cf4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 20 Jun 2023 04:18:46 -0700 Subject: [PATCH 245/290] Res: Add demo light sensor script --- res/scripts/light-oscillate.lua | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 res/scripts/light-oscillate.lua diff --git a/res/scripts/light-oscillate.lua b/res/scripts/light-oscillate.lua new file mode 100644 index 000000000..6a2848842 --- /dev/null +++ b/res/scripts/light-oscillate.lua @@ -0,0 +1,8 @@ +local theta = 0 + +function readLight() + theta = math.fmod(theta + math.pi / 120, math.pi * 2) + return (math.sin(theta) + 1) * 75 +end + +emu:setSolarSensorCallback(readLight) From 870c2f8bab1941f06939c02cbe09baf5b652e37c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 20 Jun 2023 04:34:27 -0700 Subject: [PATCH 246/290] Qt: Fix potential scripting context conflict (fixes #2948) --- src/platform/qt/scripting/ScriptingController.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/qt/scripting/ScriptingController.cpp b/src/platform/qt/scripting/ScriptingController.cpp index 701243d40..7ab7e2f49 100644 --- a/src/platform/qt/scripting/ScriptingController.cpp +++ b/src/platform/qt/scripting/ScriptingController.cpp @@ -180,6 +180,8 @@ void ScriptingController::event(QObject* obj, QEvent* event) { return; } + CoreController::Interrupter interrupter(m_controller); + switch (event->type()) { case QEvent::FocusOut: case QEvent::WindowDeactivate: From 125db5bbe7d480e7857a6ef4ef0c8f892c68d5e1 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 26 Jun 2023 00:21:03 -0700 Subject: [PATCH 247/290] GBA Audio: Fix initial channel 3 wave RAM (fixes #2947) --- CHANGES | 1 + src/gb/audio.c | 34 ++++++++++++++++++---------------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index 189ca1c3b..8e371932a 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,7 @@ Emulation fixes: - GB SIO: Disabling SIO should cancel pending transfers (fixes mgba.io/i/2537) - GB Video: Implement DMG-style sprite ordering - GBA Audio: Fix sample timing drifting when changing sample interval + - GBA Audio: Fix initial channel 3 wave RAM (fixes mgba.io/i/2947) - GBA BIOS: Fix clobbering registers with word-sized CpuSet - GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) Other fixes: diff --git a/src/gb/audio.c b/src/gb/audio.c index 9130c8711..a90d5531a 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -100,22 +100,24 @@ void GBAudioReset(struct GBAudio* audio) { audio->ch3 = (struct GBAudioWaveChannel) { .bank = 0 }; audio->ch4 = (struct GBAudioNoiseChannel) { .nSamples = 0 }; // TODO: DMG randomness - audio->ch3.wavedata8[0] = 0x00; - audio->ch3.wavedata8[1] = 0xFF; - audio->ch3.wavedata8[2] = 0x00; - audio->ch3.wavedata8[3] = 0xFF; - audio->ch3.wavedata8[4] = 0x00; - audio->ch3.wavedata8[5] = 0xFF; - audio->ch3.wavedata8[6] = 0x00; - audio->ch3.wavedata8[7] = 0xFF; - audio->ch3.wavedata8[8] = 0x00; - audio->ch3.wavedata8[9] = 0xFF; - audio->ch3.wavedata8[10] = 0x00; - audio->ch3.wavedata8[11] = 0xFF; - audio->ch3.wavedata8[12] = 0x00; - audio->ch3.wavedata8[13] = 0xFF; - audio->ch3.wavedata8[14] = 0x00; - audio->ch3.wavedata8[15] = 0xFF; + if (audio->style != GB_AUDIO_GBA) { + audio->ch3.wavedata8[0] = 0x00; + audio->ch3.wavedata8[1] = 0xFF; + audio->ch3.wavedata8[2] = 0x00; + audio->ch3.wavedata8[3] = 0xFF; + audio->ch3.wavedata8[4] = 0x00; + audio->ch3.wavedata8[5] = 0xFF; + audio->ch3.wavedata8[6] = 0x00; + audio->ch3.wavedata8[7] = 0xFF; + audio->ch3.wavedata8[8] = 0x00; + audio->ch3.wavedata8[9] = 0xFF; + audio->ch3.wavedata8[10] = 0x00; + audio->ch3.wavedata8[11] = 0xFF; + audio->ch3.wavedata8[12] = 0x00; + audio->ch3.wavedata8[13] = 0xFF; + audio->ch3.wavedata8[14] = 0x00; + audio->ch3.wavedata8[15] = 0xFF; + } audio->ch4 = (struct GBAudioNoiseChannel) { .envelope = { .dead = 2 } }; audio->frame = 0; audio->sampleInterval = SAMPLE_INTERVAL * GB_MAX_SAMPLES; From 4859e9b4c69f3ff7c9c00f1a344c75b7a2f1ad88 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 26 Jun 2023 03:54:54 -0700 Subject: [PATCH 248/290] GB: Add missing CGB0 BIOS to model detection --- src/gb/gb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gb/gb.c b/src/gb/gb.c index 6e3b92b37..4cbff7e4f 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -817,6 +817,7 @@ void GBDetectModel(struct GB* gb) { gb->model = GB_MODEL_SGB2; break; case CGB_BIOS_CHECKSUM: + case CGB0_BIOS_CHECKSUM: gb->model = GB_MODEL_CGB; break; case AGB_BIOS_CHECKSUM: From 4d94ab7a38cfcbc1b3a824fdf786306ddeb25225 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 26 Jun 2023 04:41:07 -0700 Subject: [PATCH 249/290] GB: Prevent incompatible BIOSes from being used on differing models --- CHANGES | 1 + include/mgba/gb/interface.h | 1 + src/gb/gb.c | 19 ++++++++++++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 8e371932a..a2bf6d97a 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,7 @@ Other fixes: - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) Misc: - Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826) + - GB: Prevent incompatible BIOSes from being used on differing models - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs - mGUI: Enable auto-softpatching (closes mgba.io/i/2899) diff --git a/include/mgba/gb/interface.h b/include/mgba/gb/interface.h index 0ec54559f..ecd99e2f9 100644 --- a/include/mgba/gb/interface.h +++ b/include/mgba/gb/interface.h @@ -71,6 +71,7 @@ struct VFile; bool GBIsROM(struct VFile* vf); bool GBIsBIOS(struct VFile* vf); +bool GBIsCompatibleBIOS(struct VFile* vf, enum GBModel model); enum GBModel GBNameToModel(const char*); const char* GBModelToName(enum GBModel); diff --git a/src/gb/gb.c b/src/gb/gb.c index 4cbff7e4f..899d5cfc7 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -530,6 +530,23 @@ bool GBIsBIOS(struct VFile* vf) { } } +bool GBIsCompatibleBIOS(struct VFile* vf, enum GBModel model) { + switch (_GBBiosCRC32(vf)) { + case DMG_BIOS_CHECKSUM: + case DMG0_BIOS_CHECKSUM: + case MGB_BIOS_CHECKSUM: + case SGB_BIOS_CHECKSUM: + case SGB2_BIOS_CHECKSUM: + return model < GB_MODEL_CGB; + case CGB_BIOS_CHECKSUM: + case CGB0_BIOS_CHECKSUM: + case AGB_BIOS_CHECKSUM: + return model >= GB_MODEL_CGB; + default: + return false; + } +} + void GBReset(struct SM83Core* cpu) { struct GB* gb = (struct GB*) cpu->master; gb->memory.romBase = gb->memory.rom; @@ -562,7 +579,7 @@ void GBReset(struct SM83Core* cpu) { GBMemoryReset(gb); if (gb->biosVf) { - if (!GBIsBIOS(gb->biosVf)) { + if (!GBIsCompatibleBIOS(gb->biosVf, gb->model)) { gb->biosVf->close(gb->biosVf); gb->biosVf = NULL; } else { From 200e846b8175f626651a48d2616e3c2be5fcb3dc Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 26 Jun 2023 04:43:23 -0700 Subject: [PATCH 250/290] Core: Begin modernizing game override API (fixes #2963) --- CHANGES | 1 + include/mgba/core/core.h | 1 + src/gb/core.c | 24 ++++++++++++++++++------ src/gba/core.c | 15 ++++++++++++++- src/platform/qt/CoreController.cpp | 10 +++++----- src/platform/qt/GBAOverride.cpp | 15 ++++----------- src/platform/qt/GBAOverride.h | 2 +- src/platform/qt/GBOverride.cpp | 11 ++++------- src/platform/qt/GBOverride.h | 2 +- src/platform/qt/Override.h | 2 +- 10 files changed, 50 insertions(+), 33 deletions(-) diff --git a/CHANGES b/CHANGES index a2bf6d97a..14a71073e 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,7 @@ Emulation fixes: - GBA BIOS: Fix clobbering registers with word-sized CpuSet - GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) Other fixes: + - Core: Fix inconsistencies with setting game-specific overrides (fixes mgba.io/i/2963) - Debugger: Fix writing to specific segment in command-line debugger - mGUI: Fix cases where an older save state screenshot would be shown. (fixes mgba.io/i/2183) - Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560) diff --git a/include/mgba/core/core.h b/include/mgba/core/core.h index e2a804258..052e36795 100644 --- a/include/mgba/core/core.h +++ b/include/mgba/core/core.h @@ -65,6 +65,7 @@ struct mCore { void (*setSync)(struct mCore*, struct mCoreSync*); void (*loadConfig)(struct mCore*, const struct mCoreConfig*); void (*reloadConfigOption)(struct mCore*, const char* option, const struct mCoreConfig*); + void (*setOverride)(struct mCore*, const void* override); void (*baseVideoSize)(const struct mCore*, unsigned* width, unsigned* height); void (*currentVideoSize)(const struct mCore*, unsigned* width, unsigned* height); diff --git a/src/gb/core.c b/src/gb/core.c index 96f9142f5..ca7ba1593 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -99,6 +99,8 @@ struct GBCore { uint8_t keys; struct mCPUComponent* components[CPU_COMPONENT_MAX]; const struct Configuration* overrides; + struct GBCartridgeOverride override; + bool hasOverride; struct mDebuggerPlatform* debuggerPlatform; struct mCheatDevice* cheatDevice; struct mCoreMemoryBlock memoryBlocks[8]; @@ -124,6 +126,8 @@ static bool _GBCoreInit(struct mCore* core) { gbcore->logContext = NULL; #endif memcpy(gbcore->memoryBlocks, _GBMemoryBlocks, sizeof(_GBMemoryBlocks)); + memset(&gbcore->override, 0, sizeof(gbcore->override)); + gbcore->hasOverride = false; GBCreate(gb); memset(gbcore->components, 0, sizeof(gbcore->components)); @@ -364,6 +368,12 @@ static void _GBCoreReloadConfigOption(struct mCore* core, const char* option, co } } +static void _GBCoreSetOverride(struct mCore* core, const void* override) { + struct GBCore* gbcore = (struct GBCore*) core; + memcpy(&gbcore->override, override, sizeof(gbcore->override)); + gbcore->hasOverride = true; +} + static void _GBCoreBaseVideoSize(const struct mCore* core, unsigned* width, unsigned* height) { UNUSED(core); *width = SGB_VIDEO_HORIZONTAL_PIXELS; @@ -541,14 +551,15 @@ static void _GBCoreReset(struct mCore* core) { mCoreConfigGetIntValue(&core->config, "useCgbColors", &doColorOverride); } - struct GBCartridgeOverride override; const struct GBCartridge* cart = (const struct GBCartridge*) &gb->memory.rom[0x100]; - override.headerCrc32 = doCrc32(cart, sizeof(*cart)); - bool modelOverride = GBOverrideFind(gbcore->overrides, &override) || (doColorOverride && GBOverrideColorFind(&override, doColorOverride)); - if (modelOverride) { - GBOverrideApply(gb, &override); + if (!gbcore->hasOverride) { + gbcore->override.headerCrc32 = doCrc32(cart, sizeof(*cart)); + gbcore->hasOverride = GBOverrideFind(gbcore->overrides, &gbcore->override) || (doColorOverride && GBOverrideColorFind(&gbcore->override, doColorOverride)); } - if (!modelOverride || override.model == GB_MODEL_AUTODETECT) { + if (gbcore->hasOverride) { + GBOverrideApply(gb, &gbcore->override); + } + if (!gbcore->hasOverride || gbcore->override.model == GB_MODEL_AUTODETECT) { const char* modelGB = mCoreConfigGetValue(&core->config, "gb.model"); const char* modelSGB = mCoreConfigGetValue(&core->config, "sgb.model"); const char* modelCGB = mCoreConfigGetValue(&core->config, "cgb.model"); @@ -1280,6 +1291,7 @@ struct mCore* GBCoreCreate(void) { core->setSync = _GBCoreSetSync; core->loadConfig = _GBCoreLoadConfig; core->reloadConfigOption = _GBCoreReloadConfigOption; + core->setOverride = _GBCoreSetOverride; core->baseVideoSize = _GBCoreBaseVideoSize; core->currentVideoSize = _GBCoreCurrentVideoSize; core->videoScale = _GBCoreVideoScale; diff --git a/src/gba/core.c b/src/gba/core.c index a53cc08d1..df40c6406 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -182,6 +182,8 @@ struct GBACore { #endif struct mCPUComponent* components[CPU_COMPONENT_MAX]; const struct Configuration* overrides; + struct GBACartridgeOverride override; + bool hasOverride; struct mDebuggerPlatform* debuggerPlatform; struct mCheatDevice* cheatDevice; struct GBAAudioMixer* audioMixer; @@ -426,6 +428,12 @@ static void _GBACoreReloadConfigOption(struct mCore* core, const char* option, c } } +static void _GBACoreSetOverride(struct mCore* core, const void* override) { + struct GBACore* gbacore = (struct GBACore*) core; + memcpy(&gbacore->override, override, sizeof(gbacore->override)); + gbacore->hasOverride = true; +} + static void _GBACoreBaseVideoSize(const struct mCore* core, unsigned* width, unsigned* height) { UNUSED(core); *width = GBA_VIDEO_HORIZONTAL_PIXELS; @@ -678,7 +686,11 @@ static void _GBACoreReset(struct mCore* core) { if (!forceGbp) { gba->memory.hw.devices &= ~HW_GB_PLAYER_DETECTION; } - GBAOverrideApplyDefaults(gba, gbacore->overrides); + if (gbacore->hasOverride) { + GBAOverrideApply(gba, &gbacore->override); + } else { + GBAOverrideApplyDefaults(gba, gbacore->overrides); + } if (forceGbp) { gba->memory.hw.devices |= HW_GB_PLAYER_DETECTION; } @@ -1405,6 +1417,7 @@ struct mCore* GBACoreCreate(void) { core->setSync = _GBACoreSetSync; core->loadConfig = _GBACoreLoadConfig; core->reloadConfigOption = _GBACoreReloadConfigOption; + core->setOverride = _GBACoreSetOverride; core->baseVideoSize = _GBACoreBaseVideoSize; core->currentVideoSize = _GBACoreCurrentVideoSize; core->videoScale = _GBACoreVideoScale; diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 8eacd4ea1..018b19ec0 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -79,6 +79,11 @@ CoreController::CoreController(mCore* core, QObject* parent) controller->updatePlayerSave(); } + if (controller->m_override) { + controller->m_override->identify(context->core); + context->core->setOverride(context->core, controller->m_override->raw()); + } + QMetaObject::invokeMethod(controller, "started"); }; @@ -88,11 +93,6 @@ CoreController::CoreController(mCore* core, QObject* parent) action(); } - if (controller->m_override) { - controller->m_override->identify(context->core); - controller->m_override->apply(context->core); - } - controller->m_resetActions.clear(); controller->m_frameCounter = -1; diff --git a/src/platform/qt/GBAOverride.cpp b/src/platform/qt/GBAOverride.cpp index 885a5d3ac..57fb2048e 100644 --- a/src/platform/qt/GBAOverride.cpp +++ b/src/platform/qt/GBAOverride.cpp @@ -10,17 +10,6 @@ using namespace QGBA; -void GBAOverride::apply(struct mCore* core) { - if (core->platform(core) != mPLATFORM_GBA) { - return; - } - GBA* gba = static_cast(core->board); - if (!vbaBugCompatSet) { - override.vbaBugCompat = gba->vbaBugCompat; - } - GBAOverrideApply(gba, &override); -} - void GBAOverride::identify(const struct mCore* core) { if (core->platform(core) != mPLATFORM_GBA) { return; @@ -33,3 +22,7 @@ void GBAOverride::identify(const struct mCore* core) { void GBAOverride::save(struct Configuration* config) const { GBAOverrideSave(config, &override); } + +const void* GBAOverride::raw() const { + return &override; +} diff --git a/src/platform/qt/GBAOverride.h b/src/platform/qt/GBAOverride.h index 8b07659fd..477af0014 100644 --- a/src/platform/qt/GBAOverride.h +++ b/src/platform/qt/GBAOverride.h @@ -13,9 +13,9 @@ namespace QGBA { class GBAOverride : public Override { public: - void apply(struct mCore*) override; void identify(const struct mCore*) override; void save(struct Configuration*) const override; + const void* raw() const override; struct GBACartridgeOverride override; bool vbaBugCompatSet; diff --git a/src/platform/qt/GBOverride.cpp b/src/platform/qt/GBOverride.cpp index ed66f327d..224830437 100644 --- a/src/platform/qt/GBOverride.cpp +++ b/src/platform/qt/GBOverride.cpp @@ -11,13 +11,6 @@ using namespace QGBA; -void GBOverride::apply(struct mCore* core) { - if (core->platform(core) != mPLATFORM_GB) { - return; - } - GBOverrideApply(static_cast(core->board), &override); -} - void GBOverride::identify(const struct mCore* core) { if (core->platform(core) != mPLATFORM_GB) { return; @@ -32,3 +25,7 @@ void GBOverride::identify(const struct mCore* core) { void GBOverride::save(struct Configuration* config) const { GBOverrideSave(config, &override); } + +const void* GBOverride::raw() const { + return &override; +} diff --git a/src/platform/qt/GBOverride.h b/src/platform/qt/GBOverride.h index 82c53ca7f..e967211de 100644 --- a/src/platform/qt/GBOverride.h +++ b/src/platform/qt/GBOverride.h @@ -13,9 +13,9 @@ namespace QGBA { class GBOverride : public Override { public: - void apply(struct mCore*) override; void identify(const struct mCore*) override; void save(struct Configuration*) const override; + const void* raw() const override; struct GBCartridgeOverride override; }; diff --git a/src/platform/qt/Override.h b/src/platform/qt/Override.h index 2ff209d8c..bf28aa755 100644 --- a/src/platform/qt/Override.h +++ b/src/platform/qt/Override.h @@ -14,9 +14,9 @@ class Override { public: virtual ~Override() {} - virtual void apply(struct mCore*) = 0; virtual void identify(const struct mCore*) = 0; virtual void save(struct Configuration*) const = 0; + virtual const void* raw() const = 0; }; } From 491879da95b4286f0f41ed0f650a00e91f37c671 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 26 Jun 2023 16:14:25 -0700 Subject: [PATCH 251/290] ARM: Remove obsolete force-alignment in `bx pc` (fixes #2964) --- CHANGES | 1 + src/arm/isa-thumb.c | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 14a71073e..aa3162ca1 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,7 @@ Features: - New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81 - Debugger: Add range watchpoints Emulation fixes: + - ARM: Remove obsolete force-alignment in `bx pc` (fixes mgba.io/i/2964) - GB Audio: Fix channels 1/2 staying muted if restarted after long silence - GB I/O: Read back proper SVBK value after writing 0 (fixes mgba.io/i/2921) - GB Serialize: Add missing Pocket Cam state to savestates diff --git a/src/arm/isa-thumb.c b/src/arm/isa-thumb.c index 6e3c30e11..e2af3d179 100644 --- a/src/arm/isa-thumb.c +++ b/src/arm/isa-thumb.c @@ -401,11 +401,7 @@ DEFINE_INSTRUCTION_THUMB(BL2, DEFINE_INSTRUCTION_THUMB(BX, int rm = (opcode >> 3) & 0xF; _ARMSetMode(cpu, cpu->gprs[rm] & 0x00000001); - int misalign = 0; - if (rm == ARM_PC) { - misalign = cpu->gprs[rm] & 0x00000002; - } - cpu->gprs[ARM_PC] = (cpu->gprs[rm] & 0xFFFFFFFE) - misalign; + cpu->gprs[ARM_PC] = cpu->gprs[rm] & 0xFFFFFFFE; if (cpu->executionMode == MODE_THUMB) { currentCycles += ThumbWritePC(cpu); } else { From c5a7b1a9b7b1581a6cf348cf20d755f5df08dc5a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 29 Jun 2023 01:23:58 -0700 Subject: [PATCH 252/290] GB Audio: Fix channel 1 restarting if sweep applies after stop (fixes #2965) --- CHANGES | 1 + src/gb/audio.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index aa3162ca1..fbc59a55e 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,7 @@ Features: Emulation fixes: - ARM: Remove obsolete force-alignment in `bx pc` (fixes mgba.io/i/2964) - GB Audio: Fix channels 1/2 staying muted if restarted after long silence + - GB Audio: Fix channel 1 restarting if sweep applies after stop (fixes mgba.io/i/2965) - GB I/O: Read back proper SVBK value after writing 0 (fixes mgba.io/i/2921) - GB Serialize: Add missing Pocket Cam state to savestates - GB SIO: Disabling SIO should cancel pending transfers (fixes mgba.io/i/2537) diff --git a/src/gb/audio.c b/src/gb/audio.c index a90d5531a..1a728b6f0 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -651,7 +651,9 @@ void GBAudioUpdateFrame(struct GBAudio* audio) { if (audio->ch1.sweep.enable) { --audio->ch1.sweep.step; if (audio->ch1.sweep.step == 0) { - audio->playingCh1 = _updateSweep(&audio->ch1, false); + if (!_updateSweep(&audio->ch1, false)) { + audio->playingCh1 = false; + } *audio->nr52 &= ~0x0001; *audio->nr52 |= audio->playingCh1; } From 2a974a74e72728a10b27822481e3522642f02037 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 29 Jun 2023 02:54:29 -0700 Subject: [PATCH 253/290] GB Audio: Update channels 1/2 irregularly if silent --- src/gb/audio.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/gb/audio.c b/src/gb/audio.c index 1a728b6f0..261fac5a0 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -204,7 +204,6 @@ void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) { --audio->ch1.control.length; } } - audio->ch1.lastUpdate = mTimingCurrentTime(audio->timing); _updateSquareSample(&audio->ch1); } *audio->nr52 &= ~0x0001; @@ -252,7 +251,6 @@ void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) { --audio->ch2.control.length; } } - audio->ch2.lastUpdate = mTimingCurrentTime(audio->timing); _updateSquareSample(&audio->ch2); } *audio->nr52 &= ~0x0002; @@ -510,7 +508,7 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) { GBAudioSample(audio, timestamp); } - if (audio->playingCh1 && (channels & 0x1) && audio->ch1.envelope.dead != 2) { + if ((channels & 0x1) && ((audio->playingCh1 && audio->ch1.envelope.dead != 2) || timestamp - audio->ch1.lastUpdate > 0x40000000)) { int period = 4 * (2048 - audio->ch1.control.frequency) * audio->timingFactor; int32_t diff = timestamp - audio->ch1.lastUpdate; if (diff >= period) { @@ -520,7 +518,7 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) { _updateSquareSample(&audio->ch1); } } - if (audio->playingCh2 && (channels & 0x2) && audio->ch2.envelope.dead != 2) { + if ((channels & 0x2) && ((audio->playingCh2 && audio->ch2.envelope.dead != 2) || timestamp - audio->ch2.lastUpdate > 0x40000000)) { int period = 4 * (2048 - audio->ch2.control.frequency) * audio->timingFactor; int32_t diff = timestamp - audio->ch2.lastUpdate; if (diff >= period) { From 0ee3f3f16cda54cba9ae1508f43f2a0ef8460bba Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 29 Jun 2023 03:07:10 -0700 Subject: [PATCH 254/290] GB Audio: Force update channels 1/2 if updating from a register write --- src/gb/audio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gb/audio.c b/src/gb/audio.c index 261fac5a0..569b98b84 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -508,7 +508,7 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) { GBAudioSample(audio, timestamp); } - if ((channels & 0x1) && ((audio->playingCh1 && audio->ch1.envelope.dead != 2) || timestamp - audio->ch1.lastUpdate > 0x40000000)) { + if ((channels & 0x1) && ((audio->playingCh1 && audio->ch1.envelope.dead != 2) || timestamp - audio->ch1.lastUpdate > 0x40000000 || (channels == 0x1))) { int period = 4 * (2048 - audio->ch1.control.frequency) * audio->timingFactor; int32_t diff = timestamp - audio->ch1.lastUpdate; if (diff >= period) { @@ -518,7 +518,7 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) { _updateSquareSample(&audio->ch1); } } - if ((channels & 0x2) && ((audio->playingCh2 && audio->ch2.envelope.dead != 2) || timestamp - audio->ch2.lastUpdate > 0x40000000)) { + if ((channels & 0x2) && ((audio->playingCh2 && audio->ch2.envelope.dead != 2) || timestamp - audio->ch2.lastUpdate > 0x40000000 || (channels == 0x2))) { int period = 4 * (2048 - audio->ch2.control.frequency) * audio->timingFactor; int32_t diff = timestamp - audio->ch2.lastUpdate; if (diff >= period) { From 9873073400416701f3deb37a628b0859551a6b5d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 29 Jun 2023 03:19:26 -0700 Subject: [PATCH 255/290] Util: Add ctz32 function --- include/mgba-util/math.h | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/include/mgba-util/math.h b/include/mgba-util/math.h index 8402da3db..f7b3269c9 100644 --- a/include/mgba-util/math.h +++ b/include/mgba-util/math.h @@ -25,7 +25,7 @@ static inline unsigned clz32(uint32_t bits) { } return __builtin_clz(bits); #else - static const int table[256] = { + static const int8_t table[256] = { 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, @@ -55,6 +55,43 @@ static inline unsigned clz32(uint32_t bits) { #endif } +static inline unsigned ctz32(uint32_t bits) { +#if defined(__GNUC__) || __clang__ + if (!bits) { + return 32; + } + return __builtin_ctz(bits); +#else + static const int8_t table[256] = { + 8, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + }; + + if (bits & 0x000000FF) { + return table[bits & 0xFF]; + } else if (bits & 0x0000FF00) { + return table[(bits >> 8) & 0xFF] + 8; + } else if (bits & 0x00FF0000) { + return table[(bits >> 16) & 0xFF] + 16; + } + return table[bits >> 24] + 24; +#endif +} + static inline uint32_t toPow2(uint32_t bits) { if (!bits) { return 0; From bd15285ea139be07bb407298d928b018020584e6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 29 Jun 2023 21:56:05 -0700 Subject: [PATCH 256/290] GB Audio: More accurate LFSR implementation for noise channel --- src/gb/audio.c | 54 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/src/gb/audio.c b/src/gb/audio.c index 569b98b84..e9c014d05 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -385,11 +385,7 @@ void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) { if (GBAudioRegisterNoiseControlIsRestart(value)) { audio->playingCh4 = _resetEnvelope(&audio->ch4.envelope); - if (audio->ch4.power) { - audio->ch4.lfsr = 0x7F; - } else { - audio->ch4.lfsr = 0x7FFF; - } + audio->ch4.lfsr = 0; if (!audio->ch4.length) { audio->ch4.length = 64; if (audio->ch4.stop && !(audio->frame & 1)) { @@ -601,24 +597,58 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) { } } if (audio->playingCh4 && (channels & 0x8)) { + const uint16_t noiseMaskTable[0x40] = { + 0x3f, 0x3e, 0x3c, 0x3d, 0x39, 0x38, 0x3a, 0x3b, + 0x33, 0x32, 0x30, 0x31, 0x35, 0x34, 0x36, 0x37, + 0x27, 0x26, 0x24, 0x25, 0x21, 0x20, 0x22, 0x23, + 0x2b, 0x2a, 0x28, 0x29, 0x2d, 0x2c, 0x2e, 0x2f, + 0x0f, 0x0e, 0x0c, 0x0d, 0x09, 0x08, 0x0a, 0x0b, + 0x03, 0x02, 0x00, 0x01, 0x05, 0x04, 0x06, 0x07, + 0x17, 0x16, 0x14, 0x15, 0x11, 0x10, 0x12, 0x13, + 0x1b, 0x1a, 0x18, 0x19, 0x1d, 0x1c, 0x1e, 0x1f + }; + const uint16_t noisePopulationTable[0x40] = { + 6, 5, 4, 5, 4, 3, 4, 5, 4, 3, 2, 3, 4, 3, 4, 5, + 4, 3, 2, 3, 2, 1, 2, 3, 4, 3, 2, 3, 4, 3, 4, 5, + 4, 3, 2, 3, 2, 1, 2, 3, 2, 1, 0, 1, 2, 1, 2, 3, + 4, 3, 2, 3, 2, 1, 2, 3, 4, 3, 2, 3, 4, 3, 4, 5 + }; int32_t cycles = audio->ch4.ratio ? 2 * audio->ch4.ratio : 1; cycles <<= audio->ch4.frequency; cycles *= 8 * audio->timingFactor; int32_t diff = timestamp - audio->ch4.lastEvent; if (diff >= cycles) { - int32_t last; + int32_t last = 0; int samples = 0; int positiveSamples = 0; int lsb; - int coeff = 0x60; - if (!audio->ch4.power) { - coeff <<= 8; + int coeff; + if (audio->ch4.power) { + // TODO: Can this be batched too? + coeff = 0x4040; + } else { + int bits = 0; + // Batch 5 steps at a time when possible + for (; last + cycles * 5 <= diff; last += cycles * 5) { + bits = audio->ch4.lfsr & 0x3F; + audio->ch4.lfsr >>= 5; + audio->ch4.lfsr |= 0x4000 * noiseMaskTable[bits] >> 4; + audio->ch4.lfsr &= 0x7FFF; + samples += 5; + positiveSamples += noisePopulationTable[bits]; + } + lsb = noiseMaskTable[bits] & 1; + coeff = 0x4000; } - for (last = 0; last + cycles <= diff; last += cycles) { - lsb = audio->ch4.lfsr & 1; + for (; last + cycles <= diff; last += cycles) { + lsb = (audio->ch4.lfsr ^ (audio->ch4.lfsr >> 1) ^ 1) & 1; audio->ch4.lfsr >>= 1; - audio->ch4.lfsr ^= lsb * coeff; + if (lsb) { + audio->ch4.lfsr |= coeff; + } else { + audio->ch4.lfsr &= ~coeff; + } ++samples; positiveSamples += lsb; } From 747158d5a6ba7dc6bf64d496fb24e562afb632ac Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 3 Jul 2023 16:08:52 -0700 Subject: [PATCH 257/290] Qt: Add exporting of SAV + RTC GB saves from Save Converter to strip RTC data --- CHANGES | 2 +- src/platform/qt/SaveConverter.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index fbc59a55e..e5b49f443 100644 --- a/CHANGES +++ b/CHANGES @@ -28,7 +28,7 @@ Misc: - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs - mGUI: Enable auto-softpatching (closes mgba.io/i/2899) - - Qt: Add exporting of SAV + RTC GBA saves from Save Converter to strip RTC data + - Qt: Add exporting of SAV + RTC saves from Save Converter to strip RTC data - Scripting: Add `callbacks:oneshot` for single-call callbacks 0.10.2: (2023-04-23) diff --git a/src/platform/qt/SaveConverter.cpp b/src/platform/qt/SaveConverter.cpp index 17f28bd34..e51a3f044 100644 --- a/src/platform/qt/SaveConverter.cpp +++ b/src/platform/qt/SaveConverter.cpp @@ -623,6 +623,11 @@ QList SaveConverter::AnnotatedSave::possibleConver } break; default: + if (size & 0xFF) { + AnnotatedSave noRtc = same; + noRtc.size &= ~0xFF; + possible.append(noRtc); + } break; } break; @@ -731,6 +736,9 @@ QByteArray SaveConverter::AnnotatedSave::convertTo(const SaveConverter::Annotate } break; default: + if (endianness == target.endianness && size > target.size) { + converted = backing->read(target.size); + } break; } break; From fd84cedddaeaac8f6d9a516ba211932d867b4883 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 3 Jul 2023 23:14:57 -0700 Subject: [PATCH 258/290] GBA SIO: Fix normal mode SI/SO semantics (fixes #2925) --- CHANGES | 1 + src/gba/sio/lockstep.c | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index e5b49f443..8295fbe79 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,7 @@ Emulation fixes: - GBA Audio: Fix sample timing drifting when changing sample interval - GBA Audio: Fix initial channel 3 wave RAM (fixes mgba.io/i/2947) - GBA BIOS: Fix clobbering registers with word-sized CpuSet + - GBA SIO: Fix normal mode SI/SO semantics (fixes mgba.io/i/2925) - GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) Other fixes: - Core: Fix inconsistencies with setting game-specific overrides (fixes mgba.io/i/2963) diff --git a/src/gba/sio/lockstep.c b/src/gba/sio/lockstep.c index 8e641c539..10f1d7f00 100644 --- a/src/gba/sio/lockstep.c +++ b/src/gba/sio/lockstep.c @@ -127,10 +127,10 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) { break; case SIO_NORMAL_8: case SIO_NORMAL_32: - 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)); + if (ATOMIC_ADD(node->p->attachedNormal, 1) > node->id + 1 && node->id > 0) { + 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.p->siocnt = GBASIONormalClearSi(node->d.p->siocnt); } node->d.writeRegister = GBASIOLockstepNodeNormalWriteRegister; break; @@ -514,20 +514,20 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive int attached; ATOMIC_LOAD(attached, node->p->attachedNormal); value &= 0xFF8B; - if (node->id < 3 && attached > node->id + 1) { - value = GBASIONormalSetSi(value, GBASIONormalGetIdleSo(node->p->players[node->id + 1]->d.p->siocnt)); + if (node->id > 0) { + value = GBASIONormalSetSi(value, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt)); } else { - value = GBASIONormalFillSi(value); + value = GBASIONormalClearSi(value); } enum mLockstepPhase transferActive; ATOMIC_LOAD(transferActive, node->p->d.transferActive); - if (node->id > 0 && transferActive == TRANSFER_IDLE) { + if (node->id < 3 && attached > node->id + 1 && 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)))) { + GBASIONormal nextSiocnct; + ATOMIC_LOAD(nextSiocnct, node->p->players[node->id + 1]->d.p->siocnt); + if (ATOMIC_CMPXCHG(node->p->players[node->id + 1]->d.p->siocnt, nextSiocnct, GBASIONormalSetSi(nextSiocnct, GBASIONormalGetIdleSo(value)))) { break; } } From 3f0d06e3070fece03efd5e68482d657b9e6dbcc7 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 4 Jul 2023 04:21:37 -0700 Subject: [PATCH 259/290] GBA: Unhandled bkpt should be treated as an undefined exception --- CHANGES | 1 + src/gba/gba.c | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 8295fbe79..92496052e 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,7 @@ Emulation fixes: - GB Serialize: Add missing Pocket Cam state to savestates - GB SIO: Disabling SIO should cancel pending transfers (fixes mgba.io/i/2537) - GB Video: Implement DMG-style sprite ordering + - GBA: Unhandled bkpt should be treated as an undefined exception - GBA Audio: Fix sample timing drifting when changing sample interval - GBA Audio: Fix initial channel 3 wave RAM (fixes mgba.io/i/2947) - GBA BIOS: Fix clobbering registers with word-sized CpuSet diff --git a/src/gba/gba.c b/src/gba/gba.c index e98f0102f..a839d0e7f 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -886,9 +886,6 @@ void GBAIllegal(struct ARMCore* cpu, uint32_t opcode) { void GBABreakpoint(struct ARMCore* cpu, int immediate) { struct GBA* gba = (struct GBA*) cpu->master; - if (immediate >= CPU_COMPONENT_MAX) { - return; - } switch (immediate) { #ifdef USE_DEBUGGERS case CPU_COMPONENT_DEBUGGER: @@ -899,6 +896,7 @@ void GBABreakpoint(struct ARMCore* cpu, int immediate) { .pointId = -1 }; mDebuggerEnter(gba->debugger->d.p, DEBUGGER_ENTER_BREAKPOINT, &info); + return; } break; #endif @@ -917,11 +915,13 @@ void GBABreakpoint(struct ARMCore* cpu, int immediate) { if (hook) { ARMRunFake(cpu, hook->patchedOpcode); } + return; } break; default: break; } + ARMRaiseUndefined(cpu); } void GBAFrameStarted(struct GBA* gba) { From 44e074a15e9651481f7f652ac006a7c9d58cbeb9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 4 Jul 2023 23:23:17 -0700 Subject: [PATCH 260/290] GBA BIOS: Add DACS compatibility to HLE BIOS (closes #2972) --- src/gba/hle-bios.c | 16 ++++++++++++---- src/gba/hle-bios.s | 31 ++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/gba/hle-bios.c b/src/gba/hle-bios.c index f82d01f64..841caf543 100644 --- a/src/gba/hle-bios.c +++ b/src/gba/hle-bios.c @@ -3,9 +3,9 @@ #include const uint8_t hleBios[GBA_SIZE_BIOS] = { - 0xd3, 0x00, 0x00, 0xea, 0x66, 0x00, 0x00, 0xea, 0x0c, 0x00, 0x00, 0xea, - 0xfe, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe1, - 0x59, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x00, 0x00, 0x00, 0x00, + 0xd3, 0x00, 0x00, 0xea, 0xe1, 0x00, 0x00, 0xea, 0x0c, 0x00, 0x00, 0xea, + 0xdf, 0x00, 0x00, 0xea, 0xde, 0x00, 0x00, 0xea, 0x00, 0x00, 0xa0, 0xe1, + 0x59, 0x00, 0x00, 0xea, 0xdb, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0xe3, 0x01, 0xd3, 0xa0, 0x03, @@ -78,5 +78,13 @@ const uint8_t hleBios[GBA_SIZE_BIOS] = { 0x00, 0x10, 0xa0, 0x13, 0x1e, 0xff, 0x2f, 0x11, 0x1c, 0xe0, 0x9f, 0xe5, 0x00, 0x10, 0x9e, 0xe5, 0x00, 0x00, 0x51, 0xe3, 0x00, 0x10, 0xa0, 0xe3, 0x1e, 0xff, 0x2f, 0x11, 0xc0, 0xe0, 0x4e, 0xe2, 0x1e, 0xff, 0x2f, 0xe1, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x29, 0xe1, 0xc0, 0x00, 0x00, 0x02 + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x29, 0xe1, 0xc0, 0x00, 0x00, 0x02, + 0x4c, 0xd0, 0x9f, 0xe5, 0x00, 0x50, 0x2d, 0xe9, 0x00, 0xc0, 0x4f, 0xe1, + 0x00, 0xe0, 0x0f, 0xe1, 0x00, 0x50, 0x2d, 0xe9, 0x02, 0xe3, 0xa0, 0xe3, + 0x9c, 0xc0, 0xde, 0xe5, 0xa5, 0x00, 0x5c, 0xe3, 0x04, 0x00, 0x00, 0x1a, + 0xb4, 0xc0, 0xde, 0xe5, 0x80, 0x00, 0x1c, 0xe3, 0x04, 0xe0, 0x8f, 0xe2, + 0x20, 0xf0, 0x9f, 0x15, 0x20, 0xf0, 0x9f, 0x05, 0x14, 0xd0, 0x9f, 0xe5, + 0x10, 0xc0, 0x1d, 0xe5, 0x0c, 0xf0, 0x69, 0xe1, 0x00, 0x50, 0x3d, 0xe9, + 0x04, 0xf0, 0x5e, 0xe2, 0x00, 0x00, 0x00, 0x00, 0x04, 0xe0, 0xa0, 0x03, + 0xf0, 0x7f, 0x00, 0x03, 0x00, 0x20, 0xfe, 0x09, 0x00, 0xc0, 0xff, 0x09 }; diff --git a/src/gba/hle-bios.s b/src/gba/hle-bios.s index 36e18790b..07f827b26 100644 --- a/src/gba/hle-bios.s +++ b/src/gba/hle-bios.s @@ -123,7 +123,7 @@ subs pc, lr, #4 .word 0 .word 0xE55EC002 -undefBase: +@ Padding for back compat subs pc, lr, #4 .word 0 .word 0x03A0E004 @@ -328,3 +328,32 @@ sub lr, #0xC0 bx lr .word 0 .word 0xE129F000 + +.ltorg + +undefBase: +pabtBase: +dabtBase: +fiqBase: +ldr sp, =0x03007FF0 +stmdb sp!, {r12, lr} +mrs r12, spsr +mrs lr, cpsr +stmdb sp!, {r12, lr} +mov lr, #0x08000000 +ldrb r12, [lr, #0x9C] +cmp r12, #0xA5 +bne 1f +ldrb r12, [lr, #0xB4] +tst r12, #0x80 +adr lr, 1f +ldrne pc, =0x09FE2000 +ldreq pc, =0x09FFC000 +1: +ldr sp, =0x03007FF0 +ldr r12, [sp, #-0x10] +msr spsr, r12 +ldmdb sp!, {r12, lr} +subs pc, lr, #4 +.word 0 +.word 0x03A0E004 From 00e62f231ad0e42288e503da130a0cdb535ceeb0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 5 Jul 2023 22:10:33 -0700 Subject: [PATCH 261/290] ARM: Fake bpkt instruction should take no cycles (fixes #2551) --- CHANGES | 1 + src/arm/isa-arm.c | 4 +++- src/arm/isa-thumb.c | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 92496052e..cfd866c9d 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,7 @@ Features: - Debugger: Add range watchpoints Emulation fixes: - ARM: Remove obsolete force-alignment in `bx pc` (fixes mgba.io/i/2964) + - ARM: Fake bpkt instruction should take no cycles (fixes mgba.io/i/2551) - GB Audio: Fix channels 1/2 staying muted if restarted after long silence - GB Audio: Fix channel 1 restarting if sweep applies after stop (fixes mgba.io/i/2965) - GB I/O: Read back proper SVBK value after writing 0 (fixes mgba.io/i/2921) diff --git a/src/arm/isa-arm.c b/src/arm/isa-arm.c index b3bb3c9da..7f158dc6e 100644 --- a/src/arm/isa-arm.c +++ b/src/arm/isa-arm.c @@ -663,7 +663,9 @@ DEFINE_INSTRUCTION_ARM(MRC, ARM_STUB) // Begin miscellaneous definitions -DEFINE_INSTRUCTION_ARM(BKPT, cpu->irqh.bkpt32(cpu, ((opcode >> 4) & 0xFFF0) | (opcode & 0xF))); // Not strictly in ARMv4T, but here for convenience +DEFINE_INSTRUCTION_ARM(BKPT, + cpu->irqh.bkpt32(cpu, ((opcode >> 4) & 0xFFF0) | (opcode & 0xF)); + currentCycles = 0;); // Not strictly in ARMv4T, but here for convenience DEFINE_INSTRUCTION_ARM(ILL, ARM_ILL) // Illegal opcode DEFINE_INSTRUCTION_ARM(MSR, diff --git a/src/arm/isa-thumb.c b/src/arm/isa-thumb.c index e2af3d179..0bc8fcb12 100644 --- a/src/arm/isa-thumb.c +++ b/src/arm/isa-thumb.c @@ -381,7 +381,9 @@ DEFINE_LOAD_STORE_MULTIPLE_THUMB(PUSHR, cpu->gprs[ARM_SP] = address) DEFINE_INSTRUCTION_THUMB(ILL, ARM_ILL) -DEFINE_INSTRUCTION_THUMB(BKPT, cpu->irqh.bkpt16(cpu, opcode & 0xFF);) +DEFINE_INSTRUCTION_THUMB(BKPT, + cpu->irqh.bkpt16(cpu, opcode & 0xFF); + currentCycles = 0;) // Not strictly in ARMv4T, but here for convenience DEFINE_INSTRUCTION_THUMB(B, int16_t immediate = (opcode & 0x07FF) << 5; cpu->gprs[ARM_PC] += (((int32_t) immediate) >> 4); From 0e2ede06bc681b1492e6a1d0ef3eadf272c3bdb6 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 10 Jul 2023 18:17:46 -0700 Subject: [PATCH 262/290] GBA: Fix hasOverride initialization --- src/gba/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gba/core.c b/src/gba/core.c index df40c6406..aa6128b26 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -205,6 +205,7 @@ static bool _GBACoreInit(struct mCore* core) { core->debugger = NULL; core->symbolTable = NULL; core->videoLogger = NULL; + gbacore->hasOverride = false; gbacore->overrides = NULL; gbacore->debuggerPlatform = NULL; gbacore->cheatDevice = NULL; From 4e55bc703c4e795ac8fdc36cd0130f4f30014279 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 10 Jul 2023 18:18:15 -0700 Subject: [PATCH 263/290] FFmpeg: Fix isampleRate initialization --- src/feature/ffmpeg/ffmpeg-encoder.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/feature/ffmpeg/ffmpeg-encoder.c b/src/feature/ffmpeg/ffmpeg-encoder.c index 9d61e5846..090614d1a 100644 --- a/src/feature/ffmpeg/ffmpeg-encoder.c +++ b/src/feature/ffmpeg/ffmpeg-encoder.c @@ -62,13 +62,13 @@ void FFmpegEncoderInit(struct FFmpegEncoder* encoder) { encoder->audioCodec = NULL; encoder->videoCodec = NULL; encoder->containerFormat = NULL; + encoder->isampleRate = PREFERRED_SAMPLE_RATE; FFmpegEncoderSetAudio(encoder, "flac", 0); FFmpegEncoderSetVideo(encoder, "libx264", 0, 0); FFmpegEncoderSetContainer(encoder, "matroska"); FFmpegEncoderSetDimensions(encoder, GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); encoder->iwidth = GBA_VIDEO_HORIZONTAL_PIXELS; encoder->iheight = GBA_VIDEO_VERTICAL_PIXELS; - encoder->isampleRate = PREFERRED_SAMPLE_RATE; encoder->frameskip = 1; encoder->skipResidue = 0; encoder->loop = false; From c49050fe653cf22ad490443fa58900a12570b689 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 16 Jul 2023 01:44:07 -0700 Subject: [PATCH 264/290] Qt: Let the getOpenFileName function class take an optional starting path --- src/platform/qt/GBAApp.cpp | 24 ++++++++++++++++++------ src/platform/qt/GBAApp.h | 6 +++--- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp index c7c380a7c..e21dbef05 100644 --- a/src/platform/qt/GBAApp.cpp +++ b/src/platform/qt/GBAApp.cpp @@ -169,10 +169,14 @@ void GBAApp::continueAll(const QList& paused) { } } -QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QString& filter) { +QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QString& filter, const QString& path) { QList paused; + QString base(path); + if (base.isNull()) { + base = m_configController->getOption("lastDirectory"); + } pauseAll(&paused); - QString filename = QFileDialog::getOpenFileName(owner, title, m_configController->getOption("lastDirectory"), filter); + QString filename = QFileDialog::getOpenFileName(owner, title, base, filter); continueAll(paused); if (!filename.isEmpty()) { m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath()); @@ -180,10 +184,14 @@ QString GBAApp::getOpenFileName(QWidget* owner, const QString& title, const QStr return filename; } -QStringList GBAApp::getOpenFileNames(QWidget* owner, const QString& title, const QString& filter) { +QStringList GBAApp::getOpenFileNames(QWidget* owner, const QString& title, const QString& filter, const QString& path) { QList paused; + QString base(path); + if (base.isNull()) { + base = m_configController->getOption("lastDirectory"); + } pauseAll(&paused); - QStringList filenames = QFileDialog::getOpenFileNames(owner, title, m_configController->getOption("lastDirectory"), filter); + QStringList filenames = QFileDialog::getOpenFileNames(owner, title, base, filter); continueAll(paused); if (!filenames.isEmpty()) { m_configController->setOption("lastDirectory", QFileInfo(filenames.at(0)).dir().canonicalPath()); @@ -191,10 +199,14 @@ QStringList GBAApp::getOpenFileNames(QWidget* owner, const QString& title, const return filenames; } -QString GBAApp::getSaveFileName(QWidget* owner, const QString& title, const QString& filter) { +QString GBAApp::getSaveFileName(QWidget* owner, const QString& title, const QString& filter, const QString& path) { QList paused; + QString base(path); + if (base.isNull()) { + base = m_configController->getOption("lastDirectory"); + } pauseAll(&paused); - QString filename = QFileDialog::getSaveFileName(owner, title, m_configController->getOption("lastDirectory"), filter); + QString filename = QFileDialog::getSaveFileName(owner, title, base, filter); continueAll(paused); if (!filename.isEmpty()) { m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath()); diff --git a/src/platform/qt/GBAApp.h b/src/platform/qt/GBAApp.h index 3110b0733..e9907685f 100644 --- a/src/platform/qt/GBAApp.h +++ b/src/platform/qt/GBAApp.h @@ -63,9 +63,9 @@ public: QList windows() { return m_windows; } - QString getOpenFileName(QWidget* owner, const QString& title, const QString& filter = {}); - QStringList getOpenFileNames(QWidget* owner, const QString& title, const QString& filter = {}); - QString getSaveFileName(QWidget* owner, const QString& title, const QString& filter = {}); + QString getOpenFileName(QWidget* owner, const QString& title, const QString& filter = {}, const QString& path = {}); + QStringList getOpenFileNames(QWidget* owner, const QString& title, const QString& filter = {}, const QString& path = {}); + QString getSaveFileName(QWidget* owner, const QString& title, const QString& filter = {}, const QString& path = {}); QString getOpenDirectoryName(QWidget* owner, const QString& title, const QString& path = {}); const NoIntroDB* gameDB() const { return m_db; } From 51d5f4bfd11b46a2931f40a769cd71a789a3f134 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 16 Jul 2023 01:45:04 -0700 Subject: [PATCH 265/290] Qt: Distribute shaders as zipped archives on supported builds --- CMakeLists.txt | 2 +- src/platform/qt/CMakeLists.txt | 14 +++++++++++++- src/platform/qt/ShaderSelector.cpp | 4 ++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bf638253..6407f7a81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.3) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/src/platform/cmake/") if(POLICY CMP0025) diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 0818fefbc..2ac06cc3c 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -327,7 +327,19 @@ if(NOT DEFINED DATADIR) endif() endif() if(BUILD_GL OR BUILD_GLES2 OR BUILD_EPOXY) - install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/shaders DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt) + if(NOT USE_LIBZIP AND NOT USE_MINIZIP) + install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/shaders DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt) + else() + file(GLOB SHADERS ${CMAKE_SOURCE_DIR}/res/shaders/*.shader) + message(STATUS ${SHADERS}) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/shaders) + foreach(SHADER_DIR ${SHADERS}) + get_filename_component(SHADER ${SHADER_DIR} NAME) + add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/shaders/${SHADER}" COMMAND "${CMAKE_COMMAND}" -E tar cf "${CMAKE_CURRENT_BINARY_DIR}/shaders/${SHADER}" --format=zip . WORKING_DIRECTORY "${SHADER_DIR}") + add_custom_target("${SHADER}" ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/shaders/${SHADER}") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/shaders/${SHADER}" DESTINATION ${DATADIR}/shaders COMPONENT ${BINARY_NAME}-qt) + endforeach() + endif() endif() if(ENABLE_SCRIPTING AND USE_LUA) install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/scripts DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt) diff --git a/src/platform/qt/ShaderSelector.cpp b/src/platform/qt/ShaderSelector.cpp index 885692edf..44bca3855 100644 --- a/src/platform/qt/ShaderSelector.cpp +++ b/src/platform/qt/ShaderSelector.cpp @@ -63,7 +63,11 @@ void ShaderSelector::clear() { void ShaderSelector::selectShader() { QDir path(GBAApp::dataDir()); path.cd(QLatin1String("shaders")); +#if !defined(USE_LIBZIP) && !defined(USE_MINIZIP) QString name = GBAApp::app()->getOpenDirectoryName(this, tr("Load shader"), path.absolutePath()); +#else + QString name = GBAApp::app()->getOpenFileName(this, tr("Load shader"), "mGBA Shaders (*.shader)", path.absolutePath()); +#endif if (!name.isNull()) { loadShader(name); refreshShaders(); From 90b75e4c11f5459f9c52f98fa36664131bb7da16 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 16 Jul 2023 15:27:39 -0700 Subject: [PATCH 266/290] Qt: Const correctness --- src/platform/qt/MultiplayerController.cpp | 2 +- src/platform/qt/MultiplayerController.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/qt/MultiplayerController.cpp b/src/platform/qt/MultiplayerController.cpp index 618efd1f6..2a972b63d 100644 --- a/src/platform/qt/MultiplayerController.cpp +++ b/src/platform/qt/MultiplayerController.cpp @@ -338,7 +338,7 @@ void MultiplayerController::detachGame(CoreController* controller) { emit gameDetached(); } -int MultiplayerController::playerId(CoreController* controller) { +int MultiplayerController::playerId(CoreController* controller) const { for (int i = 0; i < m_players.count(); ++i) { if (m_players[i].controller == controller) { return i; diff --git a/src/platform/qt/MultiplayerController.h b/src/platform/qt/MultiplayerController.h index 35cae13bb..ef5727628 100644 --- a/src/platform/qt/MultiplayerController.h +++ b/src/platform/qt/MultiplayerController.h @@ -38,7 +38,7 @@ public: void detachGame(CoreController*); int attached(); - int playerId(CoreController*); + int playerId(CoreController*) const; signals: void gameAttached(); From 436d6c5a08197bf680ed9ca829dbdfa5d0ee1ca9 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 16 Jul 2023 18:40:38 -0700 Subject: [PATCH 267/290] Qt: Clean up multiplayer attaching/detaching --- src/platform/qt/InputController.cpp | 21 +++- src/platform/qt/InputController.h | 6 +- src/platform/qt/MultiplayerController.cpp | 118 ++++++++++++++-------- src/platform/qt/MultiplayerController.h | 14 +-- src/platform/qt/Window.cpp | 10 +- 5 files changed, 112 insertions(+), 57 deletions(-) diff --git a/src/platform/qt/InputController.cpp b/src/platform/qt/InputController.cpp index 3af600a64..21d021edc 100644 --- a/src/platform/qt/InputController.cpp +++ b/src/platform/qt/InputController.cpp @@ -25,9 +25,11 @@ using namespace QGBA; -InputController::InputController(int playerId, QWidget* topLevel, QObject* parent) +int InputController::s_claimedPlayers = 0; + +InputController::InputController(QWidget* topLevel, QObject* parent) : QObject(parent) - , m_playerId(playerId) + , m_playerId(claimPlayer()) , m_topLevel(topLevel) , m_focusParent(topLevel) { @@ -130,6 +132,7 @@ InputController::InputController(int playerId, QWidget* topLevel, QObject* paren InputController::~InputController() { mInputMapDeinit(&m_inputMap); + freePlayer(m_playerId); } void InputController::addInputDriver(std::shared_ptr driver) { @@ -550,6 +553,20 @@ bool InputController::hasPendingEvent(int key) const { return m_pendingEvents.contains(key); } +int InputController::claimPlayer() { + for (int i = 0; i < MAX_GBAS; ++i) { + if (!(s_claimedPlayers & (1 << i))) { + s_claimedPlayers |= 1 << i; + return i; + } + } + qFatal("Can't claim 5th player. Please report this bug."); +} + +void InputController::freePlayer(int player) { + s_claimedPlayers &= ~(1 << player); +} + void InputController::stealFocus(QWidget* focus) { m_focusParent = focus; } diff --git a/src/platform/qt/InputController.h b/src/platform/qt/InputController.h index 936b211eb..5b7a01a44 100644 --- a/src/platform/qt/InputController.h +++ b/src/platform/qt/InputController.h @@ -53,7 +53,7 @@ public: static const uint32_t KEYBOARD = 0x51545F4B; - InputController(int playerId = 0, QWidget* topLevel = nullptr, QObject* parent = nullptr); + InputController(QWidget* topLevel = nullptr, QObject* parent = nullptr); ~InputController(); void addInputDriver(std::shared_ptr); @@ -140,6 +140,9 @@ private: bool hasPendingEvent(int key) const; void sendGamepadEvent(QEvent*); + static int claimPlayer(); + static void freePlayer(int); + Gamepad* gamepad(uint32_t type); QList gamepads(); @@ -172,6 +175,7 @@ private: #endif #endif + static int s_claimedPlayers; mInputMap m_inputMap; ConfigController* m_config = nullptr; int m_playerId; diff --git a/src/platform/qt/MultiplayerController.cpp b/src/platform/qt/MultiplayerController.cpp index 2a972b63d..fb3c0195f 100644 --- a/src/platform/qt/MultiplayerController.cpp +++ b/src/platform/qt/MultiplayerController.cpp @@ -6,6 +6,7 @@ #include "MultiplayerController.h" #include "CoreController.h" +#include "LogController.h" #ifdef M_CORE_GBA #include @@ -18,21 +19,10 @@ using namespace QGBA; -#ifdef M_CORE_GB -MultiplayerController::Player::Player(CoreController* coreController, GBSIOLockstepNode* gbNode) +MultiplayerController::Player::Player(CoreController* coreController) : controller(coreController) { - node.gb = gbNode; } -#endif - -#ifdef M_CORE_GBA -MultiplayerController::Player::Player(CoreController* coreController, GBASIOLockstepNode* gbaNode) - : controller(coreController) -{ - node.gba = gbaNode; -} -#endif int MultiplayerController::Player::id() const { switch (controller->platform()) { @@ -67,7 +57,7 @@ MultiplayerController::MultiplayerController() { }; m_lockstep.signal = [](mLockstep* lockstep, unsigned mask) { MultiplayerController* controller = static_cast(lockstep->context); - Player* player = &controller->m_players[0]; + Player* player = controller->player(0); bool woke = false; player->waitMask &= ~mask; if (!player->waitMask && player->awake < 1) { @@ -79,7 +69,7 @@ MultiplayerController::MultiplayerController() { }; m_lockstep.wait = [](mLockstep* lockstep, unsigned mask) { MultiplayerController* controller = static_cast(lockstep->context); - Player* player = &controller->m_players[0]; + Player* player = controller->player(0); bool slept = false; player->waitMask |= mask; if (player->awake > 0) { @@ -214,6 +204,12 @@ MultiplayerController::~MultiplayerController() { } bool MultiplayerController::attachGame(CoreController* controller) { + QList interrupters; + interrupters.append(controller); + for (Player& p : m_pids.values()) { + interrupters.append(p.controller); + } + if (m_lockstep.attached == 0) { switch (controller->platform()) { #ifdef M_CORE_GBA @@ -239,6 +235,7 @@ bool MultiplayerController::attachGame(CoreController* controller) { return false; } + Player player{controller}; switch (controller->platform()) { #ifdef M_CORE_GBA case mPLATFORM_GBA: { @@ -251,13 +248,11 @@ bool MultiplayerController::attachGame(CoreController* controller) { GBASIOLockstepNode* node = new GBASIOLockstepNode; GBASIOLockstepNodeCreate(node); GBASIOLockstepAttachNode(&m_gbaLockstep, node); - m_players.append({controller, node}); + player.node.gba = node; GBASIOSetDriver(&gba->sio, &node->d, SIO_MULTI); GBASIOSetDriver(&gba->sio, &node->d, SIO_NORMAL_32); - - emit gameAttached(); - return true; + break; } #endif #ifdef M_CORE_GB @@ -271,19 +266,22 @@ bool MultiplayerController::attachGame(CoreController* controller) { GBSIOLockstepNode* node = new GBSIOLockstepNode; GBSIOLockstepNodeCreate(node); GBSIOLockstepAttachNode(&m_gbLockstep, node); - m_players.append({controller, node}); + player.node.gb = node; GBSIOSetDriver(&gb->sio, &node->d); - - emit gameAttached(); - return true; + break; } #endif default: - break; + return false; } - return false; + m_pids.insert(m_nextPid, player); + ++m_nextPid; + fixOrder(); + + emit gameAttached(); + return true; } void MultiplayerController::detachGame(CoreController* controller) { @@ -296,8 +294,18 @@ void MultiplayerController::detachGame(CoreController* controller) { } QList interrupters; + int pid = -1; for (int i = 0; i < m_players.count(); ++i) { - interrupters.append(m_players[i].controller); + Player* p = player(i); + if (!p) { + LOG(QT, ERROR) << tr("Trying to detach a multiplayer player that's not attached"); + return; + } + CoreController* playerController = p->controller; + if (playerController == controller) { + pid = m_players[i]; + } + interrupters.append(playerController); } switch (controller->platform()) { #ifdef M_CORE_GBA @@ -329,18 +337,23 @@ void MultiplayerController::detachGame(CoreController* controller) { break; } - for (int i = 0; i < m_players.count(); ++i) { - if (m_players[i].controller == controller) { - m_players.removeAt(i); - break; - } + m_pids.remove(pid); + if (m_pids.size() == 0) { + m_platform = mPLATFORM_NONE; + } else { + fixOrder(); } emit gameDetached(); } int MultiplayerController::playerId(CoreController* controller) const { for (int i = 0; i < m_players.count(); ++i) { - if (m_players[i].controller == controller) { + const Player* p = player(i); + if (!p) { + LOG(QT, ERROR) << tr("Trying to get player ID for a multiplayer player that's not attached"); + return -1; + } + if (p->controller == controller) { return i; } } @@ -354,27 +367,52 @@ int MultiplayerController::attached() { } MultiplayerController::Player* MultiplayerController::player(int id) { - Player* player = &m_players[id]; - switch (player->controller->platform()) { + if (id >= m_players.size()) { + return nullptr; + } + int pid = m_players[id]; + auto iter = m_pids.find(pid); + if (iter == m_pids.end()) { + return nullptr; + } + return &iter.value(); +} + +const MultiplayerController::Player* MultiplayerController::player(int id) const { + if (id >= m_players.size()) { + return nullptr; + } + int pid = m_players[id]; + auto iter = m_pids.find(pid); + if (iter == m_pids.end()) { + return nullptr; + } + return &iter.value(); +} + +void MultiplayerController::fixOrder() { + m_players.clear(); + m_players = m_pids.keys(); + std::sort(m_players.begin(), m_players.end()); + switch (m_platform) { #ifdef M_CORE_GBA case mPLATFORM_GBA: - if (player->node.gba->id != id) { - std::sort(m_players.begin(), m_players.end()); - player = &m_players[id]; + for (int pid : m_pids.keys()) { + Player& p = m_pids.find(pid).value(); + GBA* gba = static_cast(p.controller->thread()->core->board); + GBASIOLockstepNode* node = reinterpret_cast(gba->sio.drivers.multiplayer); + m_players[node->id] = pid; } break; #endif #ifdef M_CORE_GB case mPLATFORM_GB: - if (player->node.gb->id != id) { + if (player(0)->node.gb->id == 1) { std::swap(m_players[0], m_players[1]); - player = &m_players[id]; } break; #endif case mPLATFORM_NONE: break; } - - return player; } diff --git a/src/platform/qt/MultiplayerController.h b/src/platform/qt/MultiplayerController.h index ef5727628..10cd63c6b 100644 --- a/src/platform/qt/MultiplayerController.h +++ b/src/platform/qt/MultiplayerController.h @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #pragma once +#include #include #include #include @@ -50,12 +51,7 @@ private: GBASIOLockstepNode* gba; }; struct Player { -#ifdef M_CORE_GB - Player(CoreController* controller, GBSIOLockstepNode* node); -#endif -#ifdef M_CORE_GBA - Player(CoreController* controller, GBASIOLockstepNode* node); -#endif + Player(CoreController* controller); int id() const; bool operator<(const Player&) const; @@ -68,6 +64,8 @@ private: }; Player* player(int id); + const Player* player(int id) const; + void fixOrder(); union { mLockstep m_lockstep; @@ -80,7 +78,9 @@ private: }; mPlatform m_platform = mPLATFORM_NONE; - QList m_players; + int m_nextPid = 0; + QHash m_pids; + QList m_players; QMutex m_lock; }; diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 0a2ebb73b..c0a4231fe 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -92,7 +92,7 @@ Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWi , m_logView(new LogView(&m_log, this)) , m_screenWidget(new WindowBackground()) , m_config(config) - , m_inputController(playerId, this) + , m_inputController(this) , m_shortcutController(new ShortcutController(this)) , m_playerId(playerId) { @@ -257,12 +257,7 @@ void Window::resizeFrame(const QSize& size) { void Window::updateMultiplayerStatus(bool canOpenAnother) { m_multiWindow->setEnabled(canOpenAnother); - if (m_controller) { - MultiplayerController* multiplayer = m_controller->multiplayerController(); - if (multiplayer) { - m_playerId = multiplayer->playerId(m_controller.get()); - } - } + multiplayerChanged(); } void Window::updateMultiplayerActive(bool active) { @@ -412,6 +407,7 @@ void Window::multiplayerChanged() { MultiplayerController* multiplayer = m_controller->multiplayerController(); if (multiplayer) { attached = multiplayer->attached(); + m_playerId = multiplayer->playerId(m_controller.get()); } for (Action* action : m_nonMpActions) { action->setEnabled(attached < 2); From 52ef5844248071b5458c5b63d801ded61ad44828 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 16 Jul 2023 20:41:48 -0700 Subject: [PATCH 268/290] Updater: Fix overwriting directories with files --- src/feature/updater-main.c | 58 +++++++++++++++++++++++++++++++--- src/platform/windows/vfs-w32.c | 9 ++++-- src/util/vfs/vfs-dirent.c | 2 +- 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/feature/updater-main.c b/src/feature/updater-main.c index e24fef593..c52a52876 100644 --- a/src/feature/updater-main.c +++ b/src/feature/updater-main.c @@ -31,6 +31,39 @@ FILE* logfile; +bool rmdirRecursive(struct VDir* dir) { + if (!dir) { + return false; + } + bool ok = true; + struct VDirEntry* vde; + while ((vde = dir->listNext(dir))) { + const char* name = vde->name(vde); + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { + continue; + } + switch (vde->type(vde)) { + case VFS_DIRECTORY: + fprintf(logfile, "cd %s\n", name); + if (!rmdirRecursive(dir->openDir(dir, name))) { + ok = false; + } + fprintf(logfile, "cd ..\n"); + // Fall through + case VFS_FILE: + case VFS_UNKNOWN: + fprintf(logfile, "rm %s\n", name); + if (!dir->deleteFile(dir, name)) { + fprintf(logfile, "error\n"); + ok = false; + } + break; + } + } + dir->close(dir); + return ok; +} + bool extractArchive(struct VDir* archive, const char* root, bool prefix) { char path[PATH_MAX] = {0}; struct VDirEntry* vde; @@ -57,11 +90,13 @@ bool extractArchive(struct VDir* archive, const char* root, bool prefix) { case VFS_DIRECTORY: fprintf(logfile, "mkdir %s\n", fname); if (mkdir(path, 0755) < 0 && errno != EEXIST) { + fprintf(logfile, "error %i\n", errno); return false; } if (!prefix) { struct VDir* subdir = archive->openDir(archive, fname); if (!subdir) { + fprintf(logfile, "error\n"); return false; } if (!extractArchive(subdir, path, false)) { @@ -76,13 +111,28 @@ bool extractArchive(struct VDir* archive, const char* root, bool prefix) { vfIn = archive->openFile(archive, vde->name(vde), O_RDONLY); errno = 0; vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); - if (!vfOut && errno == EACCES) { + if (!vfOut) { + if (errno == EACCES) { #ifdef _WIN32 - Sleep(1000); + Sleep(1000); #else - sleep(1); + sleep(1); #endif - vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); + vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); + } else if (errno == EISDIR) { + fprintf(logfile, "rm -r %s\n", path); + if (!rmdirRecursive(VDirOpen(path))) { + return false; + } +#ifdef _WIN32 + wchar_t wpath[MAX_PATH + 1]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH); + RemoveDirectoryW(wpath); +#else + rmdir(path); +#endif + vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC); + } } if (!vfOut) { vfIn->close(vfIn); diff --git a/src/platform/windows/vfs-w32.c b/src/platform/windows/vfs-w32.c index bfa2e30a3..b160bc588 100644 --- a/src/platform/windows/vfs-w32.c +++ b/src/platform/windows/vfs-w32.c @@ -151,7 +151,12 @@ bool _vdwDeleteFile(struct VDir* vd, const char* path) { MultiByteToWideChar(CP_UTF8, 0, path, -1, pathw, MAX_PATH); StringCchPrintfW(combined, MAX_PATH, L"%ws\\%ws", dir, pathw); - return DeleteFileW(combined); + DWORD attrs = GetFileAttributesW(combined); + if (attrs & FILE_ATTRIBUTE_DIRECTORY) { + return RemoveDirectoryW(combined); + } else { + return DeleteFileW(combined); + } } const char* _vdweName(struct VDirEntry* vde) { @@ -183,4 +188,4 @@ bool VDirCreate(const char* path) { return true; } return false; -} \ No newline at end of file +} diff --git a/src/util/vfs/vfs-dirent.c b/src/util/vfs/vfs-dirent.c index 610482313..806148b89 100644 --- a/src/util/vfs/vfs-dirent.c +++ b/src/util/vfs/vfs-dirent.c @@ -133,7 +133,7 @@ bool _vdDeleteFile(struct VDir* vd, const char* path) { char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); sprintf(combined, "%s%s%s", dir, PATH_SEP, path); - bool ret = !unlink(combined); + bool ret = !remove(combined); free(combined); return ret; } From c0507b8a71e16bc7318c2e168acceadae85ab8bb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 16 Jul 2023 22:02:18 -0700 Subject: [PATCH 269/290] Qt: Fix leak if loading a save file fails --- src/platform/qt/CoreController.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 018b19ec0..984d468ba 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -808,10 +808,14 @@ void CoreController::loadSave(const QString& path, bool temporary) { return; } + bool ok; if (temporary) { - m_threadContext.core->loadTemporarySave(m_threadContext.core, vf); + ok = m_threadContext.core->loadTemporarySave(m_threadContext.core, vf); } else { - m_threadContext.core->loadSave(m_threadContext.core, vf); + ok = m_threadContext.core->loadSave(m_threadContext.core, vf); + } + if (!ok) { + vf->close(vf); } }); if (hasStarted()) { @@ -821,10 +825,14 @@ void CoreController::loadSave(const QString& path, bool temporary) { void CoreController::loadSave(VFile* vf, bool temporary) { m_resetActions.append([this, vf, temporary]() { + bool ok; if (temporary) { - m_threadContext.core->loadTemporarySave(m_threadContext.core, vf); + ok = m_threadContext.core->loadTemporarySave(m_threadContext.core, vf); } else { - m_threadContext.core->loadSave(m_threadContext.core, vf); + ok = m_threadContext.core->loadSave(m_threadContext.core, vf); + } + if (!ok) { + vf->close(vf); } }); if (hasStarted()) { From 4b38883b6ab26283b521ea712956a8854e713e4c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 16 Jul 2023 22:28:23 -0700 Subject: [PATCH 270/290] Qt: Plumb through some path info into the CoreController --- src/platform/qt/CoreController.cpp | 19 ++++++++++++++++--- src/platform/qt/CoreController.h | 11 ++++++++++- src/platform/qt/CoreManager.cpp | 2 ++ src/platform/qt/ROMInfo.cpp | 7 +++++++ src/platform/qt/ROMInfo.ui | 14 ++++++++++++++ 5 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index 984d468ba..db29af538 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -231,6 +231,11 @@ CoreController::~CoreController() { m_threadContext.core->deinit(m_threadContext.core); } +void CoreController::setPath(const QString& path, const QString& base) { + m_path = path; + m_baseDirectory = base; +} + const color_t* CoreController::drawContext() { if (m_hwaccel) { return nullptr; @@ -816,6 +821,8 @@ void CoreController::loadSave(const QString& path, bool temporary) { } if (!ok) { vf->close(vf); + } else { + m_savePath = path; } }); if (hasStarted()) { @@ -823,8 +830,8 @@ void CoreController::loadSave(const QString& path, bool temporary) { } } -void CoreController::loadSave(VFile* vf, bool temporary) { - m_resetActions.append([this, vf, temporary]() { +void CoreController::loadSave(VFile* vf, bool temporary, const QString& path) { + m_resetActions.append([this, vf, temporary, path]() { bool ok; if (temporary) { ok = m_threadContext.core->loadTemporarySave(m_threadContext.core, vf); @@ -833,6 +840,8 @@ void CoreController::loadSave(VFile* vf, bool temporary) { } if (!ok) { vf->close(vf); + } else { + m_savePath = path; } }); if (hasStarted()) { @@ -1267,7 +1276,11 @@ void CoreController::updatePlayerSave() { QByteArray saveSuffixBin(saveSuffix.toUtf8()); VFile* save = mDirectorySetOpenSuffix(&m_threadContext.core->dirs, m_threadContext.core->dirs.save, saveSuffixBin.constData(), O_CREAT | O_RDWR); if (save) { - m_threadContext.core->loadSave(m_threadContext.core, save); + if (!m_threadContext.core->loadSave(m_threadContext.core, save)) { + save->close(save); + } else { + m_savePath = QString::fromUtf8(m_threadContext.core->dirs.baseName) + saveSuffix; + } } } diff --git a/src/platform/qt/CoreController.h b/src/platform/qt/CoreController.h index 1dd211d44..c46b4d6e9 100644 --- a/src/platform/qt/CoreController.h +++ b/src/platform/qt/CoreController.h @@ -82,6 +82,11 @@ public: mCoreThread* thread() { return &m_threadContext; } + void setPath(const QString& path, const QString& base = {}); + QString path() const { return m_path; } + QString baseDirectory() const { return m_baseDirectory; } + QString savePath() const { return m_savePath; } + const color_t* drawContext(); QImage getPixels(); @@ -162,7 +167,7 @@ public slots: void saveBackupState(); void loadSave(const QString&, bool temporary); - void loadSave(VFile*, bool temporary); + void loadSave(VFile*, bool temporary, const QString& path = {}); void loadPatch(const QString&); void scanCard(const QString&); void scanCards(const QStringList&); @@ -249,6 +254,10 @@ private: CoreController* self; } m_logger{}; + QString m_path; + QString m_baseDirectory; + QString m_savePath; + bool m_patched = false; bool m_preload = false; diff --git a/src/platform/qt/CoreManager.cpp b/src/platform/qt/CoreManager.cpp index 7bd6bd309..b2f7cabf0 100644 --- a/src/platform/qt/CoreManager.cpp +++ b/src/platform/qt/CoreManager.cpp @@ -122,6 +122,7 @@ CoreController* CoreManager::loadGame(VFile* vf, const QString& path, const QStr if (m_multiplayer) { cc->setMultiplayerController(m_multiplayer); } + cc->setPath(path, info.dir().canonicalPath()); emit coreLoaded(cc); return cc; } @@ -171,6 +172,7 @@ CoreController* CoreManager::loadBIOS(int platform, const QString& path) { if (m_multiplayer) { cc->setMultiplayerController(m_multiplayer); } + cc->setPath(path, info.dir().canonicalPath()); emit coreLoaded(cc); return cc; } diff --git a/src/platform/qt/ROMInfo.cpp b/src/platform/qt/ROMInfo.cpp index f690982c4..473918f26 100644 --- a/src/platform/qt/ROMInfo.cpp +++ b/src/platform/qt/ROMInfo.cpp @@ -62,4 +62,11 @@ ROMInfo::ROMInfo(std::shared_ptr controller, QWidget* parent) m_ui.crc->setText(tr("(unknown)")); m_ui.name->setText(tr("(unknown)")); } + + QString savePath = controller->savePath(); + if (!savePath.isEmpty()) { + m_ui.savefile->setText(savePath); + } else { + m_ui.savefile->setText(tr("(unknown)")); + } } diff --git a/src/platform/qt/ROMInfo.ui b/src/platform/qt/ROMInfo.ui index c458a7a07..727cff032 100644 --- a/src/platform/qt/ROMInfo.ui +++ b/src/platform/qt/ROMInfo.ui @@ -108,6 +108,20 @@ + + + + Save file: + + + + + + + {SAVEFILE} + + + From 1e68020d1c7d149c424d1ca22e755c1234cf4632 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 17 Jul 2023 21:34:37 -0700 Subject: [PATCH 271/290] Qt: Handle multiple save game files for disparate games separately (fixes #2887) --- CHANGES | 1 + src/platform/qt/CoreController.cpp | 12 +----- src/platform/qt/MultiplayerController.cpp | 52 +++++++++++++++++++++++ src/platform/qt/MultiplayerController.h | 3 ++ 4 files changed, 58 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index cfd866c9d..3a67c7be5 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,7 @@ Misc: - GBA: Improve detection of valid ELF ROMs - mGUI: Enable auto-softpatching (closes mgba.io/i/2899) - Qt: Add exporting of SAV + RTC saves from Save Converter to strip RTC data + - Qt: Handle multiple save game files for disparate games separately (fixes mgba.io/i/2887) - Scripting: Add `callbacks:oneshot` for single-call callbacks 0.10.2: (2023-04-23) diff --git a/src/platform/qt/CoreController.cpp b/src/platform/qt/CoreController.cpp index db29af538..6b21f2c8e 100644 --- a/src/platform/qt/CoreController.cpp +++ b/src/platform/qt/CoreController.cpp @@ -878,6 +878,7 @@ void CoreController::replaceGame(const QString& path) { } else { mCoreLoadFile(m_threadContext.core, fname.toUtf8().constData()); } + // TODO: Make sure updating the path is handled properly by everything that calls path() and baseDirectory() updateROMInfo(); } @@ -1256,16 +1257,7 @@ void CoreController::finishFrame() { } void CoreController::updatePlayerSave() { - int savePlayerId = 0; - mCoreConfigGetIntValue(&m_threadContext.core->config, "savePlayerId", &savePlayerId); - if (savePlayerId == 0 || m_multiplayer->attached() > 1) { - if (savePlayerId == m_multiplayer->playerId(this) + 1) { - // Player 1 is using our save, so let's use theirs, at least for now. - savePlayerId = 1; - } else { - savePlayerId = m_multiplayer->playerId(this) + 1; - } - } + int savePlayerId = m_multiplayer->saveId(this); QString saveSuffix; if (savePlayerId < 2) { diff --git a/src/platform/qt/MultiplayerController.cpp b/src/platform/qt/MultiplayerController.cpp index fb3c0195f..972213f67 100644 --- a/src/platform/qt/MultiplayerController.cpp +++ b/src/platform/qt/MultiplayerController.cpp @@ -276,6 +276,32 @@ bool MultiplayerController::attachGame(CoreController* controller) { return false; } + QPair path(controller->path(), controller->baseDirectory()); + int claimed = m_claimed[path]; + + int saveId = 0; + mCoreConfigGetIntValue(&controller->thread()->core->config, "savePlayerId", &saveId); + + if (claimed) { + player.saveId = 0; + for (int i = 0; i < MAX_GBAS; ++i) { + if (claimed & (1 << i)) { + continue; + } + player.saveId = i + 1; + break; + } + if (!player.saveId) { + LOG(QT, ERROR) << "Couldn't find available save ID"; + player.saveId = 1; + } + } else if (saveId) { + player.saveId = saveId; + } else { + player.saveId = 1; + } + m_claimed[path] |= 1 << (player.saveId - 1); + m_pids.insert(m_nextPid, player); ++m_nextPid; fixOrder(); @@ -337,6 +363,18 @@ void MultiplayerController::detachGame(CoreController* controller) { break; } + // TODO: This might change if we replace the ROM--make sure to handle this properly + QPair path(controller->path(), controller->baseDirectory()); + Player& p = m_pids.find(pid).value(); + if (!p.saveId) { + LOG(QT, ERROR) << "Clearing invalid save ID"; + } else { + m_claimed[path] &= ~(1 << (p.saveId - 1)); + if (!m_claimed[path]) { + m_claimed.remove(path); + } + } + m_pids.remove(pid); if (m_pids.size() == 0) { m_platform = mPLATFORM_NONE; @@ -360,6 +398,20 @@ int MultiplayerController::playerId(CoreController* controller) const { return -1; } +int MultiplayerController::saveId(CoreController* controller) const { + for (int i = 0; i < m_players.count(); ++i) { + const Player* p = player(i); + if (!p) { + LOG(QT, ERROR) << tr("Trying to get save ID for a multiplayer player that's not attached"); + return -1; + } + if (p->controller == controller) { + return p->saveId; + } + } + return -1; +} + int MultiplayerController::attached() { int num; num = m_lockstep.attached; diff --git a/src/platform/qt/MultiplayerController.h b/src/platform/qt/MultiplayerController.h index 10cd63c6b..b5582319f 100644 --- a/src/platform/qt/MultiplayerController.h +++ b/src/platform/qt/MultiplayerController.h @@ -40,6 +40,7 @@ public: int attached(); int playerId(CoreController*) const; + int saveId(CoreController*) const; signals: void gameAttached(); @@ -61,6 +62,7 @@ private: int awake = 1; int32_t cyclesPosted = 0; unsigned waitMask = 0; + int saveId = 1; }; Player* player(int id); @@ -82,6 +84,7 @@ private: QHash m_pids; QList m_players; QMutex m_lock; + QHash, int> m_claimed; }; } From c8cfaefcc880e668b0db8ba5f8b49978f0d108fd Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 18 Jul 2023 01:42:30 -0700 Subject: [PATCH 272/290] Updater: Fix overwriting files with directories --- src/feature/updater-main.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/feature/updater-main.c b/src/feature/updater-main.c index c52a52876..4a05bf290 100644 --- a/src/feature/updater-main.c +++ b/src/feature/updater-main.c @@ -89,9 +89,27 @@ bool extractArchive(struct VDir* archive, const char* root, bool prefix) { switch (vde->type(vde)) { case VFS_DIRECTORY: fprintf(logfile, "mkdir %s\n", fname); - if (mkdir(path, 0755) < 0 && errno != EEXIST) { - fprintf(logfile, "error %i\n", errno); - return false; + if (mkdir(path, 0755) < 0) { + bool redo = false; + if (errno == EEXIST) { + struct stat st; + if (stat(path, &st) >= 0 && !S_ISDIR(st.st_mode)) { +#ifdef _WIN32 + wchar_t wpath[MAX_PATH + 1]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH); + DeleteFileW(wpath); +#else + unlink(path); +#endif + if (mkdir(path, 0755) >= 0) { + redo = true; + } + } + } + if (!redo) { + fprintf(logfile, "error %i\n", errno); + return false; + } } if (!prefix) { struct VDir* subdir = archive->openDir(archive, fname); From 369eab8da3953bfa791df49d098b059caadb7e4a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 23 Jul 2023 12:37:59 -0700 Subject: [PATCH 273/290] Util: Fix alpha blending calculation --- include/mgba-util/image.h | 115 ++++++++++++++++++++++++++++---------- src/util/test/color.c | 4 +- 2 files changed, 88 insertions(+), 31 deletions(-) diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index 8ec91b16a..71f53fb68 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -271,51 +271,108 @@ ATTRIBUTE_UNUSED static unsigned mColorMix5Bit(int weightA, unsigned colorA, int } ATTRIBUTE_UNUSED static uint32_t mColorMixARGB8(uint32_t colorA, uint32_t colorB) { - uint32_t alpha = colorA >> 24; - if (!alpha) { + uint32_t alphaA = colorA >> 24; + if (!alphaA) { return colorB; } - + uint32_t alphaB = colorB >> 24; uint32_t color = 0; + +#if 1 + // TODO: Benchmark integer and float versions uint32_t a, b; - a = colorA & 0xFF00FF; - a *= alpha + 1; - color += (a >> 8) & 0xFF00FF; + uint32_t alpha = (alphaA * 0xFF) + alphaB * (0xFF - alphaA); - a = colorB & 0xFF00FF; - a *= 0x100 - alpha; - color += (a >> 8) & 0xFF00FF; + a = colorA & 0xFF; + a *= alphaA * 0xFF; + b = a; - if (color & 0x100) { - color &= ~0xFF; + a = colorB & 0xFF; + a *= alphaB * (0xFF - alphaA); + b += a; + + b /= alpha; + if (b > 0xFF) { color |= 0xFF; + } else { + color |= b; } - if (color & 0x1000000) { - color &= ~0xFF0000; + + a = (colorA >> 8) & 0xFF; + a *= alphaA * 0xFF; + b = a; + + a = (colorB >> 8) & 0xFF; + a *= alphaB * (0xFF - alphaA); + b += a; + + b /= alpha; + if (b > 0xFF) { + color |= 0xFF00; + } else { + color |= b << 8; + } + + a = (colorA >> 16) & 0xFF; + a *= alphaA * 0xFF; + b = a; + + a = (colorB >> 16) & 0xFF; + a *= alphaB * (0xFF - alphaA); + b += a; + + b /= alpha; + if (b > 0xFF) { color |= 0xFF0000; + } else { + color |= b << 16; } - b = 0; - a = colorA & 0xFF00; - a *= alpha + 1; - b += a & 0xFF0000; - - a = colorB & 0xFF00; - a *= 0x100 - alpha; - b += a & 0xFF0000; - - if (b & 0x1000000) { - b &= ~0xFF0000; - b |= 0xFF0000; - } - color |= b >> 8; - - alpha += colorB >> 24; + alpha /= 0xFF; if (alpha > 0xFF) { color |= 0xFF000000; } else { color |= alpha << 24; } +#else + float ca, aa; + float cb, ab; + + static const float r255 = 1 / 255.f; + aa = alphaA * r255; + ab = alphaB * r255; + float alpha = aa + ab * (1.f - aa); + float ralpha = 1.f / alpha; + alpha = alpha * 255.f; + color = ((int) alpha) << 24; + + ca = ((colorA >> 16) & 0xFF) * r255; + cb = ((colorB >> 16) & 0xFF) * r255; + ca = ca * aa + cb * ab * (1.f - aa); + ca = ca * ralpha * 255.f; + if (ca > 255.f) { + ca = 255.f; + } + color |= ((int) ca) << 16; + + ca = ((colorA >> 8) & 0xFF) * r255; + cb = ((colorB >> 8) & 0xFF) * r255; + ca = ca * aa + cb * ab * (1.f - aa); + ca = ca * ralpha * 255.f; + if (ca > 255.f) { + ca = 255.f; + } + color |= ((int) ca) << 8; + + ca = (colorA & 0xFF) * r255; + cb = (colorB & 0xFF) * r255; + ca = ca * aa + cb * ab * (1.f - aa); + ca = ca * ralpha * 255.f; + if (ca > 255.f) { + ca = 255.f; + } + color |= (int) ca; +#endif return color; } diff --git a/src/util/test/color.c b/src/util/test/color.c index f01fd2d2f..6977795c6 100644 --- a/src/util/test/color.c +++ b/src/util/test/color.c @@ -155,9 +155,9 @@ M_TEST_DEFINE(alphaBlendARGB8) { assert_int_equal(mColorMixARGB8(0xFF012345, 0xFF987654), 0xFF012345); assert_int_equal(mColorMixARGB8(0x00012345, 0xFF987654), 0xFF987654); assert_int_equal(mColorMixARGB8(0x80012345, 0xFF987654), 0xFF4C4C4C); - assert_int_equal(mColorMixARGB8(0x80012345, 0x40987654), 0xC04C4C4C); + assert_int_equal(mColorMixARGB8(0x80012345, 0x40987654), 0x9F1F3347); assert_int_equal(mColorMixARGB8(0x01012345, 0xFF987654), 0xFF977553); - assert_int_equal(mColorMixARGB8(0x01012345, 0xFD987654), 0xFE977553); + assert_int_equal(mColorMixARGB8(0x01012345, 0xFD987654), 0xFD977553); } M_TEST_SUITE_DEFINE(Color, From 5f35899ba3f6a4893887bb9731fa0f64ef5d3907 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 23 Jul 2023 21:43:15 -0700 Subject: [PATCH 274/290] Util: Start mPainter bringup with rectangle drawing --- include/mgba-util/image.h | 12 + src/util/image.c | 91 +++++++ src/util/test/image.c | 497 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 600 insertions(+) diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index 71f53fb68..c91d88518 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -101,6 +101,15 @@ struct mImage { enum mColorFormat format; }; +struct mPainter { + struct mImage* backing; + bool blend; + bool fill; + unsigned strokeWidth; + uint32_t strokeColor; + uint32_t fillColor; +}; + struct VFile; struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format); struct mImage* mImageCreateWithStride(unsigned width, unsigned height, unsigned stride, enum mColorFormat format); @@ -125,6 +134,9 @@ void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y) void mImageComposite(struct mImage* image, const struct mImage* source, int x, int y); void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source, int x, int y, float alpha); +void mPainterInit(struct mPainter*, struct mImage* backing); +void mPainterDrawRectangle(struct mPainter*, int x, int y, int width, int height); + uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to); uint32_t mImageColorConvert(uint32_t color, const struct mImage* from, enum mColorFormat to); diff --git a/src/util/image.c b/src/util/image.c index 0a4b6e68d..781641340 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -523,6 +523,97 @@ void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source, } } +#define FILL_BOUNDS_INIT(X, Y, W, H) \ + struct mRectangle dstRect = { \ + .x = 0, \ + .y = 0, \ + .width = painter->backing->width, \ + .height = painter->backing->height \ + }; \ + struct mRectangle srcRect = { \ + .x = (X), \ + .y = (Y), \ + .width = (W), \ + .height = (H) \ + }; \ + if (!mRectangleIntersection(&srcRect, &dstRect)) { \ + return; \ + } \ + int dstStartX; \ + int dstStartY; \ + if ((X) < 0) { \ + dstStartX = 0; \ + } else { \ + dstStartX = srcRect.x; \ + } \ + if ((Y) < 0) { \ + dstStartY = 0; \ + } else { \ + dstStartY = srcRect.y; \ + } + +void mPainterInit(struct mPainter* painter, struct mImage* backing) { + memset(painter, 0, sizeof(*painter)); + painter->backing = backing; +} + +static void mPainterFillRectangle(struct mPainter* painter, int x, int y, int width, int height) { + FILL_BOUNDS_INIT(x, y, width, height); + + if (!painter->blend || painter->fillColor >= 0xFF000000) { + uint32_t color = mColorConvert(painter->fillColor, mCOLOR_ARGB8, painter->backing->format); + for (y = 0; y < srcRect.height; ++y) { + uintptr_t dstPixel = (uintptr_t) PIXEL(painter->backing, dstStartX, dstStartY + y); + for (x = 0; x < srcRect.width; ++x, dstPixel += painter->backing->depth) { + PUT_PIXEL(color, dstPixel, painter->backing->depth); + } + } + } else { + for (y = 0; y < srcRect.height; ++y) { + uintptr_t dstPixel = (uintptr_t) PIXEL(painter->backing, dstStartX, dstStartY + y); + for (x = 0; x < srcRect.width; ++x, dstPixel += painter->backing->depth) { + uint32_t color; + GET_PIXEL(color, dstPixel, painter->backing->depth); + color = mColorConvert(color, painter->backing->format, mCOLOR_ARGB8); + color = mColorMixARGB8(painter->fillColor, color); + color = mColorConvert(color, mCOLOR_ARGB8, painter->backing->format); + PUT_PIXEL(color, dstPixel, painter->backing->depth); + } + } + } +} + +static void mPainterStrokeRectangle(struct mPainter* painter, int x, int y, int width, int height) { + uint32_t fillColor = painter->fillColor; + painter->fillColor = painter->strokeColor; + if (width <= painter->strokeWidth * 2 || height <= painter->strokeWidth * 2) { + mPainterFillRectangle(painter, x, y, width, height); + } else { + int lr = height - painter->strokeWidth; + int tb = width - painter->strokeWidth; + // Top, top-left corner + mPainterFillRectangle(painter, x, y, tb, painter->strokeWidth); + // Left, bottom-left corner + mPainterFillRectangle(painter, x, y + painter->strokeWidth, painter->strokeWidth, lr); + // Bottom, bottom-right corner + mPainterFillRectangle(painter, x + painter->strokeWidth, y + height - painter->strokeWidth, tb, painter->strokeWidth); + // Right, top-right corner + mPainterFillRectangle(painter, x + width - painter->strokeWidth, y, painter->strokeWidth, lr); + } + painter->fillColor = fillColor; +} + +void mPainterDrawRectangle(struct mPainter* painter, int x, int y, int width, int height) { + int interiorW = width - painter->strokeWidth * 2; + int interiorH = height - painter->strokeWidth * 2; + if (painter->fill && interiorW > 0 && interiorH > 0) { + mPainterFillRectangle(painter, x + painter->strokeWidth, y + painter->strokeWidth, interiorW, interiorH); + } + if (painter->strokeWidth) { + mPainterStrokeRectangle(painter, x, y, width, height); + } +} + uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to) { if (from == to) { return color; diff --git a/src/util/test/image.c b/src/util/test/image.c index a7955110a..80cb7332b 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -1142,6 +1142,497 @@ M_TEST_DEFINE(blitBoundaries) { mImageDestroy(sprite); } +#define COMPARE4(AA, BA, CA, DA, AB, BB, CB, DB, AC, BC, CC, DC, AD, BD, CD, DD) \ + assert_int_equal(mImageGetPixel(image, 0, 0), (AA)); \ + assert_int_equal(mImageGetPixel(image, 1, 0), (BA)); \ + assert_int_equal(mImageGetPixel(image, 2, 0), (CA)); \ + assert_int_equal(mImageGetPixel(image, 3, 0), (DA)); \ + assert_int_equal(mImageGetPixel(image, 0, 1), (AB)); \ + assert_int_equal(mImageGetPixel(image, 1, 1), (BB)); \ + assert_int_equal(mImageGetPixel(image, 2, 1), (CB)); \ + assert_int_equal(mImageGetPixel(image, 3, 1), (DB)); \ + assert_int_equal(mImageGetPixel(image, 0, 2), (AC)); \ + assert_int_equal(mImageGetPixel(image, 1, 2), (BC)); \ + assert_int_equal(mImageGetPixel(image, 2, 2), (CC)); \ + assert_int_equal(mImageGetPixel(image, 3, 2), (DC)); \ + assert_int_equal(mImageGetPixel(image, 0, 3), (AD)); \ + assert_int_equal(mImageGetPixel(image, 1, 3), (BD)); \ + assert_int_equal(mImageGetPixel(image, 2, 3), (CD)); \ + assert_int_equal(mImageGetPixel(image, 3, 3), (DD)) + +#define COMPARE4X(AA, BA, CA, DA, AB, BB, CB, DB, AC, BC, CC, DC, AD, BD, CD, DD) \ + COMPARE4(0xFF000000 | (AA), 0xFF000000 | (BA), 0xFF000000 | (CA), 0xFF000000 | (DA), \ + 0xFF000000 | (AB), 0xFF000000 | (BB), 0xFF000000 | (CB), 0xFF000000 | (DB), \ + 0xFF000000 | (AC), 0xFF000000 | (BC), 0xFF000000 | (CC), 0xFF000000 | (DC), \ + 0xFF000000 | (AD), 0xFF000000 | (BD), 0xFF000000 | (CD), 0xFF000000 | (DD)) + +#define COMPARE3(AA, BA, CA, AB, BB, CB, AC, BC, CC) \ + assert_int_equal(mImageGetPixel(image, 0, 0), (AA)); \ + assert_int_equal(mImageGetPixel(image, 1, 0), (BA)); \ + assert_int_equal(mImageGetPixel(image, 2, 0), (CA)); \ + assert_int_equal(mImageGetPixel(image, 0, 1), (AB)); \ + assert_int_equal(mImageGetPixel(image, 1, 1), (BB)); \ + assert_int_equal(mImageGetPixel(image, 2, 1), (CB)); \ + assert_int_equal(mImageGetPixel(image, 0, 2), (AC)); \ + assert_int_equal(mImageGetPixel(image, 1, 2), (BC)); \ + assert_int_equal(mImageGetPixel(image, 2, 2), (CC)) + +#define COMPARE3X(AA, BA, CA, AB, BB, CB, AC, BC, CC) \ + COMPARE3(0xFF000000 | (AA), 0xFF000000 | (BA), 0xFF000000 | (CA), \ + 0xFF000000 | (AB), 0xFF000000 | (BB), 0xFF000000 | (CB), \ + 0xFF000000 | (AC), 0xFF000000 | (BC), 0xFF000000 | (CC)) + +M_TEST_DEFINE(painterFillRectangle) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 1, 1, 2, 2); + COMPARE4X(0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x0000FF, 0x0000FF, 0x000000, + 0x000000, 0x0000FF, 0x0000FF, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, -1, -1, 2, 2); + COMPARE4X(0x0000FF, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 3, -1, 2, 2); + COMPARE4X(0x000000, 0x000000, 0x000000, 0x0000FF, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, -1, 3, 2, 2); + COMPARE4X(0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x0000FF, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 3, 3, 2, 2); + COMPARE4X(0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x0000FF); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 3, 3); + painter.fillColor = 0xFF00FF00; + mPainterDrawRectangle(&painter, 1, 1, 3, 3); + COMPARE4X(0x0000FF, 0x0000FF, 0x0000FF, 0x000000, + 0x0000FF, 0x00FF00, 0x00FF00, 0x00FF00, + 0x0000FF, 0x00FF00, 0x00FF00, 0x00FF00, + 0x000000, 0x00FF00, 0x00FF00, 0x00FF00); + mImageDestroy(image); +} + +M_TEST_DEFINE(painterFillRectangleBlend) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(3, 3, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0x400000FF; + mPainterDrawRectangle(&painter, 0, 0, 2, 2); + painter.fillColor = 0x40FF0000; + mPainterDrawRectangle(&painter, 1, 1, 2, 2); + COMPARE3(0x400000FF, 0x400000FF, 0x00000000, + 0x400000FF, 0x40FF0000, 0x40FF0000, + 0x00000000, 0x40FF0000, 0x40FF0000); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = true; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0x400000FF; + mPainterDrawRectangle(&painter, 0, 0, 2, 2); + painter.fillColor = 0x40FF0000; + mPainterDrawRectangle(&painter, 1, 1, 2, 2); + COMPARE3(0x400000FF, 0x400000FF, 0x00000000, + 0x400000FF, 0x6F91006D, 0x40FF0000, + 0x00000000, 0x40FF0000, 0x40FF0000); + mImageDestroy(image); +} + +M_TEST_DEFINE(painterStrokeRectangle) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 1, 1, 2, 2); + COMPARE4X(0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x0000FF, 0x0000FF, 0x000000, + 0x000000, 0x0000FF, 0x0000FF, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, -1, -1, 3, 3); + COMPARE4X(0x000000, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 2, -1, 3, 3); + COMPARE4X(0x000000, 0x000000, 0x0000FF, 0x000000, + 0x000000, 0x000000, 0x0000FF, 0x0000FF, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, -1, 2, 3, 3); + COMPARE4X(0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x000000, 0x0000FF, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 2, 2, 3, 3); + COMPARE4X(0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x0000FF, 0x0000FF, + 0x000000, 0x000000, 0x0000FF, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 3, 3); + painter.strokeColor = 0xFF00FF00; + mPainterDrawRectangle(&painter, 1, 1, 3, 3); + COMPARE4X(0x0000FF, 0x0000FF, 0x0000FF, 0x000000, + 0x0000FF, 0x00FF00, 0x00FF00, 0x00FF00, + 0x0000FF, 0x00FF00, 0x0000FF, 0x00FF00, + 0x000000, 0x00FF00, 0x00FF00, 0x00FF00); + mImageDestroy(image); +} + +M_TEST_DEFINE(painterStrokeRectangleWidth) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 4, 4); + COMPARE4X(0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x0000FF, 0x000000, 0x000000, 0x0000FF, + 0x0000FF, 0x000000, 0x000000, 0x0000FF, + 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 4, 1); + COMPARE4X(0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 1, 4); + COMPARE4X(0x0000FF, 0x000000, 0x000000, 0x000000, + 0x0000FF, 0x000000, 0x000000, 0x000000, + 0x0000FF, 0x000000, 0x000000, 0x000000, + 0x0000FF, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 4, 2); + COMPARE4X(0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 2, 4); + COMPARE4X(0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 4, 4); + COMPARE4X(0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 4, 2); + COMPARE4X(0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 2, 4); + COMPARE4X(0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 3; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 4, 2); + COMPARE4X(0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 3; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 0, 0, 2, 4); + COMPARE4X(0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000, + 0x0000FF, 0x0000FF, 0x000000, 0x000000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 4; + painter.strokeColor = 0xFF0000FF; + mPainterDrawRectangle(&painter, 1, 1, 2, 2); + COMPARE4X(0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x0000FF, 0x0000FF, 0x000000, + 0x000000, 0x0000FF, 0x0000FF, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000); + mImageDestroy(image); +} + +M_TEST_DEFINE(painterStrokeRectangleBlend) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(4, 4, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0x400000FF; + mPainterDrawRectangle(&painter, 0, 0, 3, 3); + painter.strokeColor = 0x40FF0000; + mPainterDrawRectangle(&painter, 1, 1, 3, 3); + COMPARE4(0x400000FF, 0x400000FF, 0x400000FF, 0x00000000, + 0x400000FF, 0x40FF0000, 0x40FF0000, 0x40FF0000, + 0x400000FF, 0x40FF0000, 0x400000FF, 0x40FF0000, + 0x00000000, 0x40FF0000, 0x40FF0000, 0x40FF0000); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = true; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0x400000FF; + mPainterDrawRectangle(&painter, 0, 0, 3, 3); + painter.strokeColor = 0x40FF0000; + mPainterDrawRectangle(&painter, 1, 1, 3, 3); + COMPARE4(0x400000FF, 0x400000FF, 0x400000FF, 0x00000000, + 0x400000FF, 0x40FF0000, 0x6F91006D, 0x40FF0000, + 0x400000FF, 0x6F91006D, 0x400000FF, 0x40FF0000, + 0x00000000, 0x40FF0000, 0x40FF0000, 0x40FF0000); +} + +M_TEST_DEFINE(painterDrawRectangle) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(3, 3, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.fillColor = 0x800000FF; + painter.strokeWidth = 1; + painter.strokeColor = 0x4000FF00; + mPainterDrawRectangle(&painter, 0, 0, 3, 3); + COMPARE3(0x4000FF00, 0x4000FF00, 0x4000FF00, + 0x4000FF00, 0x800000FF, 0x4000FF00, + 0x4000FF00, 0x4000FF00, 0x4000FF00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = true; + painter.fill = true; + painter.fillColor = 0x800000FF; + painter.strokeWidth = 1; + painter.strokeColor = 0x4000FF00; + mPainterDrawRectangle(&painter, 0, 0, 3, 3); + COMPARE3(0x4000FF00, 0x4000FF00, 0x4000FF00, + 0x4000FF00, 0x800000FF, 0x4000FF00, + 0x4000FF00, 0x4000FF00, 0x4000FF00); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.fillColor = 0x800000FF; + painter.strokeWidth = 1; + painter.strokeColor = 0x4000FF00; + mPainterDrawRectangle(&painter, 0, 0, 3, 3); + mPainterDrawRectangle(&painter, 1, 1, 3, 3); + COMPARE4(0x4000FF00, 0x4000FF00, 0x4000FF00, 0x00000000, + 0x4000FF00, 0x4000FF00, 0x4000FF00, 0x4000FF00, + 0x4000FF00, 0x4000FF00, 0x800000FF, 0x4000FF00, + 0x00000000, 0x4000FF00, 0x4000FF00, 0x4000FF00); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = true; + painter.fill = true; + painter.fillColor = 0x800000FF; + painter.strokeWidth = 1; + painter.strokeColor = 0x4000FF00; + mPainterDrawRectangle(&painter, 0, 0, 3, 3); + mPainterDrawRectangle(&painter, 1, 1, 3, 3); + COMPARE4(0x4000FF00, 0x4000FF00, 0x4000FF00, 0x00000000, + 0x4000FF00, 0x9F006698, 0x6F00FF00, 0x4000FF00, + 0x4000FF00, 0x6F00FF00, 0x9F0032CC, 0x4000FF00, + 0x00000000, 0x4000FF00, 0x4000FF00, 0x4000FF00); + mImageDestroy(image); +} + +#undef COMPARE3X +#undef COMPARE3 +#undef COMPARE4X +#undef COMPARE4 + M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(zeroDim), cmocka_unit_test(pitchRead), @@ -1166,4 +1657,10 @@ M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(convert1x2), cmocka_unit_test(convert2x2), cmocka_unit_test(blitBoundaries), + cmocka_unit_test(painterFillRectangle), + cmocka_unit_test(painterFillRectangleBlend), + cmocka_unit_test(painterStrokeRectangle), + cmocka_unit_test(painterStrokeRectangleWidth), + cmocka_unit_test(painterStrokeRectangleBlend), + cmocka_unit_test(painterDrawRectangle), ) From 02ba4f249959863e57fddfc96cf9c931c50c484e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 23 Jul 2023 22:47:47 -0700 Subject: [PATCH 275/290] GBA GPIO: Fix tilt scale and orientation (fixes #2703) --- CHANGES | 1 + src/gba/cart/gpio.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 3a67c7be5..c552ba13f 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,7 @@ Emulation fixes: - GBA: Unhandled bkpt should be treated as an undefined exception - GBA Audio: Fix sample timing drifting when changing sample interval - GBA Audio: Fix initial channel 3 wave RAM (fixes mgba.io/i/2947) + - GBA GPIO: Fix tilt scale and orientation (fixes mgba.io/i/2703) - GBA BIOS: Fix clobbering registers with word-sized CpuSet - GBA SIO: Fix normal mode SI/SO semantics (fixes mgba.io/i/2925) - GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722) diff --git a/src/gba/cart/gpio.c b/src/gba/cart/gpio.c index 35b72cb3c..62feb7335 100644 --- a/src/gba/cart/gpio.c +++ b/src/gba/cart/gpio.c @@ -423,8 +423,8 @@ void GBAHardwareTiltWrite(struct GBACartridgeHardware* hw, uint32_t address, uin int32_t x = rotationSource->readTiltX(rotationSource); int32_t y = rotationSource->readTiltY(rotationSource); // Normalize to ~12 bits, focused on 0x3A0 - hw->tiltX = (x >> 21) + 0x3A0; // Crop off an extra bit so that we can't go negative - hw->tiltY = (y >> 21) + 0x3A0; + hw->tiltX = 0x3A0 - (x >> 22); + hw->tiltY = 0x3A0 - (y >> 22); } else { mLOG(GBA_HW, GAME_ERROR, "Tilt sensor wrote wrong byte to %04x: %02x", address, value); } From 974272221392e5c7bd5b94cd194efa01bf0be85f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Sun, 23 Jul 2023 22:48:30 -0700 Subject: [PATCH 276/290] GUI: Add missing include --- src/feature/gui/cheats.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/feature/gui/cheats.c b/src/feature/gui/cheats.c index fda129a5b..9c3199631 100644 --- a/src/feature/gui/cheats.c +++ b/src/feature/gui/cheats.c @@ -9,6 +9,7 @@ #include #include "feature/gui/gui-runner.h" #include +#include enum mGUICheatAction { CHEAT_BACK = 0, From 4ca8ffe3f47e2438b7a7d52e46b9bb39c0a0ca37 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 24 Jul 2023 19:21:43 -0700 Subject: [PATCH 277/290] Updater: Fix MSVC build --- src/feature/updater-main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/feature/updater-main.c b/src/feature/updater-main.c index 4a05bf290..66582a431 100644 --- a/src/feature/updater-main.c +++ b/src/feature/updater-main.c @@ -21,6 +21,9 @@ #include #define mkdir(X, Y) _mkdir(X) +#ifndef S_ISDIR +#define S_ISDIR(MODE) (((MODE) & _S_IFMT) == _S_IFDIR) +#endif #elif defined(_POSIX_C_SOURCE) #include #endif From ba4917569739b7331024e8026ac859df55e78d2b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 24 Jul 2023 21:00:47 -0700 Subject: [PATCH 278/290] Util: Add mPainterDrawLine --- include/mgba-util/image.h | 1 + src/util/image.c | 69 ++++++++++++ src/util/test/image.c | 226 +++++++++++++++++++++++++++++++++++++- 3 files changed, 295 insertions(+), 1 deletion(-) diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index c91d88518..98a03dd0b 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -136,6 +136,7 @@ void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source, void mPainterInit(struct mPainter*, struct mImage* backing); void mPainterDrawRectangle(struct mPainter*, int x, int y, int width, int height); +void mPainterDrawLine(struct mPainter*, int x1, int y1, int x2, int y2); uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to); uint32_t mImageColorConvert(uint32_t color, const struct mImage* from, enum mColorFormat to); diff --git a/src/util/image.c b/src/util/image.c index 781641340..100f7a608 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -614,6 +614,75 @@ void mPainterDrawRectangle(struct mPainter* painter, int x, int y, int width, in } } +void mPainterDrawLine(struct mPainter* painter, int x1, int y1, int x2, int y2) { + if (!painter->strokeWidth) { + return; + } + int dx = x2 - x1; + int dy = y2 - y1; + int x, y; + int xi = 1; + int yi = 1; + int residual; + + int mx = dx; + int my = dy; + if (mx < 0) { + mx = -mx; + } + if (my < 0) { + my = -my; + } + + if (dx < 0) { + xi = -1; + dx = -dx; + } + if (dy < 0) { + yi = -1; + dy = -dy; + } + + unsigned i; + uint32_t color = painter->strokeColor; + + if (mx > my) { + residual = 2 * dy - dx; + y = y1; + for (x = x1; x != x2 + xi; x += xi) { + for (i = 0; i < painter->strokeWidth; ++i) { + if (painter->blend) { + color = mColorMixARGB8(painter->strokeColor, mImageGetPixel(painter->backing, x, y - painter->strokeWidth / 2 + i)); + } + mImageSetPixel(painter->backing, x, y - painter->strokeWidth / 2 + i, color); + } + if (residual > 0) { + y += yi; + residual -= 2 * dx; + } + residual += 2 * dy; + } + } else { + residual = 2 * dx - dy; + x = x1; + for (y = y1; y != y2 + yi; y += yi) { + for (i = 0; i < painter->strokeWidth; ++i) { + if (painter->blend) { + color = mColorMixARGB8(painter->strokeColor, mImageGetPixel(painter->backing, x - painter->strokeWidth / 2 + i, y)); + } + mImageSetPixel(painter->backing, x - painter->strokeWidth / 2 + i, y, color); + } + if (residual > 0) { + x += xi; + residual -= 2 * dy; + } + residual += 2 * dx; + } + } + + // TODO: Draw endcaps for widths >2 +} + uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to) { if (from == to) { return color; diff --git a/src/util/test/image.c b/src/util/test/image.c index 80cb7332b..c136a8318 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -1568,7 +1568,7 @@ M_TEST_DEFINE(painterStrokeRectangleBlend) { } M_TEST_DEFINE(painterDrawRectangle) { - struct mImage* image; + struct mImage* image; struct mPainter painter; image = mImageCreate(3, 3, mCOLOR_ARGB8); @@ -1628,6 +1628,227 @@ M_TEST_DEFINE(painterDrawRectangle) { mImageDestroy(image); } +M_TEST_DEFINE(painterDrawLineOctants) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 0, 2, 2); + COMPARE3X(0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00, + 0x00, 0x00, 0xFF); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 2, 2, 0, 0); + COMPARE3X(0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00, + 0x00, 0x00, 0xFF); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 2, 0, 0, 2); + COMPARE3X(0x00, 0x00, 0xFF, + 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 2, 2, 0); + COMPARE3X(0x00, 0x00, 0xFF, + 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 0, 2, 1); + COMPARE3X(0xFF, 0xFF, 0x00, + 0x00, 0x00, 0xFF, + 0x00, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 2, 1, 0, 0); + COMPARE3X(0xFF, 0x00, 0x00, + 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 0, 1, 2); + COMPARE3X(0xFF, 0x00, 0x00, + 0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 1, 2, 0, 0); + COMPARE3X(0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00, + 0x00, 0xFF, 0x00); + mImageDestroy(image); +} + +M_TEST_DEFINE(painterDrawLineWidth) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 0, 3, 3); + COMPARE4X(0xFF, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x00, + 0x00, 0x00, 0xFF, 0xFF); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 3; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 0, 3, 3); + COMPARE4X(0xFF, 0xFF, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0x00, + 0x00, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0xFF, 0xFF); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 3, 0, 0, 3); + COMPARE4X(0x00, 0x00, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0x00, 0x00, + 0xFF, 0x00, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(4, 4, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 3; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 3, 0, 0, 3); + COMPARE4X(0x00, 0x00, 0xFF, 0xFF, + 0x00, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 1, 0, 1, 2); + COMPARE3X(0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0x00, + 0xFF, 0xFF, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 3; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 1, 0, 1, 2); + COMPARE3X(0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 2; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 1, 2, 1); + COMPARE3X(0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 3; + painter.strokeColor = 0xFF0000FF; + mPainterDrawLine(&painter, 0, 1, 2, 1); + COMPARE3X(0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF); + mImageDestroy(image); +} + +M_TEST_DEFINE(painterDrawLineBlend) { + struct mImage* image; + struct mPainter painter; + + image = mImageCreate(3, 3, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.strokeWidth = 1; + painter.strokeColor = 0x400000FF; + mPainterDrawLine(&painter, 0, 0, 2, 2); + painter.strokeColor = 0x4000FF00; + mPainterDrawLine(&painter, 0, 2, 2, 0); + COMPARE3(0x400000FF, 0x00000000, 0x4000FF00, + 0x00000000, 0x4000FF00, 0x00000000, + 0x4000FF00, 0x00000000, 0x400000FF); + mImageDestroy(image); + + image = mImageCreate(3, 3, mCOLOR_ARGB8); + mPainterInit(&painter, image); + painter.blend = true; + painter.strokeWidth = 1; + painter.strokeColor = 0x400000FF; + mPainterDrawLine(&painter, 0, 0, 2, 2); + painter.strokeColor = 0x4000FF00; + mPainterDrawLine(&painter, 0, 2, 2, 0); + COMPARE3(0x400000FF, 0x00000000, 0x4000FF00, + 0x00000000, 0x6F00916D, 0x00000000, + 0x4000FF00, 0x00000000, 0x400000FF); + mImageDestroy(image); +} + #undef COMPARE3X #undef COMPARE3 #undef COMPARE4X @@ -1663,4 +1884,7 @@ M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(painterStrokeRectangleWidth), cmocka_unit_test(painterStrokeRectangleBlend), cmocka_unit_test(painterDrawRectangle), + cmocka_unit_test(painterDrawLineOctants), + cmocka_unit_test(painterDrawLineWidth), + cmocka_unit_test(painterDrawLineBlend), ) From 57ba653bc7991bc8d9641f236758d817eff19543 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 27 Jul 2023 20:26:08 -0700 Subject: [PATCH 279/290] Util: Add mPainterDrawCircle --- include/mgba-util/image.h | 1 + src/util/image.c | 144 +++++++++++++++++++++++++++++++++++--- src/util/test/image.c | 92 ++++++++++++++++++++++++ 3 files changed, 229 insertions(+), 8 deletions(-) diff --git a/include/mgba-util/image.h b/include/mgba-util/image.h index 98a03dd0b..31514687d 100644 --- a/include/mgba-util/image.h +++ b/include/mgba-util/image.h @@ -137,6 +137,7 @@ void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source, void mPainterInit(struct mPainter*, struct mImage* backing); void mPainterDrawRectangle(struct mPainter*, int x, int y, int width, int height); void mPainterDrawLine(struct mPainter*, int x1, int y1, int x2, int y2); +void mPainterDrawCircle(struct mPainter*, int x, int y, int diameter); uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to); uint32_t mImageColorConvert(uint32_t color, const struct mImage* from, enum mColorFormat to); diff --git a/src/util/image.c b/src/util/image.c index 100f7a608..d68491718 100644 --- a/src/util/image.c +++ b/src/util/image.c @@ -557,6 +557,13 @@ void mPainterInit(struct mPainter* painter, struct mImage* backing) { painter->backing = backing; } +static void mPainterDrawPixel(struct mPainter* painter, unsigned x, unsigned y, uint32_t color) { + if (painter->blend) { + color = mColorMixARGB8(painter->strokeColor, mImageGetPixel(painter->backing, x, y)); + } + mImageSetPixel(painter->backing, x, y, color); +} + static void mPainterFillRectangle(struct mPainter* painter, int x, int y, int width, int height) { FILL_BOUNDS_INIT(x, y, width, height); @@ -651,10 +658,7 @@ void mPainterDrawLine(struct mPainter* painter, int x1, int y1, int x2, int y2) y = y1; for (x = x1; x != x2 + xi; x += xi) { for (i = 0; i < painter->strokeWidth; ++i) { - if (painter->blend) { - color = mColorMixARGB8(painter->strokeColor, mImageGetPixel(painter->backing, x, y - painter->strokeWidth / 2 + i)); - } - mImageSetPixel(painter->backing, x, y - painter->strokeWidth / 2 + i, color); + mPainterDrawPixel(painter, x, y - painter->strokeWidth / 2 + i, color); } if (residual > 0) { y += yi; @@ -667,10 +671,7 @@ void mPainterDrawLine(struct mPainter* painter, int x1, int y1, int x2, int y2) x = x1; for (y = y1; y != y2 + yi; y += yi) { for (i = 0; i < painter->strokeWidth; ++i) { - if (painter->blend) { - color = mColorMixARGB8(painter->strokeColor, mImageGetPixel(painter->backing, x - painter->strokeWidth / 2 + i, y)); - } - mImageSetPixel(painter->backing, x - painter->strokeWidth / 2 + i, y, color); + mPainterDrawPixel(painter, x - painter->strokeWidth / 2 + i, y, color); } if (residual > 0) { x += xi; @@ -683,6 +684,133 @@ void mPainterDrawLine(struct mPainter* painter, int x1, int y1, int x2, int y2) // TODO: Draw endcaps for widths >2 } +static void _drawCircleOctants(struct mPainter* painter, int x, int y, int offx, int offy, int offset, uint32_t color) { + mPainterDrawPixel(painter, x + offy - offset, y + offx - offset, color); + mPainterDrawPixel(painter, x - offy, y + offx - offset, color); + mPainterDrawPixel(painter, x + offy - offset, y - offx, color); + mPainterDrawPixel(painter, x - offy, y - offx, color); + if (offx < offy) { + mPainterDrawPixel(painter, x + offx - offset, y + offy - offset, color); + mPainterDrawPixel(painter, x - offx, y + offy - offset, color); + mPainterDrawPixel(painter, x + offx - offset, y - offy, color); + mPainterDrawPixel(painter, x - offx, y - offy, color); + } +} + +static void _drawCircle2x2(struct mPainter* painter, int x, int y, uint32_t color) { + mPainterDrawPixel(painter, x, y, color); + mPainterDrawPixel(painter, x - 1, y, color); + mPainterDrawPixel(painter, x, y - 1, color); + mPainterDrawPixel(painter, x - 1, y - 1, color); +} + +void mPainterDrawCircle(struct mPainter* painter, int x, int y, int diameter) { + int radius = diameter / 2; + int offset = (diameter ^ 1) & 1; + int stroke = painter->strokeWidth; + int dx = 1; + int residual0 = 1 - radius; + int dy0 = -2 * radius; + int offx = 0; + int offy; + int y0 = radius; + if (stroke > radius) { + // Clamp stroke + stroke = radius; + if (!offset) { + // Draw center dot as stroke + mPainterDrawPixel(painter, x + radius, y + radius, painter->strokeColor); + } + } else if (!offset && painter->fill) { + // Draw center dot as fill + mPainterDrawPixel(painter, x + radius, y + radius, painter->fillColor); + } + + int residual1 = 1 - radius + stroke; + int dy1 = -2 * (radius - stroke); + int y1 = radius - stroke; + int i; + + if (!offset) { + // Draw central axes + for (i = 0; i < stroke; ++i) { + mPainterDrawPixel(painter, x + radius, y + radius * 2 - i, painter->strokeColor); + mPainterDrawPixel(painter, x + radius, y + i, painter->strokeColor); + mPainterDrawPixel(painter, x + radius * 2 - i, y + radius, painter->strokeColor); + mPainterDrawPixel(painter, x + i, y + radius, painter->strokeColor); + } + if (painter->fill) { + for (i = i; i < y1 + 1; ++i) { + mPainterDrawPixel(painter, x + radius, y + radius - i, painter->fillColor); + mPainterDrawPixel(painter, x + radius, y + radius + i, painter->fillColor); + mPainterDrawPixel(painter, x + radius - i, y + radius, painter->fillColor); + mPainterDrawPixel(painter, x + radius + i, y + radius, painter->fillColor); + } + } + } + + while (offx < y0) { + if (residual0 >= 0) { + y0 -= 1; + dy0 += 2; + residual0 += dy0; + } + if (residual1 >= 0) { + y1 -= 1; + dy1 += 2; + residual1 += dy1; + } + offx += 1; + dx += 2; + residual0 += dx; + residual1 += dx; + if (stroke) { + // Fill + if (painter->fill) { + if (offx == 1 && y1 == 0 && offset) { + // Special case for diameter-2 fill + _drawCircle2x2(painter, x + radius, y + radius, painter->fillColor); + } else { + for (offy = 0; offy < y1 + 1; ++offy) { + if (offx > offy) { + continue; + } + _drawCircleOctants(painter, x + radius, y + radius, offx, offy, offset, painter->fillColor); + } + } + } + // Stroke + if (radius == 1 && offset) { + // Special case for diameter-2 stroke + _drawCircle2x2(painter, x + radius, y + radius, painter->strokeColor); + } else { + for (offy = y1 + 1; offy < y0 + 1; ++offy) { + if (offx == 1 && offy == 1 && y1 == 0 && offset) { + // Special case for diameter-2 inner fill + continue; + } + if (offx > offy) { + continue; + } + _drawCircleOctants(painter, x + radius, y + radius, offx, offy, offset, painter->strokeColor); + } + } + } else if (painter->fill) { + if (offx == 1 && y0 == 0 && offset) { + // Special case for diameter-2 fill + _drawCircle2x2(painter, x, y, painter->fillColor); + } else { + for (offy = 0; offy < y0 + 1; ++offy) { + if (offx > offy) { + continue; + } + _drawCircleOctants(painter, x + radius, y + radius, offx, offy, offset, painter->fillColor); + } + } + } + } +} + uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to) { if (from == to) { return color; diff --git a/src/util/test/image.c b/src/util/test/image.c index c136a8318..eed41a913 100644 --- a/src/util/test/image.c +++ b/src/util/test/image.c @@ -1849,6 +1849,95 @@ M_TEST_DEFINE(painterDrawLineBlend) { mImageDestroy(image); } +M_TEST_DEFINE(painterDrawCircleArea) { + struct mImage* image; + struct mPainter painter; + + int i; + for (i = 4; i < 50; ++i) { + image = mImageCreate(i, i, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawCircle(&painter, 0, 0, i); + + int filled = 0; + + int x, y; + for (y = 0; y < i; ++y) { + for (x = 0; x < i; ++x) { + uint32_t color = mImageGetPixel(image, x, y); + if (color == painter.fillColor) { + ++filled; + } + } + } + float area = i * i; + assert_float_equal(filled / area, M_PI / 4, 0.12); + } +} + +M_TEST_DEFINE(painterDrawCircleCircumference) { + struct mImage* image; + struct mPainter painter; + + int i; + for (i = 25; i < 100; ++i) { + image = mImageCreate(i, i, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = false; + painter.strokeWidth = 1; + painter.strokeColor = 0xFF0000FF; + mPainterDrawCircle(&painter, 0, 0, i); + + int filled = 0; + + int x, y; + for (y = 0; y < i; ++y) { + for (x = 0; x < i; ++x) { + uint32_t color = mImageGetPixel(image, x, y); + if (color == painter.strokeColor) { + ++filled; + } + } + } + assert_float_equal(filled / (float) i, M_PI, M_PI * 0.11); + } +} + + +M_TEST_DEFINE(painterDrawCircleOffset) { + struct mImage* image; + struct mPainter painter; + + int i; + for (i = 4; i < 20; ++i) { + image = mImageCreate(i * 2, i * 2, mCOLOR_XRGB8); + mPainterInit(&painter, image); + painter.blend = false; + painter.fill = true; + painter.strokeWidth = 0; + painter.fillColor = 0xFF0000FF; + mPainterDrawCircle(&painter, 0, 0, i); + mPainterDrawCircle(&painter, i, 0, i); + mPainterDrawCircle(&painter, 0, i, i); + mPainterDrawCircle(&painter, i, i, i); + + int x, y; + for (y = 0; y < i; ++y) { + for (x = 0; x < i; ++x) { + uint32_t color = mImageGetPixel(image, x, y); + assert_int_equal(color, mImageGetPixel(image, x + i, y)); + assert_int_equal(color, mImageGetPixel(image, x, y + i)); + assert_int_equal(color, mImageGetPixel(image, x + i, y + i)); + } + } + } +} + #undef COMPARE3X #undef COMPARE3 #undef COMPARE4X @@ -1887,4 +1976,7 @@ M_TEST_SUITE_DEFINE(Image, cmocka_unit_test(painterDrawLineOctants), cmocka_unit_test(painterDrawLineWidth), cmocka_unit_test(painterDrawLineBlend), + cmocka_unit_test(painterDrawCircleArea), + cmocka_unit_test(painterDrawCircleCircumference), + cmocka_unit_test(painterDrawCircleOffset), ) From 652b078aabbe6d699de0b31a0e7a43ab69510139 Mon Sep 17 00:00:00 2001 From: Hoseok Seo Date: Sat, 4 Feb 2023 00:29:23 +0000 Subject: [PATCH 280/290] Qt: Update translation (Korean) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/ko/ --- src/platform/qt/ts/mgba-ko.ts | 88 +++++++++++++++++------------------ 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/src/platform/qt/ts/mgba-ko.ts b/src/platform/qt/ts/mgba-ko.ts index 567688861..6b3fb3208 100644 --- a/src/platform/qt/ts/mgba-ko.ts +++ b/src/platform/qt/ts/mgba-ko.ts @@ -6,22 +6,22 @@ Game Boy Advance ROMs (%1) - 게임 보이 어드밴스 롬 (%1) + 게임보이 어드밴스 롬 (%1) Game Boy ROMs (%1) - 게임 보이 (%1) + 게임보이 롬 (%1) All ROMs (%1) - 모든 롬 (%1) + 모든 롬 (%1) %1 Video Logs (*.mvl) - %1 비디오 로그 (*.mvl) + %1 비디오 로그 (*.mvl) @@ -530,12 +530,12 @@ Download size: %3 3DS - + 3DS Vita - + 비타 @@ -543,12 +543,12 @@ Download size: %3 Icon - + 아이콘 Banner - + 배너 @@ -556,17 +556,17 @@ Download size: %3 Bubble - + 거품 Background - 배경 + 배경 Startup - + 시작 @@ -574,144 +574,144 @@ Download size: %3 Create forwarder - + 포워더 생성 Files - + 파일 ROM file: - + 롬 파일: Browse - 브라우저 + 찾아보기 Output filename: - + 출력 파일이름: Forwarder base: - + 포워더 기반: Latest stable version - + 최신 안정 버전 Latest development build - + 최신 개발 빌드 Specific file - + 특정 파일 Base file: - + 기본 파일: System - + 시스템 3DS - + 3DS Vita - + 비타 Presentation - + 프레젠테이션 Title: - + 타이틀: Images: - + 이미지: Use default image - + 기본 이미지 사용 Preferred size: - + 선호하는 크기: Select image file - + 이미지 파일 선택 Select ROM file - + 롬 파일 선택 Select output filename - + 출력 파일이름 선택 Select base file - + 기본 파일 선택 Build finished - + 빌드 완료 Forwarder finished building - + 포워더 완료된 빌드 Build failed - + 빌드 실패 Failed to build forwarder - + 포워더 빌드 실패 %1 installable package (*.%2) - + %1 설치 가능한 패키지 (*.%2) Select an image - + 이미지 선택 @@ -1080,12 +1080,12 @@ Download size: %3 NT (old 1) - + NT (이전 1) NT (old 2) - + NT (이전 2) @@ -1110,12 +1110,12 @@ Download size: %3 GGB-81 - + GGB-81 Li Cheng - + Li Cheng @@ -6389,7 +6389,7 @@ Download size: %3 Create forwarder... - + 전달자 생성... From 1ee7b7019445562d3a7823fb77b3fa7061da0b67 Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Wed, 8 Feb 2023 14:48:39 +0000 Subject: [PATCH 281/290] Qt: Update translation (Spanish) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/es/ --- src/platform/qt/ts/mgba-es.ts | 88 +++++++++++++++++------------------ 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/src/platform/qt/ts/mgba-es.ts b/src/platform/qt/ts/mgba-es.ts index 994566710..8840525cf 100644 --- a/src/platform/qt/ts/mgba-es.ts +++ b/src/platform/qt/ts/mgba-es.ts @@ -6,22 +6,22 @@ Game Boy Advance ROMs (%1) - ROMs de Game Boy Advance (%1) + ROMs para Game Boy Advance (%1) Game Boy ROMs (%1) - ROMs de Game Boy (%1) + ROMs para Game Boy Advance (%1) All ROMs (%1) - Todas las ROMs (%1) + Todas las ROMs (%1) %1 Video Logs (*.mvl) - Registros de vídeo de %1 (*.mvl) + %1 Registros de vídeo (*.mvl) @@ -530,12 +530,12 @@ Tamaño de descarga: %3 3DS - + 3DS Vita - + Vita @@ -543,12 +543,12 @@ Tamaño de descarga: %3 Icon - + Icono Banner - + Banner @@ -556,17 +556,17 @@ Tamaño de descarga: %3 Bubble - + Burbujas Background - Fondo + En segundo plano Startup - + Puesta en marcha @@ -574,144 +574,144 @@ Tamaño de descarga: %3 Create forwarder - + Crear autocargador Files - + Archivos ROM file: - + Archivo ROM: Browse - Examinar + Visite Output filename: - + Nombre del archivo de salida: Forwarder base: - + base del transportista: Latest stable version - + Última versión estable Latest development build - + Última versión de desarrollo Specific file - + Archivo específico Base file: - + Archivo básico: System - + Sistema 3DS - + 3DS Vita - + Vita Presentation - + Presentación Title: - + Título: Images: - + Imágenes: Use default image - + Utilizar la imagen por defecto Preferred size: - + Tamaño preferido: Select image file - + Seleccione un archivo de imagen Select ROM file - + Seleccione el archivo ROM Select output filename - + Seleccione el nombre del archivo de salida Select base file - + Seleccionar el archivo inicial Build finished - + Construcción finalizada Forwarder finished building - + Transmisor terminado de construir Build failed - + Falló la construcción Failed to build forwarder - + No se ha podido crear el autocargador %1 installable package (*.%2) - + Paquete instalable %1 (*.%2) Select an image - + Selecciona una imagen @@ -1080,12 +1080,12 @@ Tamaño de descarga: %3 NT (old 1) - + NT (antiguo 1) NT (old 2) - + NT (antiguo 2) @@ -1110,12 +1110,12 @@ Tamaño de descarga: %3 GGB-81 - + GGB-81 Li Cheng - + Li Cheng @@ -6562,7 +6562,7 @@ Tamaño de descarga: %3 Create forwarder... - + Crear autocargador... From 5fb0ae88a76e0377fbc3c17f446d80b0a17c2d0c Mon Sep 17 00:00:00 2001 From: Guih48 Date: Sat, 11 Feb 2023 09:26:19 +0000 Subject: [PATCH 282/290] Qt: Update translation (Hungarian) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/hu/ --- src/platform/qt/ts/mgba-hu.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/qt/ts/mgba-hu.ts b/src/platform/qt/ts/mgba-hu.ts index 074576da6..370960cfb 100644 --- a/src/platform/qt/ts/mgba-hu.ts +++ b/src/platform/qt/ts/mgba-hu.ts @@ -6,17 +6,17 @@ Game Boy Advance ROMs (%1) - + Game Boy Advance ROMok (%1) Game Boy ROMs (%1) - + Game Boy ROMok (%1) All ROMs (%1) - + Összes ROM (%1) From b5c1330528e7a48b8cd0741a226d3b656f977db5 Mon Sep 17 00:00:00 2001 From: Lothar Serra Mari Date: Sat, 25 Mar 2023 21:07:03 +0000 Subject: [PATCH 283/290] Qt: Update translation (German) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/de/ --- src/platform/qt/ts/mgba-de.ts | 88 +++++++++++++++++------------------ 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/src/platform/qt/ts/mgba-de.ts b/src/platform/qt/ts/mgba-de.ts index 2f90b12d8..06f7d6147 100644 --- a/src/platform/qt/ts/mgba-de.ts +++ b/src/platform/qt/ts/mgba-de.ts @@ -6,22 +6,22 @@ Game Boy Advance ROMs (%1) - Game Boy Advance-ROMs (%1) + Game Boy Advance-ROMs (%1) Game Boy ROMs (%1) - Game Boy-ROMs (%1) + Game Boy-ROMs (%1) All ROMs (%1) - Alle ROMs (%1) + Alle ROMs (%1) %1 Video Logs (*.mvl) - %1 Video-Logs (*.mvl) + %1 Video-Logs (*.mvl) @@ -530,12 +530,12 @@ Download-Größe: %3 3DS - + 3DS Vita - + Vita @@ -543,12 +543,12 @@ Download-Größe: %3 Icon - + Icon Banner - + Banner @@ -556,17 +556,17 @@ Download-Größe: %3 Bubble - + Blase Background - Hintergrund + Hintergrund Startup - + Start @@ -574,144 +574,144 @@ Download-Größe: %3 Create forwarder - + Forwarder erstellen Files - + Dateien ROM file: - + ROM-Datei: Browse - Durchsuchen + Durchsuchen Output filename: - + Ausgabe-Dateiname: Forwarder base: - + Forwarder-Basis: Latest stable version - + Letzte stabile Version Latest development build - + Letzte Entwicklungs-Version Specific file - + Spezifische Datei Base file: - + Basis-Datei: System - + System 3DS - + 3DS Vita - + Vita Presentation - + Präsentation Title: - + Titel: Images: - + Bilder: Use default image - + Verwende Standard-Bild Preferred size: - + Bevorzugte Größe: Select image file - + Bild-Datei auswählen Select ROM file - + ROM-Datei auswählen Select output filename - + Ausgabe-Dateiname auswählen Select base file - + Basis-Datei auswählen Build finished - + Erzeugung erfolgreich Forwarder finished building - + Der Forwarder wurde erfolgreich erstellt Build failed - + Erzeugung fehlgeschlagen Failed to build forwarder - + Der Forwarder konnte nicht erzeugt werden %1 installable package (*.%2) - + Installierbares %1-Paket (*.%2) Select an image - + Bild auswählen @@ -1080,12 +1080,12 @@ Download-Größe: %3 NT (old 1) - + NT (alt 1) NT (old 2) - + NT (alt 2) @@ -1110,12 +1110,12 @@ Download-Größe: %3 GGB-81 - + GGB-81 Li Cheng - + Li Cheng @@ -6386,7 +6386,7 @@ Download-Größe: %3 Create forwarder... - + Forwarder erzeugen... From 8c0b9f6d0684428a03cba8d8f5b181934baf7bb5 Mon Sep 17 00:00:00 2001 From: ssantos Date: Sun, 19 Feb 2023 17:09:24 +0100 Subject: [PATCH 284/290] Qt: Added translation (Portuguese) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/pt/ --- src/platform/qt/ts/mgba-pt.ts | 6746 +++++++++++++++++++++++++++++++++ 1 file changed, 6746 insertions(+) create mode 100644 src/platform/qt/ts/mgba-pt.ts diff --git a/src/platform/qt/ts/mgba-pt.ts b/src/platform/qt/ts/mgba-pt.ts new file mode 100644 index 000000000..890f0c47b --- /dev/null +++ b/src/platform/qt/ts/mgba-pt.ts @@ -0,0 +1,6746 @@ + + + + + QGBA + + + Game Boy Advance ROMs (%1) + ROMs do Game Boy Advance (%1) + + + + Game Boy ROMs (%1) + ROMs do Game Boy (%1) + + + + All ROMs (%1) + Todas as ROMs (%1) + + + + %1 Video Logs (*.mvl) + %1 Registos do Vídeo (*.mvl) + + + + QGBA::AboutScreen + + + About + Sobre + + + + <a href="http://mgba.io/">Website</a> • <a href="https://forums.mgba.io/">Forums / Support</a> • <a href="https://patreon.com/mgba">Donate</a> • <a href="https://github.com/mgba-emu/mgba/tree/{gitBranch}">Source</a> + <a href="http://mgba.io/">Site da Web</a> • <a href="https://forums.mgba.io/">Fóruns / Suporte</a> • <a href="https://patreon.com/mgba">Doar</a> • <a href="https://github.com/mgba-emu/mgba/tree/{gitBranch}">Fonte</a> + + + + Branch: <tt>{gitBranch}</tt><br/>Revision: <tt>{gitCommit}</tt> + Branch: <tt>{gitBranch}</tt><br/>Revisão: <tt>{gitCommit}</tt> + + + + {projectName} would like to thank the following patrons from Patreon: + O {projectName} gostaria de agradecer aos seguintes patronos do Patreon: + + + + © 2013 – {year} Jeffrey Pfau, licensed under the Mozilla Public License, version 2.0 +Game Boy Advance is a registered trademark of Nintendo Co., Ltd. + © 2013 – {year} Jeffrey Pfau, licenciado sob a Licença Pública do Mozilla, versão 2.0 +O Game Boy Advance é uma marca registada da Nintendo Co., Ltd. + + + + {projectName} is an open-source Game Boy Advance emulator + O {projectName} é um emulador de Game Boy Advance de código fonte aberto + + + + QGBA::ApplicationUpdatePrompt + + + An update is available + Uma atualização está disponível + + + + An update to %1 is available. + + Uma atualização para o %1 está disponível. + + + + + +Do you want to download and install it now? You will need to restart the emulator when the download is complete. + +Quer descarregar e instalá-lo agora? Precisará reiniciar o emulador quando a descarga estiver completa. + + + + +Auto-update is not available on this platform. If you wish to update you will need to do it manually. + +Uma auto-atualização não está disponível nesta plataforma. Se deseja atualizar precisará fazê-lo manualmente. + + + + Current version: %1 +New version: %2 +Download size: %3 + Versão atual: %1 +Nova versão: %2 +Tamanho da descarga: %3 + + + + Downloading update... + A descarregar a atualização... + + + + Downloading failed. Please update manually. + A descarga falhou. Por favor, atualize manualmente. + + + + Downloading done. Press OK to restart %1 and install the update. + Descarga concluída. Pressione Ok para reiniciar o %1 e instalar a atualização. + + + + QGBA::ApplicationUpdater + + + Stable + Estável + + + + Development + Desenvolvimento + + + + Unknown + Desconhecido + + + + (None) + (Nenhum) + + + + QGBA::ArchiveInspector + + + Open in archive... + Abrir no arquivo... + + + + Loading... + A carregar... + + + + QGBA::AssetTile + + + Tile # + Ladrilho # + + + + Palette # + Paleta # + + + + Address + Endereço + + + + Red + Vermelho + + + + Green + Verde + + + + Blue + Azul + + + + + + 0x%0 (%1) + 0x%0 (%1) + + + + QGBA::AudioDevice + + + Can't set format of context-less audio device + Não pôde definir o formato do aparelho de áudio sem contexto + + + + Audio device is missing its core + O núcleo do aparelho de áudio está ausente + + + + Writing data to read-only audio device + Gravando dados no aparelho somente-leitura do áudio + + + + QGBA::AudioProcessorQt + + + Can't start an audio processor without input + Não pôde iniciar um processador de áudio sem entrada + + + + QGBA::AudioProcessorSDL + + + Can't start an audio processor without input + Não pôde iniciar um processador de áudio sem entrada + + + + QGBA::BattleChipView + + + BattleChip Gate + Portal do BattleChip + + + + Chip name + Nome do chip + + + + Insert + Inserir + + + + Save + Gravar + + + + Load + Carregar + + + + Add + Adicionar + + + + Remove + Remover + + + + Gate type + Tipo de Portal + + + + Inserted + Inserido + + + + Chip ID + ID do Chip + + + + Update Chip data + Atualizar os dados do Chip + + + + Show advanced + Mostrar as opções avançadas + + + + BattleChip data missing + Portal do BattleChip + + + + BattleChip data is missing. BattleChip Gates will still work, but some graphics will be missing. Would you like to download the data now? + Os dados do BattleChip estão ausentes. O BattleChip Gates ainda funcionará, mas alguns gráficos estarão ausentes. Gostaria de descarregar os dados agora? + + + + + Select deck file + Selecionar o ficheiro do deck + + + + Incompatible deck + Deck incompatível + + + + The selected deck is not compatible with this Chip Gate + O deck selecionado não é compatível com este Portal do Chip + + + + QGBA::CheatsModel + + + (untitled) + (sem título) + + + + Failed to open cheats file: %1 + Falha ao abrir o ficheiro das trapaças: %1 + + + + QGBA::CheatsView + + + Cheats + Trapaças + + + + Add New Code + Adicionar Novo Código + + + + Remove + Remover + + + + Add Lines + Adicionar Linhas + + + + Code type + Tipo de código + + + + Save + Gravar + + + + Load + Carregar + + + + Enter codes here... + Insira os códigos aqui... + + + + + Autodetect (recommended) + Auto-detetar (recomendado) + + + + + Select cheats file + Selecionar o ficheiro das trapaças + + + + Some cheats could not be added. Please ensure they're formatted correctly and/or try other cheat types. + Algumas trapaças não puderam ser adicionadas. Por favor tenha certeza que eles estão formatadas corretamente e/ou tente outros tipos de trapaça. + + + + QGBA::CoreController + + + Reset r%1-%2 %3 + Resetar r%1-%2 %3 + + + + + Rewinding not currently enabled + O rebobinamento não está ativado atualmente + + + + Reset the game? + Resetar o jogo? + + + + Most games will require a reset to load the new save. Do you want to reset now? + A maioria dos jogos requerirão um reset para carregar o novo save. Quer resetar agora? + + + + Failed to open save file: %1 + Falha ao abrir o ficheiro dde gravação: %1 + + + + Failed to open game file: %1 + Falha ao abrir o ficheiro do jogo: %1 + + + + Can't yank pack in unexpected platform! + Não pode arrancar o pacote numa plataforma inesperada! + + + + Failed to open snapshot file for reading: %1 + Falha ao abrir o ficheiro do snapshot para leitura: %1 + + + + Failed to open snapshot file for writing: %1 + Falha ao abrir o ficheiro do snapshot para gravação: %1 + + + + QGBA::CoreManager + + + Failed to open game file: %1 + Falha ao abrir o ficheiro do jogo: %1 + + + + Could not load game. Are you sure it's in the correct format? + Não pôde carregar o jogo. Tem certeza que está no formato correto? + + + + Failed to open save file; in-game saves cannot be updated. Please ensure the save directory is writable without additional privileges (e.g. UAC on Windows). + Falha ao abrir o ficheiro de save; os saves dentro do jogo não podem ser atualizados. Por favor tenha certeza que o diretório de save seja gravável sem privilégios adicionais (ex: UAC no Windows). + + + + QGBA::DebuggerConsole + + + Debugger + Debugger + + + + Enter command (try `help` for more info) + Insira o comando (tente `help` para mais informações) + + + + Break + Parar + + + + QGBA::DebuggerConsoleController + + + Could not open CLI history for writing + Não pôde abrir o histórico do CLI para gravar + + + + QGBA::DolphinConnector + + + Connect to Dolphin + Conectar ao Dolphin + + + + Local computer + Computador local + + + + IP address + Endereço do IP + + + + Connect + Conectar + + + + Disconnect + Desconectar + + + + Close + Fechar + + + + Reset on connect + Resetar ao conectar + + + + Couldn't Connect + Não Pôde Conectar + + + + Could not connect to Dolphin. + Não pôde conectar com o Dolphin. + + + + QGBA::ForwarderGenerator + + + 3DS + 3DS + + + + Vita + Vita + + + + QGBA::ForwarderGenerator3DS + + + Icon + Ícone + + + + Banner + Banner + + + + QGBA::ForwarderGeneratorVita + + + Bubble + Bolha + + + + Background + 2º plano + + + + Startup + Inicialização + + + + QGBA::ForwarderView + + + Create forwarder + Criar encaminhador + + + + Files + Ficheiros + + + + ROM file: + Ficheiro da ROM: + + + + + + Browse + Explorar + + + + Output filename: + Nome do ficheiro de saída: + + + + Forwarder base: + Base do encaminhador: + + + + Latest stable version + Última versão estável + + + + Latest development build + Último build de desenvolvimento + + + + Specific file + Ficheiro específico + + + + Base file: + Ficheiro base: + + + + System + Sistema + + + + 3DS + 3DS + + + + Vita + Vita + + + + Presentation + Apresentação + + + + Title: + Título: + + + + Images: + Imagens: + + + + Use default image + Usar imagem padrão + + + + Preferred size: + Tamanho preferido: + + + + Select image file + Selecionar ficheiro de imagem + + + + Select ROM file + Selecione o ficheiro da ROM + + + + Select output filename + Selecionar o nome do ficheiro de saída + + + + Select base file + Selecionar o ficheiro base + + + + Build finished + Compilação concluída + + + + Forwarder finished building + O encaminhador encerrou a compilação + + + + Build failed + Falhou em criar o build + + + + Failed to build forwarder + Falhou em criar o encaminhador + + + + %1 installable package (*.%2) + %1 pacote instalável (*.%2) + + + + Select an image + Selecione uma imagem + + + + QGBA::FrameView + + + Inspect frame + Inspecionar frame + + + + Magnification + Ampliação + + + + Freeze frame + Congelar frame + + + + Backdrop color + Cor do 2º plano + + + + Disable scanline effects + Desativar efeitos da scanline + + + + Export + Exportar + + + + Reset + Resetar + + + + Export frame + Exportar frame + + + + Portable Network Graphics (*.png) + Gráficos Portáteis da Rede (*.png) + + + + None + Nenhum + + + + Background + 2º plano + + + + Window + Janela + + + + Objwin + Objwin + + + + Sprite + Imagem Móvel + + + + Backdrop + 2º Plano + + + + Frame + Frame + + + + %1 %2 + %1 %2 + + + + QGBA::GBAApp + + + Enable Discord Rich Presence + Ativar o Discord Rich Presence + + + + QGBA::GBAKeyEditor + + + Clear Button + Limpar Botão + + + + Clear Analog + Limpar Analógico + + + + Refresh + Atualizar + + + + Set all + Definir todos + + + + QGBA::GDBWindow + + + Server settings + Configurações do servidor + + + + Local port + Porta local + + + + Bind address + Vincular endereço + + + + Write watchpoints behavior + Gravar o comportamento dos pontos de observação + + + + Standard GDB + GDB padrão + + + + Internal change detection + Detecção das mudanças internas + + + + Break on all writes + Interromper todas as gravações + + + + Break + Pausar + + + + Stop + Parar + + + + Start + Iniciar + + + + Crash + Crash + + + + Could not start GDB server + Não pôde iniciar o servidor do GDB + + + + QGBA::GIFView + + + Record GIF/WebP/APNG + Gravar GIF/WebP/APNG + + + + Loop + Repetição + + + + Start + Iniciar + + + + Stop + Parar + + + + Select File + Selecionar ficheiro + + + + APNG + APNG + + + + GIF + GIF + + + + WebP + WebP + + + + Frameskip + Frameskip + + + + Failed to open output file: %1 + Falhou em abrir o ficheiro de saída: %1 + + + + Select output file + Selecionar o ficheiro de saída + + + + Graphics Interchange Format (*.gif);;WebP ( *.webp);;Animated Portable Network Graphics (*.png *.apng) + Formato da Inter-Mudança dos Gráficos (*.gif);;WebP ( *.webp);;Gráficos da Rede Animada Portátil (*.png *.apng) + + + + QGBA::GameBoy + + + + Autodetect + Auto-detetar + + + + Game Boy (DMG) + Game Boy (DMG) + + + + Game Boy Pocket (MGB) + Game Boy Pocket (MGB) + + + + Super Game Boy (SGB) + Super Game Boy (SGB) + + + + Super Game Boy 2 (SGB) + Super Game Boy 2 (SGB) + + + + Game Boy Color (CGB) + Game Boy Color (CGB) + + + + Game Boy Advance (AGB) + Game Boy Advance (AGB) + + + + Super Game Boy Color (SGB + CGB) + Super Game Boy Color (SGB + CGB) + + + + ROM Only + So a ROM + + + + MBC1 + MBC1 + + + + MBC2 + MBC2 + + + + MBC3 + MBC3 + + + + MBC3 + RTC + MBC3 + RTC + + + + MBC5 + MBC5 + + + + MBC5 + Rumble + MBC5 + Rumble + + + + MBC6 + MBC6 + + + + MBC7 (Tilt) + MBC7 (Inclinação) + + + + MMM01 + MMM01 + + + + HuC-1 + HuC-1 + + + + HuC-3 + HuC-3 + + + + Pocket Cam + Câmara de Bolso + + + + TAMA5 + TAMA5 + + + + Wisdom Tree + Árvore da Sabedoria + + + + NT (old 1) + NT (antigo 1) + + + + NT (old 2) + NT (antigo 2) + + + + NT (new) + NT (novo) + + + + Pokémon Jade/Diamond + Pokémon Jade/Diamond + + + + BBD + BBD + + + + Hitek + Hitek + + + + GGB-81 + GGB-81 + + + + Li Cheng + Li Cheng + + + + Sachen (MMC1) + Sachen (MMC1) + + + + Sachen (MMC2) + Sachen (MMC2) + + + + QGBA::IOViewer + + + I/O Viewer + Visualizador de E/S + + + + 0x0000 + 0x0000 + + + + + + B + B + + + + Background mode + Modo do 2º plano + + + + Mode 0: 4 tile layers + Modo 0: 4 camadas de ladrilhos + + + + Mode 1: 2 tile layers + 1 rotated/scaled tile layer + Modo 1: 2 camadas de ladrilhos + 1 camada de ladrilhos rotacionada/redimensionada + + + + Mode 2: 2 rotated/scaled tile layers + Modo 2: 2 camadas de ladrilhos rotacionadas/redimensionadas + + + + Mode 3: Full 15-bit bitmap + Modo 3: Bitmap de 15 bits completo + + + + Mode 4: Full 8-bit bitmap + Modo 4: Bitmap de 8 bits completo + + + + Mode 5: Small 15-bit bitmap + Modo 5: Bitmap de 15 bits pequeno + + + + CGB Mode + Modo CGB + + + + Frame select + Selecionar frame + + + + Unlocked HBlank + HBlank Destrancado + + + + Linear OBJ tile mapping + Mapeamento do ladrilho do OBJ linear + + + + Force blank screen + Forçar ecrã vazio + + + + Enable background 0 + Ativar o 2º plano 0 + + + + Enable background 1 + Ativar o 2º plano 1 + + + + Enable background 2 + Ativar o 2º plano 2 + + + + Enable background 3 + Ativar o 2º plano 3 + + + + Enable OBJ + Ativar OBJ + + + + Enable Window 0 + Ativar a Janela 0 + + + + Enable Window 1 + Ativar a Janela 1 + + + + Enable OBJ Window + Ativar a Janela do OBJ + + + + Swap green components + Trocar componentes verdes + + + + Currently in VBlank + Atualmente no VBlank + + + + Currently in HBlank + Atualmente no HBlank + + + + Currently in VCounter + Atualmente no VCounter + + + + Enable VBlank IRQ generation + Ativar geração do IRQ VBlank + + + + Enable HBlank IRQ generation + Ativar geração do IRQ HBlank + + + + Enable VCounter IRQ generation + Ativar geração do IRQ VCounter + + + + VCounter scanline + Scanline do VCounter + + + + Current scanline + Scanline atual + + + + + + + Priority + Prioridade + + + + + + + Tile data base (* 16kB) + Banco de dados dos ladrilhos (* 16 kBs) + + + + + + + Enable mosaic + Ativar o mosaico + + + + + + + Enable 256-color + Ativar 256 cores + + + + + + + Tile map base (* 2kB) + Base do mapa dos ladrilhos (* 2 kBs) + + + + + + + Background dimensions + Dimensões do 2º plano + + + + + Overflow wraps + Embrulhos do excesso + + + + + + + + + Horizontal offset + Deslocamento horizontal + + + + + + + + + Vertical offset + Deslocamento vertical + + + + + + + + + + + + + + + Fractional part + Parte fracionária + + + + + + + + + + + Integer part + Parte inteira + + + + + + + Integer part (low) + Parte inteira (baixa) + + + + + + + Integer part (high) + Parte inteira (alta) + + + + + End x + Final x + + + + + Start x + X inicial + + + + + End y + Final y + + + + + Start y + Y inicial + + + + Window 0 enable BG 0 + Janela 0 ativar BG 0 + + + + Window 0 enable BG 1 + Janela 0 ativar BG 1 + + + + Window 0 enable BG 2 + Janela 0 ativar BG 2 + + + + Window 0 enable BG 3 + Janela 0 ativar BG 3 + + + + Window 0 enable OBJ + Janela 0 ativar OBJ + + + + Window 0 enable blend + Janela 0 ativar a mistura + + + + Window 1 enable BG 0 + Janela 1 ativar BG 0 + + + + Window 1 enable BG 1 + Janela 1 ativar BG 1 + + + + Window 1 enable BG 2 + Janela 1 ativar BG 2 + + + + Window 1 enable BG 3 + Janela 1 ativar BG 3 + + + + Window 1 enable OBJ + Janela 1 ativar OBJ + + + + Window 1 enable blend + Janela 1 ativar a mistura + + + + Outside window enable BG 0 + Fora da janela ativar BG 0 + + + + Outside window enable BG 1 + Fora da janela ativar BG 1 + + + + Outside window enable BG 2 + Fora da janela ativar BG 2 + + + + Outside window enable BG 3 + Fora da janela ativar BG 3 + + + + Outside window enable OBJ + Fora da janela ativar OBJ + + + + Outside window enable blend + Fora da janela ativar a mistura + + + + OBJ window enable BG 0 + Janela do OBJ ativar BG 0 + + + + OBJ window enable BG 1 + Janela do OBJ ativar BG 1 + + + + OBJ window enable BG 2 + Janela do OBJ ativar BG 2 + + + + OBJ window enable BG 3 + Janela do OBJ ativar BG 3 + + + + OBJ window enable OBJ + Janela do OBJ ativar OBJ + + + + OBJ window enable blend + Janela do OBJ ativar a mistura + + + + Background mosaic size vertical + Tamanho vertical do mosaico do 2º plano + + + + Background mosaic size horizontal + Tamanho horizontal do mosaico do 2º plano + + + + Object mosaic size vertical + Tamanho vertical do mosaico dos objetos + + + + Object mosaic size horizontal + Tamanho horizontal do mosaico dos objetos + + + + BG 0 target 1 + BG 0 alvo 1 + + + + BG 1 target 1 + BG 1 alvo 1 + + + + BG 2 target 1 + BG 2 alvo 1 + + + + BG 3 target 1 + BG 3 alvo 1 + + + + OBJ target 1 + Alvo do OBJ 1 + + + + Backdrop target 1 + Alvo do 2º plano 1 + + + + Blend mode + Modo de mistura + + + + Disabled + Desativado + + + + Additive blending + Mistura aditiva + + + + Brighten + Clarear + + + + Darken + Escurecer + + + + BG 0 target 2 + BG 0 alvo 2 + + + + BG 1 target 2 + BG 1 alvo 2 + + + + BG 2 target 2 + BG 2 alvo 2 + + + + BG 3 target 2 + BG 3 alvo 2 + + + + OBJ target 2 + Alvo do OBJ 2 + + + + Backdrop target 2 + Alvo do 2º plano 2 + + + + Blend A (target 1) + Mistura A (alvo 1) + + + + Blend B (target 2) + Mistura B (alvo 2) + + + + Blend Y + Mistura Y + + + + + Sweep shifts + Trocas de varredura + + + + + Sweep subtract + Subtrair varredura + + + + + Sweep time (in 1/128s) + Tempo da varredura (em 1/128s) + + + + + + + + + + + Sound length + Comprimento do som + + + + + + + Duty cycle + Ciclo do dever + + + + + + + + + Envelope step time + Tempo do passo do envelope + + + + + + + + + Envelope increase + Aumentar envelope + + + + + + + + + Initial volume + Volume inicial + + + + + + Sound frequency + Frequência do som + + + + + + + + + + + Timed + Programado + + + + + + + + + + + Reset + Resetar + + + + Double-size wave table + Tabela da onda com tamanho duplo + + + + Active wave table + Tabela de som ativa + + + + + Enable channel 3 + Ativar canal 3 + + + + + Volume + Volume + + + + + 0% + 0% + + + + + + 100% + 100% + + + + + + 50% + 50% + + + + + + 25% + 25% + + + + + + + 75% + 75% + + + + + Clock divider + Divisor de relógio + + + + + Register stages + Estágios do registo + + + + + 15 + 15 + + + + + 7 + 7 + + + + + Shifter frequency + Frequência do deslocador + + + + PSG volume right + Volume do PSG direito + + + + PSG volume left + Volume do PSG esquerdo + + + + + Enable channel 1 right + Ativar canal 1 a direita + + + + + Enable channel 2 right + Ativar canal 2 a direita + + + + + Enable channel 3 right + Ativar canal 3 a direita + + + + + Enable channel 4 right + Ativar canal 4 a direita + + + + + Enable channel 1 left + Ativar canal 1 a esquerda + + + + + Enable channel 2 left + Ativar canal 2 a esquerda + + + + + Enable channel 3 left + Ativar canal 3 a esquerda + + + + + Enable channel 4 left + Ativar canal 4 a esquerda + + + + PSG master volume + Volume mestre do PSG + + + + Loud channel A + Canal A alto + + + + Loud channel B + Canal B alto + + + + Enable channel A right + Ativar canal A a direita + + + + Enable channel A left + Ativar canal A a esquerda + + + + Channel A timer + Cronômetro do canal A + + + + + 0 + 0 + + + + + + + + + + + + 1 + 1 + + + + Channel A reset + Resetar canal A + + + + Enable channel B right + Ativar canal B a direita + + + + Enable channel B left + Ativar canal B a esquerda + + + + Channel B timer + Cronômetro do canal B + + + + Channel B reset + Resetar canal B + + + + + Active channel 1 + Canal ativo 1 + + + + + Active channel 2 + Canal ativo 2 + + + + + Active channel 3 + Canal ativo 3 + + + + + Active channel 4 + Canal ativo 4 + + + + + Enable audio + Ativar áudio + + + + Bias + Viés + + + + Resolution + Resolução + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sample + Amostra + + + + + + + + + + + Address (low) + Endereço (baixo) + + + + + + + + + + + Address (high) + Endereço (alto) + + + + + + Sound frequency (low) + Frequência do som (baixo) + + + + + + Sound frequency (high) + Frequência do som (alto) + + + + Source (high) + Fonte (alta) + + + + Source (low) + Fonte (baixa) + + + + Destination (high) + Destinação (alta) + + + + Destination (low) + Destinação (baixa) + + + + + Green (low) + Verde (baixo) + + + + + Green (high) + Verde (alto) + + + + + + + Word count + Contagem de palavras + + + + + + + Destination offset + Deslocamento do destino + + + + + + + + + + + Increment + Incrementar + + + + + + + + + + + Decrement + Decréscimo + + + + + + + + + + + Fixed + Consertado + + + + + + + Increment and reload + Incrementar e recarregar + + + + + + + Source offset + Deslocamento da fonte + + + + + + + Repeat + Repetir + + + + + + + 32-bit + 32 bits + + + + + + + Start timing + Iniciar timing + + + + + + + + Immediate + Imediato + + + + + + + + + + + VBlank + VBlank + + + + + + + + + + HBlank + HBlank + + + + + + + + + + + + IRQ + IRQ + + + + + + + + + + + + + + Enable + Ativar + + + + + + Audio FIFO + Áudio do FIFO + + + + Video Capture + Captura de Vídeo + + + + DRQ + DRQ + + + + + + + + + + + Value + Valor + + + + + + + Scale + Escala + + + + + + + + 1/64 + 1/64 + + + + + + + + 1/256 + 1/256 + + + + + + + + 1/1024 + 1/1024 + + + + + + Cascade + Em cascata + + + + + A + A + + + + + Select + Select + + + + + Start + Start + + + + + Right + Direita + + + + + Left + Esquerda + + + + + Up + Para cima + + + + + Down + Para baixo + + + + + R + R + + + + + L + L + + + + Condition + Condição + + + + SC + SC + + + + SD + SD + + + + SI + SI + + + + SO + SO + + + + + VCounter + VCounter + + + + + Timer 0 + Cronômetro 0 + + + + + Timer 1 + Cronômetro 1 + + + + + Timer 2 + Cronômetro 2 + + + + + Timer 3 + Cronômetro 3 + + + + + SIO + SIO + + + + + DMA 0 + DMA 0 + + + + + DMA 1 + DMA 1 + + + + + DMA 2 + DMA 2 + + + + + DMA 3 + DMA 3 + + + + + Keypad + Keypad + + + + + Gamepak + Gamepak + + + + SRAM wait + Espera do SRAM + + + + + + + + 4 + 4 + + + + + + + 3 + 3 + + + + + + + + 2 + 2 + + + + + + + + 8 + 8 + + + + Cart 0 non-sequential + Cartucho 0 não-sequencial + + + + Cart 0 sequential + Cartucho 0 sequencial + + + + Cart 1 non-sequential + Cartucho 1 não-sequencial + + + + Cart 1 sequential + Cartucho 1 sequencial + + + + Cart 2 non-sequential + Cartucho 2 não-sequencial + + + + Cart 2 sequential + Cartucho 2 sequencial + + + + PHI terminal + Terminal do PHI + + + + + Disable + Desativar + + + + 4.19MHz + 4.19 MHz + + + + 8.38MHz + 8.38 MHz + + + + 16.78MHz + 16.78 MHz + + + + Gamepak prefetch + Pré-carga do Gamepak + + + + Enable IRQs + Ativar IRQs + + + + Right/A + Direita/A + + + + Left/B + Esquerda/B + + + + Up/Select + Para cima/Select + + + + Down/Start + Para baixo/Start + + + + Active D-pad + D-pad Ativo + + + + Active face buttons + Botões de face ativos + + + + Internal clock + Relógio interno + + + + 32× clocking (CGB only) + 32x o clock (só no GBC) + + + + Transfer active + Transferência ativa + + + + Divider + Divisor + + + + 1/16 + 1/16 + + + + + LCD STAT + STAT do LCD + + + + + Timer + Cronômetro + + + + + Serial + Série + + + + + Joypad + Joypad + + + + Volume right + Volume direito + + + + Output right + Saída direita + + + + Volume left + Volume esquerdo + + + + Output left + Saída esquerda + + + + Background enable/priority + Ativar/prioridade do 2º plano + + + + Enable sprites + Ativar imagens móveis + + + + Double-height sprites + Imagens móveis com dobro da altura + + + + Background tile map + Mapa dos ladrilhos do 2º plano + + + + + 0x9800 – 0x9BFF + 0x9800 – 0x9BFF + + + + + 0x9C00 – 0x9FFF + 0x9C00 – 0x9FFF + + + + Background tile data + Dados dos ladrilhos do 2º plano + + + + 0x8800 – 0x87FF + 0x8800 – 0x87FF + + + + 0x8000 – 0x8FFF + 0x8000 – 0x8FFF + + + + Enable window + Ativar janela + + + + Window tile map + Mapa dos ladrilhos da janela + + + + Enable LCD + Ativar LCD + + + + Mode + Modo + + + + 0: HBlank + 0: HBlank + + + + 1: VBlank + 1: VBlank + + + + 2: OAM scan + 2: Scan do OAM + + + + 3: HDraw + 3: HDraw + + + + In LYC + Em LYC + + + + Enable HBlank (mode 0) IRQ + Ativar IRQ do HBlank (modo 0) + + + + Enable VBlank (mode 1) IRQ + Ativar IRQ do VBlank (modo 1) + + + + Enable OAM (mode 2) IRQ + Ativar IRQ do OAM (modo 2) + + + + Enable LYC IRQ + Ativar IRQ do LYC + + + + Current Y coordinate + Coordenada Y atual + + + + Comparison Y coordinate + Comparação da coordenada Y + + + + Start upper byte + Iniciar byte superior + + + + + + Color 0 shade + Tom da cor 0 + + + + + + Color 1 shade + Tom da cor 1 + + + + + + Color 2 shade + Tom da cor 2 + + + + + + Color 3 shade + Tom da cor 3 + + + + Prepare to switch speed + Preparar para trocar a velocidade + + + + Double speed + Velocidade dupla + + + + VRAM bank + Banco do VRAM + + + + Length + Comprimento + + + + Timing + Cronometragem + + + + Write bit + Bit da gravação + + + + Read bit + Bit de leitura + + + + + Unknown + Desconhecido + + + + + Current index + Índice atual + + + + + Auto-increment + Auto-incrementar + + + + + Red + Vermelho + + + + + Blue + Azul + + + + Sprite ordering + Ordenação das imagens móveis + + + + OAM order + Ordem da OAM + + + + x coordinate sorting + Organização da coordenada X + + + + WRAM bank + Banco do WRAM + + + + QGBA::KeyEditor + + + + --- + --- + + + + Super (L) + Super (E) + + + + Super (R) + Super (D) + + + + Menu + Menu + + + + QGBA::LibraryTree + + + Name + Nome + + + + Location + Local + + + + Platform + Plataforma + + + + Size + Tamanho + + + + CRC32 + CRC32 + + + + QGBA::LoadSaveState + + + + %1 State + Estado %1 + + + + + + + + + + + + No Save + Nenhum Save + + + + 5 + 5 + + + + 6 + 6 + + + + 8 + 8 + + + + 4 + 4 + + + + 1 + 1 + + + + 3 + 3 + + + + 7 + 7 + + + + 9 + 9 + + + + 2 + 2 + + + + Cancel + Cancelar + + + + Load State + Carregar o State + + + + Save State + Gravar o State + + + + Empty + Vazio + + + + Corrupted + Corrompido + + + + Slot %1 + Slot %1 + + + + QGBA::LogConfigModel + + + + Default + Padrão + + + + Fatal + Fatal + + + + Error + Erro + + + + Warning + Aviso + + + + Info + Info + + + + Debug + Debug + + + + Stub + Stub + + + + Game Error + Erro do Jogo + + + + QGBA::LogController + + + [%1] %2: %3 + [%1] %2: %3 + + + + An error occurred + Um erro ocorreu + + + + DEBUG + DEBUG + + + + STUB + STUB + + + + INFO + INFO + + + + WARN + AVISAR + + + + ERROR + ERRO + + + + FATAL + FATAL + + + + GAME ERROR + ERRO DO JOGO + + + + QGBA::LogView + + + Logs + Registos + + + + Enabled Levels + Níveis Ativados + + + + Debug + Debug + + + + Stub + Stub + + + + Info + Informações + + + + Warning + Aviso + + + + Error + Erro + + + + Fatal + Fatal + + + + Game Error + Erro do Jogo + + + + Advanced settings + Configurações avançadas + + + + Clear + Limpar + + + + Max Lines + Máximo de Linhas + + + + QGBA::MapView + + + Maps + Mapas + + + + Magnification + Ampliação + + + + Export + Exportar + + + + Copy + Copiar + + + + Priority + Prioridade + + + + + Map base + Base do mapa + + + + + Tile base + Base dos ladrilhos + + + + Size + Tamanho + + + + + Offset + Deslocamento + + + + Xform + Xform + + + + Map Addr. + Endereço do Mapa. + + + + Mirror + Espelho + + + + None + Nenhum + + + + Both + Ambos + + + + Horizontal + Horizontal + + + + Vertical + Vertical + + + + + + N/A + N/D + + + + Export map + Exportar mapa + + + + Portable Network Graphics (*.png) + Gráficos Portáteis da Rede (*.png) + + + + QGBA::MemoryDump + + + Save Memory Range + Gravar o Alcance da Memória + + + + Start Address: + Endereço Inicial: + + + + Byte Count: + Contagem dos Bytes: + + + + Dump across banks + Dumpar através dos bancos + + + + Save memory region + Gravar a região da memória + + + + Failed to open output file: %1 + Falha ao abrir o ficheiro de saída: %1 + + + + QGBA::MemoryModel + + + Copy selection + Copiar seleção + + + + Save selection + Gravar seleção + + + + Paste + Colar + + + + Load + Carregar + + + + All + Todos + + + + Load TBL + Carregar TBL + + + + Save selected memory + Gravar a memória selecionada + + + + Failed to open output file: %1 + Falha ao abrir o ficheiro de saída: %1 + + + + Load memory + Carregar memória + + + + Failed to open input file: %1 + Falha ao abrir o ficheiro de entrada: %1 + + + + TBL + TBL + + + + ISO-8859-1 + ISO-8859-1 + + + + QGBA::MemorySearch + + + Memory Search + Busca na Memória + + + + Address + Endereço + + + + Current Value + Valor Atual + + + + + Type + Tipo + + + + Value + Valor + + + + Numeric + Numérico + + + + Text + Texto + + + + Width + Largura + + + + + Guess + Palpite + + + + 1 Byte (8-bit) + 1 Byte (8 bits) + + + + 2 Bytes (16-bit) + 2 Bytes (16 bits) + + + + 4 Bytes (32-bit) + 4 Bytes (32 bits) + + + + Number type + Tipo de número + + + + Decimal + Decimal + + + + Hexadecimal + Hexadecimal + + + + Search type + Tipo de busca + + + + Equal to value + Igual ao valor + + + + Greater than value + Maior do que o valor + + + + Less than value + Menor do que o valor + + + + Unknown/changed + Desconhecido/mudado + + + + Changed by value + Mudado pelo valor + + + + Unchanged + Sem mudança + + + + Increased + Aumentado + + + + Decreased + Diminuído + + + + Search ROM + Procurar na ROM + + + + New Search + Nova Busca + + + + Search Within + Procurar por Dentro + + + + Open in Memory Viewer + Abrir no Visualizador de Memória + + + + Refresh + Atualizar + + + + (%0/%1×) + (%0/%1×) + + + + (⅟%0×) + (⅟%0×) + + + + (%0×) + (%0×) + + + + %1 byte%2 + %1 byte%2 + + + + QGBA::MemoryView + + + Memory + Memória + + + + Inspect Address: + Inspecionar Endereço: + + + + Set Alignment: + Definir Alinhamento: + + + + &1 Byte + &1 Byte + + + + &2 Bytes + &2 Bytes + + + + &4 Bytes + &4 Bytes + + + + Unsigned Integer: + Inteiro não Assinado: + + + + Signed Integer: + Inteiro com Sinal: + + + + String: + Texto: + + + + Load TBL + Carregar TBL + + + + Copy Selection + Copiar Seleção + + + + Paste + Colar + + + + Save Selection + Gravar Seleção + + + + Save Range + Gravar Alcance + + + + Load + Carregar + + + + QGBA::MessagePainter + + + Frame %1 + Frame %1 + + + + QGBA::ObjView + + + Sprites + Imagens Móveis + + + + Address + Endereço + + + + Copy + Copiar + + + + Magnification + Ampliação + + + + Geometry + Geometria + + + + Position + Posição + + + + Dimensions + Dimensões + + + + Matrix + Matriz + + + + Export + Exportar + + + + Attributes + Atributos + + + + Transform + Transformar + + + + + Off + Desligado + + + + Palette + Paleta + + + + Double Size + Tamanho Duplo + + + + + + Return, Ctrl+R + Retornar, Ctrl+R + + + + Flipped + Invertido + + + + H + Short for horizontal + H + + + + V + Short for vertical + V + + + + Mode + Modo + + + + + Normal + Normal + + + + Mosaic + Mosaico + + + + Enabled + Ativado + + + + Priority + Prioridade + + + + Tile + Ladrilho + + + + + 0x%0 + 0x%0 + + + + + + + + + + + --- + --- + + + + Trans + Trans + + + + OBJWIN + OBJWIN + + + + Invalid + Inválido + + + + + N/A + N/D + + + + Export sprite + Exportar imagem móvel + + + + Portable Network Graphics (*.png) + Gráficos Portáteis da Rede (*.png) + + + + QGBA::OverrideView + + + Game Overrides + Substituições do Jogo + + + + Game Boy Advance + Game Boy Advance + + + + + + + Autodetect + Auto-detetar + + + + Realtime clock + Relógio em tempo real + + + + Gyroscope + Giroscópio + + + + Tilt + Inclinação + + + + Light sensor + Sensor de luz + + + + Rumble + Rumble + + + + Save type + Tipo de save + + + + None + Nenhum + + + + SRAM + SRAM + + + + Flash 512kb + Flash de 512 kbs + + + + Flash 1Mb + Flash de 1 Mb + + + + EEPROM 8kB + EEPROM de 8 kBs + + + + EEPROM 512 bytes + EEPROM de 512 bytes + + + + SRAM 64kB (bootlegs only) + SRAM de 64 kBs (só bootlegs) + + + + Idle loop + Repetição inativa + + + + Game Boy Player features + Funções do Game Boy Player + + + + VBA bug compatibility mode + Modo de compatibilidade dos bugs do VBA + + + + Game Boy + Game Boy + + + + Game Boy model + Modelo do Game Boy + + + + Memory bank controller + Controle do banco de memória + + + + Background Colors + Cores do 2º Plano + + + + Sprite Colors 1 + Cores da Imagem Móvel 1 + + + + Sprite Colors 2 + Cores da Imagem Móvel 2 + + + + Palette preset + Pré-definições da paleta + + + + Official MBCs + MBCs oficiais + + + + Licensed MBCs + MBCs licenciados + + + + Unlicensed MBCs + MBCs não licenciados + + + + QGBA::PaletteView + + + Palette + Paleta + + + + Background + 2º plano + + + + Objects + Objetos + + + + Selection + Seleção + + + + Red + Vermelho + + + + Green + Verde + + + + Blue + Azul + + + + 16-bit value + Valor de 16 bits + + + + Hex code + Código hexadecimal + + + + Palette index + Indice da paleta + + + + Export BG + Exportar BG + + + + Export OBJ + Exportar OBJ + + + + #%0 + #%0 + + + + 0x%0 + 0x%0 + + + + + + + 0x%0 (%1) + 0x%0 (%1) + + + + Export palette + Exportar paleta + + + + Windows PAL (*.pal);;Adobe Color Table (*.act) + Windows PAL (*.pal);;Tabela de Cores da Adobe (*.act) + + + + Failed to open output palette file: %1 + Falha ao abrir o ficheiro de saída da paleta: %1 + + + + QGBA::PlacementControl + + + Adjust placement + Ajustar posicionamento + + + + All + Tudo + + + + Offset + Deslocamento + + + + X + X + + + + Y + Y + + + + QGBA::PrinterView + + + Game Boy Printer + Impressora do Game Boy + + + + Hurry up! + Apresse-se! + + + + Tear off + Rasgar + + + + Magnification + Ampliação + + + + Copy + Copiar + + + + Save Printout + Gravar impressão + + + + Portable Network Graphics (*.png) + Gráficos Portáteis da Rede (*.png) + + + + QGBA::ROMInfo + + + + + + (unknown) + (desconhecido) + + + + bytes + bytes + + + + (no database present) + (nenhum banco de dados presente) + + + + ROM Info + Informações da ROM + + + + Game name: + Nome do jogo: + + + + Internal name: + Nome interno: + + + + Game ID: + ID do Jogo: + + + + File size: + Tamanho do ficheiro: + + + + CRC32: + CRC32: + + + + QGBA::ReportView + + + Bug report archive + Arquivo do relatório dos bugs + + + + ZIP archive (*.zip) + Arquivo ZIP (*.zip) + + + + Generate Bug Report + Gerar Relatório do Bug + + + + <html><head/><body><p>To file a bug report, please first generate a report file to attach to the bug report you're about to file. It is recommended that you include the save files, as these often help with debugging issues. This will collect some information about the version of {projectName} you're running, your configuration, your computer, and the game you currently have open (if any). Once this collection is completed you can review all of the information gathered below and save it to a zip file. The collection will automatically attempt to redact any personal information, such as your username if it's in any of the paths gathered, but just in case you can edit it afterwards. After you have generated and saved it, please click the button below or go to <a href="https://mgba.io/i/"><span style=" text-decoration: underline; color:#2980b9;">mgba.io/i</span></a> to file the bug report on GitHub. Make sure to attach the report you generated!</p></body></html> + <html><head/><body><p>Para registar um relatório de bug comece por gerar um ficheiro de relatório para anexar ao relatório de bug que está prestes a enviar. É recomendável incluir os ficheiros save, porque geralmente ajudam com problemas de depuração. Isso coletará algumas informações sobre a versão do {projectName} que executa, a sua configuração, o seu computador e o jogo que está aberto no momento (se houver). Assim que esta colheita estiver concluída, poderá rever todas as informações coletadas abaixo e gravá-las num ficheiro zip. A colheita tentará redigir automaticamente qualquer informação pessoal, como o seu nome de utilizador, se estiver em algum dos caminhos coletados, mas pode editá-lo posteriormente. Após gerá-lo e gravá-lo, clique no botão abaixo ou vá para <a href="https://mgba.io/i/"><span style=" text-decoration: underline; color:#2980b9;">mgba.io/i</span></a> para registar o relatório de bug no GitHub. Certifique-se de anexar o relatório que gerou!</p></body></html> + + + + Generate report + Gerar relatório + + + + Save + Gravar + + + + Open issue list in browser + Abrir lista de problemas no navegador + + + + Include save file + Incluir ficheiro do save + + + + Create and include savestate + Criar e incluir savestate + + + + QGBA::SaveConverter + + + Save games and save states (%1) + Saves dos jogos e save states (%1) + + + + Select save game or save state + Selecione o save do jogo ou save state + + + + Save games (%1) + Saves dos jogos (%1) + + + + Select save game + Selecione o save do jogo + + + + Conversion failed + A conversão falhou + + + + Failed to convert the save game. This is probably a bug. + Falhou em converter o save do jogo. Isto é provavelmente um bug. + + + + No file selected + Nenhum ficheiro selecionado + + + + Could not open file + Não pôde abrir o ficheiro + + + + No valid formats found + Não foram encontrados formatos válidos + + + + Please select a valid input file + Por favor selecione um ficheiro de entrada válido + + + + No valid conversions found + Não foram encontradas conversões válidas + + + + Cannot convert save games between platforms + Não pôde converter os saves do jogo entre as plataformas + + + + Convert/Extract Save Game + Converter/Extrair o Save do Jogo + + + + Input file + Ficheiro de entrada + + + + + Browse + Explorar + + + + Output file + Ficheiro de saída + + + + %1 %2 save game + %1 %2 save do jogo + + + + little endian + little endian + + + + big endian + big endian + + + + SRAM + SRAM + + + + %1 flash + %1 flash + + + + %1 EEPROM + %1 EEPROM + + + + %1 SRAM + RTC + %1 SRAM + RTC + + + + %1 SRAM + %1 SRAM + + + + packed MBC2 + empacotou o MBC2 + + + + unpacked MBC2 + desempacotou o MBC2 + + + + MBC6 flash + Flash do MBC6 + + + + MBC6 combined SRAM + flash + MBC6 SRAM combinado + flash + + + + MBC6 SRAM + SRAM do MBC6 + + + + TAMA5 + TAMA5 + + + + %1 (%2) + %1 (%2) + + + + %1 save state with embedded %2 save game + %1 save state com %2 saves do jogo embutido + + + + %1 SharkPort %2 save game + %1 SharkPort %2 save do jogo + + + + %1 GameShark Advance SP %2 save game + %1 GameShark Advance SP %2 save do jogo + + + + QGBA::ScriptingTextBuffer + + + Untitled buffer + Buffer sem título + + + + QGBA::ScriptingView + + + Scripting + Scripting + + + + Run + Executar + + + + File + Ficheiro + + + + Load recent script + Carregar script recente + + + + Load script... + Carregar script... + + + + &Reset + &Resetar + + + + 0 + 0 + + + + Select script to load + Selecione o script a carregar + + + + Lua scripts (*.lua) + Scripts do lua (*.lua) + + + + All files (*.*) + Todos os ficheiros (*.*) + + + + QGBA::SensorView + + + Sensors + Sensores + + + + Realtime clock + Relógio em tempo real + + + + Fixed time + Tempo fixo + + + + System time + Horário do sistema + + + + Start time at + Horário do início em + + + + Now + Agora + + + + Offset time + Tempo do deslocamento + + + + sec + segs + + + + MM/dd/yy hh:mm:ss AP + MM/dd/aa hh:mm:ss AP + + + + Light sensor + Sensor de luz + + + + Brightness + Brilho + + + + Tilt sensor + Sensor de inclinação + + + + + Set Y + Definir Y + + + + + Set X + Definir X + + + + Gyroscope + Giroscópio + + + + Sensitivity + Sensibilidade + + + + QGBA::SettingsView + + + + Qt Multimedia + Multimídia do Qt + + + + SDL + SDL + + + + Software (Qt) + Software (Qt) + + + + + OpenGL + OpenGL + + + + OpenGL (force version 1.x) + OpenGL (forçar a versão 1.x) + + + + None + Nenhum + + + + None (Still Image) + Nenhum (Imagem Parada) + + + + Keyboard + Teclado + + + + Controllers + Controles + + + + Shortcuts + Atalhos + + + + + Shaders + Shaders + + + + Select BIOS + Selecionar BIOS + + + + Select directory + Selecione o diretório + + + + (%1×%2) + (%1×%2) + + + + Never + Nunca + + + + Just now + Aconteceu agora + + + + Less than an hour ago + Menos do que uma hora atrás + + + + %n hour(s) ago + + %n hora atrás + %n horas atrás + + + + + + %n day(s) ago + + %n dia atrás + %n dias atrás + + + + + + Settings + Configurações + + + + Audio/Video + Áudio/Vídeo + + + + Gameplay + Jogabilidade + + + + Interface + Interface + + + + Update + Atualizar + + + + Emulation + Emulação + + + + Enhancements + Melhorias + + + + BIOS + BIOS + + + + Paths + Caminhos + + + + Logging + Registos + + + + Game Boy + Game Boy + + + + Audio driver: + Driver de áudio: + + + + Audio buffer: + Buffer do áudio: + + + + + 1536 + 1536 + + + + 512 + 512 + + + + 768 + 768 + + + + 1024 + 1024 + + + + 2048 + 2048 + + + + 3072 + 3072 + + + + 4096 + 4096 + + + + samples + Amostras + + + + Sample rate: + Taxa das amostras: + + + + + 44100 + 44100 + + + + 22050 + 22050 + + + + 32000 + 32000 + + + + 48000 + 48000 + + + + Hz + Hz + + + + Volume: + Volume: + + + + + + + Mute + Mudo + + + + Fast forward volume: + Avanço rápido do volume: + + + + Audio in multiplayer: + Áudio no multiplayer: + + + + All windows + Todas as janelas + + + + Player 1 window only + Só a janela do jogador 1 + + + + Currently active player window + Atualmente na janela do jogador ativo + + + + Display driver: + Driver de vídeo: + + + + Frameskip: + Frameskip: + + + + Skip every + Ignorar a cada + + + + + frames + frames + + + + FPS target: + FPS alvo: + + + + frames per second + Frames por segundo + + + + Sync: + Sincronizar: + + + + + Video + Vídeo + + + + + Audio + Áudio + + + + Lock aspect ratio + Travar a proporção do aspeto + + + + Force integer scaling + Forçar o dimensionamento do inteiro + + + + Bilinear filtering + Filtragem bilinear + + + + Show filename instead of ROM name in library view + Mostrar o nome do ficheiro ao invés do nome da ROM na visualização da biblioteca + + + + + Pause + Pausar + + + + When inactive: + Quando inativo: + + + + On loading a game: + Ao carregar um jogo: + + + + Load last state + Carregar o último state + + + + Load cheats + Carregar trapaças + + + + Save entered cheats + Gravar as trapaças inseridas + + + + When minimized: + Quando minimizado: + + + + Current channel: + Canal atual: + + + + Current version: + Versão atual: + + + + Update channel: + Canal da atualização: + + + + Available version: + Versão disponível: + + + + (Unknown) + (Desconhecido) + + + + Last checked: + Verificado pela última vez: + + + + Automatically check on start + Verificar automaticamente ao iniciar + + + + Check now + Verificar agora + + + + Default color palette only + Só a cor padrão da paleta + + + + SGB color palette if available + Paleta das cores do SGB se disponível + + + + GBC color palette if available + Paleta das cores do GBC se disponível + + + + SGB (preferred) or GBC color palette if available + SGB (preferido) ou paleta das cores do GBC se disponível + + + + Game Boy Camera + Câmara do Game Boy + + + + Driver: + Driver: + + + + Source: + Fonte: + + + + Native (59.7275) + Nativo (59,7275) + + + + Interframe blending + Mistura do interframe + + + + Language + Idioma + + + + Library: + Biblioteca: + + + + List view + Visualização em lista + + + + Tree view + Visualização em árvore + + + + Show when no game open + Mostrar quando nenhum jogo estiver aberto + + + + Clear cache + Limpar cache + + + + Allow opposing input directions + Permitir direções de entrada opostas + + + + Suspend screensaver + Suspender a proteção de ecrã + + + + Dynamically update window title + Atualizar título da janela dinamicamente + + + + Show filename instead of ROM name in title bar + Mostrar o nome do fiheiro ao invés do nome da ROM na barra de título + + + + Show OSD messages + Mostrar mensagens do OSD + + + + Enable Discord Rich Presence + Ativar a Presença Rica do Discord + + + + Periodically autosave state + Auto-gravar o state periodicamente + + + + Show FPS in title bar + Mostrar o FPS na barra do título + + + + Show frame count in OSD + Mostrar contagem dos frames no OSD + + + + Show emulation info on reset + Mostrar informações da emulação ao resetar + + + + Fast forward speed: + Velocidade do avanço rápido: + + + + + Unbounded + Ilimitado + + + + Fast forward (held) speed: + Velocidade do avanço rápido (pressionado): + + + + Autofire interval: + Intervalo do auto-disparo: + + + + Enable rewind + Ativar retrocesso + + + + Rewind history: + Histórico do retrocesso: + + + + Idle loops: + Repetições inativas: + + + + Run all + Executar todos + + + + Remove known + Remover conhecidos + + + + Detect and remove + Detetar e remover + + + + Preload entire ROM into memory + Pré-carregar a ROM inteira na memória + + + + Save state extra data: + Dados extras do save state: + + + + + Save game + Save do jogo + + + + Load state extra data: + Carregar dados extras do state: + + + + Models + Modelos + + + + GB only: + Só para o GB: + + + + SGB compatible: + Compatível com o SGB: + + + + GBC only: + Só para o GBC: + + + + GBC compatible: + Compatível com o GBC: + + + + SGB and GBC compatible: + Compatível com o SGB e o GBC: + + + + Game Boy palette + Paleta do Game Boy + + + + Preset: + Pré-definições: + + + + + Screenshot + Screenshot + + + + + Cheat codes + Códigos de trapaça + + + + Enable Game Boy Player features by default + Ativar funções do Game Boy Player por padrão + + + + Enable VBA bug compatibility in ROM hacks + Ativar compatibilidade dos bugs do VBA nos hacks das ROMs + + + + Video renderer: + Renderizador do vídeo: + + + + Software + Software + + + + OpenGL enhancements + Melhorias do OpenGL + + + + High-resolution scale: + Escala de alta-resolução: + + + + (240×160) + (240×160) + + + + XQ GBA audio (experimental) + Áudio do XQ GBA (experimental) + + + + GB BIOS file: + Ficheiro da BIOS do GB: + + + + + + + + + + + + Browse + Explorar + + + + Use BIOS file if found + Usar o ficheiro da BIOS se encontrado + + + + Skip BIOS intro + Ignorar a introdução da BIOS + + + + GBA BIOS file: + Ficheiro da BIOS do GBA: + + + + GBC BIOS file: + Ficheiro da BIOS do GBC: + + + + SGB BIOS file: + Ficheiro da BIOS do SGB: + + + + Save games + Saves dos jogos + + + + + + + + Same directory as the ROM + O mesmo diretório que a ROM + + + + Save states + Save states + + + + Screenshots + Screenshots + + + + Patches + Patches + + + + Cheats + Trapaças + + + + Log to file + Registar ao ficheiro + + + + Log to console + Registar à console + + + + Select Log File + Selecionar Ficheiro de Registo + + + + Default BG colors: + Cores padrão do 2º plano: + + + + Default sprite colors 1: + Cores padrão da imagem móvel 1: + + + + Default sprite colors 2: + Cores padrão da imagem móvel 2: + + + + Super Game Boy borders + Bordas do Super Game Boy + + + + QGBA::ShaderSelector + + + No shader active + Nenhum shader ativo + + + + Load shader + Carregar shader + + + + No shader loaded + Nenhum shader carregado + + + + by %1 + por %1 + + + + Preprocessing + Pré-processamento + + + + Pass %1 + Passe %1 + + + + Shaders + Shaders + + + + Active Shader: + Shader Ativo: + + + + Name + Nome + + + + Author + Autor + + + + Description + Descrição + + + + Unload Shader + Descarregar Shader + + + + Load New Shader + Carregar Novo Shader + + + + QGBA::ShortcutModel + + + Action + Ação + + + + Keyboard + Teclado + + + + Gamepad + Controle + + + + QGBA::ShortcutView + + + Edit Shortcuts + Editar Atalhos + + + + Keyboard + Teclado + + + + Gamepad + Controle + + + + Clear + Limpar + + + + QGBA::TileView + + + Export tiles + Exportar ladrilhos + + + + + Portable Network Graphics (*.png) + Gráficos Portáteis da Rede (*.png) + + + + Export tile + Exportar ladrilho + + + + Tiles + Ladrilhos + + + + Export Selected + Exportar Selecionado + + + + Export All + Exportar Tudo + + + + 256 colors + 256 cores + + + + Palette + Paleta + + + + Magnification + Ampliação + + + + Tiles per row + Ladrilhos por linha + + + + Fit to window + Encaixar na janela + + + + Displayed tiles + Mosaicos exibidos + + + + Only BG tiles + Só os mosaicos do 2º plano + + + + Only OBJ tiles + Só os mosaicos do OBJ + + + + Both + Ambos + + + + Copy Selected + Copiar o Selecionado + + + + Copy All + Copiar Tudo + + + + QGBA::VideoView + + + Failed to open output video file: %1 + Falha ao abrir o ficheiro de saída do vídeo: %1 + + + + Native (%0x%1) + Nativo (%0x%1) + + + + Select output file + Selecione o ficheiro de saída + + + + Record Video + Gravar Vídeo + + + + Start + Iniciar + + + + Stop + Parar + + + + Select File + Selecionar Ficheiro + + + + Presets + Pre-definições + + + + High &Quality + Qualidade &Alta + + + + &YouTube + &YouTube + + + + + WebM + WebM + + + + + MP4 + MP4 + + + + &Lossless + &Sem Perdas + + + + 4K + 4K + + + + &1080p + &1080p + + + + &720p + &720p + + + + &480p + &480p + + + + &Native + &Nativo + + + + Format + Formato + + + + MKV + MKV + + + + AVI + AVI + + + + HEVC + HEVC + + + + HEVC (NVENC) + HEVC (NVENC) + + + + VP8 + VP8 + + + + VP9 + VP9 + + + + FFV1 + FFV1 + + + + + None + Nenhum + + + + FLAC + FLAC + + + + WavPack + WavPack + + + + Opus + Opus + + + + Vorbis + Vorbis + + + + MP3 + MP3 + + + + AAC + AAC + + + + Uncompressed + Descomprimido + + + + Bitrate (kbps) + Taxa dos bits (Kbps) + + + + ABR + ABR + + + + H.264 + H.264 + + + + H.264 (NVENC) + H.264 (NVENC) + + + + VBR + VBR + + + + CRF + CRF + + + + Dimensions + Dimensões + + + + Lock aspect ratio + Travar a proporção do aspeto + + + + Show advanced + Mostrar opções avançadas + + + + QGBA::Window + + + Archives (%1) + Ficheiros Compactados (%1) + + + + + + Select ROM + Selecionar ROM + + + + Select folder + Selecionar pasta + + + + + Select save + Selecionar save + + + + Select patch + Selecionar patch + + + + Patches (*.ips *.ups *.bps) + Patches (*.ips *.ups *.bps) + + + + Select e-Reader dotcode + Selecionar dotcode do e-Reader + + + + e-Reader card (*.raw *.bin *.bmp) + Cartão do e-Reader (*.raw *.bin *.bmp) + + + + Select image + Selecionar imagem + + + + Image file (*.png *.gif *.jpg *.jpeg);;All files (*) + Ficheiro de imagem (*.png *.gif *.jpg *.jpeg);;Todos os ficheiros (*) + + + + GameShark saves (*.sps *.xps) + Saves do GameShark (*.sps *.xps) + + + + Select video log + Selecionar registo do vídeo + + + + Video logs (*.mvl) + Registos do vídeo (*.mvl) + + + + Crash + Crash + + + + The game has crashed with the following error: + +%1 + O jogo teve um crash com o seguinte erro: + +%1 + + + + Couldn't Start + Não Pôde Iniciar + + + + Could not start game. + Não pôde iniciar o jogo. + + + + Unimplemented BIOS call + Chamada da BIOS não implementada + + + + This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience. + Este jogo usa uma chamada de BIOS que não está implementada. Por favor use a BIOS oficial para uma melhor experiência. + + + + Failed to create an appropriate display device, falling back to software display. Games may run slowly, especially with larger windows. + Falhou em criar um aparelho de exibição apropriado, voltando a exibição por software. Os jogos podem executar lentamente, especialmente com janelas maiores. + + + + Really make portable? + Realmente tornar portátil? + + + + This will make the emulator load its configuration from the same directory as the executable. Do you want to continue? + Isto fará o emulador carregar a configuração dele do mesmo diretório que o executável. Quer continuar? + + + + Restart needed + Reiniciar é necessário + + + + Some changes will not take effect until the emulator is restarted. + Algumas mudanças não terão efeito até o emulador ser reiniciado. + + + + - Player %1 of %2 + - Jogador %1 de %2 + + + + %1 - %2 + %1 - %2 + + + + %1 - %2 - %3 + %1 - %2 - %3 + + + + %1 - %2 (%3 fps) - %4 + %1 - %2 (%3 fps) - %4 + + + + &File + &Ficheiro + + + + Load &ROM... + Carregar &ROM... + + + + Load ROM in archive... + Carregar ROM no arquivo... + + + + Add folder to library... + Adicionar pasta a biblioteca... + + + + Save games (%1) + Saves dos jogos (%1) + + + + Select save game + Selecione save do jogo + + + + mGBA save state files (%1) + Ficheiro do save state do mGBA (%1) + + + + + Select save state + Selecione um save state + + + + Select e-Reader card images + Selecionar imagens do cartão do e-Reader + + + + Image file (*.png *.jpg *.jpeg) + Ficheiro da imagem (*.png *.jpg *.jpeg) + + + + Conversion finished + Conversão concluída + + + + %1 of %2 e-Reader cards converted successfully. + %1 de %2 cartões do e-Reader convertidos com sucesso. + + + + Load alternate save game... + Carregar save alternativo do jogo... + + + + Load temporary save game... + Carregar save temporário do jogo... + + + + Load &patch... + Carregar &patch... + + + + Boot BIOS + Dar Boot na BIOS + + + + Replace ROM... + Substituir a ROM... + + + + Scan e-Reader dotcodes... + Escanear dotcodes do e-Reader... + + + + Convert e-Reader card image to raw... + Converter imagem do cartão do e-Reader para natural... + + + + ROM &info... + Informações da &ROM... + + + + Recent + Recentes + + + + Make portable + Tornar portátil + + + + &Load state + &Carregar state + + + + Load state file... + Carregar ficheiro do state... + + + + &Save state + &Gravar o state + + + + Save state file... + Ficheiro do save state... + + + + Quick load + Carregamento rápido + + + + Quick save + Salvamento rápido + + + + Load recent + Carregar recentes + + + + Save recent + Gravar recentes + + + + Undo load state + Desfazer o carregamento do state + + + + Undo save state + Desfazer o save state + + + + + State &%1 + State &%1 + + + + Load camera image... + Carregar a imagem da câmara... + + + + Convert save game... + Converter o save do jogo... + + + + GameShark saves (*.gsv *.sps *.xps) + Saves do GameShark (*.gsv *.sps *.xps) + + + + Reset needed + É necessário resetar + + + + Some changes will not take effect until the game is reset. + Algumas mudanças não terão efeito até o jogo ser resetado. + + + + Save games + Saves dos jogos + + + + Import GameShark Save... + Importar Save do GameShark... + + + + Export GameShark Save... + Exportar Save do GameShark... + + + + Automatically determine + Determinar automaticamente + + + + Use player %0 save game + Usar o save do jogo %0 do jogador + + + + New multiplayer window + Nova janela multi-jogador + + + + Connect to Dolphin... + Conectar ao Dolphin... + + + + Report bug... + Reportar bug... + + + + About... + Sobre... + + + + E&xit + S&air + + + + &Emulation + &Emulação + + + + &Reset + &Resetar + + + + Sh&utdown + De&sligar + + + + Yank game pak + Arrancar o game pak + + + + &Pause + &Pausar + + + + &Next frame + &Próximo frame + + + + Fast forward (held) + Avanço rápido (segurado) + + + + &Fast forward + &Avanço rápido + + + + Fast forward speed + Velocidade do avanço rápido + + + + Unbounded + Ilimitado + + + + %0x + %0x + + + + Rewind (held) + Retroceder (segurado) + + + + Re&wind + Re&troceder + + + + Step backwards + Voltar um passo + + + + Solar sensor + Sensor solar + + + + Increase solar level + Aumentar nível solar + + + + Decrease solar level + Diminuir nível solar + + + + Brightest solar level + Nível solar mais brilhante + + + + Darkest solar level + Nível solar mais escuro + + + + Brightness %1 + Brilho %1 + + + + Game Boy Printer... + Impressora do Game Boy... + + + + BattleChip Gate... + Portal do BattleChip... + + + + Audio/&Video + Áudio/&Vídeo + + + + Frame size + Tamanho do frame + + + + %1× + %1× + + + + Toggle fullscreen + Alternar ecrã inteiro + + + + Lock aspect ratio + Travar a proporção do aspeto + + + + Force integer scaling + Forçar o dimensionamento do inteiro + + + + Interframe blending + Mistura do interframe + + + + Bilinear filtering + Filtragem bilinear + + + + Frame&skip + Frame&skip + + + + Mute + Mudo + + + + FPS target + FPS alvo + + + + Native (59.7275) + Nativo (59,7275) + + + + Take &screenshot + Tirar &screenshot + + + + F12 + F12 + + + + Record A/V... + Gravar A/V... + + + + Record GIF/WebP/APNG... + Gravar GIF/WebP/APNG... + + + + Video layers + Camadas do vídeo + + + + Audio channels + Canais de áudio + + + + Adjust layer placement... + Ajustar posicionamento da camada... + + + + &Tools + &Ferramentas + + + + View &logs... + Visualizar &registos... + + + + Game &overrides... + Substituições &do jogo... + + + + Game Pak sensors... + Sensores do Game Pak... + + + + &Cheats... + &Trapaças... + + + + Create forwarder... + Criar encaminhador... + + + + Settings... + Configurações... + + + + Open debugger console... + Abrir console do debugger... + + + + Start &GDB server... + Iniciar servidor do &GDB... + + + + Scripting... + Scripting... + + + + Game state views + Visualizações do estado do jogo + + + + View &palette... + Visualizar &paleta... + + + + View &sprites... + Visualizar &imagens móveis... + + + + View &tiles... + Visualizar &ladrilhos... + + + + View &map... + Visualizar &mapa... + + + + &Frame inspector... + Inspetor dos &frames... + + + + View memory... + Visualizar memória... + + + + Search memory... + Procurar na memória... + + + + View &I/O registers... + Visualizar registos de &E/S... + + + + Record debug video log... + Gravar registo do vídeo de debug... + + + + Stop debug video log + Parar o registo do vídeo de debug + + + + Exit fullscreen + Sair do ecrã inteiro + + + + GameShark Button (held) + Botão do GameShark (segurado) + + + + Autofire + Auto-disparar + + + + Autofire A + Auto-disparar A + + + + Autofire B + Auto-disparar B + + + + Autofire L + Auto-disparar L + + + + Autofire R + Auto-disparar R + + + + Autofire Start + Auto-disparar Start + + + + Autofire Select + Auto-disparar Select + + + + Autofire Up + Auto-disparar Para Cima + + + + Autofire Right + Auto-disparar Direita + + + + Autofire Down + Auto-disparar Para Baixo + + + + Autofire Left + Auto-disparar Esquerda + + + + Clear + Limpar + + + + QObject + + + %1 byte + %1 byte + + + + %1 kiB + %1 kiBs + + + + %1 MiB + %1 MiBs + + + + GBA + GBA + + + + GB + GBs + + + + ? + ? + + + + QShortcut + + + Shift + Shift + + + + Control + Control + + + + Alt + Alt + + + + Meta + Meta + + + From 92d7c1a1d18214f2cc84d4ea8349eb50aa358329 Mon Sep 17 00:00:00 2001 From: raid273 Date: Thu, 27 Apr 2023 18:54:56 +0000 Subject: [PATCH 285/290] Qt: Update translation (Japanese) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/ja/ --- src/platform/qt/ts/mgba-ja.ts | 164 +++++++++++++++++----------------- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/src/platform/qt/ts/mgba-ja.ts b/src/platform/qt/ts/mgba-ja.ts index b3c0cfbc1..68abacadb 100644 --- a/src/platform/qt/ts/mgba-ja.ts +++ b/src/platform/qt/ts/mgba-ja.ts @@ -16,7 +16,7 @@ All ROMs (%1) - すべてのファイル (%1) + すべてのROM (%1) @@ -287,28 +287,28 @@ Download size: %3 BattleChip data missing - + バトルチップのデータが見つかりません BattleChip data is missing. BattleChip Gates will still work, but some graphics will be missing. Would you like to download the data now? - + バトルチップの画像データが見つかりません。このままでもチップゲートは動作しますが、チップの画像が表示されないまま使う事になります。今すぐダウンロードしますか? Select deck file - + デッキファイルを選択 Incompatible deck - + 互換性のないデッキ The selected deck is not compatible with this Chip Gate - + 選択したデッキはこのチップゲートでは使用できません @@ -334,7 +334,7 @@ Download size: %3 Add New Code - + 新しいコード @@ -344,12 +344,12 @@ Download size: %3 Add Lines - + 入力したコードを追加 Code type - + コード形式 @@ -370,7 +370,7 @@ Download size: %3 Autodetect (recommended) - + 自動検出 (推奨) @@ -381,7 +381,7 @@ Download size: %3 Some cheats could not be added. Please ensure they're formatted correctly and/or try other cheat types. - + 一部のコードが追加できませんでした。フォーマットが正常かどうかを確認してください。もしくは、違う形式に変換して試してみてください。 @@ -517,12 +517,12 @@ Download size: %3 Couldn't Connect - + 接続失敗 Could not connect to Dolphin. - + Dolphinと接続できませんでした。 @@ -3542,7 +3542,7 @@ Download size: %3 Width - 値幅 + サイズ @@ -3572,7 +3572,7 @@ Download size: %3 Search type - 検索タイプ + 検索方法 @@ -3592,7 +3592,7 @@ Download size: %3 Unknown/changed - 不明/変更した + 不明/変化した値 @@ -3643,7 +3643,7 @@ Download size: %3 Open in Memory Viewer - メモリービューアーを開く + メモリビューアーを開く @@ -3676,7 +3676,7 @@ Download size: %3 Memory - メモリービューアー + メモリビューアー @@ -4352,7 +4352,7 @@ Download size: %3 Save games and save states (%1) - ゲームとステートをセーブ(%1) + セーブデータ または ステートセーブ (%1) @@ -4362,27 +4362,27 @@ Download size: %3 Save games (%1) - + セーブデータ (%1) Select save game - + セーブデータを選択 Conversion failed - + 変換に失敗しました Failed to convert the save game. This is probably a bug. - + セーブデータの変換に失敗しました。バグによるものである可能性があります。 No file selected - + ファイルが選択されていません @@ -4397,12 +4397,12 @@ Download size: %3 Please select a valid input file - + 有効な入力ファイルを選択してください No valid conversions found - + 利用可能な変換先がありません @@ -4412,12 +4412,12 @@ Download size: %3 Convert/Extract Save Game - + セーブデータの変換/抽出 Input file - + 入力 @@ -4428,7 +4428,7 @@ Download size: %3 Output file - + 出力 @@ -4508,7 +4508,7 @@ Download size: %3 %1 save state with embedded %2 save game - + %2 形式のセーブデータが埋め込まれた %1 のステートセーブ @@ -4617,12 +4617,12 @@ Download size: %3 Offset time - + オフセット sec - + @@ -4735,7 +4735,7 @@ Download size: %3 Select directory - + フォルダを選択 @@ -4745,30 +4745,30 @@ Download size: %3 Never - + 一度もしていない Just now - + たった今 Less than an hour ago - + 1時間以内 %n hour(s) ago - - + + %n 時間前 %n day(s) ago - - + + %n 日前 @@ -4784,7 +4784,7 @@ Download size: %3 Gameplay - + ゲームプレイ @@ -4794,7 +4794,7 @@ Download size: %3 Update - + アップデート @@ -4819,7 +4819,7 @@ Download size: %3 Logging - ロギング + ログ @@ -4929,22 +4929,22 @@ Download size: %3 Audio in multiplayer: - + マルチプレイ時に音を出すウィンドウ: All windows - + 全て Player 1 window only - + プレイヤー1のみ Currently active player window - + アクティブなウィンドウ @@ -5007,43 +5007,43 @@ Download size: %3 Show filename instead of ROM name in library view - + ライブラリにゲーム名ではなくファイル名を表示する Pause - + ポーズ When inactive: - + 非アクティブ時: On loading a game: - + ゲーム起動時: Load last state - + 最新のステートセーブを読み込む Load cheats - + チートコードを読み込む Save entered cheats - + 入力されたチートコードを保存する When minimized: - + 最小化時: @@ -5058,42 +5058,42 @@ Download size: %3 Current channel: - + 現在のチャネル: Current version: - + 現在のバージョン: Update channel: - + 更新チャネル: Available version: - + 利用可能なバージョン: (Unknown) - + (不明) Last checked: - + 最終確認: Automatically check on start - + 起動時に自動的に確認する Check now - + 更新を確認 @@ -5133,7 +5133,7 @@ Download size: %3 Native (59.7275) - ネイティブ (59,7275) + ネイティブ (59.7275) @@ -5158,23 +5158,23 @@ Download size: %3 Save state extra data: - + ステートセーブに含める: Periodically autosave state - + 定期的に自動でステートセーブする Save game - + セーブデータ Load state extra data: - + ステートロード時に読み込む: @@ -5229,12 +5229,12 @@ Download size: %3 Log to file - ファイル出力 + ファイルに出力する Log to console - コンソール出力 + コンソールに出力する @@ -5397,7 +5397,7 @@ Download size: %3 GB BIOS file: - ゲームボーイBIOS: + ゲームボーイ: @@ -5425,17 +5425,17 @@ Download size: %3 GBA BIOS file: - ゲームボーイアドバンスBIOS: + ゲームボーイアドバンス: GBC BIOS file: - ゲームボーイカラーBIOS: + ゲームボーイカラー: SGB BIOS file: - スーパーゲームボーイBIOS: + スーパーゲームボーイ: @@ -6023,12 +6023,12 @@ Download size: %3 Really make portable? - 本当にポータブルにしますか? + 本当にポータブル化しますか? This will make the emulator load its configuration from the same directory as the executable. Do you want to continue? - これによりエミュレータは実行ファイルと同じディレクトリにある設定ファイルをロードします。続けますか? + ポータブル化すると、以降は実行ファイルと同じディレクトリに設定ファイルが置かれるようになります。続行しますか? @@ -6229,12 +6229,12 @@ Download size: %3 Load recent - 最近使ったクイックロットからロード + 最後に使ったクイックスロットからロード Save recent - 最近使ったクイックロットにセーブ + 最後に使ったクイックスロットにセーブ @@ -6260,7 +6260,7 @@ Download size: %3 Convert save game... - + セーブデータを変換... @@ -6285,7 +6285,7 @@ Download size: %3 Connect to Dolphin... - + Dolphinに接続... @@ -6310,7 +6310,7 @@ Download size: %3 Yank game pak - ゲームパックをヤンク + カートリッジを抜く @@ -6485,7 +6485,7 @@ Download size: %3 BattleChip Gate... - チップゲート... + バトルチップゲート... From 6c55c26082a71566352fb94b3ca5d2eb6b1d876c Mon Sep 17 00:00:00 2001 From: Luna Lyday Date: Sun, 14 May 2023 00:08:59 +0000 Subject: [PATCH 286/290] Qt: Update translation (Polish) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/pl/ --- src/platform/qt/ts/mgba-pl.ts | 88 +++++++++++++++++------------------ 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/src/platform/qt/ts/mgba-pl.ts b/src/platform/qt/ts/mgba-pl.ts index 046b0d294..ac468328d 100644 --- a/src/platform/qt/ts/mgba-pl.ts +++ b/src/platform/qt/ts/mgba-pl.ts @@ -6,22 +6,22 @@ Game Boy Advance ROMs (%1) - ROMy Game Boy Advance (%1) + ROMy Game Boy Advance (%1) Game Boy ROMs (%1) - ROMy Game Boy (%1) + ROMy Game Boy (%1) All ROMs (%1) - Wszystkie ROMy (%1) + Wszystkie ROMy (%1) %1 Video Logs (*.mvl) - Dzienniki wideo %1 (*.mvl) + Dzienniki wideo %1 (*.mvl) @@ -530,12 +530,12 @@ Rozmiar pobierania: %3 3DS - + 3DS Vita - + Vita @@ -543,12 +543,12 @@ Rozmiar pobierania: %3 Icon - + Ikona Banner - + Baner @@ -556,17 +556,17 @@ Rozmiar pobierania: %3 Bubble - + Bańka Background - Tło + Tło Startup - + Uruchomienie @@ -574,144 +574,144 @@ Rozmiar pobierania: %3 Create forwarder - + Utwórz forwardera Files - + Pliki ROM file: - + Plik ROM: Browse - Przeglądaj + Przeglądaj Output filename: - + Nazwa pliku wyjściowego: Forwarder base: - + Baza forwardera: Latest stable version - + Najnowsza stabilna wersja Latest development build - + Najnowsza kompilacja deweloperska Specific file - + Konkretny plik Base file: - + Plik bazowy: System - + System 3DS - + 3DS Vita - + Vita Presentation - + Prezentacja Title: - + Tytuł: Images: - + Obrazy: Use default image - + Użyj obrazu domyślnego Preferred size: - + Preferowany rozmiar: Select image file - + Wybierz plik obrazu Select ROM file - + Wybierz plik ROM Select output filename - + Wybierz nazwę pliku wyjściowego Select base file - + Wybierz plik bazowy Build finished - + Kompilacja zakończona Forwarder finished building - + Forwarder zakończył kompilację Build failed - + Kompilacja nie powiodła się Failed to build forwarder - + Nie udało się skompilować forwardera %1 installable package (*.%2) - + Pakiet instalacyjny %1 (*.%2) Select an image - + Wybierz obraz @@ -1080,12 +1080,12 @@ Rozmiar pobierania: %3 NT (old 1) - + NT (stary 1) NT (old 2) - + NT (stary 2) @@ -1110,12 +1110,12 @@ Rozmiar pobierania: %3 GGB-81 - + GGB-81 Li Cheng - + Li Cheng @@ -6539,7 +6539,7 @@ Rozmiar pobierania: %3 Create forwarder... - + Utwórz forwarder... From fd6a4161ac49c745d40255e0b4e4dafb9f8c9e36 Mon Sep 17 00:00:00 2001 From: shinyoyo Date: Sun, 21 May 2023 07:06:15 +0000 Subject: [PATCH 287/290] Qt: Update translation (Chinese (Simplified)) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/zh_Hans/ --- src/platform/qt/ts/mgba-zh_CN.ts | 60 ++++++++++++++++---------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/platform/qt/ts/mgba-zh_CN.ts b/src/platform/qt/ts/mgba-zh_CN.ts index 75f8070c8..99711a24b 100644 --- a/src/platform/qt/ts/mgba-zh_CN.ts +++ b/src/platform/qt/ts/mgba-zh_CN.ts @@ -543,12 +543,12 @@ Download size: %3 Icon - + 图标 Banner - + 横幅 @@ -556,7 +556,7 @@ Download size: %3 Bubble - + 气泡 @@ -566,7 +566,7 @@ Download size: %3 Startup - + 启动 @@ -574,17 +574,17 @@ Download size: %3 Create forwarder - + 创建转发器 Files - + 文件 ROM file: - + ROM 文件: @@ -596,37 +596,37 @@ Download size: %3 Output filename: - + 输出文件名: Forwarder base: - + 转发器基础: Latest stable version - + 最新稳定版本 Latest development build - + 最新开发版本 Specific file - + 指定文件 Base file: - + 基础文件: System - + 系统 @@ -641,77 +641,77 @@ Download size: %3 Presentation - + 介绍 Title: - + 标题: Images: - + 图像: Use default image - + 使用默认图像 Preferred size: - + 首选大小: Select image file - + 选择图像文件 Select ROM file - + 选择 ROM 文件 Select output filename - + 选择输出文件名 Select base file - + 选择基础文件 Build finished - + 已完成构建 Forwarder finished building - + 已完成构建转发器 Build failed - + 构建失败 Failed to build forwarder - + 构建转发器失败 %1 installable package (*.%2) - + %1 可安装数据包 (*.%2) Select an image - + 选择一个图像 @@ -6535,7 +6535,7 @@ Download size: %3 Create forwarder... - + 创建转发器... From 5b38c0f398b29b93a3b4c733b27afa1a334e6f50 Mon Sep 17 00:00:00 2001 From: Felipe Date: Sun, 29 Jan 2023 16:38:50 +0000 Subject: [PATCH 288/290] Qt: Update translation (Portuguese (Brazil)) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/pt_BR/ --- src/platform/qt/ts/mgba-pt_BR.ts | 128 +++++++++++++++---------------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/src/platform/qt/ts/mgba-pt_BR.ts b/src/platform/qt/ts/mgba-pt_BR.ts index c6d1cf783..c2b2f384d 100644 --- a/src/platform/qt/ts/mgba-pt_BR.ts +++ b/src/platform/qt/ts/mgba-pt_BR.ts @@ -6,22 +6,22 @@ Game Boy Advance ROMs (%1) - ROMs do Game Boy Advance (%1) + ROMs do Game Boy Advance (%1) Game Boy ROMs (%1) - ROMs do Game Boy (%1) + ROMs do Game Boy (%1) All ROMs (%1) - Todas as ROMs (%1) + Todas as ROMs (%1) %1 Video Logs (*.mvl) - %1 Registros do Vídeo (*.mvl) + %1 Registros do Vídeo (*.mvl) @@ -448,7 +448,7 @@ Tamanho do download: %3 Failed to open save file; in-game saves cannot be updated. Please ensure the save directory is writable without additional privileges (e.g. UAC on Windows). - Falhou em abrir o abrir o arquivo de salvamento; Os salvamentos dentro do jogo não podem ser atualizados. Por favor tenha certeza que o diretório de salvamento seja gravável sem privilégios adicionais (ex: UAC no Windows). + Falhou em abrir o abrir o arquivo de save; Os saves dentro do jogo não podem ser atualizados. Por favor tenha certeza que o diretório de save seja gravável sem privilégios adicionais (ex: UAC no Windows). @@ -530,12 +530,12 @@ Tamanho do download: %3 3DS - + 3DS Vita - + Vita @@ -543,12 +543,12 @@ Tamanho do download: %3 Icon - + Ícone Banner - + Banner @@ -556,17 +556,17 @@ Tamanho do download: %3 Bubble - + Bolha Background - 2º plano + 2º plano Startup - + Inicialização @@ -574,144 +574,144 @@ Tamanho do download: %3 Create forwarder - + Criar encaminhador Files - + Arquivos ROM file: - + Arquivo da ROM: Browse - Explorar + Explorar Output filename: - + Nome do arquivo de saída: Forwarder base: - + Base do encaminhador: Latest stable version - + Última versão estável Latest development build - + Último build de desenvolvimento Specific file - + Arquivo específico Base file: - + Arquivo base: System - + Sistema 3DS - + 3DS Vita - + Vita Presentation - + Apresentação Title: - + Título: Images: - + Imagens: Use default image - + Usar imagem padrão Preferred size: - + Tamanho preferido: Select image file - + Selecionar arquivo de imagem Select ROM file - + Selecione o arquivo da ROM Select output filename - + Selecionar o nome do arquivo de saída Select base file - + Selecionar o arquivo base Build finished - + Compilação concluída Forwarder finished building - + O encaminhador encerrou a compilação Build failed - + Falhou em criar o build Failed to build forwarder - + Falhou em criar o encaminhador %1 installable package (*.%2) - + %1 pacote instalável (*.%2) Select an image - + Selecione uma imagem @@ -873,7 +873,7 @@ Tamanho do download: %3 Break - Pausar + Parar @@ -1080,12 +1080,12 @@ Tamanho do download: %3 NT (old 1) - + NT (antigo 1) NT (old 2) - + NT (antigo 2) @@ -1110,12 +1110,12 @@ Tamanho do download: %3 GGB-81 - + GGB-81 Li Cheng - + Li Cheng @@ -4319,7 +4319,7 @@ Tamanho do download: %3 <html><head/><body><p>To file a bug report, please first generate a report file to attach to the bug report you're about to file. It is recommended that you include the save files, as these often help with debugging issues. This will collect some information about the version of {projectName} you're running, your configuration, your computer, and the game you currently have open (if any). Once this collection is completed you can review all of the information gathered below and save it to a zip file. The collection will automatically attempt to redact any personal information, such as your username if it's in any of the paths gathered, but just in case you can edit it afterwards. After you have generated and saved it, please click the button below or go to <a href="https://mgba.io/i/"><span style=" text-decoration: underline; color:#2980b9;">mgba.io/i</span></a> to file the bug report on GitHub. Make sure to attach the report you generated!</p></body></html> - <html><head/><body><p>Pra apresentar um relatório de bug por favor gere primeiro um arquivo de relatório pra anexar ao relatório do bug que você está pra apresentar. É recomendado que você inclua os arquivos dos saves como estes frequentemente ajudam com problemas de debugging. Isto coletará um pouco de informação sobre a versão do {projectName} que você está em executando, suas configurações, seu computador e o jogo que você abriu atualmente (se algum). Uma vez que esta coleção estiver completada você pode rever toda a informação coletada abaixo e salvá-la num arquivo zip. A coleção tentará automaticamente reescrever qualquer informação pessoal, tal como seu nome de usuário se está em quaisquer dos caminhos coletados, mas no caso você pode editá-lo mais tarde. Depois que você o gerou e o salvou, por favor clique no botão abaixo ou vá em <a href="https://mgba.io/i/"><span style=" text-decoration: underline; color:#2980b9;">mgba.io/i</span></a> pra apresentar o relatório do bug no GitHub. Tenha a certeza de anexar o relatório que você gerou!</p></body></html> + <html><head/><body><p>Pra apresentar um relatório de bug por favor gere primeiro um arquivo de relatório pra anexar ao relatório do bug que você está pra apresentar. É recomendado que você inclua os arquivos dos saves como estes frequentemente ajudam com problemas de debugging. Isto coletará um pouco de informação sobre a versão do {projectName} que você está em executando, suas configurações, seu computador e o jogo que você abriu atualmente (se houver algum). Uma vez que esta coleção estiver completada você pode rever toda a informação coletada abaixo e salvá-la num arquivo zip. A coleção tentará automaticamente reescrever qualquer informação pessoal tal como seu nome de usuário se ele estiver em quaisquer dos caminhos coletados mas dependendo do caso você pode editá-lo mais tarde. Depois que você o gerou e o salvou por favor clique no botão abaixo ou vá em <a href="https://mgba.io/i/"><span style=" text-decoration: underline; color:#2980b9;">mgba.io/i</span></a> pra apresentar o relatório do bug no GitHub. Tenha a certeza de anexar o relatório que você gerou!</p></body></html> @@ -4508,7 +4508,7 @@ Tamanho do download: %3 %1 save state with embedded %2 save game - %1 save state com %2 saves dos jogos embutidos + %1 save state com %2 saves do jogo embutido @@ -4569,7 +4569,7 @@ Tamanho do download: %3 Select script to load - Selecionar o script a carregar + Selecione o script a carregar @@ -4627,7 +4627,7 @@ Tamanho do download: %3 MM/dd/yy hh:mm:ss AP - MM/dd/yy hh:mm:ss AP + MM/dd/aa hh:mm:ss AP @@ -4877,7 +4877,7 @@ Tamanho do download: %3 samples - amostras + Amostras @@ -4977,7 +4977,7 @@ Tamanho do download: %3 frames per second - frames por segundo + Frames por segundo @@ -5055,7 +5055,7 @@ Tamanho do download: %3 Show emulation info on reset - Mostrar informações da emulação no reset + Mostrar informações da emulação ao resetar @@ -5186,7 +5186,7 @@ Tamanho do download: %3 Fast forward (held) speed: - Velocidade do avanço rápido (segurado): + Velocidade do avanço rápido (pressionado): @@ -5256,12 +5256,12 @@ Tamanho do download: %3 Show FPS in title bar - Mostrar FPS na barra de título + Mostrar o FPS na barra do título Enable Discord Rich Presence - Ativar o Discord Rich Presence + Ativar a Presença Rica do Discord @@ -5349,7 +5349,7 @@ Tamanho do download: %3 High-resolution scale: - Escala da alta-resolução: + Escala de alta-resolução: @@ -5446,7 +5446,7 @@ Tamanho do download: %3 GB only: - Só no GB: + Só pro GB: @@ -5456,7 +5456,7 @@ Tamanho do download: %3 GBC only: - Só no GBC: + Só pro GBC: @@ -5476,7 +5476,7 @@ Tamanho do download: %3 Default BG colors: - Cores padrão do BG: + Cores padrão do 2º plano: @@ -5684,7 +5684,7 @@ Tamanho do download: %3 Copy Selected - Copiar Selecionado + Copiar o Selecionado @@ -5870,7 +5870,7 @@ Tamanho do download: %3 Bitrate (kbps) - Taxa dos bits (kbps) + Taxa dos bits (Kbps) @@ -6241,7 +6241,7 @@ Tamanho do download: %3 Undo save state - Desfazer o salvamento do state + Desfazer o save state @@ -6412,7 +6412,7 @@ Tamanho do download: %3 Force integer scaling - Forçar dimensionamento do inteiro + Forçar o dimensionamento do inteiro @@ -6562,7 +6562,7 @@ Tamanho do download: %3 Create forwarder... - + Criar encaminhador... From 9a8ab700f776e1b8c23d898fc5166287835d9302 Mon Sep 17 00:00:00 2001 From: Davi Lopes Date: Fri, 21 Jul 2023 21:05:21 +0000 Subject: [PATCH 289/290] Qt: Update translation (Portuguese (Brazil)) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/pt_BR/ --- src/platform/qt/ts/mgba-pt_BR.ts | 88 ++++++++++++++++---------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/src/platform/qt/ts/mgba-pt_BR.ts b/src/platform/qt/ts/mgba-pt_BR.ts index c2b2f384d..97fb326a5 100644 --- a/src/platform/qt/ts/mgba-pt_BR.ts +++ b/src/platform/qt/ts/mgba-pt_BR.ts @@ -56,7 +56,7 @@ O Game Boy Advance é uma marca registrada da Nintendo Co., Ltd. {projectName} is an open-source Game Boy Advance emulator - O {projectName} é um emulador de Game Boy Advance de código fonte aberto + O {projectName} é um emulador de Game Boy Advance de código-fonte aberto @@ -70,7 +70,7 @@ O Game Boy Advance é uma marca registrada da Nintendo Co., Ltd. An update to %1 is available. - Uma atualização pro %1 está disponível. + Uma atualização para o %1 está disponível. @@ -78,14 +78,14 @@ O Game Boy Advance é uma marca registrada da Nintendo Co., Ltd. Do you want to download and install it now? You will need to restart the emulator when the download is complete. -Você que baixar e instalá-lo agora? Você precisará reiniciar o emulador quando o download estiver completo. +Você deseja fazer o download e instalar agora? Será necessário reiniciar o emulador quando o processo for concluído. Auto-update is not available on this platform. If you wish to update you will need to do it manually. -Uma auto-atualização não está disponível nesta plataforma. Se você deseja atualizar você precisará fazê-lo manualmente. +Atualizações automáticas não estão disponíveis nesta plataforma. Para realizar atualizações, você precisará fazê-lo manualmente. @@ -193,7 +193,7 @@ Tamanho do download: %3 Can't set format of context-less audio device - Não pôde definir o formato do dispositivo de áudio sem contexto + Não é possível definir o formato de dispositivos de áudio sem contexto @@ -211,7 +211,7 @@ Tamanho do download: %3 Can't start an audio processor without input - Não pôde iniciar um processador de áudio sem entrada + Não é possível iniciar um processador de áudio sem entrada @@ -219,7 +219,7 @@ Tamanho do download: %3 Can't start an audio processor without input - Não pôde iniciar um processador de áudio sem entrada + Não é possível iniciar um processador de áudio sem entrada @@ -282,17 +282,17 @@ Tamanho do download: %3 Show advanced - Mostrar as opções avançadas + Mostrar opções avançadas BattleChip data missing - Portal do BattleChip + Dados do BattleChip não encontrados BattleChip data is missing. BattleChip Gates will still work, but some graphics will be missing. Would you like to download the data now? - Os dados do BattleChip estão ausente. Os Portais do BattleChip Gates ainda funcionarão mas alguns gráficos estarão ausentes. Você gostaria de baixar os dados agora? + Os dados do BattleChip estão ausentes. Os Portais do BattleChip ainda funcionarão, mas alguns gráficos estarão ausentes. Você gostaria de fazer download dos dados agora? @@ -376,12 +376,12 @@ Tamanho do download: %3 Select cheats file - Selecionar o arquivo das trapaças + Selecionar arquivo das trapaças Some cheats could not be added. Please ensure they're formatted correctly and/or try other cheat types. - Algumas trapaças não puderam ser adicionadas. Por favor tenha certeza que eles estão formatadas corretamente e/ou tente outros tipos de trapaça. + Algumas trapaças não foram adicionadas. Por favor, tenha certeza que elas estão formatadas corretamente e/ou tente outros tipos de trapaça. @@ -405,32 +405,32 @@ Tamanho do download: %3 Most games will require a reset to load the new save. Do you want to reset now? - A maioria dos jogos requerirão um reset pra carregar o novo save. Você quer resetar agora? + A maioria dos jogos precisam ser reiniciados para carregar o novo save. Você deseja reiniciar agora? Failed to open save file: %1 - Falhou em abrir o arquivo do save: %1 + Falha ao abrir o arquivo de save: %1 Failed to open game file: %1 - Falhou em abrir o arquivo do jogo: %1 + Falha ao abrir o arquivo do jogo: %1 Can't yank pack in unexpected platform! - Não pode arrancar o pacote numa plataforma inesperada! + Não é possível arrancar o pacote numa plataforma inesperada! Failed to open snapshot file for reading: %1 - Falhou em abrir o arquivo do snapshot pra leitura: %1 + Falha ao abrir o arquivo de snapshot para leitura: %1 Failed to open snapshot file for writing: %1 - Falhou em abrir o arquivo do snapshot pra gravação: %1 + Falha ao abrir o arquivo de snapshot pra gravação: %1 @@ -438,17 +438,17 @@ Tamanho do download: %3 Failed to open game file: %1 - Falhou em abrir o arquivo do jogo: %1 + Falha ao abrir o arquivo do jogo: %1 Could not load game. Are you sure it's in the correct format? - Não pôde carregar o jogo. Você tem certeza que está no formato correto? + Não foi possível carregar o jogo. Você tem certeza que o mesmo está no formato correto? Failed to open save file; in-game saves cannot be updated. Please ensure the save directory is writable without additional privileges (e.g. UAC on Windows). - Falhou em abrir o abrir o arquivo de save; Os saves dentro do jogo não podem ser atualizados. Por favor tenha certeza que o diretório de save seja gravável sem privilégios adicionais (ex: UAC no Windows). + Falha ao abrir o abrir o arquivo de save; Os saves dentro do jogo não podem ser atualizados. Por favor, tenha certeza que o diretório de save tenha permissões de gravação sem uso de privilégios adicionais (ex: UAC no Windows). @@ -461,7 +461,7 @@ Tamanho do download: %3 Enter command (try `help` for more info) - Insira o comando (tente `help` pra mais informações) + Insira o comando (tente `help` para mais informações) @@ -474,7 +474,7 @@ Tamanho do download: %3 Could not open CLI history for writing - Não pôde abrir o histórico do CLI pra gravar + Não foi possível abrir o histórico do CLI para gravar @@ -492,7 +492,7 @@ Tamanho do download: %3 IP address - Endereço do IP + Endereço IP @@ -517,12 +517,12 @@ Tamanho do download: %3 Couldn't Connect - Não Pôde Conectar + Falha ao Conectar Could not connect to Dolphin. - Não pôde conectar com o Dolphin. + Não foi possível conectar-se ao Dolphin. @@ -561,7 +561,7 @@ Tamanho do download: %3 Background - 2º plano + Segundo plano @@ -584,7 +584,7 @@ Tamanho do download: %3 ROM file: - Arquivo da ROM: + Arquivo ROM: @@ -611,7 +611,7 @@ Tamanho do download: %3 Latest development build - Último build de desenvolvimento + Última build de desenvolvimento @@ -671,7 +671,7 @@ Tamanho do download: %3 Select ROM file - Selecione o arquivo da ROM + Selecione o arquivo ROM @@ -696,12 +696,12 @@ Tamanho do download: %3 Build failed - Falhou em criar o build + Falha ao criar o build Failed to build forwarder - Falhou em criar o encaminhador + Falha ao criar o encaminhador @@ -734,12 +734,12 @@ Tamanho do download: %3 Backdrop color - Cor do 2º plano + Cor do segundo plano Disable scanline effects - Desativar efeitos da scanline + Desativar efeitos de scanline @@ -759,7 +759,7 @@ Tamanho do download: %3 Portable Network Graphics (*.png) - Gráficos Portáteis da Rede (*.png) + Portable Network Graphics (*.png) @@ -769,7 +769,7 @@ Tamanho do download: %3 Background - 2º plano + Segundo plano @@ -789,7 +789,7 @@ Tamanho do download: %3 Backdrop - 2º Plano + Segundo plano @@ -807,7 +807,7 @@ Tamanho do download: %3 Enable Discord Rich Presence - Ativar o Discord Rich Presence + Habilitar Discord Rich Presence @@ -893,7 +893,7 @@ Tamanho do download: %3 Could not start GDB server - Não pôde iniciar o servidor do GDB + Não foi possível iniciar o servidor do GDB @@ -941,12 +941,12 @@ Tamanho do download: %3 Frameskip - Frameskip + Salto de frames Failed to open output file: %1 - Falhou em abrir o arquivo de saída: %1 + Falha ao abrir o arquivo de saída: %1 @@ -956,7 +956,7 @@ Tamanho do download: %3 Graphics Interchange Format (*.gif);;WebP ( *.webp);;Animated Portable Network Graphics (*.png *.apng) - Formato da Inter-Mudança dos Gráficos (*.gif);;WebP ( *.webp);;Gráficos da Rede Animada Portátil (*.png *.apng) + Arquivos Graphics Interchange Format (*.gif); Arquivos WebP ( *.webp); Arquivos Animated Portable Network Graphics (*.png *.apng) @@ -1005,7 +1005,7 @@ Tamanho do download: %3 ROM Only - So a ROM + Apenas ROM @@ -1065,7 +1065,7 @@ Tamanho do download: %3 Pocket Cam - Câmara de Bolso + Câmera de Bolso From abd7ccbb1fca8d6739fae1c5bbe69f9c4bfc0c71 Mon Sep 17 00:00:00 2001 From: Felipe Date: Sat, 22 Jul 2023 13:36:40 +0000 Subject: [PATCH 290/290] Qt: Update translation (Portuguese (Brazil)) Translation: mGBA/Qt Translate-URL: https://hosted.weblate.org/projects/mgba/mgba-qt/pt_BR/ --- src/platform/qt/ts/mgba-pt_BR.ts | 128 +++++++++++++++---------------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/src/platform/qt/ts/mgba-pt_BR.ts b/src/platform/qt/ts/mgba-pt_BR.ts index 97fb326a5..da6b2de5b 100644 --- a/src/platform/qt/ts/mgba-pt_BR.ts +++ b/src/platform/qt/ts/mgba-pt_BR.ts @@ -70,7 +70,7 @@ O Game Boy Advance é uma marca registrada da Nintendo Co., Ltd. An update to %1 is available. - Uma atualização para o %1 está disponível. + Uma atualização pro %1 está disponível. @@ -78,14 +78,14 @@ O Game Boy Advance é uma marca registrada da Nintendo Co., Ltd. Do you want to download and install it now? You will need to restart the emulator when the download is complete. -Você deseja fazer o download e instalar agora? Será necessário reiniciar o emulador quando o processo for concluído. +Você que baixar e instalá-lo agora? Você precisará reiniciar o emulador quando o download estiver completo. Auto-update is not available on this platform. If you wish to update you will need to do it manually. -Atualizações automáticas não estão disponíveis nesta plataforma. Para realizar atualizações, você precisará fazê-lo manualmente. +Uma auto-atualização não está disponível nesta plataforma. Se você deseja atualizar você precisará fazê-lo manualmente. @@ -193,7 +193,7 @@ Tamanho do download: %3 Can't set format of context-less audio device - Não é possível definir o formato de dispositivos de áudio sem contexto + Não pôde definir o formato do dispositivo de áudio sem contexto @@ -211,7 +211,7 @@ Tamanho do download: %3 Can't start an audio processor without input - Não é possível iniciar um processador de áudio sem entrada + Não pôde iniciar um processador de áudio sem entrada @@ -219,7 +219,7 @@ Tamanho do download: %3 Can't start an audio processor without input - Não é possível iniciar um processador de áudio sem entrada + Não pôde iniciar um processador de áudio sem entrada @@ -282,17 +282,17 @@ Tamanho do download: %3 Show advanced - Mostrar opções avançadas + Mostrar as opções avançadas BattleChip data missing - Dados do BattleChip não encontrados + Dados do BattleChip ausentes BattleChip data is missing. BattleChip Gates will still work, but some graphics will be missing. Would you like to download the data now? - Os dados do BattleChip estão ausentes. Os Portais do BattleChip ainda funcionarão, mas alguns gráficos estarão ausentes. Você gostaria de fazer download dos dados agora? + Os dados do BattleChip estão ausente. Os Portais do BattleChip Gates ainda funcionarão mas alguns gráficos estarão ausentes. Você gostaria de baixar os dados agora? @@ -376,12 +376,12 @@ Tamanho do download: %3 Select cheats file - Selecionar arquivo das trapaças + Selecionar o arquivo das trapaças Some cheats could not be added. Please ensure they're formatted correctly and/or try other cheat types. - Algumas trapaças não foram adicionadas. Por favor, tenha certeza que elas estão formatadas corretamente e/ou tente outros tipos de trapaça. + Algumas trapaças não puderam ser adicionadas. Por favor tenha certeza que eles estão formatadas corretamente e/ou tente outros tipos de trapaça. @@ -405,32 +405,32 @@ Tamanho do download: %3 Most games will require a reset to load the new save. Do you want to reset now? - A maioria dos jogos precisam ser reiniciados para carregar o novo save. Você deseja reiniciar agora? + A maioria dos jogos requerirão um reset pra carregar o novo save. Você quer resetar agora? Failed to open save file: %1 - Falha ao abrir o arquivo de save: %1 + Falhou em abrir o arquivo do save: %1 Failed to open game file: %1 - Falha ao abrir o arquivo do jogo: %1 + Falhou em abrir o arquivo do jogo: %1 Can't yank pack in unexpected platform! - Não é possível arrancar o pacote numa plataforma inesperada! + Não pode arrancar o pacote numa plataforma inesperada! Failed to open snapshot file for reading: %1 - Falha ao abrir o arquivo de snapshot para leitura: %1 + Falhou em abrir o arquivo do snapshot pra leitura: %1 Failed to open snapshot file for writing: %1 - Falha ao abrir o arquivo de snapshot pra gravação: %1 + Falhou em abrir o arquivo do snapshot pra gravação: %1 @@ -438,17 +438,17 @@ Tamanho do download: %3 Failed to open game file: %1 - Falha ao abrir o arquivo do jogo: %1 + Falhou em abrir o arquivo do jogo: %1 Could not load game. Are you sure it's in the correct format? - Não foi possível carregar o jogo. Você tem certeza que o mesmo está no formato correto? + Não pôde carregar o jogo. Você tem certeza que está no formato correto? Failed to open save file; in-game saves cannot be updated. Please ensure the save directory is writable without additional privileges (e.g. UAC on Windows). - Falha ao abrir o abrir o arquivo de save; Os saves dentro do jogo não podem ser atualizados. Por favor, tenha certeza que o diretório de save tenha permissões de gravação sem uso de privilégios adicionais (ex: UAC no Windows). + Falhou em abrir o abrir o arquivo de save; Os saves dentro do jogo não podem ser atualizados. Por favor tenha certeza que o diretório de save seja gravável sem privilégios adicionais (ex: UAC no Windows). @@ -461,7 +461,7 @@ Tamanho do download: %3 Enter command (try `help` for more info) - Insira o comando (tente `help` para mais informações) + Insira o comando (tente `help` pra mais informações) @@ -474,7 +474,7 @@ Tamanho do download: %3 Could not open CLI history for writing - Não foi possível abrir o histórico do CLI para gravar + Não pôde abrir o histórico do CLI pra gravar @@ -492,7 +492,7 @@ Tamanho do download: %3 IP address - Endereço IP + Endereço do IP @@ -517,12 +517,12 @@ Tamanho do download: %3 Couldn't Connect - Falha ao Conectar + Não Pôde Conectar Could not connect to Dolphin. - Não foi possível conectar-se ao Dolphin. + Não pôde conectar com o Dolphin. @@ -561,7 +561,7 @@ Tamanho do download: %3 Background - Segundo plano + 2º plano @@ -584,7 +584,7 @@ Tamanho do download: %3 ROM file: - Arquivo ROM: + Arquivo da ROM: @@ -611,7 +611,7 @@ Tamanho do download: %3 Latest development build - Última build de desenvolvimento + Último build de desenvolvimento @@ -671,7 +671,7 @@ Tamanho do download: %3 Select ROM file - Selecione o arquivo ROM + Selecione o arquivo da ROM @@ -696,12 +696,12 @@ Tamanho do download: %3 Build failed - Falha ao criar o build + Falhou em criar o build Failed to build forwarder - Falha ao criar o encaminhador + Falhou em criar o encaminhador @@ -734,12 +734,12 @@ Tamanho do download: %3 Backdrop color - Cor do segundo plano + Cor do 2º plano Disable scanline effects - Desativar efeitos de scanline + Desativar efeitos da scanline @@ -759,7 +759,7 @@ Tamanho do download: %3 Portable Network Graphics (*.png) - Portable Network Graphics (*.png) + Gráficos Portáteis da Rede (*.png) @@ -769,7 +769,7 @@ Tamanho do download: %3 Background - Segundo plano + 2º plano @@ -789,7 +789,7 @@ Tamanho do download: %3 Backdrop - Segundo plano + 2º Plano @@ -807,7 +807,7 @@ Tamanho do download: %3 Enable Discord Rich Presence - Habilitar Discord Rich Presence + Ativar a Presença Rica no Discord @@ -873,7 +873,7 @@ Tamanho do download: %3 Break - Parar + Pausar @@ -893,7 +893,7 @@ Tamanho do download: %3 Could not start GDB server - Não foi possível iniciar o servidor do GDB + Não pôde iniciar o servidor do GDB @@ -941,12 +941,12 @@ Tamanho do download: %3 Frameskip - Salto de frames + Frameskip Failed to open output file: %1 - Falha ao abrir o arquivo de saída: %1 + Falhou em abrir o arquivo de saída: %1 @@ -956,7 +956,7 @@ Tamanho do download: %3 Graphics Interchange Format (*.gif);;WebP ( *.webp);;Animated Portable Network Graphics (*.png *.apng) - Arquivos Graphics Interchange Format (*.gif); Arquivos WebP ( *.webp); Arquivos Animated Portable Network Graphics (*.png *.apng) + Formato da Inter-Mudança dos Gráficos (*.gif);;WebP ( *.webp);;Gráficos da Rede Animada Portátil (*.png *.apng) @@ -1005,7 +1005,7 @@ Tamanho do download: %3 ROM Only - Apenas ROM + So a ROM @@ -1065,7 +1065,7 @@ Tamanho do download: %3 Pocket Cam - Câmera de Bolso + Câmara de Bolso @@ -1155,17 +1155,17 @@ Tamanho do download: %3 Mode 0: 4 tile layers - Modo 0: 4 camadas de ladrilhos + Modo 0: 4 camadas de mosaicos Mode 1: 2 tile layers + 1 rotated/scaled tile layer - Modo 1: 2 camadas de ladrilhos + 1 camada de ladrilhos rotacionada/redimensionada + Modo 1: 2 camadas de mosaicos + 1 camada de mosaico rotacionada/redimensionada Mode 2: 2 rotated/scaled tile layers - Modo 2: 2 camadas de ladrilhos rotacionadas/redimensionadas + Modo 2: 2 camadas de mosaicos rotacionadas/redimensionadas @@ -1200,7 +1200,7 @@ Tamanho do download: %3 Linear OBJ tile mapping - Mapeamento do ladrilho do OBJ linear + Mapeamento do mosaico do OBJ linear @@ -1306,7 +1306,7 @@ Tamanho do download: %3 Tile data base (* 16kB) - Banco de dados dos ladrilhos (* 16 kBs) + Banco de dados dos mosaicos (* 16 kBs) @@ -1330,7 +1330,7 @@ Tamanho do download: %3 Tile map base (* 2kB) - Base do mapa dos ladrilhos (* 2 kBs) + Base do mapa dos mosaicos (* 2 kBs) @@ -2775,7 +2775,7 @@ Tamanho do download: %3 Background tile map - Mapa dos ladrilhos do 2º plano + Mapa dos mosaicos do 2º plano @@ -2792,7 +2792,7 @@ Tamanho do download: %3 Background tile data - Dados dos ladrilhos do 2º plano + Dados dos mosaicos do 2º plano @@ -2812,7 +2812,7 @@ Tamanho do download: %3 Window tile map - Mapa dos ladrilhos da janela + Mapa dos mosaicos da janela @@ -3339,7 +3339,7 @@ Tamanho do download: %3 Tile base - Base dos ladrilhos + Base dos mosaicos @@ -3863,7 +3863,7 @@ Tamanho do download: %3 Tile - Ladrilho + Mosaico @@ -4344,7 +4344,7 @@ Tamanho do download: %3 Create and include savestate - Criar e incluir savestate + Criar e incluir o save state @@ -4473,12 +4473,12 @@ Tamanho do download: %3 packed MBC2 - empacotou o MBC2 + Empacotou o MBC2 unpacked MBC2 - desempacotou o MBC2 + Desempacotou o MBC2 @@ -5206,7 +5206,7 @@ Tamanho do download: %3 Select Log File - Selecionar Arquivo de Registro + Selecionar Arquivo do Registro @@ -5608,7 +5608,7 @@ Tamanho do download: %3 Export tiles - Exportar ladrilhos + Exportar mosaicos @@ -5619,12 +5619,12 @@ Tamanho do download: %3 Export tile - Exportar ladrilho + Exportar mosaico Tiles - Ladrilhos + Mosaicos @@ -5654,7 +5654,7 @@ Tamanho do download: %3 Tiles per row - Ladrilhos por linha + Mosaicos por linha @@ -6582,7 +6582,7 @@ Tamanho do download: %3 View &tiles... - Visualizar &ladrilhos... + Visualizar &mosaicos...